document.js 164 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521
  1. 'use strict';
  2. /*!
  3. * Module dependencies.
  4. */
  5. const DivergentArrayError = require('./error/divergentArray');
  6. const EventEmitter = require('events').EventEmitter;
  7. const InternalCache = require('./internal');
  8. const MongooseBuffer = require('./types/buffer');
  9. const MongooseError = require('./error/index');
  10. const MixedSchema = require('./schema/mixed');
  11. const ModifiedPathsSnapshot = require('./modifiedPathsSnapshot');
  12. const ObjectExpectedError = require('./error/objectExpected');
  13. const ObjectParameterError = require('./error/objectParameter');
  14. const ParallelValidateError = require('./error/parallelValidate');
  15. const Schema = require('./schema');
  16. const StrictModeError = require('./error/strict');
  17. const ValidationError = require('./error/validation');
  18. const ValidatorError = require('./error/validator');
  19. const $__hasIncludedChildren = require('./helpers/projection/hasIncludedChildren');
  20. const applyDefaults = require('./helpers/document/applyDefaults');
  21. const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
  22. const clone = require('./helpers/clone');
  23. const compile = require('./helpers/document/compile').compile;
  24. const defineKey = require('./helpers/document/compile').defineKey;
  25. const firstKey = require('./helpers/firstKey');
  26. const flatten = require('./helpers/common').flatten;
  27. const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDiscriminatorPath');
  28. const getKeysInSchemaOrder = require('./helpers/schema/getKeysInSchemaOrder');
  29. const getSubdocumentStrictValue = require('./helpers/schema/getSubdocumentStrictValue');
  30. const handleSpreadDoc = require('./helpers/document/handleSpreadDoc');
  31. const isBsonType = require('./helpers/isBsonType');
  32. const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
  33. const isExclusive = require('./helpers/projection/isExclusive');
  34. const isPathExcluded = require('./helpers/projection/isPathExcluded');
  35. const inspect = require('util').inspect;
  36. const internalToObjectOptions = require('./options').internalToObjectOptions;
  37. const markArraySubdocsPopulated = require('./helpers/populate/markArraySubdocsPopulated');
  38. const minimize = require('./helpers/minimize');
  39. const mpath = require('mpath');
  40. const parentPaths = require('./helpers/path/parentPaths');
  41. const queryhelpers = require('./queryHelpers');
  42. const utils = require('./utils');
  43. const isPromise = require('./helpers/isPromise');
  44. const deepEqual = utils.deepEqual;
  45. const isMongooseObject = utils.isMongooseObject;
  46. const arrayAtomicsBackupSymbol = require('./helpers/symbols').arrayAtomicsBackupSymbol;
  47. const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
  48. const documentArrayParent = require('./helpers/symbols').documentArrayParent;
  49. const documentIsModified = require('./helpers/symbols').documentIsModified;
  50. const documentModifiedPaths = require('./helpers/symbols').documentModifiedPaths;
  51. const documentSchemaSymbol = require('./helpers/symbols').documentSchemaSymbol;
  52. const getSymbol = require('./helpers/symbols').getSymbol;
  53. const modelSymbol = require('./helpers/symbols').modelSymbol;
  54. const populateModelSymbol = require('./helpers/symbols').populateModelSymbol;
  55. const scopeSymbol = require('./helpers/symbols').scopeSymbol;
  56. const schemaMixedSymbol = require('./schema/symbols').schemaMixedSymbol;
  57. const getDeepestSubdocumentForPath = require('./helpers/document/getDeepestSubdocumentForPath');
  58. const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
  59. let DocumentArray;
  60. let MongooseArray;
  61. let Embedded;
  62. const specialProperties = utils.specialProperties;
  63. const VERSION_WHERE = 1;
  64. const VERSION_INC = 2;
  65. const VERSION_ALL = VERSION_WHERE | VERSION_INC;
  66. /**
  67. * The core Mongoose document constructor. You should not call this directly,
  68. * the Mongoose [Model constructor](./api/model.html#Model) calls this for you.
  69. *
  70. * @param {Object} obj the values to set
  71. * @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
  72. * @param {Object} [options] various configuration options for the document
  73. * @param {Boolean} [options.defaults=true] if `false`, skip applying default values to this document.
  74. * @param {Boolean} [options.skipId=false] By default, Mongoose document if one is not provided and the document's schema does not override Mongoose's default `_id`. Set `skipId` to `true` to skip this generation step.
  75. * @inherits NodeJS EventEmitter https://nodejs.org/api/events.html#class-eventemitter
  76. * @event `init`: Emitted on a document after it has been retrieved from the db and fully hydrated by Mongoose.
  77. * @event `save`: Emitted when the document is successfully saved
  78. * @api private
  79. */
  80. function Document(obj, fields, options) {
  81. if (typeof options === 'boolean') {
  82. throw new Error('The skipId parameter has been removed. Use { skipId: true } in the options parameter instead.');
  83. }
  84. options = Object.assign({}, options);
  85. let skipId = options.skipId;
  86. this.$__ = new InternalCache();
  87. // Support `browserDocument.js` syntax
  88. if (this.$__schema == null) {
  89. const _schema = utils.isObject(fields) && !fields.instanceOfSchema ?
  90. new Schema(fields) :
  91. fields;
  92. this.$__setSchema(_schema);
  93. fields = options;
  94. skipId = options.skipId;
  95. }
  96. // Avoid setting `isNew` to `true`, because it is `true` by default
  97. if (options.isNew != null && options.isNew !== true) {
  98. this.$isNew = options.isNew;
  99. }
  100. if (options.priorDoc != null) {
  101. this.$__.priorDoc = options.priorDoc;
  102. }
  103. if (skipId) {
  104. this.$__.skipId = skipId;
  105. }
  106. if (obj != null && typeof obj !== 'object') {
  107. throw new ObjectParameterError(obj, 'obj', 'Document');
  108. }
  109. let defaults = true;
  110. if (options.defaults !== undefined) {
  111. this.$__.defaults = options.defaults;
  112. defaults = options.defaults;
  113. }
  114. const schema = this.$__schema;
  115. if (typeof fields === 'boolean' || fields === 'throw') {
  116. if (fields !== true) {
  117. this.$__.strictMode = fields;
  118. }
  119. fields = undefined;
  120. } else if (schema.options.strict !== true) {
  121. this.$__.strictMode = schema.options.strict;
  122. }
  123. const requiredPaths = schema.requiredPaths(true);
  124. for (const path of requiredPaths) {
  125. this.$__.activePaths.require(path);
  126. }
  127. let exclude = null;
  128. // determine if this doc is a result of a query with
  129. // excluded fields
  130. if (utils.isPOJO(fields) && utils.hasOwnKeys(fields)) {
  131. exclude = isExclusive(fields);
  132. this.$__.selected = fields;
  133. this.$__.exclude = exclude;
  134. }
  135. const hasIncludedChildren = exclude === false && fields ?
  136. $__hasIncludedChildren(fields) :
  137. null;
  138. if (this._doc == null) {
  139. this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false);
  140. // By default, defaults get applied **before** setting initial values
  141. // Re: gh-6155
  142. if (defaults) {
  143. applyDefaults(this, fields, exclude, hasIncludedChildren, true, null, {
  144. skipParentChangeTracking: true
  145. });
  146. }
  147. }
  148. if (obj) {
  149. // Skip set hooks
  150. if (this.$__original_set) {
  151. this.$__original_set(obj, undefined, true, options);
  152. } else {
  153. this.$set(obj, undefined, true, options);
  154. }
  155. if (obj instanceof Document) {
  156. this.$isNew = obj.$isNew;
  157. }
  158. }
  159. // Function defaults get applied **after** setting initial values so they
  160. // see the full doc rather than an empty one, unless they opt out.
  161. // Re: gh-3781, gh-6155
  162. if (options.willInit && defaults) {
  163. if (options.skipDefaults) {
  164. this.$__.skipDefaults = options.skipDefaults;
  165. }
  166. } else if (defaults) {
  167. applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
  168. }
  169. if (!this.$__.strictMode && obj) {
  170. const _this = this;
  171. const keys = Object.keys(this._doc);
  172. keys.forEach(function(key) {
  173. // Avoid methods, virtuals, existing fields, and `$` keys. The latter is to avoid overwriting
  174. // Mongoose internals.
  175. if (!(key in schema.tree) && !(key in schema.methods) && !(key in schema.virtuals) && !key.startsWith('$')) {
  176. defineKey({ prop: key, subprops: null, prototype: _this });
  177. }
  178. });
  179. }
  180. applyQueue(this);
  181. }
  182. Document.prototype.$isMongooseDocumentPrototype = true;
  183. /**
  184. * Boolean flag specifying if the document is new. If you create a document
  185. * using `new`, this document will be considered "new". `$isNew` is how
  186. * Mongoose determines whether `save()` should use `insertOne()` to create
  187. * a new document or `updateOne()` to update an existing document.
  188. *
  189. * #### Example:
  190. *
  191. * const user = new User({ name: 'John Smith' });
  192. * user.$isNew; // true
  193. *
  194. * await user.save(); // Sends an `insertOne` to MongoDB
  195. *
  196. * On the other hand, if you load an existing document from the database
  197. * using `findOne()` or another [query operation](https://mongoosejs.com/docs/queries.html),
  198. * `$isNew` will be false.
  199. *
  200. * #### Example:
  201. *
  202. * const user = await User.findOne({ name: 'John Smith' });
  203. * user.$isNew; // false
  204. *
  205. * Mongoose sets `$isNew` to `false` immediately after `save()` succeeds.
  206. * That means Mongoose sets `$isNew` to false **before** `post('save')` hooks run.
  207. * In `post('save')` hooks, `$isNew` will be `false` if `save()` succeeded.
  208. *
  209. * #### Example:
  210. *
  211. * userSchema.post('save', function() {
  212. * this.$isNew; // false
  213. * });
  214. * await User.create({ name: 'John Smith' });
  215. *
  216. * For subdocuments, `$isNew` is true if either the parent has `$isNew` set,
  217. * or if you create a new subdocument.
  218. *
  219. * #### Example:
  220. *
  221. * // Assume `Group` has a document array `users`
  222. * const group = await Group.findOne();
  223. * group.users[0].$isNew; // false
  224. *
  225. * group.users.push({ name: 'John Smith' });
  226. * group.users[1].$isNew; // true
  227. *
  228. * @api public
  229. * @property $isNew
  230. * @memberOf Document
  231. * @instance
  232. */
  233. Object.defineProperty(Document.prototype, 'isNew', {
  234. get: function() {
  235. return this.$isNew;
  236. },
  237. set: function(value) {
  238. this.$isNew = value;
  239. }
  240. });
  241. /**
  242. * Hash containing current validation errors.
  243. *
  244. * @api public
  245. * @property errors
  246. * @memberOf Document
  247. * @instance
  248. */
  249. Object.defineProperty(Document.prototype, 'errors', {
  250. get: function() {
  251. return this.$errors;
  252. },
  253. set: function(value) {
  254. this.$errors = value;
  255. }
  256. });
  257. /*!
  258. * ignore
  259. */
  260. Document.prototype.$isNew = true;
  261. /*!
  262. * Document exposes the NodeJS event emitter API, so you can use
  263. * `on`, `once`, etc.
  264. */
  265. utils.each(
  266. ['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners',
  267. 'removeAllListeners', 'addListener'],
  268. function(emitterFn) {
  269. Document.prototype[emitterFn] = function() {
  270. // Delay creating emitter until necessary because emitters take up a lot of memory,
  271. // especially for subdocuments.
  272. if (!this.$__.emitter) {
  273. if (emitterFn === 'emit') {
  274. return;
  275. }
  276. this.$__.emitter = new EventEmitter();
  277. this.$__.emitter.setMaxListeners(0);
  278. }
  279. return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments);
  280. };
  281. Document.prototype[`$${emitterFn}`] = Document.prototype[emitterFn];
  282. });
  283. Document.prototype.constructor = Document;
  284. for (const i in EventEmitter.prototype) {
  285. Document[i] = EventEmitter.prototype[i];
  286. }
  287. /**
  288. * The document's internal schema.
  289. *
  290. * @api private
  291. * @property schema
  292. * @memberOf Document
  293. * @instance
  294. */
  295. Document.prototype.$__schema;
  296. /**
  297. * The document's schema.
  298. *
  299. * @api public
  300. * @property schema
  301. * @memberOf Document
  302. * @instance
  303. */
  304. Document.prototype.schema;
  305. /**
  306. * Empty object that you can use for storing properties on the document. This
  307. * is handy for passing data to middleware without conflicting with Mongoose
  308. * internals.
  309. *
  310. * #### Example:
  311. *
  312. * schema.pre('save', function() {
  313. * // Mongoose will set `isNew` to `false` if `save()` succeeds
  314. * this.$locals.wasNew = this.isNew;
  315. * });
  316. *
  317. * schema.post('save', function() {
  318. * // Prints true if `isNew` was set before `save()`
  319. * console.log(this.$locals.wasNew);
  320. * });
  321. *
  322. * @api public
  323. * @property $locals
  324. * @memberOf Document
  325. * @instance
  326. */
  327. Object.defineProperty(Document.prototype, '$locals', {
  328. configurable: false,
  329. enumerable: false,
  330. get: function() {
  331. if (this.$__.locals == null) {
  332. this.$__.locals = {};
  333. }
  334. return this.$__.locals;
  335. },
  336. set: function(v) {
  337. this.$__.locals = v;
  338. }
  339. });
  340. /**
  341. * Legacy alias for `$isNew`.
  342. *
  343. * @api public
  344. * @property isNew
  345. * @memberOf Document
  346. * @see $isNew https://mongoosejs.com/docs/api/document.html#Document.prototype.$isNew
  347. * @instance
  348. */
  349. Document.prototype.isNew;
  350. /**
  351. * Set this property to add additional query filters when Mongoose saves this document and `isNew` is false.
  352. *
  353. * #### Example:
  354. *
  355. * // Make sure `save()` never updates a soft deleted document.
  356. * schema.pre('save', function() {
  357. * this.$where = { isDeleted: false };
  358. * });
  359. *
  360. * @api public
  361. * @property $where
  362. * @memberOf Document
  363. * @instance
  364. */
  365. Object.defineProperty(Document.prototype, '$where', {
  366. configurable: false,
  367. enumerable: false,
  368. writable: true
  369. });
  370. /**
  371. * The string version of this documents _id.
  372. *
  373. * #### Note:
  374. *
  375. * This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](https://mongoosejs.com/docs/guide.html#id) of its `Schema` to false at construction time.
  376. *
  377. * new Schema({ name: String }, { id: false });
  378. *
  379. * @api public
  380. * @see Schema options https://mongoosejs.com/docs/guide.html#options
  381. * @property id
  382. * @memberOf Document
  383. * @instance
  384. */
  385. Document.prototype.id;
  386. /**
  387. * Hash containing current validation $errors.
  388. *
  389. * @api public
  390. * @property $errors
  391. * @memberOf Document
  392. * @instance
  393. */
  394. Document.prototype.$errors;
  395. /**
  396. * A string containing the current operation that Mongoose is executing
  397. * on this document. May be `null`, `'save'`, `'validate'`, or `'remove'`.
  398. *
  399. * #### Example:
  400. *
  401. * const doc = new Model({ name: 'test' });
  402. * doc.$op; // null
  403. *
  404. * const promise = doc.save();
  405. * doc.$op; // 'save'
  406. *
  407. * await promise;
  408. * doc.$op; // null
  409. *
  410. * @api public
  411. * @property $op
  412. * @memberOf Document
  413. * @instance
  414. */
  415. Object.defineProperty(Document.prototype, '$op', {
  416. get: function() {
  417. return this.$__.op || null;
  418. },
  419. set: function(value) {
  420. this.$__.op = value;
  421. }
  422. });
  423. /*!
  424. * ignore
  425. */
  426. function $applyDefaultsToNested(val, path, doc) {
  427. if (val == null) {
  428. return;
  429. }
  430. const paths = Object.keys(doc.$__schema.paths);
  431. const plen = paths.length;
  432. const pathPieces = path.indexOf('.') === -1 ? [path] : path.split('.');
  433. for (let i = 0; i < plen; ++i) {
  434. let curPath = '';
  435. const p = paths[i];
  436. if (!p.startsWith(path + '.')) {
  437. continue;
  438. }
  439. const type = doc.$__schema.paths[p];
  440. const pieces = type.splitPath().slice(pathPieces.length);
  441. const len = pieces.length;
  442. if (type.defaultValue === void 0) {
  443. continue;
  444. }
  445. let cur = val;
  446. for (let j = 0; j < len; ++j) {
  447. if (cur == null) {
  448. break;
  449. }
  450. const piece = pieces[j];
  451. if (j === len - 1) {
  452. if (cur[piece] !== void 0) {
  453. break;
  454. }
  455. try {
  456. const def = type.getDefault(doc, false);
  457. if (def !== void 0) {
  458. cur[piece] = def;
  459. }
  460. } catch (err) {
  461. doc.invalidate(path + '.' + curPath, err);
  462. break;
  463. }
  464. break;
  465. }
  466. curPath += (!curPath.length ? '' : '.') + piece;
  467. cur[piece] = cur[piece] || {};
  468. cur = cur[piece];
  469. }
  470. }
  471. }
  472. /**
  473. * Builds the default doc structure
  474. *
  475. * @param {Object} obj
  476. * @param {Object} [fields]
  477. * @param {Boolean} [skipId]
  478. * @param {Boolean} [exclude]
  479. * @param {Object} [hasIncludedChildren]
  480. * @api private
  481. * @method $__buildDoc
  482. * @memberOf Document
  483. * @instance
  484. */
  485. Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasIncludedChildren) {
  486. const doc = {};
  487. const paths = Object.keys(this.$__schema.paths).
  488. // Don't build up any paths that are underneath a map, we don't know
  489. // what the keys will be
  490. filter(p => !p.includes('$*'));
  491. const plen = paths.length;
  492. let ii = 0;
  493. for (; ii < plen; ++ii) {
  494. const p = paths[ii];
  495. if (p === '_id') {
  496. if (skipId) {
  497. continue;
  498. }
  499. if (obj && '_id' in obj) {
  500. continue;
  501. }
  502. }
  503. const path = this.$__schema.paths[p].splitPath();
  504. const len = path.length;
  505. const last = len - 1;
  506. let curPath = '';
  507. let doc_ = doc;
  508. let included = false;
  509. for (let i = 0; i < len; ++i) {
  510. const piece = path[i];
  511. if (!curPath.length) {
  512. curPath = piece;
  513. } else {
  514. curPath += '.' + piece;
  515. }
  516. // support excluding intermediary levels
  517. if (exclude === true) {
  518. if (curPath in fields) {
  519. break;
  520. }
  521. } else if (exclude === false && fields && !included) {
  522. if (curPath in fields) {
  523. included = true;
  524. } else if (!hasIncludedChildren[curPath]) {
  525. break;
  526. }
  527. }
  528. if (i < last) {
  529. doc_ = doc_[piece] || (doc_[piece] = {});
  530. }
  531. }
  532. }
  533. this._doc = doc;
  534. };
  535. /*!
  536. * Converts to POJO when you use the document for querying
  537. */
  538. Document.prototype.toBSON = function() {
  539. return this.toObject(internalToObjectOptions);
  540. };
  541. /**
  542. * Hydrates this document with the data in `doc`. Does not run setters or mark any paths modified.
  543. *
  544. * Called internally after a document is returned from MongoDB. Normally,
  545. * you do **not** need to call this function on your own.
  546. *
  547. * This function triggers `init` [middleware](https://mongoosejs.com/docs/middleware.html).
  548. * Note that `init` hooks are [synchronous](https://mongoosejs.com/docs/middleware.html#synchronous).
  549. *
  550. * @param {Object} doc raw document returned by mongo
  551. * @param {Object} [opts]
  552. * @param {Boolean} [opts.hydratedPopulatedDocs=false] If true, hydrate and mark as populated any paths that are populated in the raw document
  553. * @param {Function} [fn]
  554. * @api public
  555. * @memberOf Document
  556. * @instance
  557. */
  558. Document.prototype.init = function(doc, opts, fn) {
  559. if (typeof opts === 'function') {
  560. fn = opts;
  561. opts = null;
  562. }
  563. if (doc == null) {
  564. throw new ObjectParameterError(doc, 'doc', 'init');
  565. }
  566. this.$__init(doc, opts);
  567. if (fn) {
  568. fn(null, this);
  569. }
  570. return this;
  571. };
  572. /**
  573. * Alias for [`.init`](https://mongoosejs.com/docs/api/document.html#Document.prototype.init())
  574. *
  575. * @api public
  576. */
  577. Document.prototype.$init = function() {
  578. return this.constructor.prototype.init.apply(this, arguments);
  579. };
  580. /**
  581. * Internal "init" function
  582. *
  583. * @param {Document} doc
  584. * @param {Object} [opts]
  585. * @returns {Document} this
  586. * @api private
  587. */
  588. Document.prototype.$__init = function(doc, opts) {
  589. if (doc == null) {
  590. throw new ObjectParameterError(doc, 'doc', 'init');
  591. }
  592. this.$isNew = false;
  593. opts = opts || {};
  594. // handle docs with populated paths
  595. // If doc._id is not null or undefined
  596. if (doc._id != null && opts.populated && opts.populated.length) {
  597. const id = String(doc._id);
  598. for (const item of opts.populated) {
  599. if (item.isVirtual) {
  600. this.$populated(item.path, utils.getValue(item.path, doc), item);
  601. } else {
  602. this.$populated(item.path, item._docs[id], item);
  603. }
  604. if (item._childDocs == null) {
  605. continue;
  606. }
  607. for (const child of item._childDocs) {
  608. if (child?.$__ == null) {
  609. continue;
  610. }
  611. child.$__.parent = this;
  612. }
  613. item._childDocs = [];
  614. }
  615. }
  616. init(this, doc, this._doc, opts);
  617. markArraySubdocsPopulated(this, opts.populated);
  618. this.$emit('init', this);
  619. this.constructor.emit('init', this);
  620. const hasIncludedChildren = this.$__.exclude === false && this.$__.selected ?
  621. $__hasIncludedChildren(this.$__.selected) :
  622. null;
  623. applyDefaults(this, this.$__.selected, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);
  624. return this;
  625. };
  626. /**
  627. * Init helper.
  628. *
  629. * @param {Object} self document instance
  630. * @param {Object} obj raw mongodb doc
  631. * @param {Object} doc object we are initializing
  632. * @param {Object} [opts] Optional Options
  633. * @param {Boolean} [opts.setters] Call `applySetters` instead of `cast`
  634. * @param {String} [prefix] Prefix to add to each path
  635. * @api private
  636. */
  637. function init(self, obj, doc, opts, prefix) {
  638. prefix = prefix || '';
  639. if (typeof obj !== 'object' || Array.isArray(obj)) {
  640. throw new ObjectExpectedError(self.$basePath, obj);
  641. }
  642. if (obj.$__ != null) {
  643. obj = obj._doc;
  644. }
  645. const keys = Object.keys(obj);
  646. const len = keys.length;
  647. let schemaType;
  648. let path;
  649. let i;
  650. const strict = self.$__.strictMode;
  651. const docSchema = self.$__schema;
  652. for (let index = 0; index < len; ++index) {
  653. i = keys[index];
  654. // avoid prototype pollution
  655. if (specialProperties.has(i)) {
  656. continue;
  657. }
  658. path = prefix ? prefix + i : i;
  659. schemaType = docSchema.path(path);
  660. // Should still work if not a model-level discriminator, but should not be
  661. // necessary. This is *only* to catch the case where we queried using the
  662. // base model and the discriminated model has a projection
  663. if (docSchema.$isRootDiscriminator && !self.$__isSelected(path)) {
  664. continue;
  665. }
  666. const value = obj[i];
  667. if (!schemaType && utils.isPOJO(value)) {
  668. // assume nested object
  669. if (!doc[i]) {
  670. doc[i] = {};
  671. if (!strict && !(i in docSchema.tree) && !(i in docSchema.methods) && !(i in docSchema.virtuals)) {
  672. self[i] = doc[i];
  673. } else if (opts?.virtuals && (i in docSchema.virtuals)) {
  674. self[i] = doc[i];
  675. }
  676. }
  677. init(self, value, doc[i], opts, path + '.');
  678. } else if (!schemaType) {
  679. doc[i] = value;
  680. if (!strict && !prefix) {
  681. self[i] = value;
  682. } else if (opts?.virtuals && (i in docSchema.virtuals)) {
  683. self[i] = value;
  684. }
  685. } else {
  686. // Retain order when overwriting defaults
  687. if (Object.hasOwn(doc, i) && value !== void 0 && !opts.hydratedPopulatedDocs) {
  688. delete doc[i];
  689. }
  690. if (value === null) {
  691. doc[i] = schemaType._castNullish(null);
  692. } else if (value !== undefined) {
  693. const wasPopulated = value.$__ == null ? null : value.$__.wasPopulated;
  694. if (schemaType && !wasPopulated && !opts.hydratedPopulatedDocs) {
  695. try {
  696. if (opts?.setters) {
  697. // Call applySetters with `init = false` because otherwise setters are a noop
  698. const overrideInit = false;
  699. doc[i] = schemaType.applySetters(value, self, overrideInit, null, opts);
  700. } else {
  701. doc[i] = schemaType.cast(value, self, true, undefined, opts);
  702. }
  703. } catch (e) {
  704. self.invalidate(e.path, new ValidatorError({
  705. path: e.path,
  706. message: e.message,
  707. type: 'cast',
  708. value: e.value,
  709. reason: e
  710. }));
  711. }
  712. } else if (schemaType && opts.hydratedPopulatedDocs) {
  713. doc[i] = schemaType.cast(value, self, true, undefined, { hydratedPopulatedDocs: true });
  714. if (doc[i]?.$__?.wasPopulated) {
  715. self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
  716. } else if (Array.isArray(doc[i]) && doc[i].length && doc[i][0]?.$__?.wasPopulated) {
  717. self.$populated(path, doc[i].map(populatedDoc => populatedDoc?.$__?.wasPopulated?.value).filter(val => val != null), doc[i][0].$__.wasPopulated.options);
  718. }
  719. } else {
  720. doc[i] = value;
  721. }
  722. }
  723. // mark as hydrated
  724. if (!self.$isModified(path)) {
  725. self.$__.activePaths.init(path);
  726. }
  727. }
  728. }
  729. }
  730. /**
  731. * Sends an updateOne command with this document `_id` as the query selector.
  732. *
  733. * #### Example:
  734. *
  735. * weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 });
  736. *
  737. * #### Valid options:
  738. *
  739. * - same as in [Model.updateOne](https://mongoosejs.com/docs/api/model.html#Model.updateOne)
  740. *
  741. * @see Model.updateOne https://mongoosejs.com/docs/api/model.html#Model.updateOne
  742. * @param {Object} update
  743. * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
  744. * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and the [Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
  745. * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
  746. * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/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.
  747. * @return {Query}
  748. * @api public
  749. * @memberOf Document
  750. * @instance
  751. */
  752. Document.prototype.updateOne = function updateOne(update, options) {
  753. const query = this.constructor.updateOne();
  754. const self = this;
  755. query.pre(async function queryPreUpdateOne() {
  756. const res = await self._execDocumentPreHooks('updateOne', self, update, options);
  757. // `self` is passed to pre hooks as argument for backwards compatibility, but that
  758. // isn't the actual arguments passed to the wrapped function.
  759. if (res[0] !== self || res[1] !== update || res[2] !== options) {
  760. throw new Error('Document updateOne pre hooks cannot overwrite arguments');
  761. }
  762. query.updateOne({ _id: self._doc._id }, update, options);
  763. // Apply custom where conditions _after_ document updateOne middleware for
  764. // consistency with save() - sharding plugin needs to set $where
  765. if (self.$where != null) {
  766. this.where(self.$where);
  767. }
  768. if (self.$session() != null) {
  769. if (!('session' in query.options)) {
  770. query.options.session = self.$session();
  771. }
  772. }
  773. return res;
  774. });
  775. query.post(function queryPostUpdateOne() {
  776. return self._execDocumentPostHooks('updateOne');
  777. });
  778. return query;
  779. };
  780. /**
  781. * Sends a replaceOne command with this document `_id` as the query selector.
  782. *
  783. * #### Valid options:
  784. *
  785. * - same as in [Model.replaceOne](https://mongoosejs.com/docs/api/model.html#Model.replaceOne())
  786. *
  787. * @see Model.replaceOne https://mongoosejs.com/docs/api/model.html#Model.replaceOne()
  788. * @param {Object} doc
  789. * @param {Object} [options]
  790. * @param {Function} [callback]
  791. * @return {Query}
  792. * @api public
  793. * @memberOf Document
  794. * @instance
  795. */
  796. Document.prototype.replaceOne = function replaceOne() {
  797. const args = [...arguments];
  798. args.unshift({ _id: this._doc._id });
  799. return this.constructor.replaceOne.apply(this.constructor, args);
  800. };
  801. /**
  802. * Getter/setter around the session associated with this document. Used to
  803. * automatically set `session` if you `save()` a doc that you got from a
  804. * query with an associated session.
  805. *
  806. * #### Example:
  807. *
  808. * const session = MyModel.startSession();
  809. * const doc = await MyModel.findOne().session(session);
  810. * doc.$session() === session; // true
  811. * doc.$session(null);
  812. * doc.$session() === null; // true
  813. *
  814. * If this is a top-level document, setting the session propagates to all child
  815. * docs.
  816. *
  817. * @param {ClientSession} [session] overwrite the current session
  818. * @return {ClientSession}
  819. * @method $session
  820. * @api public
  821. * @memberOf Document
  822. */
  823. Document.prototype.$session = function $session(session) {
  824. if (arguments.length === 0) {
  825. if (this.$__.session?.hasEnded) {
  826. this.$__.session = null;
  827. return null;
  828. }
  829. return this.$__.session;
  830. }
  831. if (session?.hasEnded) {
  832. throw new MongooseError('Cannot set a document\'s session to a session that has ended. Make sure you haven\'t ' +
  833. 'called `endSession()` on the session you are passing to `$session()`.');
  834. }
  835. if (session == null && this.$__.session == null) {
  836. return;
  837. }
  838. this.$__.session = session;
  839. if (!this.$isSubdocument) {
  840. const subdocs = this.$getAllSubdocs();
  841. for (const child of subdocs) {
  842. child.$session(session);
  843. }
  844. }
  845. return session;
  846. };
  847. /**
  848. * Getter/setter around whether this document will apply timestamps by
  849. * default when using `save()` and `bulkSave()`.
  850. *
  851. * #### Example:
  852. *
  853. * const TestModel = mongoose.model('Test', new Schema({ name: String }, { timestamps: true }));
  854. * const doc = new TestModel({ name: 'John Smith' });
  855. *
  856. * doc.$timestamps(); // true
  857. *
  858. * doc.$timestamps(false);
  859. * await doc.save(); // Does **not** apply timestamps
  860. *
  861. * @param {Boolean} [value] overwrite the current session
  862. * @return {Document|boolean|undefined} When used as a getter (no argument), a boolean will be returned indicating the timestamps option state or if unset "undefined" will be used, otherwise will return "this"
  863. * @method $timestamps
  864. * @api public
  865. * @memberOf Document
  866. */
  867. Document.prototype.$timestamps = function $timestamps(value) {
  868. if (arguments.length === 0) {
  869. if (this.$__.timestamps != null) {
  870. return this.$__.timestamps;
  871. }
  872. if (this.$__schema) {
  873. return this.$__schema.options.timestamps;
  874. }
  875. return undefined;
  876. }
  877. const currentValue = this.$timestamps();
  878. if (value !== currentValue) {
  879. this.$__.timestamps = value;
  880. }
  881. return this;
  882. };
  883. /**
  884. * Overwrite all values in this document with the values of `obj`, except
  885. * for immutable properties. Behaves similarly to `set()`, except for it
  886. * unsets all properties that aren't in `obj`.
  887. *
  888. * @param {Object} obj the object to overwrite this document with
  889. * @method overwrite
  890. * @memberOf Document
  891. * @instance
  892. * @api public
  893. * @return {Document} this
  894. */
  895. Document.prototype.overwrite = function overwrite(obj) {
  896. const keys = new Set(Object.keys(this._doc));
  897. for (const key of Object.keys(obj)) {
  898. keys.add(key);
  899. }
  900. const schemaOptions = this.$__schema.options;
  901. for (const key of keys) {
  902. if (key === '_id') {
  903. continue;
  904. }
  905. // Explicitly skip version key
  906. if (schemaOptions.versionKey && key === schemaOptions.versionKey) {
  907. continue;
  908. }
  909. if (schemaOptions.discriminatorKey && key === schemaOptions.discriminatorKey) {
  910. continue;
  911. }
  912. this.$set(key, obj[key]);
  913. }
  914. return this;
  915. };
  916. /**
  917. * Alias for `set()`, used internally to avoid conflicts
  918. *
  919. * @param {String|Object} path path or object of key/vals to set
  920. * @param {Any} val the value to set
  921. * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
  922. * @param {Object} [options] optionally specify options that modify the behavior of the set
  923. * @param {Boolean} [options.merge=false] if true, setting a [nested path](https://mongoosejs.com/docs/subdocs.html#subdocuments-versus-nested-paths) will merge existing values rather than overwrite the whole object. So `doc.set('nested', { a: 1, b: 2 })` becomes `doc.set('nested.a', 1); doc.set('nested.b', 2);`
  924. * @return {Document} this
  925. * @method $set
  926. * @memberOf Document
  927. * @instance
  928. * @api public
  929. */
  930. Document.prototype.$set = function $set(path, val, type, options) {
  931. if (utils.isPOJO(type)) {
  932. options = type;
  933. type = undefined;
  934. }
  935. const merge = options?.merge;
  936. const adhoc = type && type !== true;
  937. const constructing = type === true;
  938. let adhocs;
  939. let keys;
  940. let i = 0;
  941. let pathtype;
  942. let key;
  943. let prefix;
  944. const userSpecifiedStrict = options && 'strict' in options;
  945. let strict = userSpecifiedStrict
  946. ? options.strict
  947. : this.$__.strictMode;
  948. if (adhoc) {
  949. adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
  950. adhocs[path] = this.$__schema.interpretAsType(path, type, this.$__schema.options);
  951. }
  952. if (path == null) {
  953. [path, val] = [val, path];
  954. } else if (typeof path !== 'string') {
  955. // new Document({ key: val })
  956. if (path instanceof Document) {
  957. if (path.$__isNested) {
  958. path = path.toObject();
  959. } else {
  960. // This ternary is to support gh-7898 (copying virtuals if same schema)
  961. // while not breaking gh-10819, which for some reason breaks if we use toObject()
  962. path = path.$__schema === this.$__schema
  963. ? applyVirtuals(path, { ...path._doc })
  964. : path._doc;
  965. }
  966. }
  967. if (path == null) {
  968. [path, val] = [val, path];
  969. }
  970. prefix = val ? val + '.' : '';
  971. keys = getKeysInSchemaOrder(this.$__schema, path);
  972. const len = keys.length;
  973. // `_skipMinimizeTopLevel` is because we may have deleted the top-level
  974. // nested key to ensure key order.
  975. const _skipMinimizeTopLevel = options?._skipMinimizeTopLevel || false;
  976. if (len === 0 && _skipMinimizeTopLevel) {
  977. delete options._skipMinimizeTopLevel;
  978. if (val) {
  979. this.$set(val, {});
  980. }
  981. return this;
  982. }
  983. options = Object.assign({}, options, { _skipMinimizeTopLevel: false });
  984. for (let i = 0; i < len; ++i) {
  985. key = keys[i];
  986. const pathName = prefix ? prefix + key : key;
  987. pathtype = this.$__schema.pathType(pathName);
  988. const valForKey = path[key];
  989. // On initial set, delete any nested keys if we're going to overwrite
  990. // them to ensure we keep the user's key order.
  991. if (type === true &&
  992. !prefix &&
  993. valForKey != null &&
  994. pathtype === 'nested' &&
  995. this._doc[key] != null) {
  996. delete this._doc[key];
  997. }
  998. if (utils.isNonBuiltinObject(valForKey) && pathtype === 'nested') {
  999. this.$set(pathName, valForKey, constructing, options);
  1000. $applyDefaultsToNested(this.$get(pathName), pathName, this);
  1001. continue;
  1002. } else if (strict) {
  1003. // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039)
  1004. if (constructing && valForKey === void 0 &&
  1005. this.$get(pathName) !== void 0) {
  1006. continue;
  1007. }
  1008. if (pathtype === 'adhocOrUndefined') {
  1009. pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true });
  1010. }
  1011. if (pathtype === 'real' || pathtype === 'virtual') {
  1012. this.$set(pathName, valForKey, constructing, options);
  1013. } else if (pathtype === 'nested' && valForKey instanceof Document) {
  1014. this.$set(pathName,
  1015. valForKey.toObject({ transform: false }), constructing, options);
  1016. } else if (strict === 'throw') {
  1017. if (pathtype === 'nested') {
  1018. throw new ObjectExpectedError(key, valForKey);
  1019. } else {
  1020. throw new StrictModeError(key);
  1021. }
  1022. } else if (pathtype === 'nested' && valForKey == null) {
  1023. this.$set(pathName, valForKey, constructing, options);
  1024. }
  1025. } else {
  1026. this.$set(pathName, valForKey, constructing, options);
  1027. }
  1028. }
  1029. // Ensure all properties are in correct order
  1030. const orderedDoc = {};
  1031. const orderedKeys = Object.keys(this.$__schema.tree);
  1032. for (let i = 0, len = orderedKeys.length; i < len; ++i) {
  1033. (key = orderedKeys[i]) &&
  1034. (Object.hasOwn(this._doc, key)) &&
  1035. (orderedDoc[key] = undefined);
  1036. }
  1037. this._doc = Object.assign(orderedDoc, this._doc);
  1038. return this;
  1039. }
  1040. let pathType = this.$__schema.pathType(path);
  1041. let parts = null;
  1042. if (pathType === 'adhocOrUndefined') {
  1043. parts = path.indexOf('.') === -1 ? [path] : path.split('.');
  1044. pathType = getEmbeddedDiscriminatorPath(this, parts, { typeOnly: true });
  1045. }
  1046. if (pathType === 'adhocOrUndefined' && !userSpecifiedStrict) {
  1047. // May be path underneath non-strict schema
  1048. if (parts == null) {
  1049. parts = path.indexOf('.') === -1 ? [path] : path.split('.');
  1050. }
  1051. const subdocStrict = getSubdocumentStrictValue(this.$__schema, parts);
  1052. if (subdocStrict !== undefined) {
  1053. strict = subdocStrict;
  1054. }
  1055. }
  1056. // Assume this is a Mongoose document that was copied into a POJO using
  1057. // `Object.assign()` or `{...doc}`
  1058. val = handleSpreadDoc(val, true);
  1059. // if this doc is being constructed we should not trigger getters
  1060. const priorVal = (() => {
  1061. if (this.$__.priorDoc != null) {
  1062. return this.$__.priorDoc.$__getValue(path);
  1063. }
  1064. if (constructing) {
  1065. return void 0;
  1066. }
  1067. return this.$__getValue(path);
  1068. })();
  1069. if (pathType === 'nested' && val) {
  1070. if (typeof val === 'object' && val != null) {
  1071. if (val.$__ != null) {
  1072. val = val.toObject(internalToObjectOptions);
  1073. }
  1074. if (val == null) {
  1075. this.invalidate(path, new MongooseError.CastError('Object', val, path));
  1076. return this;
  1077. }
  1078. const wasModified = this.$isModified(path);
  1079. const hasInitialVal = this.$__.savedState != null && Object.hasOwn(this.$__.savedState, path);
  1080. if (this.$__.savedState != null && !this.$isNew && !Object.hasOwn(this.$__.savedState, path)) {
  1081. const initialVal = this.$__getValue(path);
  1082. this.$__.savedState[path] = initialVal;
  1083. const keys = Object.keys(initialVal || {});
  1084. for (const key of keys) {
  1085. this.$__.savedState[path + '.' + key] = initialVal[key];
  1086. }
  1087. }
  1088. if (!merge) {
  1089. this.$__setValue(path, null);
  1090. cleanModifiedSubpaths(this, path);
  1091. } else {
  1092. return this.$set(val, path, constructing, options);
  1093. }
  1094. const keys = getKeysInSchemaOrder(this.$__schema, val, path);
  1095. this.$__setValue(path, {});
  1096. for (const key of keys) {
  1097. this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true });
  1098. }
  1099. if (priorVal != null &&
  1100. (!wasModified || hasInitialVal) &&
  1101. utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) {
  1102. this.unmarkModified(path);
  1103. } else {
  1104. this.markModified(path);
  1105. }
  1106. return this;
  1107. }
  1108. this.invalidate(path, new MongooseError.CastError('Object', val, path));
  1109. return this;
  1110. }
  1111. let schema;
  1112. if (parts == null) {
  1113. parts = path.indexOf('.') === -1 ? [path] : path.split('.');
  1114. }
  1115. // Might need to change path for top-level alias
  1116. if (typeof this.$__schema.aliases[parts[0]] === 'string') {
  1117. parts[0] = this.$__schema.aliases[parts[0]];
  1118. }
  1119. if (pathType === 'adhocOrUndefined' && strict) {
  1120. // check for roots that are Mixed types
  1121. let mixed;
  1122. for (i = 0; i < parts.length; ++i) {
  1123. const subpath = parts.slice(0, i + 1).join('.');
  1124. // If path is underneath a virtual, bypass everything and just set it.
  1125. if (i + 1 < parts.length && this.$__schema.pathType(subpath) === 'virtual') {
  1126. mpath.set(path, val, this);
  1127. return this;
  1128. }
  1129. schema = this.$__schema.path(subpath);
  1130. if (schema == null) {
  1131. continue;
  1132. }
  1133. if (schema instanceof MixedSchema) {
  1134. // allow changes to sub paths of mixed types
  1135. mixed = true;
  1136. break;
  1137. } else if (schema.$isSchemaMap && schema.$__schemaType instanceof MixedSchema && i < parts.length - 1) {
  1138. // Map of mixed and not the last element in the path resolves to mixed
  1139. mixed = true;
  1140. schema = schema.$__schemaType;
  1141. break;
  1142. }
  1143. }
  1144. if (schema == null) {
  1145. // Check for embedded discriminators
  1146. schema = getEmbeddedDiscriminatorPath(this, path);
  1147. }
  1148. if (!mixed && !schema) {
  1149. if (strict === 'throw') {
  1150. throw new StrictModeError(path);
  1151. }
  1152. return this;
  1153. }
  1154. } else if (pathType === 'virtual') {
  1155. schema = this.$__schema.virtualpath(path);
  1156. schema.applySetters(val, this);
  1157. return this;
  1158. } else {
  1159. schema = this.$__path(path);
  1160. }
  1161. // gh-4578, if setting a deeply nested path that doesn't exist yet, create it
  1162. let cur = this._doc;
  1163. let curPath = '';
  1164. for (i = 0; i < parts.length - 1; ++i) {
  1165. cur = cur instanceof Map ? cur.get(parts[i]) : cur[parts[i]];
  1166. curPath += (curPath.length !== 0 ? '.' : '') + parts[i];
  1167. if (!cur) {
  1168. this.$set(curPath, {});
  1169. // Hack re: gh-5800. If nested field is not selected, it probably exists
  1170. // so `MongoServerError: cannot use the part (nested of nested.num) to
  1171. // traverse the element ({nested: null})` is not likely. If user gets
  1172. // that error, its their fault for now. We should reconsider disallowing
  1173. // modifying not selected paths for 6.x
  1174. if (!this.$__isSelected(curPath)) {
  1175. this.unmarkModified(curPath);
  1176. }
  1177. cur = this.$__getValue(curPath);
  1178. }
  1179. }
  1180. let pathToMark;
  1181. // When using the $set operator the path to the field must already exist.
  1182. // Else mongodb throws: "LEFT_SUBFIELD only supports Object"
  1183. if (parts.length <= 1) {
  1184. pathToMark = path;
  1185. } else {
  1186. const len = parts.length;
  1187. for (i = 0; i < len; ++i) {
  1188. const subpath = parts.slice(0, i + 1).join('.');
  1189. if (this.$get(subpath, null, { getters: false }) === null) {
  1190. pathToMark = subpath;
  1191. break;
  1192. }
  1193. }
  1194. if (!pathToMark) {
  1195. pathToMark = path;
  1196. }
  1197. }
  1198. if (!schema) {
  1199. this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal);
  1200. if (pathType === 'nested' && val == null) {
  1201. cleanModifiedSubpaths(this, path);
  1202. }
  1203. return this;
  1204. }
  1205. // If overwriting a subdocument path, make sure to clear out
  1206. // any errors _before_ setting, so new errors that happen
  1207. // get persisted. Re: #9080
  1208. if (schema.$isSingleNested || schema.$isMongooseArray) {
  1209. _markValidSubpaths(this, path);
  1210. }
  1211. if (val != null && merge && schema.$isSingleNested) {
  1212. if (val instanceof Document) {
  1213. val = val.toObject({ virtuals: false, transform: false });
  1214. }
  1215. const keys = Object.keys(val);
  1216. for (const key of keys) {
  1217. this.$set(path + '.' + key, val[key], constructing, options);
  1218. }
  1219. return this;
  1220. }
  1221. let shouldSet = true;
  1222. try {
  1223. // If the user is trying to set a ref path to a document with
  1224. // the correct model name, treat it as populated
  1225. const refMatches = (() => {
  1226. if (schema.options == null) {
  1227. return false;
  1228. }
  1229. if (!(val instanceof Document)) {
  1230. return false;
  1231. }
  1232. const model = val.constructor;
  1233. // Check ref
  1234. const refOpt = typeof schema.options.ref === 'function' && !schema.options.ref[modelSymbol] ? schema.options.ref.call(this, this) : schema.options.ref;
  1235. const ref = refOpt?.modelName || refOpt;
  1236. if (ref != null && (ref === model.modelName || ref === model.baseModelName)) {
  1237. return true;
  1238. }
  1239. // Check refPath
  1240. const refPath = schema.options.refPath;
  1241. if (refPath == null) {
  1242. return false;
  1243. }
  1244. const modelName = val.get(refPath);
  1245. return modelName === model.modelName || modelName === model.baseModelName;
  1246. })();
  1247. let didPopulate = false;
  1248. if (refMatches && val instanceof Document && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) {
  1249. const unpopulatedValue = schema?.$isSingleNested ? schema.cast(val, this) : val._doc._id;
  1250. this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor });
  1251. val.$__.wasPopulated = { value: unpopulatedValue };
  1252. didPopulate = true;
  1253. }
  1254. let popOpts;
  1255. const typeKey = this.$__schema.options.typeKey;
  1256. if (schema.options &&
  1257. Array.isArray(schema.options[typeKey]) &&
  1258. schema.options[typeKey].length &&
  1259. schema.options[typeKey][0] &&
  1260. schema.options[typeKey][0].ref &&
  1261. _isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) {
  1262. popOpts = { [populateModelSymbol]: val[0].constructor };
  1263. this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts);
  1264. for (const doc of val) {
  1265. doc.$__.wasPopulated = { value: doc._doc._id };
  1266. }
  1267. didPopulate = true;
  1268. }
  1269. if (!refMatches || !schema.$isSingleNested || !val.$__) {
  1270. // If this path is underneath a single nested schema, we'll call the setter
  1271. // later in `$__set()` because we don't take `_doc` when we iterate through
  1272. // a single nested doc. That's to make sure we get the correct context.
  1273. // Otherwise we would double-call the setter, see gh-7196.
  1274. let setterContext = this;
  1275. if (this.$__schema.singleNestedPaths[path] != null && parts.length > 1) {
  1276. setterContext = getDeepestSubdocumentForPath(this, parts, this.schema);
  1277. }
  1278. if (options?.overwriteImmutable) {
  1279. val = schema.applySetters(val, setterContext, false, priorVal, { path, overwriteImmutable: true });
  1280. } else {
  1281. val = schema.applySetters(val, setterContext, false, priorVal, { path });
  1282. }
  1283. }
  1284. if (Array.isArray(val) &&
  1285. !Array.isArray(schema) &&
  1286. schema.$isMongooseDocumentArray &&
  1287. val.length !== 0 &&
  1288. val[0]?.$__?.populated != null) {
  1289. const populatedPaths = Object.keys(val[0].$__.populated);
  1290. for (const populatedPath of populatedPaths) {
  1291. this.$populated(path + '.' + populatedPath,
  1292. val.map(v => v.$populated(populatedPath)),
  1293. val[0].$__.populated[populatedPath].options);
  1294. }
  1295. didPopulate = true;
  1296. }
  1297. if (!didPopulate && this.$__.populated) {
  1298. // If this array partially contains populated documents, convert them
  1299. // all to ObjectIds re: #8443
  1300. if (Array.isArray(val) && this.$__.populated[path]) {
  1301. for (let i = 0; i < val.length; ++i) {
  1302. if (val[i] instanceof Document) {
  1303. val.set(i, val[i]._doc._id, true);
  1304. }
  1305. }
  1306. }
  1307. delete this.$__.populated[path];
  1308. }
  1309. if (val != null && schema.$isSingleNested) {
  1310. _checkImmutableSubpaths(val, schema, priorVal);
  1311. }
  1312. this.$markValid(path);
  1313. } catch (e) {
  1314. if (e instanceof MongooseError.StrictModeError && e.isImmutableError) {
  1315. this.invalidate(path, e);
  1316. } else if (e instanceof MongooseError.CastError) {
  1317. this.invalidate(e.path, e);
  1318. if (e.$originalErrorPath) {
  1319. this.invalidate(path,
  1320. new MongooseError.CastError(schema.instance, val, path, e.$originalErrorPath));
  1321. }
  1322. } else {
  1323. this.invalidate(path,
  1324. new MongooseError.CastError(schema.instance, val, path, e));
  1325. }
  1326. shouldSet = false;
  1327. }
  1328. if (shouldSet) {
  1329. let savedState = null;
  1330. let savedStatePath = null;
  1331. if (!constructing) {
  1332. const doc = this.$isSubdocument ? this.ownerDocument() : this;
  1333. savedState = doc.$__.savedState;
  1334. savedStatePath = this.$isSubdocument ? this.$__.fullPath + '.' + path : path;
  1335. doc.$__saveInitialState(savedStatePath);
  1336. }
  1337. this.$__set(pathToMark, path, options, constructing, parts, schema, val, priorVal);
  1338. const isInTransaction = !!this.$__.session?.transaction;
  1339. const isModifiedWithinTransaction = this.$__.session &&
  1340. this.$__.session[sessionNewDocuments] &&
  1341. this.$__.session[sessionNewDocuments].has(this) &&
  1342. this.$__.session[sessionNewDocuments].get(this).modifiedPaths &&
  1343. !this.$__.session[sessionNewDocuments].get(this).modifiedPaths.has(savedStatePath);
  1344. if (savedState != null &&
  1345. Object.hasOwn(savedState, savedStatePath) &&
  1346. (!isInTransaction || isModifiedWithinTransaction) &&
  1347. utils.deepEqual(val, savedState[savedStatePath])) {
  1348. this.unmarkModified(path);
  1349. }
  1350. }
  1351. if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) {
  1352. cleanModifiedSubpaths(this, path);
  1353. } else if (schema.$isSchemaMap && val == null) {
  1354. cleanModifiedSubpaths(this, path);
  1355. }
  1356. return this;
  1357. };
  1358. /*!
  1359. * ignore
  1360. */
  1361. function _isManuallyPopulatedArray(val, ref) {
  1362. if (!Array.isArray(val)) {
  1363. return false;
  1364. }
  1365. if (val.length === 0) {
  1366. return false;
  1367. }
  1368. for (const el of val) {
  1369. if (!(el instanceof Document)) {
  1370. return false;
  1371. }
  1372. const modelName = el.constructor.modelName;
  1373. if (modelName == null) {
  1374. return false;
  1375. }
  1376. if (el.constructor.modelName != ref && el.constructor.baseModelName != ref) {
  1377. return false;
  1378. }
  1379. }
  1380. return true;
  1381. }
  1382. /**
  1383. * Sets the value of a path, or many paths.
  1384. * Alias for [`.$set`](https://mongoosejs.com/docs/api/document.html#Document.prototype.$set()).
  1385. *
  1386. * #### Example:
  1387. *
  1388. * // path, value
  1389. * doc.set(path, value)
  1390. *
  1391. * // object
  1392. * doc.set({
  1393. * path : value
  1394. * , path2 : {
  1395. * path : value
  1396. * }
  1397. * })
  1398. *
  1399. * // on-the-fly cast to number
  1400. * doc.set(path, value, Number)
  1401. *
  1402. * // on-the-fly cast to string
  1403. * doc.set(path, value, String)
  1404. *
  1405. * // changing strict mode behavior
  1406. * doc.set(path, value, { strict: false });
  1407. *
  1408. * @param {String|Object} path path or object of key/vals to set
  1409. * @param {Any} val the value to set
  1410. * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes
  1411. * @param {Object} [options] optionally specify options that modify the behavior of the set
  1412. * @return {Document} this
  1413. * @api public
  1414. * @method set
  1415. * @memberOf Document
  1416. * @instance
  1417. */
  1418. Document.prototype.set = Document.prototype.$set;
  1419. /**
  1420. * Determine if we should mark this change as modified.
  1421. *
  1422. * @param {never} pathToMark UNUSED
  1423. * @param {String|Symbol} path
  1424. * @param {Object} options
  1425. * @param {Any} constructing
  1426. * @param {never} parts UNUSED
  1427. * @param {Schema} schema
  1428. * @param {Any} val
  1429. * @param {Any} priorVal
  1430. * @return {Boolean}
  1431. * @api private
  1432. * @method $__shouldModify
  1433. * @memberOf Document
  1434. * @instance
  1435. */
  1436. Document.prototype.$__shouldModify = function(pathToMark, path, options, constructing, parts, schema, val, priorVal) {
  1437. if (options?._skipMarkModified) {
  1438. return false;
  1439. }
  1440. if (this.$isNew) {
  1441. return true;
  1442. }
  1443. // Is path already modified? If so, always modify. We may unmark modified later.
  1444. if (path in this.$__.activePaths.getStatePaths('modify')) {
  1445. return true;
  1446. }
  1447. if (val === void 0 && !this.$__isSelected(path)) {
  1448. // when a path is not selected in a query, its initial
  1449. // value will be undefined.
  1450. return true;
  1451. }
  1452. if (val === void 0 && path in this.$__.activePaths.getStatePaths('default')) {
  1453. // we're just unsetting the default value which was never saved
  1454. return false;
  1455. }
  1456. // gh-3992: if setting a populated field to a doc, don't mark modified
  1457. // if they have the same _id
  1458. if (this.$populated(path) &&
  1459. val instanceof Document &&
  1460. deepEqual(val._doc._id, priorVal)) {
  1461. return false;
  1462. }
  1463. if (!deepEqual(val, priorVal !== undefined ? priorVal : utils.getValue(path, this))) {
  1464. return true;
  1465. }
  1466. if (!constructing &&
  1467. val !== null &&
  1468. val !== undefined &&
  1469. path in this.$__.activePaths.getStatePaths('default') &&
  1470. deepEqual(val, schema.getDefault(this, constructing))) {
  1471. // a path with a default was $unset on the server
  1472. // and the user is setting it to the same value again
  1473. return true;
  1474. }
  1475. return false;
  1476. };
  1477. /**
  1478. * Handles the actual setting of the value and marking the path modified if appropriate.
  1479. *
  1480. * @param {String} pathToMark
  1481. * @param {String|Symbol} path
  1482. * @param {Object} options
  1483. * @param {Any} constructing
  1484. * @param {Array} parts
  1485. * @param {Schema} schema
  1486. * @param {Any} val
  1487. * @param {Any} priorVal
  1488. * @api private
  1489. * @method $__set
  1490. * @memberOf Document
  1491. * @instance
  1492. */
  1493. Document.prototype.$__set = function(pathToMark, path, options, constructing, parts, schema, val, priorVal) {
  1494. Embedded = Embedded || require('./types/arraySubdocument');
  1495. const shouldModify = this.$__shouldModify(pathToMark, path, options, constructing, parts,
  1496. schema, val, priorVal);
  1497. if (shouldModify) {
  1498. if (this.$__.primitiveAtomics?.[path]) {
  1499. delete this.$__.primitiveAtomics[path];
  1500. if (utils.hasOwnKeys(this.$__.primitiveAtomics) === false) {
  1501. delete this.$__.primitiveAtomics;
  1502. }
  1503. }
  1504. this.markModified(pathToMark);
  1505. // handle directly setting arrays (gh-1126)
  1506. MongooseArray || (MongooseArray = require('./types/array'));
  1507. if (val && utils.isMongooseArray(val)) {
  1508. val._registerAtomic('$set', val);
  1509. // Update embedded document parent references (gh-5189)
  1510. if (utils.isMongooseDocumentArray(val)) {
  1511. val.forEach(function(item) {
  1512. item && item.__parentArray && (item.__parentArray = val);
  1513. });
  1514. }
  1515. }
  1516. } else if (Array.isArray(val) && Array.isArray(priorVal) && utils.isMongooseArray(val) && utils.isMongooseArray(priorVal)) {
  1517. val[arrayAtomicsSymbol] = priorVal[arrayAtomicsSymbol];
  1518. val[arrayAtomicsBackupSymbol] = priorVal[arrayAtomicsBackupSymbol];
  1519. if (utils.isMongooseDocumentArray(val)) {
  1520. val.forEach(doc => {
  1521. if (doc != null) {
  1522. doc.$isNew = false;
  1523. }
  1524. });
  1525. }
  1526. }
  1527. let obj = this._doc;
  1528. let i = 0;
  1529. const l = parts.length;
  1530. let cur = '';
  1531. for (; i < l; i++) {
  1532. const next = i + 1;
  1533. const last = next === l;
  1534. cur += (cur ? '.' + parts[i] : parts[i]);
  1535. if (specialProperties.has(parts[i])) {
  1536. continue;
  1537. }
  1538. if (last) {
  1539. if (obj instanceof Map) {
  1540. obj.set(parts[i], val);
  1541. } else if (obj.$isSingleNested) {
  1542. if (!(parts[i] in obj)) {
  1543. obj[parts[i]] = val;
  1544. obj._doc[parts[i]] = val;
  1545. } else {
  1546. obj._doc[parts[i]] = val;
  1547. }
  1548. if (shouldModify) {
  1549. obj.markModified(parts[i]);
  1550. }
  1551. } else {
  1552. obj[parts[i]] = val;
  1553. }
  1554. } else {
  1555. const isMap = obj instanceof Map;
  1556. let value = isMap ? obj.get(parts[i]) : obj[parts[i]];
  1557. if (utils.isPOJO(value)) {
  1558. obj = value;
  1559. } else if (value && value instanceof Embedded) {
  1560. obj = value;
  1561. } else if (value && !Array.isArray(value) && value.$isSingleNested) {
  1562. obj = value;
  1563. } else if (value && Array.isArray(value)) {
  1564. obj = value;
  1565. } else if (value == null) {
  1566. value = {};
  1567. if (isMap) {
  1568. obj.set(parts[i], value);
  1569. } else {
  1570. obj[parts[i]] = value;
  1571. }
  1572. obj = value;
  1573. } else {
  1574. obj = value;
  1575. }
  1576. }
  1577. }
  1578. };
  1579. /**
  1580. * Gets a raw value from a path (no getters)
  1581. *
  1582. * @param {String} path
  1583. * @return {Any} Returns the value from the given `path`.
  1584. * @api private
  1585. */
  1586. Document.prototype.$__getValue = function(path) {
  1587. if (typeof path !== 'string' && !Array.isArray(path)) {
  1588. throw new TypeError(
  1589. `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})`
  1590. );
  1591. }
  1592. return utils.getValue(path, this._doc);
  1593. };
  1594. /**
  1595. * Increments the numeric value at `path` by the given `val`.
  1596. * When you call `save()` on this document, Mongoose will send a
  1597. * [`$inc`](https://www.mongodb.com/docs/manual/reference/operator/update/inc/)
  1598. * as opposed to a `$set`.
  1599. *
  1600. * #### Example:
  1601. *
  1602. * const schema = new Schema({ counter: Number });
  1603. * const Test = db.model('Test', schema);
  1604. *
  1605. * const doc = await Test.create({ counter: 0 });
  1606. * doc.$inc('counter', 2);
  1607. * await doc.save(); // Sends a `{ $inc: { counter: 2 } }` to MongoDB
  1608. * doc.counter; // 2
  1609. *
  1610. * doc.counter += 2;
  1611. * await doc.save(); // Sends a `{ $set: { counter: 2 } }` to MongoDB
  1612. *
  1613. * @param {String|Array} path path or paths to update
  1614. * @param {Number} val increment `path` by this value
  1615. * @return {Document} this
  1616. */
  1617. Document.prototype.$inc = function $inc(path, val) {
  1618. if (val == null) {
  1619. val = 1;
  1620. }
  1621. if (Array.isArray(path)) {
  1622. path.forEach((p) => this.$inc(p, val));
  1623. return this;
  1624. }
  1625. const schemaType = this.$__path(path);
  1626. if (schemaType == null) {
  1627. if (this.$__.strictMode === 'throw') {
  1628. throw new StrictModeError(path);
  1629. } else if (this.$__.strictMode === true) {
  1630. return this;
  1631. }
  1632. } else if (schemaType.instance !== 'Number') {
  1633. this.invalidate(path, new MongooseError.CastError(schemaType.instance, val, path));
  1634. return this;
  1635. }
  1636. const currentValue = this.$__getValue(path) || 0;
  1637. let shouldSet = false;
  1638. let valToSet = null;
  1639. let valToInc = val;
  1640. try {
  1641. val = schemaType.cast(val);
  1642. valToSet = schemaType.applySetters(currentValue + val, this);
  1643. valToInc = valToSet - currentValue;
  1644. shouldSet = true;
  1645. } catch (err) {
  1646. this.invalidate(path, new MongooseError.CastError('number', val, path, err));
  1647. }
  1648. if (shouldSet) {
  1649. this.$__.primitiveAtomics = this.$__.primitiveAtomics || {};
  1650. if (this.$__.primitiveAtomics[path] == null) {
  1651. this.$__.primitiveAtomics[path] = { $inc: valToInc };
  1652. } else {
  1653. this.$__.primitiveAtomics[path].$inc += valToInc;
  1654. }
  1655. this.markModified(path);
  1656. this.$__setValue(path, valToSet);
  1657. }
  1658. return this;
  1659. };
  1660. /**
  1661. * Sets a raw value for a path (no casting, setters, transformations)
  1662. *
  1663. * @param {String} path
  1664. * @param {Object} value
  1665. * @return {Document} this
  1666. * @api private
  1667. */
  1668. Document.prototype.$__setValue = function(path, val) {
  1669. utils.setValue(path, val, this._doc);
  1670. return this;
  1671. };
  1672. /**
  1673. * Returns the value of a path.
  1674. *
  1675. * #### Example:
  1676. *
  1677. * // path
  1678. * doc.get('age') // 47
  1679. *
  1680. * // dynamic casting to a string
  1681. * doc.get('age', String) // "47"
  1682. *
  1683. * @param {String} path
  1684. * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for on-the-fly attributes
  1685. * @param {Object} [options]
  1686. * @param {Boolean} [options.virtuals=false] Apply virtuals before getting this path
  1687. * @param {Boolean} [options.getters=true] If false, skip applying getters and just get the raw value
  1688. * @return {Any}
  1689. * @api public
  1690. */
  1691. Document.prototype.get = function(path, type, options) {
  1692. let adhoc;
  1693. if (options == null) {
  1694. options = {};
  1695. }
  1696. if (type) {
  1697. adhoc = this.$__schema.interpretAsType(path, type, this.$__schema.options);
  1698. }
  1699. const noDottedPath = options.noDottedPath;
  1700. // Fast path if we know we're just accessing top-level path on the document:
  1701. // just get the schema path, avoid `$__path()` because that does string manipulation
  1702. let schema = noDottedPath ? this.$__schema.paths[path] : this.$__path(path);
  1703. if (schema == null) {
  1704. schema = this.$__schema.virtualpath(path);
  1705. if (schema != null) {
  1706. return schema.applyGetters(void 0, this);
  1707. }
  1708. }
  1709. if (noDottedPath) {
  1710. let obj = this._doc[path];
  1711. if (adhoc) {
  1712. obj = adhoc.cast(obj);
  1713. }
  1714. if (schema != null && options.getters !== false) {
  1715. return schema.applyGetters(obj, this);
  1716. }
  1717. return obj;
  1718. }
  1719. if (schema?.instance === 'Mixed') {
  1720. const virtual = this.$__schema.virtualpath(path);
  1721. if (virtual != null) {
  1722. schema = virtual;
  1723. }
  1724. }
  1725. const hasDot = path.indexOf('.') !== -1;
  1726. let obj = this._doc;
  1727. const pieces = hasDot ? path.split('.') : [path];
  1728. // Might need to change path for top-level alias
  1729. if (typeof this.$__schema.aliases[pieces[0]] === 'string') {
  1730. pieces[0] = this.$__schema.aliases[pieces[0]];
  1731. }
  1732. for (let i = 0, l = pieces.length; i < l; i++) {
  1733. if (obj?._doc) {
  1734. obj = obj._doc;
  1735. }
  1736. if (obj == null) {
  1737. obj = void 0;
  1738. } else if (obj instanceof Map) {
  1739. obj = obj.get(pieces[i], { getters: false });
  1740. } else if (i === l - 1) {
  1741. obj = utils.getValue(pieces[i], obj);
  1742. } else {
  1743. obj = obj[pieces[i]];
  1744. }
  1745. }
  1746. if (adhoc) {
  1747. obj = adhoc.cast(obj);
  1748. }
  1749. if (schema != null && options.getters !== false) {
  1750. obj = schema.applyGetters(obj, this);
  1751. } else if (this.$__schema.nested[path] && options.virtuals) {
  1752. // Might need to apply virtuals if this is a nested path
  1753. return applyVirtuals(this, clone(obj) || {}, { path: path });
  1754. }
  1755. return obj;
  1756. };
  1757. /*!
  1758. * ignore
  1759. */
  1760. Document.prototype[getSymbol] = Document.prototype.get;
  1761. Document.prototype.$get = Document.prototype.get;
  1762. /**
  1763. * Returns the schematype for the given `path`.
  1764. *
  1765. * @param {String} path
  1766. * @return {SchemaPath}
  1767. * @api private
  1768. * @method $__path
  1769. * @memberOf Document
  1770. * @instance
  1771. */
  1772. Document.prototype.$__path = function(path) {
  1773. const adhocs = this.$__.adhocPaths;
  1774. const adhocType = adhocs && Object.hasOwn(adhocs, path) ? adhocs[path] : null;
  1775. if (adhocType) {
  1776. return adhocType;
  1777. }
  1778. return this.$__schema.path(path);
  1779. };
  1780. /**
  1781. * Marks the path as having pending changes to write to the db.
  1782. *
  1783. * _Very helpful when using [Mixed](https://mongoosejs.com/docs/schematypes.html#mixed) types._
  1784. *
  1785. * #### Example:
  1786. *
  1787. * doc.mixed.type = 'changed';
  1788. * doc.markModified('mixed.type');
  1789. * doc.save() // changes to mixed.type are now persisted
  1790. *
  1791. * @param {String} path the path to mark modified
  1792. * @param {Document} [scope] the scope to run validators with
  1793. * @api public
  1794. */
  1795. Document.prototype.markModified = function(path, scope) {
  1796. this.$__saveInitialState(path);
  1797. this.$__.activePaths.modify(path);
  1798. if (scope != null && !this.$isSubdocument) {
  1799. this.$__.pathsToScopes = this.$__pathsToScopes || {};
  1800. this.$__.pathsToScopes[path] = scope;
  1801. }
  1802. };
  1803. /*!
  1804. * ignore
  1805. */
  1806. Document.prototype.$__saveInitialState = function $__saveInitialState(path) {
  1807. const savedState = this.$__.savedState;
  1808. const savedStatePath = path;
  1809. if (savedState != null) {
  1810. const firstDot = savedStatePath.indexOf('.');
  1811. const topLevelPath = firstDot === -1 ? savedStatePath : savedStatePath.slice(0, firstDot);
  1812. if (!Object.hasOwn(savedState, topLevelPath)) {
  1813. savedState[topLevelPath] = clone(this.$__getValue(topLevelPath));
  1814. }
  1815. }
  1816. };
  1817. /**
  1818. * Clears the modified state on the specified path.
  1819. *
  1820. * #### Example:
  1821. *
  1822. * doc.foo = 'bar';
  1823. * doc.unmarkModified('foo');
  1824. * doc.save(); // changes to foo will not be persisted
  1825. *
  1826. * @param {String} path the path to unmark modified
  1827. * @api public
  1828. */
  1829. Document.prototype.unmarkModified = function(path) {
  1830. this.$__.activePaths.init(path);
  1831. if (this.$__.pathsToScopes != null) {
  1832. delete this.$__.pathsToScopes[path];
  1833. }
  1834. };
  1835. /**
  1836. * Don't run validation on this path or persist changes to this path.
  1837. *
  1838. * #### Example:
  1839. *
  1840. * doc.foo = null;
  1841. * doc.$ignore('foo');
  1842. * doc.save(); // changes to foo will not be persisted and validators won't be run
  1843. *
  1844. * @memberOf Document
  1845. * @instance
  1846. * @method $ignore
  1847. * @param {String} path the path to ignore
  1848. * @api public
  1849. */
  1850. Document.prototype.$ignore = function(path) {
  1851. this.$__.activePaths.ignore(path);
  1852. };
  1853. /**
  1854. * Returns the list of paths that have been directly modified. A direct
  1855. * modified path is a path that you explicitly set, whether via `doc.foo = 'bar'`,
  1856. * `Object.assign(doc, { foo: 'bar' })`, or `doc.set('foo', 'bar')`.
  1857. *
  1858. * A path `a` may be in `modifiedPaths()` but not in `directModifiedPaths()`
  1859. * because a child of `a` was directly modified.
  1860. *
  1861. * #### Example:
  1862. *
  1863. * const schema = new Schema({ foo: String, nested: { bar: String } });
  1864. * const Model = mongoose.model('Test', schema);
  1865. * await Model.create({ foo: 'original', nested: { bar: 'original' } });
  1866. *
  1867. * const doc = await Model.findOne();
  1868. * doc.nested.bar = 'modified';
  1869. * doc.directModifiedPaths(); // ['nested.bar']
  1870. * doc.modifiedPaths(); // ['nested', 'nested.bar']
  1871. *
  1872. * @return {String[]}
  1873. * @api public
  1874. */
  1875. Document.prototype.directModifiedPaths = function() {
  1876. return Object.keys(this.$__.activePaths.getStatePaths('modify'));
  1877. };
  1878. /**
  1879. * Returns true if the given path is nullish or only contains empty objects.
  1880. * Useful for determining whether this subdoc will get stripped out by the
  1881. * [minimize option](https://mongoosejs.com/docs/guide.html#minimize).
  1882. *
  1883. * #### Example:
  1884. *
  1885. * const schema = new Schema({ nested: { foo: String } });
  1886. * const Model = mongoose.model('Test', schema);
  1887. * const doc = new Model({});
  1888. * doc.$isEmpty('nested'); // true
  1889. * doc.nested.$isEmpty(); // true
  1890. *
  1891. * doc.nested.foo = 'bar';
  1892. * doc.$isEmpty('nested'); // false
  1893. * doc.nested.$isEmpty(); // false
  1894. *
  1895. * @param {String} [path]
  1896. * @memberOf Document
  1897. * @instance
  1898. * @api public
  1899. * @method $isEmpty
  1900. * @return {Boolean}
  1901. */
  1902. Document.prototype.$isEmpty = function(path) {
  1903. const isEmptyOptions = {
  1904. minimize: true,
  1905. virtuals: false,
  1906. getters: false,
  1907. transform: false
  1908. };
  1909. if (arguments.length !== 0) {
  1910. const v = this.$get(path);
  1911. if (v == null) {
  1912. return true;
  1913. }
  1914. if (typeof v !== 'object') {
  1915. return false;
  1916. }
  1917. if (utils.isPOJO(v)) {
  1918. return _isEmpty(v);
  1919. }
  1920. return Object.keys(v.toObject(isEmptyOptions)).length === 0;
  1921. }
  1922. return Object.keys(this.toObject(isEmptyOptions)).length === 0;
  1923. };
  1924. /*!
  1925. * ignore
  1926. */
  1927. function _isEmpty(v) {
  1928. if (v == null) {
  1929. return true;
  1930. }
  1931. if (typeof v !== 'object' || Array.isArray(v)) {
  1932. return false;
  1933. }
  1934. for (const key of Object.keys(v)) {
  1935. if (!_isEmpty(v[key])) {
  1936. return false;
  1937. }
  1938. }
  1939. return true;
  1940. }
  1941. /**
  1942. * Returns the list of paths that have been modified.
  1943. *
  1944. * @param {Object} [options]
  1945. * @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`.
  1946. * @return {String[]}
  1947. * @api public
  1948. */
  1949. Document.prototype.modifiedPaths = function(options) {
  1950. options = options || {};
  1951. const directModifiedPaths = Object.keys(this.$__.activePaths.getStatePaths('modify'));
  1952. const result = new Set();
  1953. let i = 0;
  1954. let j = 0;
  1955. const len = directModifiedPaths.length;
  1956. for (i = 0; i < len; ++i) {
  1957. const path = directModifiedPaths[i];
  1958. const parts = parentPaths(path);
  1959. const pLen = parts.length;
  1960. for (j = 0; j < pLen; ++j) {
  1961. result.add(parts[j]);
  1962. }
  1963. if (!options.includeChildren) {
  1964. continue;
  1965. }
  1966. let ii = 0;
  1967. let cur = this.$get(path);
  1968. if (typeof cur === 'object' && cur !== null) {
  1969. if (cur._doc) {
  1970. cur = cur._doc;
  1971. }
  1972. const len = cur.length;
  1973. if (Array.isArray(cur)) {
  1974. for (ii = 0; ii < len; ++ii) {
  1975. const subPath = path + '.' + ii;
  1976. if (!result.has(subPath)) {
  1977. result.add(subPath);
  1978. if (cur[ii] != null && cur[ii].$__) {
  1979. const modified = cur[ii].modifiedPaths();
  1980. let iii = 0;
  1981. const iiiLen = modified.length;
  1982. for (iii = 0; iii < iiiLen; ++iii) {
  1983. result.add(subPath + '.' + modified[iii]);
  1984. }
  1985. }
  1986. }
  1987. }
  1988. } else {
  1989. const keys = Object.keys(cur);
  1990. let ii = 0;
  1991. const len = keys.length;
  1992. for (ii = 0; ii < len; ++ii) {
  1993. result.add(path + '.' + keys[ii]);
  1994. }
  1995. }
  1996. }
  1997. }
  1998. return Array.from(result);
  1999. };
  2000. Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths;
  2001. /**
  2002. * Returns true if any of the given paths is modified, else false. If no arguments, returns `true` if any path
  2003. * in this document is modified.
  2004. *
  2005. * If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified.
  2006. *
  2007. * #### Example:
  2008. *
  2009. * doc.set('documents.0.title', 'changed');
  2010. * doc.isModified() // true
  2011. * doc.isModified('documents') // true
  2012. * doc.isModified('documents.0.title') // true
  2013. * doc.isModified('documents otherProp') // true
  2014. * doc.isDirectModified('documents') // false
  2015. *
  2016. * @param {String} [path] optional
  2017. * @param {Object} [options]
  2018. * @param {Boolean} [options.ignoreAtomics=false] If true, doesn't return true if path is underneath an array that was modified with atomic operations like `push()`
  2019. * @return {Boolean}
  2020. * @api public
  2021. */
  2022. Document.prototype.isModified = function(paths, options, modifiedPaths) {
  2023. if (paths) {
  2024. const ignoreAtomics = options?.ignoreAtomics;
  2025. const directModifiedPathsObj = this.$__.activePaths.states.modify;
  2026. if (directModifiedPathsObj == null) {
  2027. return false;
  2028. }
  2029. if (typeof paths === 'string') {
  2030. paths = paths.indexOf(' ') === -1 ? [paths] : paths.split(' ');
  2031. }
  2032. for (const path of paths) {
  2033. if (directModifiedPathsObj[path] != null) {
  2034. return true;
  2035. }
  2036. }
  2037. const modified = modifiedPaths || this[documentModifiedPaths]();
  2038. const isModifiedChild = paths.some(function(path) {
  2039. return !!~modified.indexOf(path);
  2040. });
  2041. let directModifiedPaths = Object.keys(directModifiedPathsObj);
  2042. if (ignoreAtomics) {
  2043. directModifiedPaths = directModifiedPaths.filter(path => {
  2044. const value = this.$__getValue(path);
  2045. if (value?.[arrayAtomicsSymbol] != null && value[arrayAtomicsSymbol].$set === undefined) {
  2046. return false;
  2047. }
  2048. return true;
  2049. });
  2050. }
  2051. return isModifiedChild || paths.some(function(path) {
  2052. return directModifiedPaths.some(function(mod) {
  2053. return mod === path || path.startsWith(mod + '.');
  2054. });
  2055. });
  2056. }
  2057. return this.$__.activePaths.some('modify');
  2058. };
  2059. /**
  2060. * Alias of [`.isModified`](https://mongoosejs.com/docs/api/document.html#Document.prototype.isModified())
  2061. *
  2062. * @method $isModified
  2063. * @memberOf Document
  2064. * @api public
  2065. */
  2066. Document.prototype.$isModified = Document.prototype.isModified;
  2067. Document.prototype[documentIsModified] = Document.prototype.isModified;
  2068. /**
  2069. * Checks if a path is set to its default.
  2070. *
  2071. * #### Example:
  2072. *
  2073. * MyModel = mongoose.model('test', { name: { type: String, default: 'Val '} });
  2074. * const m = new MyModel();
  2075. * m.$isDefault('name'); // true
  2076. *
  2077. * @memberOf Document
  2078. * @instance
  2079. * @method $isDefault
  2080. * @param {String} [path]
  2081. * @return {Boolean}
  2082. * @api public
  2083. */
  2084. Document.prototype.$isDefault = function(path) {
  2085. if (path == null) {
  2086. return this.$__.activePaths.some('default');
  2087. }
  2088. if (typeof path === 'string' && path.indexOf(' ') === -1) {
  2089. return Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path);
  2090. }
  2091. let paths = path;
  2092. if (!Array.isArray(paths)) {
  2093. paths = paths.split(' ');
  2094. }
  2095. return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('default'), path));
  2096. };
  2097. /**
  2098. * Getter/setter, determines whether the document was deleted. The `Model.prototype.deleteOne()` method sets `$isDeleted` if the delete operation succeeded.
  2099. *
  2100. * #### Example:
  2101. *
  2102. * const product = await product.deleteOne();
  2103. * product.$isDeleted(); // true
  2104. * product.deleteOne(); // no-op, doesn't send anything to the db
  2105. *
  2106. * product.$isDeleted(false);
  2107. * product.$isDeleted(); // false
  2108. * product.deleteOne(); // will execute a remove against the db
  2109. *
  2110. *
  2111. * @param {Boolean} [val] optional, overrides whether mongoose thinks the doc is deleted
  2112. * @return {Boolean|Document} whether mongoose thinks this doc is deleted.
  2113. * @method $isDeleted
  2114. * @memberOf Document
  2115. * @instance
  2116. * @api public
  2117. */
  2118. Document.prototype.$isDeleted = function(val) {
  2119. if (arguments.length === 0) {
  2120. return !!this.$__.isDeleted;
  2121. }
  2122. this.$__.isDeleted = !!val;
  2123. return this;
  2124. };
  2125. /**
  2126. * Returns true if `path` was directly set and modified, else false.
  2127. *
  2128. * #### Example:
  2129. *
  2130. * doc.set('documents.0.title', 'changed');
  2131. * doc.isDirectModified('documents.0.title') // true
  2132. * doc.isDirectModified('documents') // false
  2133. *
  2134. * @param {String|String[]} [path]
  2135. * @return {Boolean}
  2136. * @api public
  2137. */
  2138. Document.prototype.isDirectModified = function(path) {
  2139. if (path == null) {
  2140. return this.$__.activePaths.some('modify');
  2141. }
  2142. if (typeof path === 'string' && path.indexOf(' ') === -1) {
  2143. const res = Object.hasOwn(this.$__.activePaths.getStatePaths('modify'), path);
  2144. if (res || path.indexOf('.') === -1) {
  2145. return res;
  2146. }
  2147. const pieces = path.split('.');
  2148. for (let i = 0; i < pieces.length - 1; ++i) {
  2149. const subpath = pieces.slice(0, i + 1).join('.');
  2150. const subdoc = this.$get(subpath);
  2151. if (subdoc?.$__ != null && subdoc.isDirectModified(pieces.slice(i + 1).join('.'))) {
  2152. return true;
  2153. }
  2154. }
  2155. return false;
  2156. }
  2157. let paths = path;
  2158. if (typeof paths === 'string') {
  2159. paths = paths.split(' ');
  2160. }
  2161. return paths.some(path => this.isDirectModified(path));
  2162. };
  2163. /**
  2164. * Checks if `path` is in the `init` state, that is, it was set by `Document#init()` and not modified since.
  2165. *
  2166. * @param {String} [path]
  2167. * @return {Boolean}
  2168. * @api public
  2169. */
  2170. Document.prototype.isInit = function(path) {
  2171. if (path == null) {
  2172. return this.$__.activePaths.some('init');
  2173. }
  2174. if (typeof path === 'string' && path.indexOf(' ') === -1) {
  2175. return Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path);
  2176. }
  2177. let paths = path;
  2178. if (!Array.isArray(paths)) {
  2179. paths = paths.split(' ');
  2180. }
  2181. return paths.some(path => Object.hasOwn(this.$__.activePaths.getStatePaths('init'), path));
  2182. };
  2183. /**
  2184. * Checks if `path` was selected in the source query which initialized this document.
  2185. *
  2186. * #### Example:
  2187. *
  2188. * const doc = await Thing.findOne().select('name');
  2189. * doc.isSelected('name') // true
  2190. * doc.isSelected('age') // false
  2191. *
  2192. * @param {String|String[]} path
  2193. * @return {Boolean}
  2194. * @api public
  2195. */
  2196. Document.prototype.isSelected = function isSelected(path) {
  2197. if (this.$__.selected == null) {
  2198. return true;
  2199. }
  2200. if (!path) {
  2201. return false;
  2202. }
  2203. if (path === '_id') {
  2204. return this.$__.selected._id !== 0;
  2205. }
  2206. if (path.indexOf(' ') !== -1) {
  2207. path = path.split(' ');
  2208. }
  2209. if (Array.isArray(path)) {
  2210. return path.some(p => this.$__isSelected(p));
  2211. }
  2212. const paths = Object.keys(this.$__.selected);
  2213. let inclusive = null;
  2214. if (paths.length === 1 && paths[0] === '_id') {
  2215. // only _id was selected.
  2216. return this.$__.selected._id === 0;
  2217. }
  2218. for (const cur of paths) {
  2219. if (cur === '_id') {
  2220. continue;
  2221. }
  2222. if (!isDefiningProjection(this.$__.selected[cur])) {
  2223. continue;
  2224. }
  2225. inclusive = !!this.$__.selected[cur];
  2226. break;
  2227. }
  2228. if (inclusive === null) {
  2229. return true;
  2230. }
  2231. if (path in this.$__.selected) {
  2232. return inclusive;
  2233. }
  2234. const pathDot = path + '.';
  2235. for (const cur of paths) {
  2236. if (cur === '_id') {
  2237. continue;
  2238. }
  2239. if (cur.startsWith(pathDot)) {
  2240. return inclusive || cur !== pathDot;
  2241. }
  2242. if (pathDot.startsWith(cur + '.')) {
  2243. return inclusive;
  2244. }
  2245. }
  2246. return !inclusive;
  2247. };
  2248. Document.prototype.$__isSelected = Document.prototype.isSelected;
  2249. /**
  2250. * Checks if `path` was explicitly selected. If no projection, always returns
  2251. * true.
  2252. *
  2253. * #### Example:
  2254. *
  2255. * Thing.findOne().select('nested.name').exec(function (err, doc) {
  2256. * doc.isDirectSelected('nested.name') // true
  2257. * doc.isDirectSelected('nested.otherName') // false
  2258. * doc.isDirectSelected('nested') // false
  2259. * })
  2260. *
  2261. * @param {String} path
  2262. * @return {Boolean}
  2263. * @api public
  2264. */
  2265. Document.prototype.isDirectSelected = function isDirectSelected(path) {
  2266. if (this.$__.selected == null) {
  2267. return true;
  2268. }
  2269. if (path === '_id') {
  2270. return this.$__.selected._id !== 0;
  2271. }
  2272. if (path.indexOf(' ') !== -1) {
  2273. path = path.split(' ');
  2274. }
  2275. if (Array.isArray(path)) {
  2276. return path.some(p => this.isDirectSelected(p));
  2277. }
  2278. const paths = Object.keys(this.$__.selected);
  2279. let inclusive = null;
  2280. if (paths.length === 1 && paths[0] === '_id') {
  2281. // only _id was selected.
  2282. return this.$__.selected._id === 0;
  2283. }
  2284. for (const cur of paths) {
  2285. if (cur === '_id') {
  2286. continue;
  2287. }
  2288. if (!isDefiningProjection(this.$__.selected[cur])) {
  2289. continue;
  2290. }
  2291. inclusive = !!this.$__.selected[cur];
  2292. break;
  2293. }
  2294. if (inclusive === null) {
  2295. return true;
  2296. }
  2297. if (Object.hasOwn(this.$__.selected, path)) {
  2298. return inclusive;
  2299. }
  2300. return !inclusive;
  2301. };
  2302. /**
  2303. * Executes registered validation rules for this document.
  2304. *
  2305. * #### Note:
  2306. *
  2307. * This method is called `pre` save and if a validation rule is violated, [save](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) is aborted and the error is thrown.
  2308. *
  2309. * #### Example:
  2310. *
  2311. * await doc.validate({ validateModifiedOnly: false, pathsToSkip: ['name', 'email']});
  2312. *
  2313. * @param {Array|String} [pathsToValidate] list of paths to validate. If set, Mongoose will validate only the modified paths that are in the given list.
  2314. * @param {Object} [options] internal options
  2315. * @param {Boolean} [options.validateModifiedOnly=false] if `true` mongoose validates only modified paths.
  2316. * @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
  2317. * @return {Promise} Returns a Promise.
  2318. * @api public
  2319. */
  2320. Document.prototype.validate = async function validate(pathsToValidate, options) {
  2321. if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') {
  2322. throw new MongooseError('Document.prototype.validate() no longer accepts a callback');
  2323. }
  2324. this.$op = 'validate';
  2325. if (arguments.length === 1) {
  2326. if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) {
  2327. options = arguments[0];
  2328. pathsToValidate = null;
  2329. }
  2330. }
  2331. if (options && typeof options.pathsToSkip === 'string') {
  2332. const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1;
  2333. options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' ');
  2334. }
  2335. const _skipParallelValidateCheck = options?._skipParallelValidateCheck;
  2336. if (this.$isSubdocument != null) {
  2337. // Skip parallel validate check for subdocuments
  2338. } else if (this.$__.validating && !_skipParallelValidateCheck) {
  2339. throw new ParallelValidateError(this);
  2340. } else if (!_skipParallelValidateCheck) {
  2341. this.$__.validating = true;
  2342. }
  2343. try {
  2344. await this.$__validate(pathsToValidate, options);
  2345. } finally {
  2346. this.$op = null;
  2347. this.$__.validating = null;
  2348. }
  2349. };
  2350. /**
  2351. * Alias of [`.validate`](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate())
  2352. *
  2353. * @method $validate
  2354. * @memberOf Document
  2355. * @api public
  2356. */
  2357. Document.prototype.$validate = Document.prototype.validate;
  2358. /*!
  2359. * ignore
  2360. */
  2361. function _evaluateRequiredFunctions(doc) {
  2362. const requiredFields = Object.keys(doc.$__.activePaths.getStatePaths('require'));
  2363. let i = 0;
  2364. const len = requiredFields.length;
  2365. for (i = 0; i < len; ++i) {
  2366. const path = requiredFields[i];
  2367. const p = doc.$__schema.path(path);
  2368. if (typeof p?.originalRequiredValue === 'function') {
  2369. doc.$__.cachedRequired = doc.$__.cachedRequired || {};
  2370. try {
  2371. doc.$__.cachedRequired[path] = p.originalRequiredValue.call(doc, doc);
  2372. } catch (err) {
  2373. doc.invalidate(path, err);
  2374. }
  2375. }
  2376. }
  2377. }
  2378. /*!
  2379. * ignore
  2380. */
  2381. function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate) {
  2382. const doValidateOptions = {};
  2383. _evaluateRequiredFunctions(doc);
  2384. // only validate required fields when necessary
  2385. let paths = new Set(Object.keys(doc.$__.activePaths.getStatePaths('require')).filter(function(path) {
  2386. if (!doc.$__isSelected(path) && !doc.$isModified(path)) {
  2387. return false;
  2388. }
  2389. if (path.endsWith('.$*')) {
  2390. // Skip $* paths - they represent map schemas, not actual document paths
  2391. return false;
  2392. }
  2393. if (doc.$__.cachedRequired != null && path in doc.$__.cachedRequired) {
  2394. return doc.$__.cachedRequired[path];
  2395. }
  2396. return true;
  2397. }));
  2398. Object.keys(doc.$__.activePaths.getStatePaths('init')).forEach(addToPaths);
  2399. Object.keys(doc.$__.activePaths.getStatePaths('modify')).forEach(addToPaths);
  2400. Object.keys(doc.$__.activePaths.getStatePaths('default')).forEach(addToPaths);
  2401. function addToPaths(p) {
  2402. if (p.endsWith('.$*')) {
  2403. // Skip $* paths - they represent map schemas, not actual document paths
  2404. return;
  2405. }
  2406. paths.add(p);
  2407. }
  2408. if (!isNestedValidate) {
  2409. // If we're validating a subdocument, all this logic will run anyway on the top-level document, so skip for subdocuments.
  2410. // But only run for top-level subdocuments, because we're looking for subdocuments that are not modified at top-level but
  2411. // have a modified path. If that is the case, we will run validation on the top-level subdocument, and via that run validation
  2412. // on any subdocuments down to the modified path.
  2413. const topLevelSubdocs = [];
  2414. for (const path of Object.keys(doc.$__schema.paths)) {
  2415. const schemaType = doc.$__schema.path(path);
  2416. if (schemaType.$isSingleNested) {
  2417. const subdoc = doc.$get(path);
  2418. if (subdoc) {
  2419. topLevelSubdocs.push(subdoc);
  2420. }
  2421. } else if (schemaType.$isMongooseDocumentArray) {
  2422. const arr = doc.$get(path);
  2423. if (arr?.length) {
  2424. for (const subdoc of arr) {
  2425. if (subdoc) {
  2426. topLevelSubdocs.push(subdoc);
  2427. }
  2428. }
  2429. }
  2430. }
  2431. }
  2432. const modifiedPaths = doc.modifiedPaths();
  2433. for (const subdoc of topLevelSubdocs) {
  2434. if (subdoc.$basePath) {
  2435. const fullPathToSubdoc = subdoc.$__pathRelativeToParent();
  2436. // Remove child paths for now, because we'll be validating the whole
  2437. // subdoc.
  2438. // The following is a faster take on looping through every path in `paths`
  2439. // and checking if the path starts with `fullPathToSubdoc` re: gh-13191
  2440. for (const modifiedPath of subdoc.modifiedPaths()) {
  2441. paths.delete(fullPathToSubdoc + '.' + modifiedPath);
  2442. }
  2443. const subdocParent = subdoc.$parent();
  2444. if (subdocParent == null) {
  2445. throw new Error('Cannot validate subdocument that does not have a parent');
  2446. }
  2447. if (doc.$isModified(fullPathToSubdoc, null, modifiedPaths) &&
  2448. // Avoid using isDirectModified() here because that does additional checks on whether the parent path
  2449. // is direct modified, which can cause performance issues re: gh-14897
  2450. !Object.hasOwn(subdocParent.$__.activePaths.getStatePaths('modify'), fullPathToSubdoc) &&
  2451. !subdocParent.$isDefault(fullPathToSubdoc)) {
  2452. paths.add(fullPathToSubdoc);
  2453. if (doc.$__.pathsToScopes == null) {
  2454. doc.$__.pathsToScopes = {};
  2455. }
  2456. doc.$__.pathsToScopes[fullPathToSubdoc] = subdoc.$isDocumentArrayElement ?
  2457. subdoc.__parentArray :
  2458. subdoc.$parent();
  2459. doValidateOptions[fullPathToSubdoc] = { skipSchemaValidators: true };
  2460. if (subdoc.$isDocumentArrayElement && subdoc.__index != null) {
  2461. doValidateOptions[fullPathToSubdoc].index = subdoc.__index;
  2462. }
  2463. }
  2464. }
  2465. }
  2466. }
  2467. for (const path of paths) {
  2468. const _pathType = doc.$__schema.path(path);
  2469. if (!_pathType) {
  2470. continue;
  2471. }
  2472. if (_pathType.$isMongooseDocumentArray) {
  2473. for (const p of paths) {
  2474. if (p == null || p.startsWith(_pathType.path + '.')) {
  2475. paths.delete(p);
  2476. }
  2477. }
  2478. }
  2479. // Optimization: if primitive path with no validators, or array of primitives
  2480. // with no validators, skip validating this path entirely.
  2481. if (!_pathType.schema && !_pathType.embeddedSchemaType && _pathType.validators.length === 0 && !_pathType.$parentSchemaDocArray) {
  2482. paths.delete(path);
  2483. } else if (_pathType.$isMongooseArray &&
  2484. !_pathType.$isMongooseDocumentArray && // Skip document arrays...
  2485. !_pathType.embeddedSchemaType.$isMongooseArray && // and arrays of arrays
  2486. _pathType.validators.length === 0 && // and arrays with top-level validators
  2487. _pathType.embeddedSchemaType.validators.length === 0) {
  2488. paths.delete(path);
  2489. }
  2490. }
  2491. if (Array.isArray(pathsToValidate)) {
  2492. paths = _handlePathsToValidate(paths, pathsToValidate);
  2493. } else if (Array.isArray(pathsToSkip)) {
  2494. paths = _handlePathsToSkip(paths, pathsToSkip);
  2495. }
  2496. // from here on we're not removing items from paths
  2497. // gh-661: if a whole array is modified, make sure to run validation on all
  2498. // the children as well
  2499. _addArrayPathsToValidate(doc, paths);
  2500. const flattenOptions = { skipArrays: true };
  2501. for (const pathToCheck of paths) {
  2502. if (doc.$__schema.nested[pathToCheck]) {
  2503. let _v = doc.$__getValue(pathToCheck);
  2504. if (isMongooseObject(_v)) {
  2505. _v = _v.toObject({ transform: false });
  2506. }
  2507. const flat = flatten(_v, pathToCheck, flattenOptions, doc.$__schema);
  2508. // Single nested paths (paths embedded under single nested subdocs) will
  2509. // be validated on their own when we call `validate()` on the subdoc itself.
  2510. // Re: gh-8468
  2511. const singleNestedPaths = doc.$__schema.singleNestedPaths;
  2512. for (const path of Object.keys(flat)) {
  2513. if (!Object.hasOwn(singleNestedPaths, path)) {
  2514. addToPaths(path);
  2515. }
  2516. }
  2517. }
  2518. }
  2519. for (const path of paths) {
  2520. const _pathType = doc.$__schema.path(path);
  2521. if (!_pathType) {
  2522. continue;
  2523. }
  2524. // If underneath a document array, may need to re-validate the parent
  2525. // array re: gh-6818. Do this _after_ adding subpaths, because
  2526. // we don't want to add every array subpath.
  2527. if (_pathType.$parentSchemaDocArray && typeof _pathType.$parentSchemaDocArray.path === 'string') {
  2528. paths.add(_pathType.$parentSchemaDocArray.path);
  2529. }
  2530. if (!_pathType.$isSchemaMap) {
  2531. continue;
  2532. }
  2533. const val = doc.$__getValue(path);
  2534. if (val == null) {
  2535. continue;
  2536. }
  2537. for (const key of val.keys()) {
  2538. paths.add(path + '.' + key);
  2539. }
  2540. }
  2541. paths = Array.from(paths);
  2542. return [paths, doValidateOptions];
  2543. }
  2544. function _addArrayPathsToValidate(doc, paths) {
  2545. for (const path of paths) {
  2546. const _pathType = doc.$__schema.path(path);
  2547. if (!_pathType) {
  2548. continue;
  2549. }
  2550. if (!_pathType.$isMongooseArray ||
  2551. // To avoid potential performance issues, skip doc arrays whose children
  2552. // are not required. `getPositionalPathType()` may be slow, so avoid
  2553. // it unless we have a case of #6364
  2554. (!Array.isArray(_pathType) &&
  2555. _pathType.$isMongooseDocumentArray &&
  2556. !_pathType?.schemaOptions?.required)) {
  2557. continue;
  2558. }
  2559. // gh-11380: optimization. If the array isn't a document array and there's no validators
  2560. // on the array type, there's no need to run validation on the individual array elements.
  2561. if (_pathType.$isMongooseArray &&
  2562. !_pathType.$isMongooseDocumentArray && // Skip document arrays...
  2563. !_pathType.embeddedSchemaType.$isMongooseArray && // and arrays of arrays
  2564. _pathType.embeddedSchemaType.validators.length === 0) {
  2565. continue;
  2566. }
  2567. const val = doc.$__getValue(path);
  2568. _pushNestedArrayPaths(val, paths, path);
  2569. }
  2570. }
  2571. function _pushNestedArrayPaths(val, paths, path) {
  2572. if (val != null) {
  2573. const numElements = val.length;
  2574. for (let j = 0; j < numElements; ++j) {
  2575. if (Array.isArray(val[j])) {
  2576. _pushNestedArrayPaths(val[j], paths, path + '.' + j);
  2577. } else {
  2578. paths.add(path + '.' + j);
  2579. }
  2580. }
  2581. }
  2582. }
  2583. /*!
  2584. * ignore
  2585. */
  2586. Document.prototype._execDocumentPreHooks = async function _execDocumentPreHooks(opName, ...args) {
  2587. return this.$__middleware.execPre(opName, this, [...args]);
  2588. };
  2589. /*!
  2590. * ignore
  2591. */
  2592. Document.prototype._execDocumentPostHooks = async function _execDocumentPostHooks(opName, error) {
  2593. return this.$__middleware.execPost(opName, this, [this], { error });
  2594. };
  2595. /*!
  2596. * ignore
  2597. */
  2598. Document.prototype.$__validate = async function $__validate(pathsToValidate, options) {
  2599. try {
  2600. [options] = await this._execDocumentPreHooks('validate', options);
  2601. } catch (error) {
  2602. await this._execDocumentPostHooks('validate', error);
  2603. return;
  2604. }
  2605. if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) {
  2606. pathsToValidate = [...this.$__.saveOptions.pathsToSave];
  2607. }
  2608. const hasValidateModifiedOnlyOption = options &&
  2609. (typeof options === 'object') &&
  2610. ('validateModifiedOnly' in options);
  2611. const pathsToSkip = options?.pathsToSkip || null;
  2612. let shouldValidateModifiedOnly;
  2613. if (hasValidateModifiedOnlyOption) {
  2614. shouldValidateModifiedOnly = !!options.validateModifiedOnly;
  2615. } else {
  2616. shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly;
  2617. }
  2618. const validateAllPaths = options?.validateAllPaths;
  2619. if (validateAllPaths) {
  2620. if (pathsToSkip) {
  2621. throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`');
  2622. }
  2623. if (pathsToValidate) {
  2624. throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`');
  2625. }
  2626. if (hasValidateModifiedOnlyOption && shouldValidateModifiedOnly) {
  2627. throw new TypeError('Cannot set both `validateAllPaths` and `validateModifiedOnly`');
  2628. }
  2629. }
  2630. const _this = this;
  2631. const _complete = () => {
  2632. let validationError = this.$__.validationError;
  2633. this.$__.validationError = null;
  2634. this.$__.validating = null;
  2635. if (shouldValidateModifiedOnly && validationError != null) {
  2636. // Remove any validation errors that aren't from modified paths
  2637. const errors = Object.keys(validationError.errors);
  2638. for (const errPath of errors) {
  2639. if (!this.$isModified(errPath)) {
  2640. delete validationError.errors[errPath];
  2641. }
  2642. }
  2643. if (utils.hasOwnKeys(validationError.errors) === false) {
  2644. validationError = void 0;
  2645. }
  2646. }
  2647. this.$__.cachedRequired = {};
  2648. this.$emit('validate', _this);
  2649. this.constructor.emit('validate', _this);
  2650. if (validationError) {
  2651. for (const key in validationError.errors) {
  2652. // Make sure cast errors persist
  2653. if (!this[documentArrayParent] &&
  2654. validationError.errors[key] instanceof MongooseError.CastError) {
  2655. this.invalidate(key, validationError.errors[key]);
  2656. }
  2657. }
  2658. return validationError;
  2659. }
  2660. };
  2661. // only validate required fields when necessary
  2662. let paths;
  2663. let doValidateOptionsByPath;
  2664. if (validateAllPaths) {
  2665. paths = new Set(Object.keys(this.$__schema.paths));
  2666. // gh-661: if a whole array is modified, make sure to run validation on all
  2667. // the children as well
  2668. for (const path of paths) {
  2669. const schemaType = this.$__schema.path(path);
  2670. if (!schemaType?.$isMongooseArray) {
  2671. continue;
  2672. }
  2673. const val = this.$__getValue(path);
  2674. if (!val) {
  2675. continue;
  2676. }
  2677. _pushNestedArrayPaths(val, paths, path);
  2678. }
  2679. paths = [...paths];
  2680. doValidateOptionsByPath = {};
  2681. } else {
  2682. const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options?._nestedValidate);
  2683. paths = shouldValidateModifiedOnly ?
  2684. pathDetails[0].filter((path) => this.$isModified(path)) :
  2685. pathDetails[0];
  2686. doValidateOptionsByPath = pathDetails[1];
  2687. }
  2688. if (typeof pathsToValidate === 'string') {
  2689. pathsToValidate = pathsToValidate.split(' ');
  2690. }
  2691. if (paths.length === 0) {
  2692. const error = _complete();
  2693. await this._execDocumentPostHooks('validate', error);
  2694. return;
  2695. }
  2696. const validated = {};
  2697. let pathsToSave = this.$__.saveOptions?.pathsToSave;
  2698. const promises = [];
  2699. if (Array.isArray(pathsToSave)) {
  2700. pathsToSave = new Set(pathsToSave);
  2701. for (const path of paths) {
  2702. if (!pathsToSave.has(path)) {
  2703. continue;
  2704. }
  2705. promises.push(validatePath(path));
  2706. }
  2707. } else {
  2708. for (const path of paths) {
  2709. promises.push(validatePath(path));
  2710. }
  2711. }
  2712. await Promise.all(promises);
  2713. const error = _complete();
  2714. await this._execDocumentPostHooks('validate', error);
  2715. async function validatePath(path) {
  2716. if (path == null || validated[path]) {
  2717. return;
  2718. }
  2719. validated[path] = true;
  2720. const schemaType = _this.$__schema.path(path);
  2721. if (!schemaType) {
  2722. return;
  2723. }
  2724. // If user marked as invalid or there was a cast error, don't validate
  2725. if (!_this.$isValid(path)) {
  2726. return;
  2727. }
  2728. // If setting a path under a mixed path, avoid using the mixed path validator (gh-10141)
  2729. if (schemaType[schemaMixedSymbol] != null && path !== schemaType.path) {
  2730. return;
  2731. }
  2732. let val = _this.$__getValue(path);
  2733. // If you `populate()` and get back a null value, required validators
  2734. // shouldn't fail (gh-8018). We should always fall back to the populated
  2735. // value.
  2736. let pop;
  2737. if ((pop = _this.$populated(path))) {
  2738. val = pop;
  2739. } else if (val?.$__?.wasPopulated) {
  2740. // Array paths, like `somearray.1`, do not show up as populated with `$populated()`,
  2741. // so in that case pull out the document's id
  2742. val = val._doc._id;
  2743. }
  2744. const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ?
  2745. _this.$__.pathsToScopes[path] :
  2746. _this;
  2747. const doValidateOptions = {
  2748. ...doValidateOptionsByPath[path],
  2749. path: path,
  2750. validateAllPaths,
  2751. _nestedValidate: true
  2752. };
  2753. try {
  2754. await schemaType.doValidate(val, scope, doValidateOptions);
  2755. } catch (err) {
  2756. const isSubdoc = schemaType.$isSingleNested ||
  2757. schemaType.$isArraySubdocument ||
  2758. schemaType.$isMongooseDocumentArray;
  2759. if (isSubdoc && err instanceof ValidationError) {
  2760. return;
  2761. }
  2762. _this.invalidate(path, err, undefined, true);
  2763. }
  2764. }
  2765. };
  2766. /*!
  2767. * ignore
  2768. */
  2769. function _handlePathsToValidate(paths, pathsToValidate) {
  2770. const _pathsToValidate = new Set(pathsToValidate);
  2771. const parentPaths = new Map([]);
  2772. for (const path of pathsToValidate) {
  2773. if (path.indexOf('.') === -1) {
  2774. continue;
  2775. }
  2776. const pieces = path.split('.');
  2777. let cur = pieces[0];
  2778. for (let i = 1; i < pieces.length; ++i) {
  2779. // Since we skip subpaths under single nested subdocs to
  2780. // avoid double validation, we need to add back the
  2781. // single nested subpath if the user asked for it (gh-8626)
  2782. parentPaths.set(cur, path);
  2783. cur = cur + '.' + pieces[i];
  2784. }
  2785. }
  2786. const ret = new Set();
  2787. for (const path of paths) {
  2788. if (_pathsToValidate.has(path)) {
  2789. ret.add(path);
  2790. } else if (parentPaths.has(path)) {
  2791. ret.add(parentPaths.get(path));
  2792. }
  2793. }
  2794. return ret;
  2795. }
  2796. /*!
  2797. * ignore
  2798. */
  2799. function _handlePathsToSkip(paths, pathsToSkip) {
  2800. pathsToSkip = new Set(pathsToSkip);
  2801. paths = Array.from(paths).filter(p => !pathsToSkip.has(p));
  2802. return new Set(paths);
  2803. }
  2804. /**
  2805. * Executes registered validation rules (skipping asynchronous validators) for this document.
  2806. *
  2807. * #### Note:
  2808. *
  2809. * This method is useful if you need synchronous validation.
  2810. *
  2811. * #### Example:
  2812. *
  2813. * const err = doc.validateSync();
  2814. * if (err) {
  2815. * handleError(err);
  2816. * } else {
  2817. * // validation passed
  2818. * }
  2819. *
  2820. * @param {Array|string} [pathsToValidate] only validate the given paths
  2821. * @param {Object} [options] options for validation
  2822. * @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
  2823. * @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
  2824. * @return {ValidationError|undefined} ValidationError if there are errors during validation, or undefined if there is no error.
  2825. * @api public
  2826. */
  2827. Document.prototype.validateSync = function(pathsToValidate, options) {
  2828. const _this = this;
  2829. if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) {
  2830. options = arguments[0];
  2831. pathsToValidate = null;
  2832. }
  2833. const hasValidateModifiedOnlyOption = options &&
  2834. (typeof options === 'object') &&
  2835. ('validateModifiedOnly' in options);
  2836. let shouldValidateModifiedOnly;
  2837. if (hasValidateModifiedOnlyOption) {
  2838. shouldValidateModifiedOnly = !!options.validateModifiedOnly;
  2839. } else {
  2840. shouldValidateModifiedOnly = this.$__schema.options.validateModifiedOnly;
  2841. }
  2842. let pathsToSkip = options?.pathsToSkip;
  2843. const validateAllPaths = options?.validateAllPaths;
  2844. if (validateAllPaths) {
  2845. if (pathsToSkip) {
  2846. throw new TypeError('Cannot set both `validateAllPaths` and `pathsToSkip`');
  2847. }
  2848. if (pathsToValidate) {
  2849. throw new TypeError('Cannot set both `validateAllPaths` and `pathsToValidate`');
  2850. }
  2851. }
  2852. if (typeof pathsToValidate === 'string') {
  2853. const isOnePathOnly = pathsToValidate.indexOf(' ') === -1;
  2854. pathsToValidate = isOnePathOnly ? [pathsToValidate] : pathsToValidate.split(' ');
  2855. } else if (typeof pathsToSkip === 'string' && pathsToSkip.indexOf(' ') !== -1) {
  2856. pathsToSkip = pathsToSkip.split(' ');
  2857. }
  2858. // only validate required fields when necessary
  2859. let paths;
  2860. let skipSchemaValidators;
  2861. if (validateAllPaths) {
  2862. paths = new Set(Object.keys(this.$__schema.paths));
  2863. // gh-661: if a whole array is modified, make sure to run validation on all
  2864. // the children as well
  2865. for (const path of paths) {
  2866. const schemaType = this.$__schema.path(path);
  2867. if (!schemaType?.$isMongooseArray) {
  2868. continue;
  2869. }
  2870. const val = this.$__getValue(path);
  2871. if (!val) {
  2872. continue;
  2873. }
  2874. _pushNestedArrayPaths(val, paths, path);
  2875. }
  2876. paths = [...paths];
  2877. skipSchemaValidators = {};
  2878. } else {
  2879. const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip);
  2880. paths = shouldValidateModifiedOnly ?
  2881. pathDetails[0].filter((path) => this.$isModified(path)) :
  2882. pathDetails[0];
  2883. skipSchemaValidators = pathDetails[1];
  2884. }
  2885. const validating = {};
  2886. for (let i = 0, len = paths.length; i < len; ++i) {
  2887. const path = paths[i];
  2888. if (validating[path]) {
  2889. continue;
  2890. }
  2891. validating[path] = true;
  2892. const p = _this.$__schema.path(path);
  2893. if (!p) {
  2894. continue;
  2895. }
  2896. if (!_this.$isValid(path)) {
  2897. continue;
  2898. }
  2899. const val = _this.$__getValue(path);
  2900. const err = p.doValidateSync(val, _this, {
  2901. skipSchemaValidators: skipSchemaValidators[path],
  2902. path: path,
  2903. validateModifiedOnly: shouldValidateModifiedOnly,
  2904. validateAllPaths
  2905. });
  2906. if (err) {
  2907. const isSubdoc = p.$isSingleNested ||
  2908. p.$isArraySubdocument ||
  2909. p.$isMongooseDocumentArray;
  2910. if (isSubdoc && err instanceof ValidationError) {
  2911. continue;
  2912. }
  2913. _this.invalidate(path, err, undefined, true);
  2914. }
  2915. }
  2916. const err = _this.$__.validationError;
  2917. _this.$__.validationError = undefined;
  2918. _this.$emit('validate', _this);
  2919. _this.constructor.emit('validate', _this);
  2920. if (err) {
  2921. for (const key in err.errors) {
  2922. // Make sure cast errors persist
  2923. if (err.errors[key] instanceof MongooseError.CastError) {
  2924. _this.invalidate(key, err.errors[key]);
  2925. }
  2926. }
  2927. }
  2928. return err;
  2929. };
  2930. /**
  2931. * Marks a path as invalid, causing validation to fail.
  2932. *
  2933. * The `errorMsg` argument will become the message of the `ValidationError`.
  2934. *
  2935. * The `value` argument (if passed) will be available through the `ValidationError.value` property.
  2936. *
  2937. * doc.invalidate('size', 'must be less than 20', 14);
  2938. *
  2939. * doc.validate(function (err) {
  2940. * console.log(err)
  2941. * // prints
  2942. * { message: 'Validation failed',
  2943. * name: 'ValidationError',
  2944. * errors:
  2945. * { size:
  2946. * { message: 'must be less than 20',
  2947. * name: 'ValidatorError',
  2948. * path: 'size',
  2949. * type: 'user defined',
  2950. * value: 14 } } }
  2951. * })
  2952. *
  2953. * @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.
  2954. * @param {String|Error} err the error which states the reason `path` was invalid
  2955. * @param {Object|String|Number|any} val optional invalid value
  2956. * @param {String} [kind] optional `kind` property for the error
  2957. * @return {ValidationError} the current ValidationError, with all currently invalidated paths
  2958. * @api public
  2959. */
  2960. Document.prototype.invalidate = function(path, err, val, kind) {
  2961. if (!this.$__.validationError) {
  2962. this.$__.validationError = new ValidationError(this);
  2963. }
  2964. if (this.$__.validationError.errors[path]) {
  2965. return;
  2966. }
  2967. if (!err || typeof err === 'string') {
  2968. err = new ValidatorError({
  2969. path: path,
  2970. message: err,
  2971. type: kind || 'user defined',
  2972. value: val
  2973. });
  2974. }
  2975. if (this.$__.validationError === err) {
  2976. return this.$__.validationError;
  2977. }
  2978. this.$__.validationError.addError(path, err);
  2979. return this.$__.validationError;
  2980. };
  2981. /**
  2982. * Marks a path as valid, removing existing validation errors.
  2983. *
  2984. * @param {String} path the field to mark as valid
  2985. * @api public
  2986. * @memberOf Document
  2987. * @instance
  2988. * @method $markValid
  2989. */
  2990. Document.prototype.$markValid = function(path) {
  2991. if (!this.$__.validationError?.errors[path]) {
  2992. return;
  2993. }
  2994. delete this.$__.validationError.errors[path];
  2995. if (utils.hasOwnKeys(this.$__.validationError.errors) === false) {
  2996. this.$__.validationError = null;
  2997. }
  2998. };
  2999. /*!
  3000. * ignore
  3001. */
  3002. function _markValidSubpaths(doc, path) {
  3003. if (!doc.$__.validationError) {
  3004. return;
  3005. }
  3006. const keys = Object.keys(doc.$__.validationError.errors);
  3007. for (const key of keys) {
  3008. if (key.startsWith(path + '.')) {
  3009. delete doc.$__.validationError.errors[key];
  3010. }
  3011. }
  3012. if (utils.hasOwnKeys(doc.$__.validationError.errors) === false) {
  3013. doc.$__.validationError = null;
  3014. }
  3015. }
  3016. /*!
  3017. * ignore
  3018. */
  3019. function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
  3020. const schema = schematype.schema;
  3021. if (schema == null) {
  3022. return;
  3023. }
  3024. for (const key of Object.keys(schema.paths)) {
  3025. const path = schema.paths[key];
  3026. if (path.$immutableSetter == null) {
  3027. continue;
  3028. }
  3029. const oldVal = priorVal == null ? void 0 : priorVal.$__getValue(key);
  3030. // Calling immutableSetter with `oldVal` even though it expects `newVal`
  3031. // is intentional. That's because `$immutableSetter` compares its param
  3032. // to the current value.
  3033. path.$immutableSetter.call(subdoc, oldVal);
  3034. }
  3035. }
  3036. /**
  3037. * Saves this document by inserting a new document into the database if [document.isNew](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()) is `true`,
  3038. * or sends an [updateOne](https://mongoosejs.com/docs/api/document.html#Document.prototype.updateOne()) operation **only** with the modifications to the database, it does not replace the whole document in the latter case.
  3039. *
  3040. * #### Example:
  3041. *
  3042. * product.sold = Date.now();
  3043. * product = await product.save();
  3044. *
  3045. * If save is successful, the returned promise will fulfill with the document
  3046. * saved.
  3047. *
  3048. * #### Example:
  3049. *
  3050. * const newProduct = await product.save();
  3051. * newProduct === product; // true
  3052. *
  3053. * @param {Object} [options] options optional options
  3054. * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.$session()).
  3055. * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead.
  3056. * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
  3057. * @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
  3058. * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
  3059. * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
  3060. * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern).
  3061. * @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://www.mongodb.com/docs/manual/reference/limits/#Restrictions-on-Field-Names)
  3062. * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
  3063. * @method save
  3064. * @memberOf Document
  3065. * @instance
  3066. * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.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).
  3067. * @return {Promise}
  3068. * @api public
  3069. * @see middleware https://mongoosejs.com/docs/middleware.html
  3070. */
  3071. /**
  3072. * Checks if a path is invalid
  3073. *
  3074. * @param {String|String[]} [path] the field to check. If unset will always return "false"
  3075. * @method $isValid
  3076. * @memberOf Document
  3077. * @instance
  3078. * @api private
  3079. */
  3080. Document.prototype.$isValid = function(path) {
  3081. if (this.$__.validationError == null || utils.hasOwnKeys(this.$__.validationError.errors) === false) {
  3082. return true;
  3083. }
  3084. if (path == null) {
  3085. return false;
  3086. }
  3087. if (path.indexOf(' ') !== -1) {
  3088. path = path.split(' ');
  3089. }
  3090. if (Array.isArray(path)) {
  3091. return path.some(p => this.$__.validationError.errors[p] == null);
  3092. }
  3093. return this.$__.validationError.errors[path] == null;
  3094. };
  3095. /**
  3096. * Resets the internal modified state of this document.
  3097. *
  3098. * @api private
  3099. * @return {Document} this
  3100. * @method $__reset
  3101. * @memberOf Document
  3102. * @instance
  3103. */
  3104. Document.prototype.$__reset = function reset() {
  3105. let _this = this;
  3106. // Skip for subdocuments
  3107. const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null;
  3108. if (subdocs?.length > 0) {
  3109. for (const subdoc of subdocs) {
  3110. subdoc.$__reset();
  3111. }
  3112. }
  3113. // clear atomics
  3114. this.$__dirty().forEach(function(dirt) {
  3115. const type = dirt.value;
  3116. if (type && typeof type.clearAtomics === 'function') {
  3117. type.clearAtomics();
  3118. } else if (type && type[arrayAtomicsSymbol]) {
  3119. type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol];
  3120. type[arrayAtomicsSymbol] = {};
  3121. }
  3122. });
  3123. this.$__.backup = {};
  3124. this.$__.backup.activePaths = {
  3125. modify: Object.assign({}, this.$__.activePaths.getStatePaths('modify')),
  3126. default: Object.assign({}, this.$__.activePaths.getStatePaths('default'))
  3127. };
  3128. this.$__.backup.validationError = this.$__.validationError;
  3129. this.$__.backup.errors = this.$errors;
  3130. // Clear 'dirty' cache
  3131. this.$__.activePaths.clear('modify');
  3132. this.$__.activePaths.clear('default');
  3133. this.$__.validationError = undefined;
  3134. this.$errors = undefined;
  3135. _this = this;
  3136. this.$__schema.requiredPaths().forEach(function(path) {
  3137. _this.$__.activePaths.require(path);
  3138. });
  3139. return this;
  3140. };
  3141. /*!
  3142. * ignore
  3143. */
  3144. Document.prototype.$__undoReset = function $__undoReset() {
  3145. if (this.$__.backup?.activePaths == null) {
  3146. return;
  3147. }
  3148. this.$__.activePaths.states.modify = this.$__.backup.activePaths.modify;
  3149. this.$__.activePaths.states.default = this.$__.backup.activePaths.default;
  3150. this.$__.validationError = this.$__.backup.validationError;
  3151. this.$errors = this.$__.backup.errors;
  3152. for (const dirt of this.$__dirty()) {
  3153. const type = dirt.value;
  3154. if (type?.[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) {
  3155. type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol];
  3156. }
  3157. }
  3158. if (!this.$isSubdocument) {
  3159. for (const subdoc of this.$getAllSubdocs()) {
  3160. subdoc.$__undoReset();
  3161. }
  3162. }
  3163. };
  3164. /**
  3165. * Returns this documents dirty paths / vals.
  3166. *
  3167. * @return {Array}
  3168. * @api private
  3169. * @method $__dirty
  3170. * @memberOf Document
  3171. * @instance
  3172. */
  3173. Document.prototype.$__dirty = function() {
  3174. const _this = this;
  3175. let all = this.$__.activePaths.map('modify', function(path) {
  3176. return {
  3177. path: path,
  3178. value: _this.$__getValue(path),
  3179. schema: _this.$__path(path)
  3180. };
  3181. });
  3182. // gh-2558: if we had to set a default and the value is not undefined,
  3183. // we have to save as well
  3184. all = all.concat(this.$__.activePaths.map('default', function(path) {
  3185. if (path === '_id' || _this.$__getValue(path) == null) {
  3186. return;
  3187. }
  3188. return {
  3189. path: path,
  3190. value: _this.$__getValue(path),
  3191. schema: _this.$__path(path)
  3192. };
  3193. }));
  3194. const allPaths = new Map(all.filter((el) => el != null).map((el) => [el.path, el.value]));
  3195. // Ignore "foo.a" if "foo" is dirty already.
  3196. const minimal = [];
  3197. all.forEach(function(item) {
  3198. if (!item) {
  3199. return;
  3200. }
  3201. let top = null;
  3202. const array = parentPaths(item.path);
  3203. for (let i = 0; i < array.length - 1; i++) {
  3204. if (allPaths.has(array[i])) {
  3205. top = allPaths.get(array[i]);
  3206. break;
  3207. }
  3208. }
  3209. if (top == null) {
  3210. minimal.push(item);
  3211. } else if (top != null &&
  3212. top[arrayAtomicsSymbol] != null &&
  3213. top.hasAtomics()) {
  3214. // special case for top level MongooseArrays
  3215. // the `top` array itself and a sub path of `top` are being set.
  3216. // the only way to honor all of both modifications is through a $set
  3217. // of entire array.
  3218. top[arrayAtomicsSymbol] = {};
  3219. top[arrayAtomicsSymbol].$set = top;
  3220. }
  3221. });
  3222. return minimal;
  3223. };
  3224. /**
  3225. * Assigns/compiles `schema` into this documents prototype.
  3226. *
  3227. * @param {Schema} schema
  3228. * @api private
  3229. * @method $__setSchema
  3230. * @memberOf Document
  3231. * @instance
  3232. */
  3233. Document.prototype.$__setSchema = function(schema) {
  3234. compile(schema.tree, this, undefined, schema.options);
  3235. // Apply default getters if virtual doesn't have any (gh-6262)
  3236. for (const key of Object.keys(schema.virtuals)) {
  3237. schema.virtuals[key]._applyDefaultGetters();
  3238. }
  3239. if (schema.path('schema') == null) {
  3240. this.schema = schema;
  3241. }
  3242. this.$__schema = schema;
  3243. this.$__middleware = schema._getDocumentMiddleware();
  3244. this[documentSchemaSymbol] = schema;
  3245. };
  3246. /**
  3247. * Get active path that were changed and are arrays
  3248. *
  3249. * @return {Array}
  3250. * @api private
  3251. * @method $__getArrayPathsToValidate
  3252. * @memberOf Document
  3253. * @instance
  3254. */
  3255. Document.prototype.$__getArrayPathsToValidate = function() {
  3256. DocumentArray || (DocumentArray = require('./types/documentArray'));
  3257. // validate all document arrays.
  3258. return this.$__.activePaths
  3259. .map('init', 'modify', function(i) {
  3260. return this.$__getValue(i);
  3261. }.bind(this))
  3262. .filter(function(val) {
  3263. return val && Array.isArray(val) && utils.isMongooseDocumentArray(val) && val.length;
  3264. }).reduce(function(seed, array) {
  3265. return seed.concat(array);
  3266. }, [])
  3267. .filter(function(doc) {
  3268. return doc;
  3269. });
  3270. };
  3271. /**
  3272. * Get all subdocs (by bfs)
  3273. *
  3274. * @param {Object} [options] options. Currently for internal use.
  3275. * @return {Array}
  3276. * @api public
  3277. * @method $getAllSubdocs
  3278. * @memberOf Document
  3279. * @instance
  3280. */
  3281. Document.prototype.$getAllSubdocs = function(options) {
  3282. if (options?.useCache && this.$__.saveOptions?.__subdocs) {
  3283. return this.$__.saveOptions.__subdocs;
  3284. }
  3285. DocumentArray || (DocumentArray = require('./types/documentArray'));
  3286. Embedded = Embedded || require('./types/arraySubdocument');
  3287. const subDocs = [];
  3288. function getSubdocs(doc) {
  3289. const newSubdocs = [];
  3290. for (const { model } of doc.$__schema.childSchemas) {
  3291. // Avoid using `childSchemas.path` to avoid compatibility versions with pre-8.8 versions of Mongoose
  3292. const val = doc.$__getValue(model.path);
  3293. if (val == null) {
  3294. continue;
  3295. }
  3296. if (val.$__) {
  3297. newSubdocs.push(val);
  3298. }
  3299. if (Array.isArray(val)) {
  3300. for (const el of val) {
  3301. if (el?.$__) {
  3302. newSubdocs.push(el);
  3303. }
  3304. }
  3305. }
  3306. if (val instanceof Map) {
  3307. for (const el of val.values()) {
  3308. if (el?.$__) {
  3309. newSubdocs.push(el);
  3310. }
  3311. }
  3312. }
  3313. }
  3314. for (const subdoc of newSubdocs) {
  3315. getSubdocs(subdoc);
  3316. }
  3317. subDocs.push(...newSubdocs);
  3318. }
  3319. getSubdocs(this);
  3320. if (this.$__.saveOptions) {
  3321. this.$__.saveOptions.__subdocs = subDocs;
  3322. }
  3323. return subDocs;
  3324. };
  3325. /*!
  3326. * Runs queued functions
  3327. */
  3328. function applyQueue(doc) {
  3329. const q = doc.$__schema && doc.$__schema.callQueue;
  3330. if (!q.length) {
  3331. return;
  3332. }
  3333. for (const pair of q) {
  3334. if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') {
  3335. doc[pair[0]].apply(doc, pair[1]);
  3336. }
  3337. }
  3338. }
  3339. /*!
  3340. * ignore
  3341. */
  3342. Document.prototype.$__handleReject = function handleReject(err) {
  3343. // emit on the Model if listening
  3344. if (this.$listeners('error').length) {
  3345. this.$emit('error', err);
  3346. } else if (this.constructor.listeners?.('error').length) {
  3347. this.constructor.emit('error', err);
  3348. }
  3349. };
  3350. /**
  3351. * Internal common logic for toObject() and toJSON()
  3352. *
  3353. * @return {Object}
  3354. * @api private
  3355. * @method $toObject
  3356. * @memberOf Document
  3357. * @instance
  3358. */
  3359. Document.prototype.$toObject = function(options, json) {
  3360. const defaultOptions = this.$__schema._defaultToObjectOptions(json);
  3361. const hasOnlyPrimitiveValues = this.$__hasOnlyPrimitiveValues();
  3362. // If options do not exist or is not an object, set it to empty object
  3363. options = utils.isPOJO(options) ? { ...options } : {};
  3364. options._calledWithOptions = options._calledWithOptions || { ...options };
  3365. let _minimize;
  3366. if (options._calledWithOptions.minimize != null) {
  3367. _minimize = options.minimize;
  3368. } else if (this.$__schemaTypeOptions?.minimize != null) {
  3369. _minimize = this.$__schemaTypeOptions.minimize;
  3370. } else if (defaultOptions?.minimize != null) {
  3371. _minimize = defaultOptions.minimize;
  3372. } else {
  3373. _minimize = this.$__schema.options.minimize;
  3374. }
  3375. options.minimize = _minimize;
  3376. if (!hasOnlyPrimitiveValues) {
  3377. options._seen = options._seen || new Map();
  3378. }
  3379. const depopulate = options._calledWithOptions.depopulate
  3380. ?? defaultOptions?.depopulate
  3381. ?? options.depopulate
  3382. ?? false;
  3383. // _isNested will only be true if this is not the top level document, we
  3384. // should never depopulate the top-level document
  3385. if (depopulate && options._isNested && this.$__.wasPopulated) {
  3386. return clone(this.$__.wasPopulated.value || this._doc._id, options);
  3387. }
  3388. if (depopulate) {
  3389. options.depopulate = true;
  3390. }
  3391. // merge default options with input options.
  3392. if (defaultOptions != null) {
  3393. for (const key of Object.keys(defaultOptions)) {
  3394. if (options[key] == null) {
  3395. options[key] = defaultOptions[key];
  3396. }
  3397. }
  3398. }
  3399. options._isNested = true;
  3400. options.json = json;
  3401. options.minimize = _minimize;
  3402. const parentOptions = options._parentOptions;
  3403. // Parent options should only bubble down for subdocuments, not populated docs
  3404. options._parentOptions = this.$isSubdocument ? options : null;
  3405. const schemaFieldsOnly = options._calledWithOptions.schemaFieldsOnly
  3406. ?? options.schemaFieldsOnly
  3407. ?? defaultOptions.schemaFieldsOnly
  3408. ?? false;
  3409. let ret;
  3410. if (hasOnlyPrimitiveValues && !options.flattenObjectIds) {
  3411. // Fast path: if we don't have any nested objects or arrays, we only need a
  3412. // shallow clone.
  3413. ret = this.$__toObjectShallow(schemaFieldsOnly);
  3414. } else if (schemaFieldsOnly) {
  3415. ret = {};
  3416. for (const path of Object.keys(this.$__schema.paths)) {
  3417. const value = this.$__getValue(path);
  3418. if (value === undefined) {
  3419. continue;
  3420. }
  3421. let pathToSet = path;
  3422. let objToSet = ret;
  3423. if (path.indexOf('.') !== -1) {
  3424. const segments = path.split('.');
  3425. pathToSet = segments[segments.length - 1];
  3426. for (let i = 0; i < segments.length - 1; ++i) {
  3427. objToSet[segments[i]] = objToSet[segments[i]] ?? {};
  3428. objToSet = objToSet[segments[i]];
  3429. }
  3430. }
  3431. if (value === null) {
  3432. objToSet[pathToSet] = null;
  3433. continue;
  3434. }
  3435. objToSet[pathToSet] = clone(value, options);
  3436. }
  3437. } else {
  3438. ret = clone(this._doc, options) || {};
  3439. }
  3440. const getters = options._calledWithOptions.getters
  3441. ?? options.getters
  3442. ?? defaultOptions.getters
  3443. ?? false;
  3444. if (getters) {
  3445. applyGetters(this, ret);
  3446. if (options.minimize) {
  3447. ret = minimize(ret) || {};
  3448. }
  3449. }
  3450. const virtuals = options._calledWithOptions.virtuals
  3451. ?? defaultOptions.virtuals
  3452. ?? parentOptions?.virtuals
  3453. ?? undefined;
  3454. if (virtuals || (getters && virtuals !== false)) {
  3455. applyVirtuals(this, ret, options, options);
  3456. }
  3457. if (options.versionKey === false && this.$__schema.options.versionKey) {
  3458. delete ret[this.$__schema.options.versionKey];
  3459. }
  3460. const transform = options._calledWithOptions.transform ?? true;
  3461. let transformFunction = undefined;
  3462. if (transform === true) {
  3463. transformFunction = defaultOptions.transform;
  3464. } else if (typeof transform === 'function') {
  3465. transformFunction = transform;
  3466. }
  3467. // In the case where a subdocument has its own transform function, we need to
  3468. // check and see if the parent has a transform (options.transform) and if the
  3469. // child schema has a transform (this.schema.options.toObject) In this case,
  3470. // we need to adjust options.transform to be the child schema's transform and
  3471. // not the parent schema's
  3472. if (transform) {
  3473. applySchemaTypeTransforms(this, ret);
  3474. }
  3475. if (options.useProjection) {
  3476. omitDeselectedFields(this, ret);
  3477. }
  3478. if (typeof transformFunction === 'function') {
  3479. const xformed = transformFunction(this, ret, options);
  3480. if (typeof xformed !== 'undefined') {
  3481. ret = xformed;
  3482. }
  3483. }
  3484. return ret;
  3485. };
  3486. /*!
  3487. * Internal shallow clone alternative to `$toObject()`: much faster, no options processing
  3488. */
  3489. Document.prototype.$__toObjectShallow = function $__toObjectShallow(schemaFieldsOnly) {
  3490. const ret = {};
  3491. if (this._doc != null) {
  3492. const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc);
  3493. for (const key of keys) {
  3494. // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths
  3495. const value = this._doc[key];
  3496. if (value instanceof Date) {
  3497. ret[key] = new Date(value);
  3498. } else if (value !== undefined) {
  3499. ret[key] = value;
  3500. }
  3501. }
  3502. }
  3503. return ret;
  3504. };
  3505. /**
  3506. * Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)).
  3507. *
  3508. * Buffers are converted to instances of [mongodb.Binary](https://mongodb.github.io/node-mongodb-native/4.9/classes/Binary.html) for proper storage.
  3509. *
  3510. * #### Getters/Virtuals
  3511. *
  3512. * Example of only applying path getters
  3513. *
  3514. * doc.toObject({ getters: true, virtuals: false })
  3515. *
  3516. * Example of only applying virtual getters
  3517. *
  3518. * doc.toObject({ virtuals: true })
  3519. *
  3520. * Example of applying both path and virtual getters
  3521. *
  3522. * doc.toObject({ getters: true })
  3523. *
  3524. * To apply these options to every document of your schema by default, set your [schemas](https://mongoosejs.com/docs/api/schema.html#Schema()) `toObject` option to the same argument.
  3525. *
  3526. * schema.set('toObject', { virtuals: true })
  3527. *
  3528. * #### Transform:
  3529. *
  3530. * 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.
  3531. *
  3532. * Transform functions receive three arguments
  3533. *
  3534. * function (doc, ret, options) {}
  3535. *
  3536. * - `doc` The mongoose document which is being converted
  3537. * - `ret` The plain object representation which has been converted
  3538. * - `options` The options in use (either schema options or the options passed inline)
  3539. *
  3540. * #### Example:
  3541. *
  3542. * // specify the transform schema option
  3543. * if (!schema.options.toObject) schema.options.toObject = {};
  3544. * schema.options.toObject.transform = function (doc, ret, options) {
  3545. * // remove the _id of every document before returning the result
  3546. * delete ret._id;
  3547. * return ret;
  3548. * }
  3549. *
  3550. * // without the transformation in the schema
  3551. * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
  3552. *
  3553. * // with the transformation
  3554. * doc.toObject(); // { name: 'Wreck-it Ralph' }
  3555. *
  3556. * With transformations we can do a lot more than remove properties. We can even return completely new customized objects:
  3557. *
  3558. * if (!schema.options.toObject) schema.options.toObject = {};
  3559. * schema.options.toObject.transform = function (doc, ret, options) {
  3560. * return { movie: ret.name }
  3561. * }
  3562. *
  3563. * // without the transformation in the schema
  3564. * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
  3565. *
  3566. * // with the transformation
  3567. * doc.toObject(); // { movie: 'Wreck-it Ralph' }
  3568. *
  3569. * _Note: if a transform function returns `undefined`, the return value will be ignored._
  3570. *
  3571. * Transformations may also be applied inline, overridding any transform set in the schema options.
  3572. * Any transform function specified in `toObject` options also propagates to any subdocuments.
  3573. *
  3574. * function deleteId(doc, ret, options) {
  3575. * delete ret._id;
  3576. * return ret;
  3577. * }
  3578. *
  3579. * const schema = mongoose.Schema({ name: String, docArr: [{ name: String }] });
  3580. * const TestModel = mongoose.model('Test', schema);
  3581. *
  3582. * const doc = new TestModel({ name: 'test', docArr: [{ name: 'test' }] });
  3583. *
  3584. * // pass the transform as an inline option. Deletes `_id` property
  3585. * // from both the top-level document and the subdocument.
  3586. * const obj = doc.toObject({ transform: deleteId });
  3587. * obj._id; // undefined
  3588. * obj.docArr[0]._id; // undefined
  3589. *
  3590. * If you want to skip transformations, use `transform: false`:
  3591. *
  3592. * schema.options.toObject = {
  3593. * hide: '_id',
  3594. * transform: function(doc, ret, options) {
  3595. * if (options.hide) {
  3596. * options.hide.split(' ').forEach(function(prop) {
  3597. * delete ret[prop];
  3598. * });
  3599. * }
  3600. * return ret;
  3601. * }
  3602. * };
  3603. *
  3604. * const doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
  3605. * doc.toObject(); // { secret: 47, name: 'Wreck-it Ralph' }
  3606. * doc.toObject({ hide: 'secret _id', transform: false });// { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
  3607. * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
  3608. *
  3609. * If you pass a transform in `toObject()` options, Mongoose will apply the transform
  3610. * to [subdocuments](https://mongoosejs.com/docs/subdocs.html) in addition to the top-level document.
  3611. * Similarly, `transform: false` skips transforms for all subdocuments.
  3612. * Note that this behavior is different for transforms defined in the schema:
  3613. * if you define a transform in `schema.options.toObject.transform`, that transform
  3614. * will **not** apply to subdocuments.
  3615. *
  3616. * const memberSchema = new Schema({ name: String, email: String });
  3617. * const groupSchema = new Schema({ members: [memberSchema], name: String, email });
  3618. * const Group = mongoose.model('Group', groupSchema);
  3619. *
  3620. * const doc = new Group({
  3621. * name: 'Engineering',
  3622. * email: 'dev@mongoosejs.io',
  3623. * members: [{ name: 'Val', email: 'val@mongoosejs.io' }]
  3624. * });
  3625. *
  3626. * // Removes `email` from both top-level document **and** array elements
  3627. * // { name: 'Engineering', members: [{ name: 'Val' }] }
  3628. * doc.toObject({ transform: (doc, ret) => { delete ret.email; return ret; } });
  3629. *
  3630. * 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.
  3631. *
  3632. * See [schema options](https://mongoosejs.com/docs/guide.html#toObject) for some more details.
  3633. *
  3634. * _During save, no custom options are applied to the document before being sent to the database._
  3635. *
  3636. * @param {Object} [options]
  3637. * @param {Boolean} [options.getters=false] if true, apply all getters, including virtuals
  3638. * @param {Boolean|Object} [options.virtuals=false] if true, apply virtuals, including aliases. Use `{ getters: true, virtuals: false }` to just apply getters, not virtuals. An object of the form `{ pathsToSkip: ['someVirtual'] }` may also be used to omit specific virtuals.
  3639. * @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`.
  3640. * @param {Boolean} [options.minimize=true] if true, omit any empty objects from the output
  3641. * @param {Function|null} [options.transform=null] if set, mongoose will call this function to allow you to transform the returned object
  3642. * @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.
  3643. * @param {Boolean} [options.versionKey=true] if false, exclude the version key (`__v` by default) from the output
  3644. * @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`.
  3645. * @param {Boolean} [options.flattenObjectIds=false] if true, convert any ObjectIds in the result to 24 character hex strings.
  3646. * @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.
  3647. * @param {Boolean} [options.schemaFieldsOnly=false] - If true, the resulting object will only have fields that are defined in the document's schema. By default, `toObject()` returns all fields in the underlying document from MongoDB, including ones that are not listed in the schema.
  3648. * @return {Object} document as a plain old JavaScript object (POJO). This object may contain ObjectIds, Maps, Dates, mongodb.Binary, Buffers, and other non-POJO values.
  3649. * @see mongodb.Binary https://mongodb.github.io/node-mongodb-native/4.9/classes/Binary.html
  3650. * @api public
  3651. * @memberOf Document
  3652. * @instance
  3653. */
  3654. Document.prototype.toObject = function(options) {
  3655. return this.$toObject(options);
  3656. };
  3657. /*!
  3658. * Applies virtuals properties to `json`.
  3659. */
  3660. function applyVirtuals(self, json, options, toObjectOptions) {
  3661. const schema = self.$__schema;
  3662. const virtuals = schema.virtuals;
  3663. const paths = Object.keys(virtuals);
  3664. let i = paths.length;
  3665. const numPaths = i;
  3666. let path;
  3667. let assignPath;
  3668. let cur = self._doc;
  3669. let v;
  3670. const aliases = typeof toObjectOptions?.aliases === 'boolean'
  3671. ? toObjectOptions.aliases
  3672. : true;
  3673. options = options || {};
  3674. let virtualsToApply = null;
  3675. if (Array.isArray(options.virtuals)) {
  3676. virtualsToApply = new Set(options.virtuals);
  3677. } else if (options.virtuals?.pathsToSkip) {
  3678. virtualsToApply = new Set(paths);
  3679. for (let i = 0; i < options.virtuals.pathsToSkip.length; i++) {
  3680. if (virtualsToApply.has(options.virtuals.pathsToSkip[i])) {
  3681. virtualsToApply.delete(options.virtuals.pathsToSkip[i]);
  3682. }
  3683. }
  3684. }
  3685. if (!cur) {
  3686. return json;
  3687. }
  3688. for (i = 0; i < numPaths; ++i) {
  3689. path = paths[i];
  3690. if (virtualsToApply != null && !virtualsToApply.has(path)) {
  3691. continue;
  3692. }
  3693. // Allow skipping aliases with `toObject({ virtuals: true, aliases: false })`
  3694. if (!aliases && Object.hasOwn(schema.aliases, path)) {
  3695. continue;
  3696. }
  3697. // We may be applying virtuals to a nested object, for example if calling
  3698. // `doc.nestedProp.toJSON()`. If so, the path we assign to, `assignPath`,
  3699. // will be a trailing substring of the `path`.
  3700. assignPath = path;
  3701. if (options.path != null) {
  3702. if (!path.startsWith(options.path + '.')) {
  3703. continue;
  3704. }
  3705. assignPath = path.substring(options.path.length + 1);
  3706. }
  3707. if (assignPath.indexOf('.') === -1 && assignPath === path) {
  3708. v = virtuals[path].applyGetters(void 0, self);
  3709. if (v === void 0) {
  3710. continue;
  3711. }
  3712. v = clone(v, options);
  3713. json[assignPath] = v;
  3714. continue;
  3715. }
  3716. const parts = assignPath.split('.');
  3717. v = clone(self.get(path), options);
  3718. if (v === void 0) {
  3719. continue;
  3720. }
  3721. const plen = parts.length;
  3722. cur = json;
  3723. for (let j = 0; j < plen - 1; ++j) {
  3724. cur[parts[j]] = cur[parts[j]] || {};
  3725. cur = cur[parts[j]];
  3726. }
  3727. cur[parts[plen - 1]] = v;
  3728. }
  3729. return json;
  3730. }
  3731. /**
  3732. * Applies virtuals properties to `json`.
  3733. *
  3734. * @param {Document} self
  3735. * @param {Object} json
  3736. * @return {Object} `json`
  3737. * @api private
  3738. */
  3739. function applyGetters(self, json) {
  3740. const schema = self.$__schema;
  3741. const paths = Object.keys(schema.paths);
  3742. let i = paths.length;
  3743. let path;
  3744. let cur = self._doc;
  3745. let v;
  3746. if (!cur) {
  3747. return json;
  3748. }
  3749. while (i--) {
  3750. path = paths[i];
  3751. const parts = path.split('.');
  3752. const plen = parts.length;
  3753. const last = plen - 1;
  3754. let branch = json;
  3755. let part;
  3756. cur = self._doc;
  3757. if (!self.$__isSelected(path)) {
  3758. continue;
  3759. }
  3760. for (let ii = 0; ii < plen; ++ii) {
  3761. part = parts[ii];
  3762. v = cur[part];
  3763. // If we've reached a non-object part of the branch, continuing would
  3764. // cause "Cannot create property 'foo' on string 'bar'" error.
  3765. // Necessary for mongoose-intl plugin re: gh-14446
  3766. if (branch != null && typeof branch !== 'object') {
  3767. break;
  3768. } else if (ii === last) {
  3769. branch[part] = schema.paths[path].applyGetters(
  3770. branch[part],
  3771. self
  3772. );
  3773. if (Array.isArray(branch[part]) && schema.paths[path].embeddedSchemaType) {
  3774. for (let i = 0; i < branch[part].length; ++i) {
  3775. branch[part][i] = schema.paths[path].embeddedSchemaType.applyGetters(
  3776. branch[part][i],
  3777. self
  3778. );
  3779. }
  3780. }
  3781. } else if (v == null) {
  3782. if (part in cur) {
  3783. branch[part] = v;
  3784. }
  3785. break;
  3786. } else {
  3787. branch = branch[part] || (branch[part] = {});
  3788. }
  3789. cur = v;
  3790. }
  3791. }
  3792. return json;
  3793. }
  3794. /**
  3795. * Applies schema type transforms to `json`.
  3796. *
  3797. * @param {Document} self
  3798. * @param {Object} json
  3799. * @return {Object} `json`
  3800. * @api private
  3801. */
  3802. function applySchemaTypeTransforms(self, json) {
  3803. const schema = self.$__schema;
  3804. const paths = Object.keys(schema.paths || {});
  3805. const cur = self._doc;
  3806. if (!cur) {
  3807. return json;
  3808. }
  3809. for (const path of paths) {
  3810. const schematype = schema.paths[path];
  3811. const topLevelTransformFunction = schematype.options.transform ?? schematype.constructor?.defaultOptions?.transform;
  3812. const embeddedSchemaTypeTransformFunction = schematype.embeddedSchemaType?.options?.transform
  3813. ?? schematype.embeddedSchemaType?.constructor?.defaultOptions?.transform;
  3814. if (typeof topLevelTransformFunction === 'function') {
  3815. const val = self.$get(path);
  3816. if (val === undefined) {
  3817. continue;
  3818. }
  3819. const transformedValue = topLevelTransformFunction.call(self, val);
  3820. throwErrorIfPromise(path, transformedValue);
  3821. utils.setValue(path, transformedValue, json);
  3822. } else if (typeof embeddedSchemaTypeTransformFunction === 'function') {
  3823. const val = self.$get(path);
  3824. if (val === undefined) {
  3825. continue;
  3826. }
  3827. const vals = [].concat(val);
  3828. for (let i = 0; i < vals.length; ++i) {
  3829. const transformedValue = embeddedSchemaTypeTransformFunction.call(self, vals[i]);
  3830. vals[i] = transformedValue;
  3831. throwErrorIfPromise(path, transformedValue);
  3832. }
  3833. json[path] = vals;
  3834. }
  3835. }
  3836. return json;
  3837. }
  3838. function throwErrorIfPromise(path, transformedValue) {
  3839. if (isPromise(transformedValue)) {
  3840. throw new Error('`transform` function must be synchronous, but the transform on path `' + path + '` returned a promise.');
  3841. }
  3842. }
  3843. /*!
  3844. * ignore
  3845. */
  3846. function omitDeselectedFields(self, json) {
  3847. const schema = self.$__schema;
  3848. const paths = Object.keys(schema.paths || {});
  3849. const cur = self._doc;
  3850. if (!cur) {
  3851. return json;
  3852. }
  3853. let selected = self.$__.selected;
  3854. if (selected === void 0) {
  3855. selected = {};
  3856. queryhelpers.applyPaths(selected, schema);
  3857. }
  3858. if (selected == null || utils.hasOwnKeys(selected) === false) {
  3859. return json;
  3860. }
  3861. for (const path of paths) {
  3862. if (selected[path] != null && !selected[path]) {
  3863. delete json[path];
  3864. }
  3865. }
  3866. return json;
  3867. }
  3868. /**
  3869. * The return value of this method is used in calls to [`JSON.stringify(doc)`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript#the-tojson-function).
  3870. *
  3871. * This method accepts the same options as [Document#toObject](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()). To apply the options to every document of your schema by default, set your [schemas](https://mongoosejs.com/docs/api/schema.html#Schema()) `toJSON` option to the same argument.
  3872. *
  3873. * schema.set('toJSON', { virtuals: true });
  3874. *
  3875. * There is one difference between `toJSON()` and `toObject()` options.
  3876. * When you call `toJSON()`, the [`flattenMaps` option](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()) defaults to `true`, because `JSON.stringify()` doesn't convert maps to objects by default.
  3877. * When you call `toObject()`, the `flattenMaps` option is `false` by default.
  3878. *
  3879. * See [schema options](https://mongoosejs.com/docs/guide.html#toJSON) for more information on setting `toJSON` option defaults.
  3880. *
  3881. * @param {Object} options
  3882. * @param {Boolean} [options.flattenMaps=true] if true, convert Maps to [POJOs](https://masteringjs.io/tutorials/fundamentals/pojo). Useful if you want to `JSON.stringify()` the result.
  3883. * @param {Boolean} [options.flattenObjectIds=false] if true, convert any ObjectIds in the result to 24 character hex strings.
  3884. * @param {Boolean} [options.schemaFieldsOnly=false] - If true, the resulting object will only have fields that are defined in the document's schema. By default, `toJSON()` returns all fields in the underlying document from MongoDB, including ones that are not listed in the schema.
  3885. * @return {Object}
  3886. * @see Document#toObject https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()
  3887. * @see JSON.stringify() in JavaScript https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html
  3888. * @api public
  3889. * @memberOf Document
  3890. * @instance
  3891. */
  3892. Document.prototype.toJSON = function(options) {
  3893. return this.$toObject(options, true);
  3894. };
  3895. /*!
  3896. * ignore
  3897. */
  3898. Document.prototype.ownerDocument = function() {
  3899. return this;
  3900. };
  3901. /**
  3902. * If this document is a subdocument or populated document, returns the document's
  3903. * parent. Returns the original document if there is no parent.
  3904. *
  3905. * @return {Document}
  3906. * @api public
  3907. * @method parent
  3908. * @memberOf Document
  3909. * @instance
  3910. */
  3911. Document.prototype.parent = function() {
  3912. if (this.$isSubdocument || this.$__.wasPopulated) {
  3913. return this.$__.parent;
  3914. }
  3915. return this;
  3916. };
  3917. /**
  3918. * Alias for [`parent()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.parent()). If this document is a subdocument or populated
  3919. * document, returns the document's parent. Returns `undefined` otherwise.
  3920. *
  3921. * @return {Document}
  3922. * @api public
  3923. * @method $parent
  3924. * @memberOf Document
  3925. * @instance
  3926. */
  3927. Document.prototype.$parent = Document.prototype.parent;
  3928. /**
  3929. * Set the parent of this document.
  3930. *
  3931. * @param {Document} parent
  3932. * @api private
  3933. * @method $__setParent
  3934. * @memberOf Document
  3935. * @instance
  3936. */
  3937. Document.prototype.$__setParent = function $__setParent(parent) {
  3938. this.$__.parent = parent;
  3939. };
  3940. /**
  3941. * Helper for console.log
  3942. *
  3943. * @return {String}
  3944. * @api public
  3945. * @method inspect
  3946. * @memberOf Document
  3947. * @instance
  3948. */
  3949. Document.prototype.inspect = function(options) {
  3950. const isPOJO = utils.isPOJO(options);
  3951. let opts;
  3952. if (isPOJO) {
  3953. opts = options;
  3954. opts.minimize = false;
  3955. }
  3956. const ret = arguments.length > 0 ? this.toObject(opts) : this.toObject();
  3957. if (ret == null) {
  3958. // If `toObject()` returns null, `this` is still an object, so if `inspect()`
  3959. // prints out null this can cause some serious confusion. See gh-7942.
  3960. return 'MongooseDocument { ' + ret + ' }';
  3961. }
  3962. return ret;
  3963. };
  3964. if (inspect.custom) {
  3965. // Avoid Node deprecation warning DEP0079
  3966. Document.prototype[inspect.custom] = Document.prototype.inspect;
  3967. }
  3968. /**
  3969. * Helper for console.log
  3970. *
  3971. * @return {String}
  3972. * @api public
  3973. * @method toString
  3974. * @memberOf Document
  3975. * @instance
  3976. */
  3977. Document.prototype.toString = function() {
  3978. const ret = this.inspect();
  3979. if (typeof ret === 'string') {
  3980. return ret;
  3981. }
  3982. return inspect(ret);
  3983. };
  3984. /**
  3985. * Returns true if this document is equal to another document.
  3986. *
  3987. * Documents are considered equal when they have matching `_id`s, unless neither
  3988. * document has an `_id`, in which case this function falls back to using
  3989. * `deepEqual()`.
  3990. *
  3991. * @param {Document} [doc] a document to compare. If falsy, will always return "false".
  3992. * @return {Boolean}
  3993. * @api public
  3994. * @memberOf Document
  3995. * @instance
  3996. */
  3997. Document.prototype.equals = function(doc) {
  3998. if (!doc) {
  3999. return false;
  4000. }
  4001. const tid = this.$__getValue('_id');
  4002. const docid = doc.$__ != null ? doc.$__getValue('_id') : doc;
  4003. if (!tid && !docid) {
  4004. return deepEqual(this, doc);
  4005. }
  4006. return tid && tid.equals
  4007. ? tid.equals(docid)
  4008. : tid === docid;
  4009. };
  4010. /**
  4011. * Populates paths on an existing document.
  4012. *
  4013. * #### Example:
  4014. *
  4015. * // Given a document, `populate()` lets you pull in referenced docs
  4016. * await doc.populate([
  4017. * 'stories',
  4018. * { path: 'fans', sort: { name: -1 } }
  4019. * ]);
  4020. * doc.populated('stories'); // Array of ObjectIds
  4021. * doc.stories[0].title; // 'Casino Royale'
  4022. * doc.populated('fans'); // Array of ObjectIds
  4023. *
  4024. * // If the referenced doc has been deleted, `populate()` will
  4025. * // remove that entry from the array.
  4026. * await Story.delete({ title: 'Casino Royale' });
  4027. * await doc.populate('stories'); // Empty array
  4028. *
  4029. * // You can also pass additional query options to `populate()`,
  4030. * // like projections:
  4031. * await doc.populate('fans', '-email');
  4032. * doc.fans[0].email // undefined because of 2nd param `select`
  4033. *
  4034. * @param {String|Object|Array} path either the path to populate or an object specifying all parameters, or either an array of those
  4035. * @param {Object|String} [select] Field selection for the population query
  4036. * @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.
  4037. * @param {Object} [match] Conditions for the population query
  4038. * @param {Object} [options] Options for the population query (sort, etc)
  4039. * @param {String} [options.path=null] The path to populate.
  4040. * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](https://mongoosejs.com/docs/populate.html#deep-populate).
  4041. * @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.
  4042. * @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`](https://mongoosejs.com/docs/schematypes.html#schematype-options).
  4043. * @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.
  4044. * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object.
  4045. * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
  4046. * @param {Object} [options.options=null] Additional options like `limit` and `lean`.
  4047. * @param {Boolean} [options.forceRepopulate=true] Set to `false` to prevent Mongoose from repopulating paths that are already populated
  4048. * @param {Boolean} [options.ordered=false] Set to `true` to execute any populate queries one at a time, as opposed to in parallel. We recommend setting this option to `true` if using transactions, especially if also populating multiple paths or paths with multiple models. MongoDB server does **not** support multiple operations in parallel on a single transaction.
  4049. * @param {Function} [callback] Callback
  4050. * @see population https://mongoosejs.com/docs/populate.html
  4051. * @see Query#select https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
  4052. * @see Model.populate https://mongoosejs.com/docs/api/model.html#Model.populate()
  4053. * @memberOf Document
  4054. * @instance
  4055. * @return {Promise|null} Returns a Promise if no `callback` is given.
  4056. * @api public
  4057. */
  4058. Document.prototype.populate = async function populate() {
  4059. const pop = {};
  4060. const args = [...arguments];
  4061. if (typeof args[args.length - 1] === 'function') {
  4062. throw new MongooseError('Document.prototype.populate() no longer accepts a callback');
  4063. }
  4064. if (args.length !== 0) {
  4065. // use hash to remove duplicate paths
  4066. const res = utils.populate.apply(null, args);
  4067. for (const populateOptions of res) {
  4068. pop[populateOptions.path] = populateOptions;
  4069. }
  4070. }
  4071. const paths = utils.object.vals(pop);
  4072. let topLevelModel = this.constructor;
  4073. if (this.$__isNested) {
  4074. topLevelModel = this.$__[scopeSymbol].constructor;
  4075. const nestedPath = this.$__.nestedPath;
  4076. paths.forEach(function(populateOptions) {
  4077. populateOptions.path = nestedPath + '.' + populateOptions.path;
  4078. });
  4079. }
  4080. // Use `$session()` by default if the document has an associated session
  4081. // See gh-6754
  4082. if (this.$session() != null) {
  4083. const session = this.$session();
  4084. paths.forEach(path => {
  4085. if (path.options == null) {
  4086. path.options = { session: session };
  4087. return;
  4088. }
  4089. if (!('session' in path.options)) {
  4090. path.options.session = session;
  4091. }
  4092. });
  4093. }
  4094. paths.forEach(p => {
  4095. p._localModel = topLevelModel;
  4096. });
  4097. return topLevelModel.populate(this, paths);
  4098. };
  4099. /**
  4100. * Gets all populated documents associated with this document.
  4101. *
  4102. * @api public
  4103. * @return {Document[]} array of populated documents. Empty array if there are no populated documents associated with this document.
  4104. * @memberOf Document
  4105. * @method $getPopulatedDocs
  4106. * @instance
  4107. */
  4108. Document.prototype.$getPopulatedDocs = function $getPopulatedDocs() {
  4109. let keys = [];
  4110. if (this.$__.populated != null) {
  4111. keys = keys.concat(Object.keys(this.$__.populated));
  4112. }
  4113. let result = [];
  4114. for (const key of keys) {
  4115. const value = this.$get(key);
  4116. if (Array.isArray(value)) {
  4117. result = result.concat(value);
  4118. } else if (value instanceof Document) {
  4119. result.push(value);
  4120. }
  4121. }
  4122. return result;
  4123. };
  4124. /**
  4125. * Gets _id(s) used during population of the given `path`.
  4126. *
  4127. * #### Example:
  4128. *
  4129. * const doc = await Model.findOne().populate('author');
  4130. *
  4131. * console.log(doc.author.name); // Dr.Seuss
  4132. * console.log(doc.populated('author')); // '5144cf8050f071d979c118a7'
  4133. *
  4134. * If the path was not populated, returns `undefined`.
  4135. *
  4136. * @param {String} path
  4137. * @param {Any} [val]
  4138. * @param {Object} [options]
  4139. * @return {Array|ObjectId|Number|Buffer|String|undefined}
  4140. * @memberOf Document
  4141. * @instance
  4142. * @api public
  4143. */
  4144. Document.prototype.populated = function(path, val, options) {
  4145. // val and options are internal
  4146. if (val == null || val === true) {
  4147. if (!this.$__.populated) {
  4148. return undefined;
  4149. }
  4150. if (typeof path !== 'string') {
  4151. return undefined;
  4152. }
  4153. // Map paths can be populated with either `path.$*` or just `path`
  4154. const _path = path.endsWith('.$*') ? path.replace(/\.\$\*$/, '') : path;
  4155. const v = this.$__.populated[_path];
  4156. if (v) {
  4157. return val === true ? v : v.value;
  4158. }
  4159. return undefined;
  4160. }
  4161. this.$__.populated || (this.$__.populated = {});
  4162. this.$__.populated[path] = { value: val, options: options };
  4163. // If this was a nested populate, make sure each populated doc knows
  4164. // about its populated children (gh-7685)
  4165. const pieces = path.split('.');
  4166. for (let i = 0; i < pieces.length - 1; ++i) {
  4167. const subpath = pieces.slice(0, i + 1).join('.');
  4168. const subdoc = this.$get(subpath);
  4169. if (subdoc?.$__ != null && this.$populated(subpath)) {
  4170. const rest = pieces.slice(i + 1).join('.');
  4171. subdoc.$populated(rest, val, options);
  4172. // No need to continue because the above recursion should take care of
  4173. // marking the rest of the docs as populated
  4174. break;
  4175. }
  4176. }
  4177. return val;
  4178. };
  4179. /**
  4180. * Alias of [`.populated`](https://mongoosejs.com/docs/api/document.html#Document.prototype.populated()).
  4181. *
  4182. * @method $populated
  4183. * @memberOf Document
  4184. * @api public
  4185. */
  4186. Document.prototype.$populated = Document.prototype.populated;
  4187. /**
  4188. * Throws an error if a given path is not populated
  4189. *
  4190. * #### Example:
  4191. *
  4192. * const doc = await Model.findOne().populate('author');
  4193. *
  4194. * doc.$assertPopulated('author'); // does not throw
  4195. * doc.$assertPopulated('other path'); // throws an error
  4196. *
  4197. * // Manually populate and assert in one call. The following does
  4198. * // `doc.$set({ likes })` before asserting.
  4199. * doc.$assertPopulated('likes', { likes });
  4200. *
  4201. *
  4202. * @param {String|String[]} path path or array of paths to check. `$assertPopulated` throws if any of the given paths is not populated.
  4203. * @param {Object} [values] optional values to `$set()`. Convenient if you want to manually populate a path and assert that the path was populated in 1 call.
  4204. * @return {Document} this
  4205. * @memberOf Document
  4206. * @method $assertPopulated
  4207. * @instance
  4208. * @api public
  4209. */
  4210. Document.prototype.$assertPopulated = function $assertPopulated(path, values) {
  4211. if (Array.isArray(path)) {
  4212. path.forEach(p => this.$assertPopulated(p, values));
  4213. return this;
  4214. }
  4215. if (arguments.length > 1) {
  4216. this.$set(values);
  4217. }
  4218. if (!this.$populated(path)) {
  4219. throw new MongooseError(`Expected path "${path}" to be populated`);
  4220. }
  4221. return this;
  4222. };
  4223. /**
  4224. * Takes a populated field and returns it to its unpopulated state.
  4225. *
  4226. * #### Example:
  4227. *
  4228. * Model.findOne().populate('author').exec(function (err, doc) {
  4229. * console.log(doc.author.name); // Dr.Seuss
  4230. * console.log(doc.depopulate('author'));
  4231. * console.log(doc.author); // '5144cf8050f071d979c118a7'
  4232. * })
  4233. *
  4234. * If the path was not provided, then all populated fields are returned to their unpopulated state.
  4235. *
  4236. * @param {String|String[]} [path] Specific Path to depopulate. If unset, will depopulate all paths on the Document. Or multiple space-delimited paths.
  4237. * @return {Document} this
  4238. * @see Document.populate https://mongoosejs.com/docs/api/document.html#Document.prototype.populate()
  4239. * @api public
  4240. * @memberOf Document
  4241. * @instance
  4242. */
  4243. Document.prototype.depopulate = function(path) {
  4244. if (typeof path === 'string') {
  4245. path = path.indexOf(' ') === -1 ? [path] : path.split(' ');
  4246. }
  4247. let populatedIds;
  4248. const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : [];
  4249. const populated = this.$__?.populated || {};
  4250. if (arguments.length === 0) {
  4251. // Depopulate all
  4252. for (const virtualKey of virtualKeys) {
  4253. delete this.$$populatedVirtuals[virtualKey];
  4254. delete this._doc[virtualKey];
  4255. delete populated[virtualKey];
  4256. }
  4257. const keys = Object.keys(populated);
  4258. for (const key of keys) {
  4259. populatedIds = this.$populated(key);
  4260. if (!populatedIds) {
  4261. continue;
  4262. }
  4263. delete populated[key];
  4264. if (Array.isArray(populatedIds)) {
  4265. const arr = utils.getValue(key, this._doc);
  4266. if (arr.isMongooseArray) {
  4267. const rawArray = arr.__array;
  4268. for (let i = 0; i < rawArray.length; ++i) {
  4269. const subdoc = rawArray[i];
  4270. if (subdoc == null) {
  4271. continue;
  4272. }
  4273. rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id;
  4274. }
  4275. } else {
  4276. utils.setValue(key, populatedIds, this._doc);
  4277. }
  4278. } else {
  4279. utils.setValue(key, populatedIds, this._doc);
  4280. }
  4281. }
  4282. return this;
  4283. }
  4284. for (const singlePath of path) {
  4285. populatedIds = this.$populated(singlePath);
  4286. delete populated[singlePath];
  4287. if (virtualKeys.indexOf(singlePath) !== -1) {
  4288. delete this.$$populatedVirtuals[singlePath];
  4289. delete this._doc[singlePath];
  4290. } else if (populatedIds) {
  4291. if (Array.isArray(populatedIds)) {
  4292. const arr = utils.getValue(singlePath, this._doc);
  4293. if (arr.isMongooseArray) {
  4294. const rawArray = arr.__array;
  4295. for (let i = 0; i < rawArray.length; ++i) {
  4296. const subdoc = rawArray[i];
  4297. if (subdoc == null) {
  4298. continue;
  4299. }
  4300. rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id;
  4301. }
  4302. } else {
  4303. utils.setValue(singlePath, populatedIds, this._doc);
  4304. }
  4305. } else {
  4306. utils.setValue(singlePath, populatedIds, this._doc);
  4307. }
  4308. }
  4309. }
  4310. return this;
  4311. };
  4312. /**
  4313. * Returns the full path to this document.
  4314. *
  4315. * @param {String} [path]
  4316. * @return {String}
  4317. * @api private
  4318. * @method $__fullPath
  4319. * @memberOf Document
  4320. * @instance
  4321. */
  4322. Document.prototype.$__fullPath = function(path) {
  4323. // overridden in SubDocuments
  4324. return path || '';
  4325. };
  4326. /**
  4327. * Returns the changes that happened to the document
  4328. * in the format that will be sent to MongoDB.
  4329. *
  4330. * #### Example:
  4331. *
  4332. * const userSchema = new Schema({
  4333. * name: String,
  4334. * age: Number,
  4335. * country: String
  4336. * });
  4337. * const User = mongoose.model('User', userSchema);
  4338. * const user = await User.create({
  4339. * name: 'Hafez',
  4340. * age: 25,
  4341. * country: 'Egypt'
  4342. * });
  4343. *
  4344. * // returns an empty object, no changes happened yet
  4345. * user.getChanges(); // { }
  4346. *
  4347. * user.country = undefined;
  4348. * user.age = 26;
  4349. *
  4350. * user.getChanges(); // { $set: { age: 26 }, { $unset: { country: 1 } } }
  4351. *
  4352. * await user.save();
  4353. *
  4354. * user.getChanges(); // { }
  4355. *
  4356. * Modifying the object that `getChanges()` returns does not affect the document's
  4357. * change tracking state. Even if you `delete user.getChanges().$set`, Mongoose
  4358. * will still send a `$set` to the server.
  4359. *
  4360. * @return {Object}
  4361. * @api public
  4362. * @method getChanges
  4363. * @memberOf Document
  4364. * @instance
  4365. */
  4366. Document.prototype.getChanges = function() {
  4367. const delta = this.$__delta();
  4368. const changes = delta ? delta[1] : {};
  4369. return changes;
  4370. };
  4371. /**
  4372. * Produces a special query document of the modified properties used in updates.
  4373. *
  4374. * @api private
  4375. * @method $__delta
  4376. * @memberOf Document
  4377. * @instance
  4378. */
  4379. Document.prototype.$__delta = function $__delta() {
  4380. const dirty = this.$__dirty();
  4381. const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
  4382. if (optimisticConcurrency) {
  4383. if (Array.isArray(optimisticConcurrency)) {
  4384. const optCon = new Set(optimisticConcurrency);
  4385. const modPaths = this.modifiedPaths();
  4386. if (modPaths.some(path => optCon.has(path))) {
  4387. this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
  4388. }
  4389. } else if (Array.isArray(optimisticConcurrency?.exclude)) {
  4390. const excluded = new Set(optimisticConcurrency.exclude);
  4391. const modPaths = this.modifiedPaths();
  4392. if (modPaths.some(path => !excluded.has(path))) {
  4393. this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
  4394. }
  4395. } else {
  4396. this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
  4397. }
  4398. }
  4399. if (!dirty.length && VERSION_ALL !== this.$__.version) {
  4400. return;
  4401. }
  4402. const where = {};
  4403. const delta = {};
  4404. const len = dirty.length;
  4405. const divergent = [];
  4406. let d = 0;
  4407. where._id = this._doc._id;
  4408. // If `_id` is an object, need to depopulate, but also need to be careful
  4409. // because `_id` can technically be null (see gh-6406)
  4410. if (where?._id?.$__ != null) {
  4411. where._id = where._id.toObject({ transform: false, depopulate: true });
  4412. }
  4413. for (; d < len; ++d) {
  4414. const data = dirty[d];
  4415. let value = data.value;
  4416. const match = checkDivergentArray(this, data.path, value);
  4417. if (match) {
  4418. divergent.push(match);
  4419. continue;
  4420. }
  4421. const pop = this.$populated(data.path, true);
  4422. if (!pop && this.$__.selected) {
  4423. // If any array was selected using an $elemMatch projection, we alter the path and where clause
  4424. // NOTE: MongoDB only supports projected $elemMatch on top level array.
  4425. const pathSplit = data.path.split('.');
  4426. const top = pathSplit[0];
  4427. if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) {
  4428. // If the selected array entry was modified
  4429. if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') {
  4430. where[top] = this.$__.selected[top];
  4431. pathSplit[1] = '$';
  4432. data.path = pathSplit.join('.');
  4433. }
  4434. // if the selected array was modified in any other way throw an error
  4435. else {
  4436. divergent.push(data.path);
  4437. continue;
  4438. }
  4439. }
  4440. }
  4441. // If this path is set to default, and either this path or one of
  4442. // its parents is excluded, don't treat this path as dirty.
  4443. if (this.$isDefault(data.path) && this.$__.selected) {
  4444. if (data.path.indexOf('.') === -1 && isPathExcluded(this.$__.selected, data.path)) {
  4445. continue;
  4446. }
  4447. const pathsToCheck = parentPaths(data.path);
  4448. if (pathsToCheck.find(path => isPathExcluded(this.$__.isSelected, path))) {
  4449. continue;
  4450. }
  4451. }
  4452. if (divergent.length) continue;
  4453. if (value === undefined) {
  4454. operand(this, where, delta, data, 1, '$unset');
  4455. } else if (value === null) {
  4456. operand(this, where, delta, data, null);
  4457. } else if (typeof value.getAtomics === 'function') {
  4458. // arrays and other custom container types
  4459. handleAtomics(this, where, delta, data, value);
  4460. } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) {
  4461. // MongooseBuffer
  4462. value = value.toObject();
  4463. operand(this, where, delta, data, value);
  4464. } else {
  4465. if (this.$__.primitiveAtomics?.[data.path] != null) {
  4466. const val = this.$__.primitiveAtomics[data.path];
  4467. const op = firstKey(val);
  4468. operand(this, where, delta, data, val[op], op);
  4469. } else {
  4470. value = clone(value, {
  4471. depopulate: true,
  4472. transform: false,
  4473. virtuals: false,
  4474. getters: false,
  4475. omitUndefined: true,
  4476. _isNested: true
  4477. });
  4478. operand(this, where, delta, data, value);
  4479. }
  4480. }
  4481. }
  4482. if (divergent.length) {
  4483. throw new DivergentArrayError(divergent);
  4484. }
  4485. if (this.$__.version) {
  4486. this.$__version(where, delta);
  4487. }
  4488. if (utils.hasOwnKeys(delta) === false) {
  4489. return [where, null];
  4490. }
  4491. return [where, delta];
  4492. };
  4493. /**
  4494. * Determine if array was populated with some form of filter and is now
  4495. * being updated in a manner which could overwrite data unintentionally.
  4496. *
  4497. * @see https://github.com/Automattic/mongoose/issues/1334
  4498. * @param {Document} doc
  4499. * @param {String} path
  4500. * @param {Any} array
  4501. * @return {String|undefined}
  4502. * @api private
  4503. */
  4504. function checkDivergentArray(doc, path, array) {
  4505. // see if we populated this path
  4506. const pop = doc.$populated(path, true);
  4507. if (!pop && doc.$__.selected) {
  4508. // If any array was selected using an $elemMatch projection, we deny the update.
  4509. // NOTE: MongoDB only supports projected $elemMatch on top level array.
  4510. const top = path.split('.')[0];
  4511. if (doc.$__.selected[top + '.$']) {
  4512. return top;
  4513. }
  4514. }
  4515. if (!(pop && utils.isMongooseArray(array))) return;
  4516. // If the array was populated using options that prevented all
  4517. // documents from being returned (match, skip, limit) or they
  4518. // deselected the _id field, $pop and $set of the array are
  4519. // not safe operations. If _id was deselected, we do not know
  4520. // how to remove elements. $pop will pop off the _id from the end
  4521. // of the array in the db which is not guaranteed to be the
  4522. // same as the last element we have here. $set of the entire array
  4523. // would be similarly destructive as we never received all
  4524. // elements of the array and potentially would overwrite data.
  4525. const check = pop.options.match ||
  4526. pop.options.options && Object.hasOwn(pop.options.options, 'limit') || // 0 is not permitted
  4527. pop.options.options?.skip || // 0 is permitted
  4528. pop.options.select && // deselected _id?
  4529. (pop.options.select._id === 0 ||
  4530. /\s?-_id\s?/.test(pop.options.select));
  4531. if (check) {
  4532. const atomics = array[arrayAtomicsSymbol];
  4533. if (utils.hasOwnKeys(atomics) === false || atomics.$set || atomics.$pop) {
  4534. return path;
  4535. }
  4536. }
  4537. }
  4538. /**
  4539. * Apply the operation to the delta (update) clause as
  4540. * well as track versioning for our where clause.
  4541. *
  4542. * @param {Document} self
  4543. * @param {Object} where Unused
  4544. * @param {Object} delta
  4545. * @param {Object} data
  4546. * @param {Mixed} val
  4547. * @param {String} [op]
  4548. * @api private
  4549. */
  4550. function operand(self, where, delta, data, val, op) {
  4551. // delta
  4552. op || (op = '$set');
  4553. if (!delta[op]) delta[op] = {};
  4554. delta[op][data.path] = val;
  4555. // disabled versioning?
  4556. if (self.$__schema.options.versionKey === false) return;
  4557. // path excluded from versioning?
  4558. if (shouldSkipVersioning(self, data.path)) return;
  4559. // already marked for versioning?
  4560. if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return;
  4561. if (
  4562. self.$__schema.options.optimisticConcurrency === true ||
  4563. Array.isArray(self.$__schema.options.optimisticConcurrency) ||
  4564. Array.isArray(self.$__schema.options.optimisticConcurrency?.exclude)
  4565. ) {
  4566. return;
  4567. }
  4568. switch (op) {
  4569. case '$set':
  4570. case '$unset':
  4571. case '$pop':
  4572. case '$pull':
  4573. case '$pullAll':
  4574. case '$push':
  4575. case '$addToSet':
  4576. case '$inc':
  4577. break;
  4578. default:
  4579. // nothing to do
  4580. return;
  4581. }
  4582. // ensure updates sent with positional notation are
  4583. // editing the correct array element.
  4584. // only increment the version if an array position changes.
  4585. // modifying elements of an array is ok if position does not change.
  4586. if (op === '$push' || op === '$addToSet' || op === '$pullAll' || op === '$pull') {
  4587. if (/\.\d+\.|\.\d+$/.test(data.path)) {
  4588. self.$__.version = VERSION_ALL;
  4589. } else {
  4590. self.$__.version |= VERSION_INC;
  4591. }
  4592. } else if (/^\$p/.test(op)) {
  4593. // potentially changing array positions
  4594. self.$__.version = VERSION_ALL;
  4595. } else if (Array.isArray(val)) {
  4596. // $set an array
  4597. self.$__.version = VERSION_ALL;
  4598. } else if (/\.\d+\.|\.\d+$/.test(data.path)) {
  4599. // now handling $set, $unset
  4600. // subpath of array
  4601. self.$__.version |= VERSION_WHERE;
  4602. }
  4603. }
  4604. /**
  4605. * Compiles an update and where clause for a `val` with _atomics.
  4606. *
  4607. * @param {Document} self
  4608. * @param {Object} where
  4609. * @param {Object} delta
  4610. * @param {Object} data
  4611. * @param {Array} value
  4612. * @api private
  4613. */
  4614. function handleAtomics(self, where, delta, data, value) {
  4615. if (delta.$set?.[data.path]) {
  4616. // $set has precedence over other atomics
  4617. return;
  4618. }
  4619. if (typeof value.getAtomics === 'function') {
  4620. value.getAtomics().forEach(function(atomic) {
  4621. const op = atomic[0];
  4622. const val = atomic[1];
  4623. operand(self, where, delta, data, val, op);
  4624. });
  4625. return;
  4626. }
  4627. if (typeof value.$__getAtomics === 'function') {
  4628. value.$__getAtomics().forEach(function(atomic) {
  4629. const op = atomic[0];
  4630. const val = atomic[1];
  4631. operand(self, where, delta, data, val, op);
  4632. });
  4633. return;
  4634. }
  4635. // legacy support for plugins
  4636. const atomics = value[arrayAtomicsSymbol];
  4637. const ops = Object.keys(atomics);
  4638. let i = ops.length;
  4639. let val;
  4640. let op;
  4641. if (i === 0) {
  4642. // $set
  4643. if (utils.isMongooseObject(value)) {
  4644. value = value.toObject({ depopulate: 1, _isNested: true });
  4645. } else if (value.valueOf) {
  4646. value = value.valueOf();
  4647. }
  4648. return operand(self, where, delta, data, value);
  4649. }
  4650. function iter(mem) {
  4651. return utils.isMongooseObject(mem)
  4652. ? mem.toObject({ depopulate: 1, _isNested: true })
  4653. : mem;
  4654. }
  4655. while (i--) {
  4656. op = ops[i];
  4657. val = atomics[op];
  4658. if (utils.isMongooseObject(val)) {
  4659. val = val.toObject({ depopulate: true, transform: false, _isNested: true });
  4660. } else if (Array.isArray(val)) {
  4661. val = val.map(iter);
  4662. } else if (val.valueOf) {
  4663. val = val.valueOf();
  4664. }
  4665. if (op === '$addToSet') {
  4666. val = { $each: val };
  4667. }
  4668. operand(self, where, delta, data, val, op);
  4669. }
  4670. }
  4671. /**
  4672. * Determines whether versioning should be skipped for the given path
  4673. *
  4674. * @param {Document} self
  4675. * @param {String} path
  4676. * @return {Boolean} true if versioning should be skipped for the given path
  4677. * @api private
  4678. */
  4679. function shouldSkipVersioning(self, path) {
  4680. const skipVersioning = self.$__schema.options.skipVersioning;
  4681. if (!skipVersioning) return false;
  4682. // Remove any array indexes from the path
  4683. path = path.replace(/\.\d+\./, '.');
  4684. return skipVersioning[path];
  4685. }
  4686. /**
  4687. * Returns a copy of this document with a deep clone of `_doc` and `$__`.
  4688. *
  4689. * @return {Document} a copy of this document
  4690. * @api public
  4691. * @method $clone
  4692. * @memberOf Document
  4693. * @instance
  4694. */
  4695. Document.prototype.$clone = function() {
  4696. const Model = this.constructor;
  4697. const clonedDoc = new Model();
  4698. clonedDoc.$isNew = this.$isNew;
  4699. if (this._doc) {
  4700. clonedDoc._doc = clone(this._doc, { retainDocuments: true, parentDoc: clonedDoc });
  4701. }
  4702. if (this.$__) {
  4703. const Cache = this.$__.constructor;
  4704. const clonedCache = new Cache();
  4705. for (const key of Object.getOwnPropertyNames(this.$__)) {
  4706. if (key === 'activePaths') {
  4707. continue;
  4708. }
  4709. clonedCache[key] = clone(this.$__[key]);
  4710. }
  4711. Object.assign(
  4712. clonedCache.activePaths,
  4713. clone({ ...this.$__.activePaths })
  4714. );
  4715. clonedDoc.$__ = clonedCache;
  4716. }
  4717. return clonedDoc;
  4718. };
  4719. /**
  4720. * Creates a snapshot of this document's internal change tracking state. You can later
  4721. * reset this document's change tracking state using `$restoreModifiedPathsSnapshot()`.
  4722. *
  4723. * #### Example:
  4724. *
  4725. * const doc = await TestModel.findOne();
  4726. * const snapshot = doc.$createModifiedPathsSnapshot();
  4727. *
  4728. * @return {ModifiedPathsSnapshot} a copy of this document's internal change tracking state
  4729. * @api public
  4730. * @method $createModifiedPathsSnapshot
  4731. * @memberOf Document
  4732. * @instance
  4733. */
  4734. Document.prototype.$createModifiedPathsSnapshot = function $createModifiedPathsSnapshot() {
  4735. const subdocSnapshot = new WeakMap();
  4736. if (!this.$isSubdocument) {
  4737. const subdocs = this.$getAllSubdocs();
  4738. for (const child of subdocs) {
  4739. subdocSnapshot.set(child, child.$__.activePaths.clone());
  4740. }
  4741. }
  4742. return new ModifiedPathsSnapshot(
  4743. subdocSnapshot,
  4744. this.$__.activePaths.clone(),
  4745. this.$__.version
  4746. );
  4747. };
  4748. /**
  4749. * Restore this document's change tracking state to the given snapshot.
  4750. * Note that `$restoreModifiedPathsSnapshot()` does **not** modify the document's
  4751. * properties, just resets the change tracking state.
  4752. *
  4753. * This method is especially useful when writing [custom transaction wrappers](https://github.com/Automattic/mongoose/issues/14268#issuecomment-2100505554) that need to restore change tracking when aborting a transaction.
  4754. *
  4755. * #### Example:
  4756. *
  4757. * const doc = await TestModel.findOne();
  4758. * const snapshot = doc.$createModifiedPathsSnapshot();
  4759. *
  4760. * doc.name = 'test';
  4761. * doc.$restoreModifiedPathsSnapshot(snapshot);
  4762. * doc.$isModified('name'); // false because `name` was not modified when snapshot was taken
  4763. * doc.name; // 'test', `$restoreModifiedPathsSnapshot()` does **not** modify the document's data, only change tracking
  4764. *
  4765. * @param {ModifiedPathsSnapshot} snapshot of the document's internal change tracking state snapshot to restore
  4766. * @api public
  4767. * @method $restoreModifiedPathsSnapshot
  4768. * @return {Document} this
  4769. * @memberOf Document
  4770. * @instance
  4771. */
  4772. Document.prototype.$restoreModifiedPathsSnapshot = function $restoreModifiedPathsSnapshot(snapshot) {
  4773. this.$__.activePaths = snapshot.activePaths.clone();
  4774. this.$__.version = snapshot.version;
  4775. if (!this.$isSubdocument) {
  4776. const subdocs = this.$getAllSubdocs();
  4777. for (const child of subdocs) {
  4778. if (snapshot.subdocSnapshot.has(child)) {
  4779. child.$__.activePaths = snapshot.subdocSnapshot.get(child);
  4780. }
  4781. }
  4782. }
  4783. return this;
  4784. };
  4785. /**
  4786. * Clear the document's modified paths.
  4787. *
  4788. * #### Example:
  4789. *
  4790. * const doc = await TestModel.findOne();
  4791. *
  4792. * doc.name = 'test';
  4793. * doc.$isModified('name'); // true
  4794. *
  4795. * doc.$clearModifiedPaths();
  4796. * doc.name; // 'test', `$clearModifiedPaths()` does **not** modify the document's data, only change tracking
  4797. *
  4798. * @api public
  4799. * @return {Document} this
  4800. * @method $clearModifiedPaths
  4801. * @memberOf Document
  4802. * @instance
  4803. */
  4804. Document.prototype.$clearModifiedPaths = function $clearModifiedPaths() {
  4805. this.$__.activePaths.clear('modify');
  4806. this.$__.activePaths.clear('init');
  4807. this.$__.version = 0;
  4808. if (!this.$isSubdocument) {
  4809. const subdocs = this.$getAllSubdocs();
  4810. for (const child of subdocs) {
  4811. child.$clearModifiedPaths();
  4812. }
  4813. }
  4814. return this;
  4815. };
  4816. /*!
  4817. * Check if the given document only has primitive values
  4818. */
  4819. Document.prototype.$__hasOnlyPrimitiveValues = function $__hasOnlyPrimitiveValues() {
  4820. return !this.$__.populated && !this.$__.wasPopulated && (this._doc == null || Object.values(this._doc).every(v => {
  4821. return v == null
  4822. || typeof v !== 'object'
  4823. || (utils.isNativeObject(v) && !Array.isArray(v))
  4824. || isBsonType(v, 'ObjectId')
  4825. || isBsonType(v, 'Decimal128');
  4826. }));
  4827. };
  4828. /*!
  4829. * Increment this document's version if necessary.
  4830. */
  4831. Document.prototype._applyVersionIncrement = function _applyVersionIncrement() {
  4832. if (!this.$__.version) return;
  4833. const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
  4834. this.$__.version = undefined;
  4835. if (doIncrement) {
  4836. const key = this.$__schema.options.versionKey;
  4837. const version = this.$__getValue(key) || 0;
  4838. this.$__setValue(key, version + 1); // increment version if was successful
  4839. }
  4840. };
  4841. /*!
  4842. * Increment this document's version if necessary.
  4843. */
  4844. Document.prototype._applyVersionIncrement = function _applyVersionIncrement() {
  4845. if (!this.$__.version) return;
  4846. const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
  4847. this.$__.version = undefined;
  4848. if (doIncrement) {
  4849. const key = this.$__schema.options.versionKey;
  4850. const version = this.$__getValue(key) || 0;
  4851. this.$__setValue(key, version + 1); // increment version if was successful
  4852. }
  4853. };
  4854. /*!
  4855. * Module exports.
  4856. */
  4857. Document.VERSION_WHERE = VERSION_WHERE;
  4858. Document.VERSION_INC = VERSION_INC;
  4859. Document.VERSION_ALL = VERSION_ALL;
  4860. Document.ValidationError = ValidationError;
  4861. module.exports = exports = Document;