document.js 125 KB

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