2026 EV 1036km Flagship

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 GT
Leasing Calculator
Est. Monthly Payment
0
25% down payment (25%) · 8% interest rate · 5 years (60 months)
Enter the vehicle price
€0€80,000
Down Payment (25%)€0
Financed Amount€0

This is an estimate only and does not reflect the actual vehicle price. Request a quote for exact pricing.

Call Now

2026 EV 1036km Flagship Specifications

Performance

5.8s
0-100 km/h

Power & Battery

503 hp
(370 kW)

122.5 kWh
Battery Capacity

Charging

0.15h
30-80%

Vehicle

5 seats
Seating

Rear-engine rear-wheel drive
Drivetrain

Complete Specifications

Basic
Range Cltc1036
Max Power Kw370
Max Power Hp503
Battery Kwh122.5
Acceleration 0 1005.8
Top Speed240
DrivetrainRear-engine rear-wheel drive
Basic Parameters
$IsNew
Manufacturer腾势汽车
Vehicle Level中大型车
Energy TypePure electric
Launch Date2026-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
TransmissionElectric vehicle single-speed transmission
Body Structure5-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 Consumption1.54
Vehicle WarrantySix 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathbasicParameters
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Body
$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 StructureLiftback
Door Opening MethodSwing door
Number of Doors5
Number of Seats5
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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathbody
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Engine
$IsNew
Energy TypePure 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathengine
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Electric Motor
$IsNew
Motor Type永磁
Total Motor Power (kW)370
Total Motor Horsepower (Ps)503
Total Motor Torque (N·m)500
Rear Motor Brand比亚迪
Rear Motor ModelTZ226QYD
Rear Motor Maximum Power (kW)370
Rear Motor Maximum Torque (N·m)500
Number of Drive MotorsSingle motor
Motor LayoutRear
Three-Electric First Owner Warranty PolicyLifetime 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathelectricMotor
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Battery & Charging
$IsNew
Battery TypeLithium iron phosphate battery
Battery Cell BrandFinDreams
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 FunctionSupported
Fast Charging Time (hours)0.15
Fast Charging Range (%)10-97
Slow Charging Port LocationRight rear side
Fast Charging Port LocationRight 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathbatteryCharging
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Battery
Capacity Kwh122.5
Charging Time 30 800.15h (10-97)
Charging
Charging Time 30 800.15h
Dimensions
Seats5
Length (mm)5180
Width (mm)1990
Height (mm)1490
Wheelbase (mm)3125
Gearbox
$IsNew
AbbreviationElectric vehicle single-speed transmission
Number of Gears1
Transmission TypeFixed 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathgearbox
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Chassis & Steering
$IsNew
Drive ModeRear-engine rear-wheel drive
Front Suspension TypeDouble wishbone independent suspension
Rear Suspension TypeFive-link independent suspension
Power Steering TypeElectric power steering
Body StructureUnibody
function Object() { [native code] }function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathchassisSteering
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Wheels & Brakes
$IsNew
Front Brake TypeVentilated disc
Rear Brake TypeVentilated disc
Parking Brake TypeElectronic parking
Front Tire Specifications255/45 R20
Rear Tire Specifications255/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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathwheelBrakes
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Passive Safety
$IsNew
Driver/Passenger Seat AirbagsDriver +/Passenger +
Front/Rear Side AirbagsFront +/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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathpassiveSafety
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Active Safety
$IsNew
Tire Pressure MonitoringTire pressure display
Seat Belt Unfastened ReminderFull 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathactiveSafety
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Driving Control
$IsNew
Driving Mode SwitchSnow
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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathdrivingControl
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Driving Hardware
$IsNew
Front/Rear Parking RadarFront +/Rear +
Driving Assistance Images360-degree panoramic camera
Forward Perception Camera双目
Number of Cameras11
Number of In-Car Cameras1
Number of Ultrasonic Radars12
Number of Millimeter Wave Radars3
Number of LiDARs1
function Object() { [native code] }function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathdrivingHardware
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Driving Features
$IsNew
Cruise Control SystemFull-speed adaptive cruise
Assisted Driving SystemDiPilot
Driving Assistance LevelL2
Map Brand高德
Assisted Driving SectionsHighway 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathdrivingFeatures
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Appearance & Anti-theft
$IsNew
Rim MaterialAluminum alloy
Key TypeNFC, RFID钥匙
Keyless Entry FunctionFull 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathappearanceAntiTheft
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Exterior Lights
$IsNew
Low Beam Light SourceLED
High Beam Light SourceLED
function Object() { [native code] }function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathexteriorLights
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Skylight & Glass
$IsNew
Skylight Type分段式不可开启天窗
Front/Rear Electric WindowsFront +/Rear +
One-Touch Window Lift FunctionFull vehicle
Side Window Multi-Layer Soundproof Glass后排
Car Makeup Mirror主驾驶+照明灯
Rain Sensor Wiper FunctionRain-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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathskylightGlass
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Exterior Rearview Mirror
$IsNew
Exterior Rearview Mirror FunctionMirror 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathexteriorRearviewMirror
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Screen System
$IsNew
Central Control Color ScreenTouch LCD screen
Central Control Screen Size17.3英寸
Central Control Screen Resolution2.5K
Mobile Phone Interconnection/Mapping原厂互联/映射
Voice Recognition Control SystemAir conditioning
Voice Assistant Wake-Up WordHi, Denza
Voice Wake-Up Recognition by RegionFour zones
In-Vehicle Intelligent SystemDiLink
function Object() { [native code] }function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathscreenSystem
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Intelligent Configuration
$IsNew
4G/5G Network5G
Mobile App Remote FunctionCharging 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathintelligentConfiguration
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Steering Wheel & Interior Rearview Mirror
$IsNew
Steering Wheel MaterialLeather
Steering Wheel Position AdjustmentElectric up/down + front/rear adjustment
Gear ShiftingElectronic column-mounted shifting
Trip Computer Display ScreenColor
LCD Instrument Size13.2英寸
HUD Head-Up Size50英寸
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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathsteeringWheelInteriorRearviewMirror
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
In-Car Charging
$IsNew
Multimedia/Charging PortType-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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathinCarCharging
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Seat Configuration
$IsNew
Seat MaterialGenuine leather
Main Seat Adjustment Method腿托调节
Passenger Seat Adjustment Method腿托调节
Driver/Passenger Seat Electric AdjustmentDriver +/Passenger +
Front Seat Functions头枕扬声器(仅驾驶位)
Electric Seat Memory Function副驾驶位
Second Row Seat Adjustment靠背调节
Second Row Seat FunctionVentilation
Rear Seat FoldingProportional folding
Front/Rear Center ArmrestFront +/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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathseatConfiguration
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Audio & Interior Lighting
$IsNew
Speaker Brand NameDEVIALET帝瓦雷
Number of Speakers24喇叭
Interior Ambient Lighting128色
function Object() { [native code] }function SingleNested(value, path, parent) { this.$__parent = parent; SubdocumentType.apply(this, arguments); if (parent == null) { return; } this.$session(parent.$session()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathaudioInteriorLighting
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Air Conditioning & Refrigerator
$IsNew
Air Conditioning Temperature Control MethodAutomatic 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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathairConditioningRefrigerator
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }
Colors
$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()); }
ToBSONfunction() { return this.toObject(internalToObjectOptions); }
$BasePathcolors
$ Initfunction 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]; }
Saveasync 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(); }
$ FullPathfunction(path) { if (!this.$__.fullPath) { this.ownerDocument(); } return path ? this.$__.fullPath + '.' + path : this.$__.fullPath; }
$ PathRelativeToParentfunction(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('.'); }
$ Saveasync function $__save() { try { await this._execDocumentPreHooks('save'); } catch (error) { await this._execDocumentPostHooks('save', error); return; } await this._execDocumentPostHooks('save'); }
$IsValidfunction(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); }
MarkModifiedfunction(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); }
IsModifiedfunction(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); }
$MarkValidfunction(path) { Document.prototype.$markValid.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$markValid(fullPath); } }
Invalidatefunction(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; }
$Ignorefunction(path) { Document.prototype.$ignore.call(this, path); const parent = this.$parent(); const fullPath = this.$__pathRelativeToParent(path); if (parent != null && fullPath != null) { parent.$ignore(fullPath); } }
OwnerDocumentfunction() { 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; }
$ FullPathWithIndexesfunction() { 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('.'); }
Parentfunction() { return this.$__parent; }
$Parentfunction() { return this.$__parent; }
$ SetParentfunction $__setParent(parent) { this.$__parent = parent; }
$ RemoveFromParentfunction() { this.$__parent.set(this.$basePath, null); }
DeleteOnefunction 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); } }
Populatefunction() { throw new Error('Mongoose does not support calling populate() on nested ' + 'docs. Instead of `doc.nested.populate("path")`, use ' + '`doc.populate("nested.path")`'); }
Inspectfunction() { return this.toObject(); }
$ToObjectfunction $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
Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Onfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Oncefunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Emitfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$Listenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$SetMaxListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$RemoveAllListenersfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$AddListenerfunction() { // Delay creating emitter until necessary because emitters take up a lot of memory, // especially for subdocuments. if (!this.$__.emitter) { if (emitterFn === 'emit') { return; } this.$__.emitter = new EventEmitter(); this.$__.emitter.setMaxListeners(0); } return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); }
$ BuildDocfunction(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; }
Initfunction(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; }
$Initfunction() { return this.constructor.prototype.init.apply(this, arguments); }
UpdateOnefunction 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; }
ReplaceOnefunction replaceOne() { const args = [...arguments]; args.unshift({ _id: this._doc._id }); return this.constructor.replaceOne.apply(this.constructor, args); }
$Sessionfunction $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; }
$Timestampsfunction $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; }
Overwritefunction 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; }
$Setfunction $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; }
Setfunction $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; }
$ ShouldModifyfunction(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; }
$ Setfunction(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; } } } }
$ GetValuefunction(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); }
$Incfunction $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; }
$ SetValuefunction(path, val) { utils.setValue(path, val, this._doc); return this; }
Getfunction(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; }
$Getfunction(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; }
$ Pathfunction(path) { const adhocs = this.$__.adhocPaths; const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null; if (adhocType) { return adhocType; } return this.$__schema.path(path); }
$ SaveInitialStatefunction $__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)); } } }
UnmarkModifiedfunction(path) { this.$__.activePaths.init(path); if (this.$__.pathsToScopes != null) { delete this.$__.pathsToScopes[path]; } }
DirectModifiedPathsfunction() { return Object.keys(this.$__.activePaths.getStatePaths('modify')); }
$IsEmptyfunction(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; }
ModifiedPathsfunction(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); }
$IsModifiedfunction(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'); }
$IsDefaultfunction(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)); }
$IsDeletedfunction(val) { if (arguments.length === 0) { return !!this.$__.isDeleted; } this.$__.isDeleted = !!val; return this; }
IsDirectModifiedfunction(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)); }
IsInitfunction(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)); }
IsSelectedfunction 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; }
$ IsSelectedfunction 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; }
IsDirectSelectedfunction 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; }
Validateasync 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; } }
$Validateasync 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; } }
ExecDocumentPreHooksasync function _execDocumentPreHooks(opName, ...args) { return this.$__middleware.execPre(opName, this, [...args]); }
ExecDocumentPostHooksasync function _execDocumentPostHooks(opName, error) { return this.$__middleware.execPost(opName, this, [this], { error }); }
$ Validateasync 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); } } }
ValidateSyncfunction(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; }
$ Resetfunction 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; }
$ UndoResetfunction $__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(); } } }
$ Dirtyfunction() { 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; }
$ SetSchemafunction(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; }
$ GetArrayPathsToValidatefunction() { 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; }); }
$GetAllSubdocsfunction(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; }
$ HandleRejectfunction 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); } }
$ ToObjectShallowfunction $__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; }
ToObjectfunction(options) { return this.$toObject(options); }
ToJSONfunction(options) { return this.$toObject(options, true); }
ToStringfunction() { const ret = this.inspect(); if (typeof ret === 'string') { return ret; } return inspect(ret); }
Equalsfunction(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; }
$GetPopulatedDocsfunction $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; }
Populatedfunction(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; }
$Populatedfunction(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; }
$AssertPopulatedfunction $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; }
Depopulatefunction(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; }
GetChangesfunction() { const delta = this.$__delta(); const changes = delta ? delta[1] : {}; return changes; }
$ Deltafunction $__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]; }
$Clonefunction() { 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; }
$CreateModifiedPathsSnapshotfunction $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 ); }
$RestoreModifiedPathsSnapshotfunction $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; }
$ClearModifiedPathsfunction $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; }
$ HasOnlyPrimitiveValuesfunction $__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'); })); }
ApplyVersionIncrementfunction _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 } }

Interested in the 2026 EV 1036km Flagship?

Get personalized pricing and schedule a test drive with our expert team

Visit Showroom

Siri Kodra Street, Tirana, Albania