| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010 |
- 'use strict';
- /*!
- * Module dependencies.
- */
- const Aggregate = require('./aggregate');
- const ChangeStream = require('./cursor/changeStream');
- const Document = require('./document');
- const DocumentNotFoundError = require('./error/notFound');
- const EventEmitter = require('events').EventEmitter;
- const Kareem = require('kareem');
- const MongooseBulkWriteError = require('./error/bulkWriteError');
- const MongooseError = require('./error/index');
- const ObjectParameterError = require('./error/objectParameter');
- const OverwriteModelError = require('./error/overwriteModel');
- const Query = require('./query');
- const SaveOptions = require('./options/saveOptions');
- const Schema = require('./schema');
- const ValidationError = require('./error/validation');
- const VersionError = require('./error/version');
- const ParallelSaveError = require('./error/parallelSave');
- const applyDefaultsHelper = require('./helpers/document/applyDefaults');
- const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO');
- const applyEmbeddedDiscriminators = require('./helpers/discriminator/applyEmbeddedDiscriminators');
- const applyHooks = require('./helpers/model/applyHooks');
- const applyMethods = require('./helpers/model/applyMethods');
- const applyProjection = require('./helpers/projection/applyProjection');
- const applyReadConcern = require('./helpers/schema/applyReadConcern');
- const applySchemaCollation = require('./helpers/indexes/applySchemaCollation');
- const applyStaticHooks = require('./helpers/model/applyStaticHooks');
- const applyStatics = require('./helpers/model/applyStatics');
- const applyTimestampsHelper = require('./helpers/document/applyTimestamps');
- const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
- const applyVirtualsHelper = require('./helpers/document/applyVirtuals');
- const assignVals = require('./helpers/populate/assignVals');
- const castBulkWrite = require('./helpers/model/castBulkWrite');
- const clone = require('./helpers/clone');
- const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
- const decorateUpdateWithVersionKey = require('./helpers/update/decorateUpdateWithVersionKey');
- const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
- const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue');
- const discriminator = require('./helpers/model/discriminator');
- const each = require('./helpers/each');
- const get = require('./helpers/get');
- const getConstructorName = require('./helpers/getConstructorName');
- const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue');
- const getModelsMapForPopulate = require('./helpers/populate/getModelsMapForPopulate');
- const immediate = require('./helpers/immediate');
- const internalToObjectOptions = require('./options').internalToObjectOptions;
- const isDefaultIdIndex = require('./helpers/indexes/isDefaultIdIndex');
- const isIndexEqual = require('./helpers/indexes/isIndexEqual');
- const isTimeseriesIndex = require('./helpers/indexes/isTimeseriesIndex');
- const {
- getRelatedDBIndexes,
- getRelatedSchemaIndexes
- } = require('./helpers/indexes/getRelatedIndexes');
- const decorateDiscriminatorIndexOptions = require('./helpers/indexes/decorateDiscriminatorIndexOptions');
- const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
- const leanPopulateMap = require('./helpers/populate/leanPopulateMap');
- const parallelLimit = require('./helpers/parallelLimit');
- const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
- const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
- const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
- const setDottedPath = require('./helpers/path/setDottedPath');
- const util = require('util');
- const utils = require('./utils');
- const minimize = require('./helpers/minimize');
- const MongooseBulkSaveIncompleteError = require('./error/bulkSaveIncompleteError');
- const ObjectExpectedError = require('./error/objectExpected');
- const decorateBulkWriteResult = require('./helpers/model/decorateBulkWriteResult');
- const modelCollectionSymbol = Symbol('mongoose#Model#collection');
- const modelDbSymbol = Symbol('mongoose#Model#db');
- const modelSymbol = require('./helpers/symbols').modelSymbol;
- const subclassedSymbol = Symbol('mongoose#Model#subclassed');
- const { VERSION_INC, VERSION_WHERE, VERSION_ALL } = Document;
- const saveToObjectOptions = Object.assign({}, internalToObjectOptions, {
- bson: true
- });
- /**
- * A Model is a class that's your primary tool for interacting with MongoDB.
- * An instance of a Model is called a [Document](https://mongoosejs.com/docs/api/document.html#Document).
- *
- * In Mongoose, the term "Model" refers to subclasses of the `mongoose.Model`
- * class. You should not use the `mongoose.Model` class directly. The
- * [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) and
- * [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()) functions
- * create subclasses of `mongoose.Model` as shown below.
- *
- * #### Example:
- *
- * // `UserModel` is a "Model", a subclass of `mongoose.Model`.
- * const UserModel = mongoose.model('User', new Schema({ name: String }));
- *
- * // You can use a Model to create new documents using `new`:
- * const userDoc = new UserModel({ name: 'Foo' });
- * await userDoc.save();
- *
- * // You also use a model to create queries:
- * const userFromDb = await UserModel.findOne({ name: 'Foo' });
- *
- * @param {Object} doc values for initial set
- * @param {Object} [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()).
- * @param {Object} [options] optional object containing the options for the document.
- * @param {Boolean} [options.defaults=true] if `false`, skip applying default values to this document.
- * @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.
- * @inherits Document https://mongoosejs.com/docs/api/document.html
- * @event `error`: If listening to this event, 'error' is emitted when a document was saved and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model.
- * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event.
- * @event `index-single-start`: Emitted when an individual index starts within `Model#ensureIndexes`. The fields and options being used to build the index are also passed with the event.
- * @event `index-single-done`: Emitted when an individual index finishes within `Model#ensureIndexes`. If an error occurred it is passed with the event. The fields, options, and index name are also passed.
- * @api public
- */
- function Model(doc, fields, options) {
- if (fields instanceof Schema) {
- throw new TypeError('2nd argument to `Model` constructor must be a POJO or string, ' +
- '**not** a schema. Make sure you\'re calling `mongoose.model()`, not ' +
- '`mongoose.Model()`.');
- }
- if (typeof doc === 'string') {
- throw new TypeError('First argument to `Model` constructor must be an object, ' +
- '**not** a string. Make sure you\'re calling `mongoose.model()`, not ' +
- '`mongoose.Model()`.');
- }
- Document.call(this, doc, fields, options);
- }
- /**
- * Inherits from Document.
- *
- * All Model.prototype features are available on
- * top level (non-sub) documents.
- * @api private
- */
- Object.setPrototypeOf(Model.prototype, Document.prototype);
- Model.prototype.$isMongooseModelPrototype = true;
- /**
- * Connection the model uses.
- *
- * @api public
- * @property db
- * @memberOf Model
- * @instance
- */
- Model.prototype.db;
- /**
- * Changes the Connection instance this model uses to make requests to MongoDB.
- * This function is most useful for changing the Connection that a Model defined using `mongoose.model()` uses
- * after initialization.
- *
- * #### Example:
- *
- * await mongoose.connect('mongodb://127.0.0.1:27017/db1');
- * const UserModel = mongoose.model('User', mongoose.Schema({ name: String }));
- * UserModel.connection === mongoose.connection; // true
- *
- * const conn2 = await mongoose.createConnection('mongodb://127.0.0.1:27017/db2').asPromise();
- * UserModel.useConnection(conn2); // `UserModel` now stores documents in `db2`, not `db1`
- *
- * UserModel.connection === mongoose.connection; // false
- * UserModel.connection === conn2; // true
- *
- * conn2.model('User') === UserModel; // true
- * mongoose.model('User'); // Throws 'MissingSchemaError'
- *
- * Note: `useConnection()` does **not** apply any [connection-level plugins](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.plugin()) from the new connection.
- * If you use `useConnection()` to switch a model's connection, the model will still have the old connection's plugins.
- *
- * @function useConnection
- * @param [Connection] connection The new connection to use
- * @return [Model] this
- * @api public
- */
- Model.useConnection = function useConnection(connection) {
- if (!connection) {
- throw new Error('Please provide a connection.');
- }
- if (this.db) {
- delete this.db.models[this.modelName];
- delete this.prototype.db;
- delete this.prototype[modelDbSymbol];
- delete this.prototype.collection;
- delete this.prototype.$collection;
- delete this.prototype[modelCollectionSymbol];
- }
- this.db = connection;
- const collection = connection.collection(this.collection.collectionName, connection.options);
- this.prototype.collection = collection;
- this.prototype.$collection = collection;
- this.prototype[modelCollectionSymbol] = collection;
- this.prototype.db = connection;
- this.prototype[modelDbSymbol] = connection;
- this.collection = collection;
- this.$__collection = collection;
- connection.models[this.modelName] = this;
- return this;
- };
- /**
- * The collection instance this model uses.
- * A Mongoose collection is a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)).
- * Using `Model.collection` means you bypass Mongoose middleware, validation, and casting.
- *
- * This property is read-only. Modifying this property is a no-op.
- *
- * @api public
- * @property collection
- * @memberOf Model
- * @instance
- */
- Model.prototype.collection;
- /**
- * Internal collection the model uses.
- *
- * This property is read-only. Modifying this property is a no-op.
- *
- * @api private
- * @property collection
- * @memberOf Model
- * @instance
- */
- Model.prototype.$__collection;
- /**
- * The name of the model
- *
- * @api public
- * @property modelName
- * @memberOf Model
- * @instance
- */
- Model.prototype.modelName;
- /**
- * Additional properties to attach to the query when calling `save()` and
- * `isNew` is false.
- *
- * @api public
- * @property $where
- * @memberOf Model
- * @instance
- */
- Model.prototype.$where;
- /**
- * If this is a discriminator model, `baseModelName` is the name of
- * the base model.
- *
- * @api public
- * @property baseModelName
- * @memberOf Model
- * @instance
- */
- Model.prototype.baseModelName;
- /**
- * Event emitter that reports any errors that occurred. Useful for global error
- * handling.
- *
- * #### Example:
- *
- * MyModel.events.on('error', err => console.log(err.message));
- *
- * // Prints a 'CastError' because of the above handler
- * await MyModel.findOne({ _id: 'Not a valid ObjectId' }).catch(noop);
- *
- * @api public
- * @property events
- * @fires error whenever any query or model function errors
- * @memberOf Model
- * @static
- */
- Model.events;
- /**
- * Compiled middleware for this model. Set in `applyHooks()`.
- *
- * @api private
- * @property _middleware
- * @memberOf Model
- * @static
- */
- Model._middleware;
- /*!
- * ignore
- */
- function _applyCustomWhere(doc, where) {
- if (doc.$where == null) {
- return;
- }
- for (const key of Object.keys(doc.$where)) {
- where[key] = doc.$where[key];
- }
- }
- /*!
- * ignore
- */
- function _createSaveOptions(doc, options) {
- const saveOptions = {};
- applyWriteConcern(doc.$__schema, options);
- if (typeof options.writeConcern !== 'undefined') {
- saveOptions.writeConcern = {};
- if ('w' in options.writeConcern) {
- saveOptions.writeConcern.w = options.writeConcern.w;
- }
- if ('j' in options.writeConcern) {
- saveOptions.writeConcern.j = options.writeConcern.j;
- }
- if ('wtimeout' in options.writeConcern) {
- saveOptions.writeConcern.wtimeout = options.writeConcern.wtimeout;
- }
- } else {
- if ('w' in options) {
- saveOptions.w = options.w;
- }
- if ('j' in options) {
- saveOptions.j = options.j;
- }
- if ('wtimeout' in options) {
- saveOptions.wtimeout = options.wtimeout;
- }
- }
- if ('checkKeys' in options) {
- saveOptions.checkKeys = options.checkKeys;
- }
- const session = doc.$session();
- const asyncLocalStorage = doc[modelDbSymbol].base.transactionAsyncLocalStorage?.getStore();
- if (session != null) {
- saveOptions.session = session;
- } else if (!Object.hasOwn(options, 'session') && asyncLocalStorage?.session != null) {
- // Only set session from asyncLocalStorage if `session` option wasn't originally passed in options
- saveOptions.session = asyncLocalStorage.session;
- }
- return saveOptions;
- }
- /*!
- * ignore
- */
- Model.prototype.$__save = async function $__save(options) {
- try {
- await this._execDocumentPreHooks('save', options);
- } catch (error) {
- await this._execDocumentPostHooks('save', error);
- return;
- }
- let result = null;
- let where = null;
- try {
- const saveOptions = _createSaveOptions(this, options);
- if (this.$isNew) {
- // send entire doc
- const obj = this.toObject(saveToObjectOptions);
- if ((obj || {})._id === void 0) {
- // documents must have an _id else mongoose won't know
- // what to update later if more changes are made. the user
- // wouldn't know what _id was generated by mongodb either
- // nor would the ObjectId generated by mongodb necessarily
- // match the schema definition.
- throw new MongooseError('document must have an _id before saving');
- }
- this.$__version(true, obj);
- this.$__reset();
- _setIsNew(this, false);
- // Make it possible to retry the insert
- this.$__.inserting = true;
- result = await this[modelCollectionSymbol].insertOne(obj, saveOptions).catch(err => {
- _setIsNew(this, true);
- throw err;
- });
- } else {
- // Make sure we don't treat it as a new object on error,
- // since it already exists
- this.$__.inserting = false;
- const delta = this.$__delta();
- if (options.pathsToSave) {
- for (const key in delta[1]['$set']) {
- if (options.pathsToSave.includes(key)) {
- continue;
- } else if (options.pathsToSave.some(pathToSave => key.slice(0, pathToSave.length) === pathToSave && key.charAt(pathToSave.length) === '.')) {
- continue;
- } else {
- delete delta[1]['$set'][key];
- }
- }
- }
- if (delta) {
- where = this.$__where(delta[0]);
- _applyCustomWhere(this, where);
- const update = delta[1];
- if (this.$__schema.options.minimize) {
- for (const updateOp of Object.values(update)) {
- if (updateOp == null) {
- continue;
- }
- for (const key of Object.keys(updateOp)) {
- if (updateOp[key] == null || typeof updateOp[key] !== 'object') {
- continue;
- }
- if (!utils.isPOJO(updateOp[key])) {
- continue;
- }
- minimize(updateOp[key]);
- if (utils.hasOwnKeys(updateOp[key]) === false) {
- delete updateOp[key];
- update.$unset = update.$unset || {};
- update.$unset[key] = 1;
- }
- }
- }
- }
- // store the modified paths before the document is reset
- this.$__.modifiedPaths = this.modifiedPaths();
- this.$__reset();
- _setIsNew(this, false);
- result = await this[modelCollectionSymbol].updateOne(where, update, saveOptions).catch(err => {
- this.$__undoReset();
- throw err;
- });
- } else {
- where = this.$__where();
- _applyCustomWhere(this, where);
- if (this.$__.version) {
- this.$__version(where, delta);
- }
- applyReadConcern(this.$__schema, saveOptions);
- result = await this.constructor.collection.findOne(where, { ...saveOptions, projection: { _id: 1 } })
- .then(documentExists => ({ matchedCount: !documentExists ? 0 : 1 }));
- }
- }
- } catch (err) {
- const error = this.$__schema._transformDuplicateKeyError(err);
- await this._execDocumentPostHooks('save', error);
- return;
- }
- let numAffected = 0;
- const writeConcern = options != null ?
- options.writeConcern != null ?
- options.writeConcern.w :
- options.w :
- 0;
- if (writeConcern !== 0) {
- // Skip checking if write succeeded if writeConcern is set to
- // unacknowledged writes, because otherwise `numAffected` will always be 0
- if (result != null) {
- if (Array.isArray(result)) {
- numAffected = result.length;
- } else if (result.matchedCount != null) {
- numAffected = result.matchedCount;
- } else {
- numAffected = result;
- }
- }
- const versionBump = this.$__.version;
- // was this an update that required a version bump?
- if (versionBump && !this.$__.inserting) {
- const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
- this.$__.version = undefined;
- const key = this.$__schema.options.versionKey;
- const version = this.$__getValue(key) || 0;
- if (numAffected <= 0) {
- // the update failed. pass an error back
- this.$__undoReset();
- const err = this.$__.$versionError ||
- new VersionError(this, version, this.$__.modifiedPaths);
- await this._execDocumentPostHooks('save', err);
- return;
- }
- // increment version if was successful
- if (doIncrement) {
- this.$__setValue(key, version + 1);
- }
- }
- if (result != null && numAffected <= 0) {
- this.$__undoReset();
- const error = new DocumentNotFoundError(where, this.constructor.modelName, numAffected, result);
- await this._execDocumentPostHooks('save', error);
- return;
- }
- }
- this.$__.saving = undefined;
- this.$__.savedState = {};
- this.$emit('save', this, numAffected);
- this.constructor.emit('save', this, numAffected);
- await this._execDocumentPostHooks('save');
- };
- /*!
- * ignore
- */
- function generateVersionError(doc, modifiedPaths, defaultPaths) {
- const key = doc.$__schema.options.versionKey;
- if (!key) {
- return null;
- }
- const version = doc.$__getValue(key) || 0;
- return new VersionError(doc, version, modifiedPaths.concat(defaultPaths));
- }
- /**
- * 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`,
- * or sends an [updateOne](https://mongoosejs.com/docs/api/document.html#Document.prototype.updateOne()) operation with just the modified paths if `isNew` is `false`.
- *
- * #### Example:
- *
- * product.sold = Date.now();
- * product = await product.save();
- *
- * If save is successful, the returned promise will fulfill with the document
- * saved.
- *
- * #### Example:
- *
- * const newProduct = await product.save();
- * newProduct === product; // true
- *
- * @param {Object} [options] options optional options
- * @param {Session} [options.session=null] the [session](https://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()).
- * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead.
- * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
- * @param {Boolean} [options.validateModifiedOnly=false] if `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
- * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
- * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
- * @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).
- * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Restrictions-on-Field-Names)
- * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
- * @param {Array} [options.pathsToSave] An array of paths that tell mongoose to only validate and save the paths in `pathsToSave`.
- * @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).
- * @return {Promise}
- * @api public
- * @see middleware https://mongoosejs.com/docs/middleware.html
- */
- Model.prototype.save = async function save(options) {
- if (typeof options === 'function' || typeof arguments[1] === 'function') {
- throw new MongooseError('Model.prototype.save() no longer accepts a callback');
- }
- let parallelSave;
- this.$op = 'save';
- if (this.$__.saving) {
- parallelSave = new ParallelSaveError(this);
- } else {
- this.$__.saving = new ParallelSaveError(this);
- }
- options = new SaveOptions(options);
- if (Object.hasOwn(options, 'session')) {
- this.$session(options.session);
- }
- if (this.$__.timestamps != null) {
- options.timestamps = this.$__.timestamps;
- }
- this.$__.$versionError = generateVersionError(
- this,
- this.modifiedPaths(),
- Object.keys(this.$__.activePaths.getStatePaths('default'))
- );
- if (parallelSave) {
- this.$__handleReject(parallelSave);
- throw parallelSave;
- }
- this.$__.saveOptions = options;
- try {
- await this.$__save(options);
- } catch (error) {
- this.$__handleReject(error);
- throw error;
- } finally {
- this.$__.saving = null;
- this.$__.saveOptions = null;
- this.$__.$versionError = null;
- this.$op = null;
- }
- return this;
- };
- Model.prototype.$save = Model.prototype.save;
- /**
- * Appends versioning to the where and update clauses.
- *
- * @api private
- * @method $__version
- * @memberOf Model
- * @instance
- */
- Model.prototype.$__version = function(where, delta) {
- const key = this.$__schema.options.versionKey;
- if (where === true) {
- // this is an insert
- if (key) {
- setDottedPath(delta, key, 0);
- this.$__setValue(key, 0);
- }
- return;
- }
- if (key === false) {
- return;
- }
- // updates
- // only apply versioning if our versionKey was selected. else
- // there is no way to select the correct version. we could fail
- // fast here and force them to include the versionKey but
- // thats a bit intrusive. can we do this automatically?
- if (!this.$__isSelected(key)) {
- return;
- }
- // $push $addToSet don't need the where clause set
- if (VERSION_WHERE === (VERSION_WHERE & this.$__.version)) {
- const value = this.$__getValue(key);
- if (value != null) where[key] = value;
- }
- if (VERSION_INC === (VERSION_INC & this.$__.version)) {
- if (get(delta.$set, key, null) != null) {
- // Version key is getting set, means we'll increment the doc's version
- // after a successful save, so we should set the incremented version so
- // future saves don't fail (gh-5779)
- ++delta.$set[key];
- } else {
- delta.$inc = delta.$inc || {};
- delta.$inc[key] = 1;
- }
- }
- };
- /**
- * Signal that we desire an increment of this documents version.
- *
- * #### Example:
- *
- * const doc = await Model.findById(id);
- * doc.increment();
- * await doc.save();
- *
- * @see versionKeys https://mongoosejs.com/docs/guide.html#versionKey
- * @memberOf Model
- * @method increment
- * @api public
- */
- Model.prototype.increment = function increment() {
- this.$__.version = VERSION_ALL;
- return this;
- };
- /**
- * Returns a query object
- *
- * @api private
- * @method $__where
- * @memberOf Model
- * @instance
- */
- Model.prototype.$__where = function _where(where) {
- where || (where = {});
- if (!where._id) {
- where._id = this._doc._id;
- }
- if (this._doc._id === void 0) {
- throw new MongooseError('No _id found on document!');
- }
- return where;
- };
- /**
- * Delete this document from the db. Returns a Query instance containing a `deleteOne` operation by this document's `_id`.
- *
- * #### Example:
- *
- * await product.deleteOne();
- * await Product.findById(product._id); // null
- *
- * Since `deleteOne()` returns a Query, the `deleteOne()` will **not** execute unless you use either `await`, `.then()`, `.catch()`, or [`.exec()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.exec())
- *
- * #### Example:
- *
- * product.deleteOne(); // Doesn't do anything
- * product.deleteOne().exec(); // Deletes the document, returns a promise
- *
- * @return {Query} Query
- * @api public
- */
- Model.prototype.deleteOne = function deleteOne(options) {
- if (typeof options === 'function' ||
- typeof arguments[1] === 'function') {
- throw new MongooseError('Model.prototype.deleteOne() no longer accepts a callback');
- }
- if (!options) {
- options = {};
- }
- if (Object.hasOwn(options, 'session')) {
- this.$session(options.session);
- }
- const self = this;
- const where = this.$__where();
- const query = self.constructor.deleteOne();
- if (this.$session() != null) {
- if (!('session' in query.options)) {
- query.options.session = this.$session();
- }
- }
- query.pre(async function queryPreDeleteOne() {
- const res = await self.constructor._middleware.execPre('deleteOne', self, [self, options]);
- // `self` is passed to pre hooks as argument for backwards compatibility, but that
- // isn't the actual arguments passed to the wrapped function.
- if (res[0] !== self || res[1] !== options) {
- throw new Error('Document deleteOne pre hooks cannot overwrite arguments');
- }
- query.deleteOne(where, options);
- // Apply custom where conditions _after_ document deleteOne middleware for
- // consistency with save() - sharding plugin needs to set $where
- if (self.$where != null) {
- this.where(self.$where);
- }
- return res;
- });
- query.pre(function callSubdocPreHooks() {
- return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc])));
- });
- query.pre(function skipIfAlreadyDeleted() {
- if (self.$__.isDeleted) {
- throw new Kareem.skipWrappedFunction();
- }
- });
- query.post(function callSubdocPostHooks() {
- return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPost('deleteOne', subdoc, [subdoc])));
- });
- query.post(function queryPostDeleteOne() {
- return self.constructor._middleware.execPost('deleteOne', self, [self], {});
- });
- query.transform(function setIsDeleted(result) {
- if (result?.deletedCount > 0) {
- self.$isDeleted(true);
- }
- return result;
- });
- return query;
- };
- /**
- * Returns the model instance used to create this document if no `name` specified.
- * If `name` specified, returns the model with the given `name`.
- *
- * #### Example:
- *
- * const doc = new Tank({});
- * doc.$model() === Tank; // true
- * await doc.$model('User').findById(id);
- *
- * @param {String} [name] model name
- * @method $model
- * @api public
- * @return {Model}
- */
- Model.prototype.$model = function $model(name) {
- if (arguments.length === 0) {
- return this.constructor;
- }
- return this[modelDbSymbol].model(name);
- };
- /**
- * Returns the model instance used to create this document if no `name` specified.
- * If `name` specified, returns the model with the given `name`.
- *
- * #### Example:
- *
- * const doc = new Tank({});
- * doc.$model() === Tank; // true
- * await doc.$model('User').findById(id);
- *
- * @param {String} [name] model name
- * @method model
- * @api public
- * @return {Model}
- */
- Model.prototype.model = Model.prototype.$model;
- /**
- * Returns a document with `_id` only if at least one document exists in the database that matches
- * the given `filter`, and `null` otherwise.
- *
- * Under the hood, `MyModel.exists({ answer: 42 })` is equivalent to
- * `MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean()`
- *
- * #### Example:
- *
- * await Character.deleteMany({});
- * await Character.create({ name: 'Jean-Luc Picard' });
- *
- * await Character.exists({ name: /picard/i }); // { _id: ... }
- * await Character.exists({ name: /riker/i }); // null
- *
- * This function triggers the following middleware.
- *
- * - `findOne()`
- *
- * @param {Object} filter
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @return {Query}
- */
- Model.exists = function exists(filter, options) {
- _checkContext(this, 'exists');
- if (typeof arguments[2] === 'function') {
- throw new MongooseError('Model.exists() no longer accepts a callback');
- }
- const query = this.findOne(filter).
- select({ _id: 1 }).
- lean().
- setOptions(options);
- return query;
- };
- /**
- * Adds a discriminator type.
- *
- * #### Example:
- *
- * function BaseSchema() {
- * Schema.apply(this, arguments);
- *
- * this.add({
- * name: String,
- * createdAt: Date
- * });
- * }
- * util.inherits(BaseSchema, Schema);
- *
- * const PersonSchema = new BaseSchema();
- * const BossSchema = new BaseSchema({ department: String });
- *
- * const Person = mongoose.model('Person', PersonSchema);
- * const Boss = Person.discriminator('Boss', BossSchema);
- * new Boss().__t; // "Boss". `__t` is the default `discriminatorKey`
- *
- * const employeeSchema = new Schema({ boss: ObjectId });
- * const Employee = Person.discriminator('Employee', employeeSchema, 'staff');
- * new Employee().__t; // "staff" because of 3rd argument above
- *
- * @param {String} name discriminator model name
- * @param {Schema} schema discriminator model schema
- * @param {Object|String} [options] If string, same as `options.value`.
- * @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
- * @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
- * @param {Boolean} [options.overwriteModels=false] by default, Mongoose does not allow you to define a discriminator with the same name as another discriminator. Set this to allow overwriting discriminators with the same name.
- * @param {Boolean} [options.mergeHooks=true] By default, Mongoose merges the base schema's hooks with the discriminator schema's hooks. Set this option to `false` to make Mongoose use the discriminator schema's hooks instead.
- * @param {Boolean} [options.mergePlugins=true] By default, Mongoose merges the base schema's plugins with the discriminator schema's plugins. Set this option to `false` to make Mongoose use the discriminator schema's plugins instead.
- * @return {Model} The newly created discriminator model
- * @api public
- */
- Model.discriminator = function(name, schema, options) {
- let model;
- if (typeof name === 'function') {
- model = name;
- name = utils.getFunctionName(model);
- if (!(model.prototype instanceof Model)) {
- throw new MongooseError('The provided class ' + name + ' must extend Model');
- }
- }
- options = options || {};
- const value = utils.isPOJO(options) ? options.value : options;
- const clone = typeof options.clone === 'boolean' ? options.clone : true;
- const mergePlugins = typeof options.mergePlugins === 'boolean' ? options.mergePlugins : true;
- const overwriteModels = typeof options.overwriteModels === 'boolean' ? options.overwriteModels : false;
- _checkContext(this, 'discriminator');
- if (utils.isObject(schema) && !schema.instanceOfSchema) {
- schema = new Schema(schema);
- }
- if (schema instanceof Schema && clone) {
- schema = schema.clone();
- }
- schema = discriminator(this, name, schema, value, mergePlugins, options.mergeHooks, overwriteModels);
- if (this.db.models[name] && !schema.options.overwriteModels && !overwriteModels) {
- throw new OverwriteModelError(name);
- }
- schema.$isRootDiscriminator = true;
- schema.$globalPluginsApplied = true;
- model = this.db.model(model || name, schema, this.$__collection.name);
- this.discriminators[name] = model;
- const d = this.discriminators[name];
- Object.setPrototypeOf(d.prototype, this.prototype);
- Object.defineProperty(d, 'baseModelName', {
- value: this.modelName,
- configurable: true,
- writable: false
- });
- // apply methods and statics
- applyMethods(d, schema);
- applyStatics(d, schema);
- if (this[subclassedSymbol] != null) {
- for (const submodel of this[subclassedSymbol]) {
- submodel.discriminators = submodel.discriminators || {};
- submodel.discriminators[name] =
- model.__subclass(model.db, schema, submodel.collection.name);
- }
- }
- return d;
- };
- /**
- * Make sure `this` is a model
- * @api private
- */
- function _checkContext(ctx, fnName) {
- // Check context, because it is easy to mistakenly type
- // `new Model.discriminator()` and get an incomprehensible error
- if (ctx == null || ctx === global) {
- throw new MongooseError('`Model.' + fnName + '()` cannot run without a ' +
- 'model as `this`. Make sure you are calling `MyModel.' + fnName + '()` ' +
- 'where `MyModel` is a Mongoose model.');
- } else if (ctx[modelSymbol] == null) {
- throw new MongooseError('`Model.' + fnName + '()` cannot run without a ' +
- 'model as `this`. Make sure you are not calling ' +
- '`new Model.' + fnName + '()`');
- }
- }
- // Model (class) features
- /*!
- * Give the constructor the ability to emit events.
- */
- for (const i in EventEmitter.prototype) {
- Model[i] = EventEmitter.prototype[i];
- }
- /**
- * This function is responsible for initializing the underlying connection in MongoDB based on schema options.
- * This function performs the following operations:
- *
- * - `createCollection()` unless [`autoCreate`](https://mongoosejs.com/docs/guide.html#autoCreate) option is turned off
- * - `ensureIndexes()` unless [`autoIndex`](https://mongoosejs.com/docs/guide.html#autoIndex) option is turned off
- * - `createSearchIndex()` on all schema search indexes if `autoSearchIndex` is enabled.
- *
- * Mongoose calls this function automatically when a model is a created using
- * [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) or
- * [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()), so you
- * don't need to call `init()` to trigger index builds.
- *
- * However, you _may_ need to call `init()` to get back a promise that will resolve when your indexes are finished.
- * Calling `await Model.init()` is helpful if you need to wait for indexes to build before continuing.
- * For example, if you want to wait for unique indexes to build before continuing with a test case.
- *
- * #### Example:
- *
- * const eventSchema = new Schema({ thing: { type: 'string', unique: true } })
- * // This calls `Event.init()` implicitly, so you don't need to call
- * // `Event.init()` on your own.
- * const Event = mongoose.model('Event', eventSchema);
- *
- * await Event.init();
- * console.log('Indexes are done building!');
- *
- * @api public
- * @returns {Promise}
- */
- Model.init = function init() {
- _checkContext(this, 'init');
- if (typeof arguments[0] === 'function') {
- throw new MongooseError('Model.init() no longer accepts a callback');
- }
- this.schema.emit('init', this);
- if (this.$init != null) {
- return this.$init;
- }
- const conn = this.db;
- const _ensureIndexes = async() => {
- const autoIndex = utils.getOption(
- 'autoIndex',
- this.schema.options,
- conn.config,
- conn.base.options
- );
- if (!autoIndex) {
- return;
- }
- return await this.ensureIndexes({ _automatic: true });
- };
- const _createSearchIndexes = async() => {
- const autoSearchIndex = utils.getOption(
- 'autoSearchIndex',
- this.schema.options,
- conn.config,
- conn.base.options
- );
- if (!autoSearchIndex) {
- return;
- }
- return await this.createSearchIndexes();
- };
- const _createCollection = async() => {
- let autoCreate = utils.getOption(
- 'autoCreate',
- this.schema.options,
- conn.config
- // No base.options here because we don't want to take the base value if the connection hasn't
- // set it yet
- );
- if (autoCreate == null) {
- // `autoCreate` may later be set when the connection is opened, so wait for connect before checking
- await conn._waitForConnect(true);
- autoCreate = utils.getOption(
- 'autoCreate',
- this.schema.options,
- conn.config,
- conn.base.options
- );
- }
- if (!autoCreate) {
- return;
- }
- return await this.createCollection();
- };
- this.$init = _createCollection().
- then(() => _ensureIndexes()).
- then(() => _createSearchIndexes());
- const _catch = this.$init.catch;
- const _this = this;
- this.$init.catch = function() {
- _this.$caught = true;
- return _catch.apply(_this.$init, arguments);
- };
- return this.$init;
- };
- /**
- * Create the collection for this model. By default, if no indexes are specified,
- * mongoose will not create the collection for the model until any documents are
- * created. Use this method to create the collection explicitly.
- *
- * Note 1: You may need to call this before starting a transaction
- * See https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
- *
- * Note 2: You don't have to call this if your schema contains index or unique field.
- * In that case, just use `Model.init()`
- *
- * #### Example:
- *
- * const userSchema = new Schema({ name: String })
- * const User = mongoose.model('User', userSchema);
- *
- * User.createCollection().then(function(collection) {
- * console.log('Collection is created!');
- * });
- *
- * @api public
- * @param {Object} [options] see [MongoDB driver docs](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createCollection)
- * @returns {Promise}
- */
- Model.createCollection = async function createCollection(options) {
- _checkContext(this, 'createCollection');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
- throw new MongooseError('Model.createCollection() no longer accepts a callback');
- }
- [options] = await this.hooks.execPre('createCollection', this, [options]).catch(err => {
- if (err instanceof Kareem.skipWrappedFunction) {
- return [err];
- }
- throw err;
- });
- const collectionOptions = this?.schema?.options?.collectionOptions;
- if (collectionOptions != null) {
- options = Object.assign({}, collectionOptions, options);
- }
- const schemaCollation = this?.schema?.options?.collation;
- if (schemaCollation != null) {
- options = Object.assign({ collation: schemaCollation }, options);
- }
- const capped = this?.schema?.options?.capped;
- if (capped != null) {
- if (typeof capped === 'number') {
- options = Object.assign({ capped: true, size: capped }, options);
- } else if (typeof capped === 'object') {
- options = Object.assign({ capped: true }, capped, options);
- }
- }
- const timeseries = this?.schema?.options?.timeseries;
- if (timeseries != null) {
- options = Object.assign({ timeseries }, options);
- if (options.expireAfterSeconds != null) {
- // do nothing
- } else if (options.expires != null) {
- utils.expires(options);
- } else if (this.schema.options.expireAfterSeconds != null) {
- options.expireAfterSeconds = this.schema.options.expireAfterSeconds;
- } else if (this.schema.options.expires != null) {
- options.expires = this.schema.options.expires;
- utils.expires(options);
- }
- }
- const clusteredIndex = this?.schema?.options?.clusteredIndex;
- if (clusteredIndex != null) {
- options = Object.assign({ clusteredIndex: { ...clusteredIndex, unique: true } }, options);
- }
- try {
- if (!(options instanceof Kareem.skipWrappedFunction)) {
- await this.db.createCollection(this.$__collection.collectionName, options);
- }
- } catch (err) {
- if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
- await this.hooks.execPost('createCollection', this, [null], { error: err });
- }
- }
- await this.hooks.execPost('createCollection', this, [this.$__collection]);
- return this.$__collection;
- };
- /**
- * Makes the indexes in MongoDB match the indexes defined in this model's
- * schema. This function will drop any indexes that are not defined in
- * the model's schema except the `_id` index, and build any indexes that
- * are in your schema but not in MongoDB.
- *
- * See the [introductory blog post](https://thecodebarbarian.com/whats-new-in-mongoose-5-2-syncindexes)
- * for more information.
- *
- * #### Example:
- *
- * const schema = new Schema({ name: { type: String, unique: true } });
- * const Customer = mongoose.model('Customer', schema);
- * await Customer.collection.createIndex({ age: 1 }); // Index is not in schema
- * // Will drop the 'age' index and create an index on `name`
- * await Customer.syncIndexes();
- *
- * You should be careful about running `syncIndexes()` on production applications under heavy load,
- * because index builds are expensive operations, and unexpected index drops can lead to degraded
- * performance. Before running `syncIndexes()`, you can use the [`diffIndexes()` function](#Model.diffIndexes())
- * to check what indexes `syncIndexes()` will drop and create.
- *
- * #### Example:
- *
- * const { toDrop, toCreate } = await Model.diffIndexes();
- * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
- * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create
- *
- * @param {Object} [options] options to pass to `ensureIndexes()`
- * @param {Boolean} [options.hideIndexes=false] set to `true` to hide indexes instead of dropping. Requires MongoDB server 4.4 or higher
- * @return {Promise}
- * @api public
- */
- Model.syncIndexes = async function syncIndexes(options) {
- _checkContext(this, 'syncIndexes');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
- throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
- }
- const autoCreate = options?.autoCreate ??
- this.schema.options?.autoCreate ??
- this.db.config.autoCreate ??
- this.db.base?.options?.autoCreate ??
- true;
- if (autoCreate) {
- try {
- await this.createCollection();
- } catch (err) {
- if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
- throw err;
- }
- }
- }
- const diffIndexesResult = await this.diffIndexes({ indexOptionsToCreate: true });
- const dropped = await this.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
- await this.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
- return dropped;
- };
- /**
- * Create an [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
- * This function only works when connected to MongoDB Atlas.
- *
- * #### Example:
- *
- * const schema = new Schema({ name: { type: String, unique: true } });
- * const Customer = mongoose.model('Customer', schema);
- * await Customer.createSearchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
- *
- * @param {Object} description index options, including `name` and `definition`
- * @param {String} description.name
- * @param {Object} description.definition
- * @return {Promise}
- * @api public
- */
- Model.createSearchIndex = async function createSearchIndex(description) {
- _checkContext(this, 'createSearchIndex');
- return await this.$__collection.createSearchIndex(description);
- };
- /**
- * Update an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
- * This function only works when connected to MongoDB Atlas.
- *
- * #### Example:
- *
- * const schema = new Schema({ name: { type: String, unique: true } });
- * const Customer = mongoose.model('Customer', schema);
- * await Customer.updateSearchIndex('test', { mappings: { dynamic: true } });
- *
- * @param {String} name
- * @param {Object} definition
- * @return {Promise}
- * @api public
- */
- Model.updateSearchIndex = async function updateSearchIndex(name, definition) {
- _checkContext(this, 'updateSearchIndex');
- return await this.$__collection.updateSearchIndex(name, definition);
- };
- /**
- * Delete an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) by name.
- * This function only works when connected to MongoDB Atlas.
- *
- * #### Example:
- *
- * const schema = new Schema({ name: { type: String, unique: true } });
- * const Customer = mongoose.model('Customer', schema);
- * await Customer.dropSearchIndex('test');
- *
- * @param {String} name
- * @return {Promise}
- * @api public
- */
- Model.dropSearchIndex = async function dropSearchIndex(name) {
- _checkContext(this, 'dropSearchIndex');
- return await this.$__collection.dropSearchIndex(name);
- };
- /**
- * List all [Atlas search indexes](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) on this model's collection.
- * This function only works when connected to MongoDB Atlas.
- *
- * #### Example:
- *
- * const schema = new Schema({ name: { type: String, unique: true } });
- * const Customer = mongoose.model('Customer', schema);
- *
- * await Customer.createSearchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
- * const res = await Customer.listSearchIndexes(); // Includes `[{ name: 'test' }]`
- *
- * @param {Object} [options]
- * @return {Promise<Array>}
- * @api public
- */
- Model.listSearchIndexes = async function listSearchIndexes(options) {
- _checkContext(this, 'listSearchIndexes');
- const cursor = await this.$__collection.listSearchIndexes(options);
- return await cursor.toArray();
- };
- /**
- * Does a dry-run of `Model.syncIndexes()`, returning the indexes that `syncIndexes()` would drop and create if you were to run `syncIndexes()`.
- *
- * #### Example:
- *
- * const { toDrop, toCreate } = await Model.diffIndexes();
- * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
- * toCreate; // Array of index specs containing the keys of indexes that `syncIndexes()` will create
- *
- * @param {Object} [options]
- * @param {Boolean} [options.indexOptionsToCreate=false] If true, `toCreate` will include both the index spec and the index options, not just the index spec
- * @return {Promise<Object>} contains the indexes that would be dropped in MongoDB and indexes that would be created in MongoDB as `{ toDrop: string[], toCreate: string[] }`.
- */
- Model.diffIndexes = async function diffIndexes(options) {
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
- throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
- }
- const model = this;
- let dbIndexes = await model.listIndexes().catch(err => {
- if (err.codeName == 'NamespaceNotFound') {
- return undefined;
- }
- throw err;
- });
- if (dbIndexes === undefined) {
- dbIndexes = [];
- }
- dbIndexes = getRelatedDBIndexes(model, dbIndexes);
- const schema = model.schema;
- const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes());
- const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes);
- const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options);
- return { toDrop, toCreate };
- };
- function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options) {
- const toCreate = [];
- const indexOptionsToCreate = options?.indexOptionsToCreate ?? false;
- for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
- let found = false;
- const options = decorateDiscriminatorIndexOptions(schema, clone(schemaIndexOptions));
- for (const index of dbIndexes) {
- if (isDefaultIdIndex(index)) {
- continue;
- }
- if (
- isIndexEqual(schemaIndexKeysObject, options, index) &&
- !toDrop.includes(index.name)
- ) {
- found = true;
- break;
- }
- }
- if (!found) {
- if (indexOptionsToCreate) {
- toCreate.push([schemaIndexKeysObject, schemaIndexOptions]);
- } else {
- toCreate.push(schemaIndexKeysObject);
- }
- }
- }
- return toCreate;
- }
- function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
- const toDrop = [];
- for (const dbIndex of dbIndexes) {
- let found = false;
- // Never try to drop `_id` index, MongoDB server doesn't allow it
- if (isDefaultIdIndex(dbIndex)) {
- continue;
- }
- // Timeseries collections have a default index on { timeField: 1, metaField: 1 }.
- if (isTimeseriesIndex(dbIndex, schema.options)) {
- continue;
- }
- for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
- const options = decorateDiscriminatorIndexOptions(schema, clone(schemaIndexOptions));
- applySchemaCollation(schemaIndexKeysObject, options, schema.options);
- if (isIndexEqual(schemaIndexKeysObject, options, dbIndex)) {
- found = true;
- break;
- }
- }
- if (found) {
- continue;
- }
- toDrop.push(dbIndex.name);
- }
- return toDrop;
- }
- /**
- * Deletes all indexes that aren't defined in this model's schema. Used by
- * `syncIndexes()`.
- *
- * The returned promise resolves to a list of the dropped indexes' names as an array
- *
- * @param {Object} [options]
- * @param {Array<String>} [options.toDrop] if specified, contains a list of index names to drop
- * @param {Boolean} [options.hideIndexes=false] set to `true` to hide indexes instead of dropping. Requires MongoDB server 4.4 or higher
- * @return {Promise<Array<String>>} list of dropped or hidden index names
- * @api public
- */
- Model.cleanIndexes = async function cleanIndexes(options) {
- _checkContext(this, 'cleanIndexes');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
- throw new MongooseError('Model.cleanIndexes() no longer accepts a callback');
- }
- const model = this;
- if (Array.isArray(options?.toDrop)) {
- const res = await _dropIndexes(options.toDrop, model, options);
- return res;
- }
- const res = await model.diffIndexes();
- return await _dropIndexes(res.toDrop, model, options);
- };
- async function _dropIndexes(toDrop, model, options) {
- if (toDrop.length === 0) {
- return [];
- }
- const collection = model.$__collection;
- if (options?.hideIndexes) {
- await Promise.all(toDrop.map(indexName => {
- return model.db.db.command({
- collMod: collection.collectionName,
- index: { name: indexName, hidden: true }
- });
- }));
- } else {
- await Promise.all(toDrop.map(indexName => collection.dropIndex(indexName)));
- }
- return toDrop;
- }
- /**
- * Lists the indexes currently defined in MongoDB. This may or may not be
- * the same as the indexes defined in your schema depending on whether you
- * use the [`autoIndex` option](https://mongoosejs.com/docs/guide.html#autoIndex) and if you
- * build indexes manually.
- *
- * @return {Promise}
- * @api public
- */
- Model.listIndexes = async function listIndexes() {
- _checkContext(this, 'listIndexes');
- if (typeof arguments[0] === 'function') {
- throw new MongooseError('Model.listIndexes() no longer accepts a callback');
- }
- if (this.$__collection.buffer) {
- await new Promise(resolve => {
- this.$__collection.addQueue(resolve);
- });
- }
- return this.$__collection.listIndexes().toArray();
- };
- /**
- * Sends `createIndex` commands to mongo for each index declared in the schema.
- * The `createIndex` commands are sent in series.
- *
- * #### Example:
- *
- * await Event.ensureIndexes();
- *
- * After completion, an `index` event is emitted on this `Model` passing an error if one occurred.
- *
- * #### Example:
- *
- * const eventSchema = new Schema({ thing: { type: 'string', unique: true } })
- * const Event = mongoose.model('Event', eventSchema);
- *
- * Event.on('index', function (err) {
- * if (err) console.error(err); // error occurred during index creation
- * });
- *
- * _NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution._
- *
- * @param {Object} [options] internal options
- * @return {Promise}
- * @api public
- */
- Model.ensureIndexes = async function ensureIndexes(options) {
- _checkContext(this, 'ensureIndexes');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
- throw new MongooseError('Model.ensureIndexes() no longer accepts a callback');
- }
- await new Promise((resolve, reject) => {
- _ensureIndexes(this, options, (err) => {
- if (err != null) {
- return reject(err);
- }
- resolve();
- });
- });
- };
- /**
- * Similar to `ensureIndexes()`, except for it uses the [`createIndex`](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createIndex)
- * function.
- *
- * @param {Object} [options] internal options
- * @return {Promise}
- * @api public
- */
- Model.createIndexes = async function createIndexes(options) {
- _checkContext(this, 'createIndexes');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
- throw new MongooseError('Model.createIndexes() no longer accepts a callback');
- }
- return this.ensureIndexes(options);
- };
- /*!
- * ignore
- */
- function _ensureIndexes(model, options, callback) {
- const indexes = Array.isArray(options?.toCreate) ? options.toCreate : model.schema.indexes();
- let indexError;
- options = options || {};
- const done = function(err) {
- if (err && !model.$caught) {
- model.emit('error', err);
- }
- model.emit('index', err || indexError);
- callback && callback(err || indexError);
- };
- for (const index of indexes) {
- if (isDefaultIdIndex(index)) {
- utils.warn('mongoose: Cannot specify a custom index on `_id` for ' +
- 'model name "' + model.modelName + '", ' +
- 'MongoDB does not allow overwriting the default `_id` index. See ' +
- 'https://bit.ly/mongodb-id-index');
- }
- }
- if (!indexes.length) {
- immediate(function() {
- done();
- });
- return;
- }
- // Indexes are created one-by-one
- const indexSingleDone = function(err, fields, options, name) {
- model.emit('index-single-done', err, fields, options, name);
- };
- const indexSingleStart = function(fields, options) {
- model.emit('index-single-start', fields, options);
- };
- const baseSchema = model.schema._baseSchema;
- const baseSchemaIndexes = baseSchema ? baseSchema.indexes() : [];
- immediate(function() {
- // If buffering is off, do this manually.
- if (options._automatic && !model.collection.collection) {
- model.collection.addQueue(create, []);
- } else {
- create();
- }
- });
- function create() {
- if (options._automatic) {
- if (model.schema.options.autoIndex === false ||
- (model.schema.options.autoIndex == null && model.db.config.autoIndex === false)) {
- return done();
- }
- }
- const index = indexes.shift();
- if (!index) {
- return done();
- }
- if (options._automatic && index[1]._autoIndex === false) {
- return create();
- }
- if (baseSchemaIndexes.find(i => utils.deepEqual(i, index))) {
- return create();
- }
- const indexFields = clone(index[0]);
- const indexOptions = clone(index[1]);
- delete indexOptions._autoIndex;
- decorateDiscriminatorIndexOptions(model.schema, indexOptions);
- applyWriteConcern(model.schema, indexOptions);
- applySchemaCollation(indexFields, indexOptions, model.schema.options);
- indexSingleStart(indexFields, options);
- // Just in case `createIndex()` throws a sync error
- let promise = null;
- try {
- promise = model.collection.createIndex(indexFields, indexOptions);
- } catch (err) {
- if (!indexError) {
- indexError = err;
- }
- if (!model.$caught) {
- model.emit('error', err);
- }
- indexSingleDone(err, indexFields, indexOptions);
- create();
- return;
- }
- promise.then(
- name => {
- indexSingleDone(null, indexFields, indexOptions, name);
- create();
- },
- err => {
- if (!indexError) {
- indexError = err;
- }
- if (!model.$caught) {
- model.emit('error', err);
- }
- indexSingleDone(err, indexFields, indexOptions);
- create();
- }
- );
- }
- }
- /**
- * Creates all [Atlas search indexes](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) defined in this model's schema.
- * This function only works when connected to MongoDB Atlas.
- *
- * #### Example:
- *
- * const schema = new Schema({
- * name: String,
- * description: String
- * });
- * schema.searchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
- * const Product = mongoose.model('Product', schema);
- *
- * // Creates the search index defined in the schema
- * await Product.createSearchIndexes();
- *
- * @api public
- * @return {Promise} resolves to the results of creating the search indexes
- */
- Model.createSearchIndexes = async function createSearchIndexes() {
- _checkContext(this, 'createSearchIndexes');
- const results = [];
- for (const searchIndex of this.schema._searchIndexes) {
- results.push(await this.createSearchIndex(searchIndex));
- }
- return results;
- };
- /**
- * Schema the model uses.
- *
- * @property schema
- * @static
- * @api public
- * @memberOf Model
- */
- Model.schema;
- /**
- * Connection instance the model uses.
- *
- * @property db
- * @static
- * @api public
- * @memberOf Model
- */
- Model.db;
- /**
- * Collection the model uses.
- *
- * @property collection
- * @api public
- * @memberOf Model
- */
- Model.collection;
- /**
- * Internal collection the model uses.
- *
- * @property collection
- * @api private
- * @memberOf Model
- */
- Model.$__collection;
- /**
- * Base Mongoose instance the model uses.
- *
- * @property base
- * @api public
- * @memberOf Model
- */
- Model.base;
- /**
- * Registered discriminators for this model.
- *
- * @property discriminators
- * @api public
- * @memberOf Model
- */
- Model.discriminators;
- /**
- * Translate any aliases fields/conditions so the final query or document object is pure
- *
- * #### Example:
- *
- * await Character.find(Character.translateAliases({
- * '名': 'Eddard Stark' // Alias for 'name'
- * });
- *
- * By default, `translateAliases()` overwrites raw fields with aliased fields.
- * So if `n` is an alias for `name`, `{ n: 'alias', name: 'raw' }` will resolve to `{ name: 'alias' }`.
- * However, you can set the `errorOnDuplicates` option to throw an error if there are potentially conflicting paths.
- * The `translateAliases` option for queries uses `errorOnDuplicates`.
- *
- * #### Note:
- *
- * Only translate arguments of object type anything else is returned raw
- *
- * @param {Object} fields fields/conditions that may contain aliased keys
- * @param {Boolean} [errorOnDuplicates] if true, throw an error if there's both a key and an alias for that key in `fields`
- * @return {Object} the translated 'pure' fields/conditions
- */
- Model.translateAliases = function translateAliases(fields, errorOnDuplicates) {
- _checkContext(this, 'translateAliases');
- const translate = (key, value) => {
- let alias;
- const translated = [];
- const fieldKeys = key.split('.');
- let currentSchema = this.schema;
- for (const i in fieldKeys) {
- const name = fieldKeys[i];
- if (currentSchema?.aliases[name]) {
- alias = currentSchema.aliases[name];
- if (errorOnDuplicates && alias in fields) {
- throw new MongooseError(`Provided object has both field "${name}" and its alias "${alias}"`);
- }
- // Alias found,
- translated.push(alias);
- } else {
- alias = name;
- // Alias not found, so treat as un-aliased key
- translated.push(name);
- }
- // Check if aliased path is a schema
- if (currentSchema?.paths[alias]) {
- currentSchema = currentSchema.paths[alias].schema;
- }
- else
- currentSchema = null;
- }
- const translatedKey = translated.join('.');
- if (fields instanceof Map)
- fields.set(translatedKey, value);
- else
- fields[translatedKey] = value;
- if (translatedKey !== key) {
- // We'll be using the translated key instead
- if (fields instanceof Map) {
- // Delete from map
- fields.delete(key);
- } else {
- // Delete from object
- delete fields[key]; // We'll be using the translated key instead
- }
- }
- return fields;
- };
- if (typeof fields === 'object') {
- // Fields is an object (query conditions or document fields)
- if (fields instanceof Map) {
- // A Map was supplied
- for (const field of new Map(fields)) {
- fields = translate(field[0], field[1]);
- }
- } else {
- // Infer a regular object was supplied
- for (const key of Object.keys(fields)) {
- fields = translate(key, fields[key]);
- if (key[0] === '$') {
- if (Array.isArray(fields[key])) {
- for (const i in fields[key]) {
- // Recursively translate nested queries
- fields[key][i] = this.translateAliases(fields[key][i]);
- }
- } else {
- this.translateAliases(fields[key]);
- }
- }
- }
- }
- return fields;
- } else {
- // Don't know typeof fields
- return fields;
- }
- };
- /**
- * Deletes the first document that matches `conditions` from the collection.
- * It returns an object with the property `deletedCount` indicating how many documents were deleted.
- *
- * #### Example:
- *
- * await Character.deleteOne({ name: 'Eddard Stark' }); // returns {deletedCount: 1}
- *
- * #### Note:
- *
- * This function triggers `deleteOne` query hooks. Read the
- * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more.
- *
- * @param {Object} conditions
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @return {Query}
- * @api public
- */
- Model.deleteOne = function deleteOne(conditions, options) {
- _checkContext(this, 'deleteOne');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.prototype.deleteOne() no longer accepts a callback');
- }
- const mq = new this.Query({}, {}, this, this.$__collection);
- mq.setOptions(options);
- return mq.deleteOne(conditions);
- };
- /**
- * Deletes all of the documents that match `conditions` from the collection.
- * It returns an object with the property `deletedCount` containing the number of documents deleted.
- *
- * #### Example:
- *
- * await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }); // returns {deletedCount: x} where x is the number of documents deleted.
- *
- * #### Note:
- *
- * This function triggers `deleteMany` query hooks. Read the
- * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more.
- *
- * @param {Object} conditions
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @return {Query}
- * @api public
- */
- Model.deleteMany = function deleteMany(conditions, options) {
- _checkContext(this, 'deleteMany');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.deleteMany() no longer accepts a callback');
- }
- const mq = new this.Query({}, {}, this, this.$__collection);
- mq.setOptions(options);
- return mq.deleteMany(conditions);
- };
- /**
- * Finds documents.
- *
- * Mongoose casts the `filter` to match the model's schema before the command is sent.
- * See our [query casting tutorial](https://mongoosejs.com/docs/tutorials/query_casting.html) for
- * more information on how Mongoose casts `filter`.
- *
- * #### Example:
- *
- * // find all documents
- * await MyModel.find({});
- *
- * // find all documents named john and at least 18
- * await MyModel.find({ name: 'john', age: { $gte: 18 } }).exec();
- *
- * // executes, name LIKE john and only selecting the "name" and "friends" fields
- * await MyModel.find({ name: /john/i }, 'name friends').exec();
- *
- * // passing options
- * await MyModel.find({ name: /john/i }, null, { skip: 10 }).exec();
- *
- * @param {Object|ObjectId} filter
- * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @return {Query}
- * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
- * @see query casting https://mongoosejs.com/docs/tutorials/query_casting.html
- * @api public
- */
- Model.find = function find(conditions, projection, options) {
- _checkContext(this, 'find');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
- throw new MongooseError('Model.find() no longer accepts a callback');
- }
- const mq = new this.Query({}, {}, this, this.$__collection);
- mq.select(projection);
- mq.setOptions(options);
- return mq.find(conditions);
- };
- /**
- * Finds a single document by its _id field. `findById(id)` is equivalent to `findOne({ _id: id })`.
- *
- * The `id` is cast based on the Schema before sending the command.
- *
- * This function triggers the following middleware.
- *
- * - `findOne()`
- *
- * #### Example:
- *
- * // Find the adventure with the given `id`, or `null` if not found
- * await Adventure.findById(id).exec();
- *
- * // select only the adventures name and length
- * await Adventure.findById(id, 'name length').exec();
- *
- * @param {Any} id value of `_id` to query by
- * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @return {Query}
- * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
- * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html
- * @see findById in Mongoose https://masteringjs.io/tutorials/mongoose/find-by-id
- * @api public
- */
- Model.findById = function findById(id, projection, options) {
- _checkContext(this, 'findById');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.findById() no longer accepts a callback');
- }
- return this.findOne({ _id: id }, projection, options);
- };
- /**
- * Finds one document.
- *
- * The `conditions` are cast to their respective SchemaTypes before the command is sent.
- *
- * *Note:* `conditions` is optional, and if `conditions` is null or undefined,
- * mongoose will send an empty `findOne` command to MongoDB, which will return
- * an arbitrary document. If you're querying by `_id`, use `findById()` instead.
- *
- * #### Example:
- *
- * // Find one adventure whose `country` is 'Croatia', otherwise `null`
- * await Adventure.findOne({ country: 'Croatia' }).exec();
- *
- * // Model.findOne() no longer accepts a callback
- *
- * // Select only the adventures name and length
- * await Adventure.findOne({ country: 'Croatia' }, 'name length').exec();
- *
- * @param {Object} [conditions]
- * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @return {Query}
- * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
- * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html
- * @api public
- */
- Model.findOne = function findOne(conditions, projection, options) {
- _checkContext(this, 'findOne');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.findOne() no longer accepts a callback');
- }
- const mq = new this.Query({}, {}, this, this.$__collection);
- mq.select(projection);
- mq.setOptions(options);
- return mq.findOne(conditions);
- };
- /**
- * Estimates the number of documents in the MongoDB collection. Faster than
- * using `countDocuments()` for large collections because
- * `estimatedDocumentCount()` uses collection metadata rather than scanning
- * the entire collection.
- *
- * #### Example:
- *
- * const numAdventures = await Adventure.estimatedDocumentCount();
- *
- * @param {Object} [options]
- * @return {Query}
- * @api public
- */
- Model.estimatedDocumentCount = function estimatedDocumentCount(options) {
- _checkContext(this, 'estimatedDocumentCount');
- const mq = new this.Query({}, {}, this, this.$__collection);
- return mq.estimatedDocumentCount(options);
- };
- /**
- * Counts number of documents matching `filter` in a database collection.
- *
- * #### Example:
- *
- * const count = await Adventure.countDocuments({ type: 'jungle' });
- * console.log('there are %d jungle adventures', count);
- *
- * If you want to count all documents in a large collection,
- * use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount())
- * instead. If you call `countDocuments({})`, MongoDB will always execute
- * a full collection scan and **not** use any indexes.
- *
- * The `countDocuments()` function is similar to `count()`, but there are a
- * [few operators that `countDocuments()` does not support](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#countDocuments).
- * Below are the operators that `count()` supports but `countDocuments()` does not,
- * and the suggested replacement:
- *
- * - `$where`: [`$expr`](https://www.mongodb.com/docs/manual/reference/operator/query/expr/)
- * - `$near`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$center`](https://www.mongodb.com/docs/manual/reference/operator/query/center/#op._S_center)
- * - `$nearSphere`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$centerSphere`](https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/#op._S_centerSphere)
- *
- * @param {Object} filter
- * @return {Query}
- * @api public
- */
- Model.countDocuments = function countDocuments(conditions, options) {
- _checkContext(this, 'countDocuments');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.countDocuments() no longer accepts a callback');
- }
- const mq = new this.Query({}, {}, this, this.$__collection);
- if (options != null) {
- mq.setOptions(options);
- }
- return mq.countDocuments(conditions);
- };
- /**
- * Creates a Query for a `distinct` operation.
- *
- * #### Example:
- *
- * const query = Link.distinct('url');
- * query.exec();
- *
- * @param {String} field
- * @param {Object} [conditions] optional
- * @param {Object} [options] optional
- * @return {Query}
- * @api public
- */
- Model.distinct = function distinct(field, conditions, options) {
- _checkContext(this, 'distinct');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.distinct() no longer accepts a callback');
- }
- const mq = new this.Query({}, {}, this, this.$__collection);
- if (options != null) {
- mq.setOptions(options);
- }
- return mq.distinct(field, conditions);
- };
- /**
- * Creates a Query, applies the passed conditions, and returns the Query.
- *
- * For example, instead of writing:
- *
- * User.find({ age: { $gte: 21, $lte: 65 } });
- *
- * we can instead write:
- *
- * User.where('age').gte(21).lte(65).exec();
- *
- * Since the Query class also supports `where` you can continue chaining
- *
- * User
- * .where('age').gte(21).lte(65)
- * .where('name', /^b/i)
- * ... etc
- *
- * @param {String} path
- * @param {Object} [val] optional value
- * @return {Query}
- * @api public
- */
- Model.where = function where(path, val) {
- _checkContext(this, 'where');
- void val; // eslint
- const mq = new this.Query({}, {}, this, this.$__collection).find({});
- return mq.where.apply(mq, arguments);
- };
- /**
- * Creates a `Query` and specifies a `$where` condition.
- *
- * Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.
- *
- * Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {});
- *
- * @param {String|Function} argument is a javascript string or anonymous function
- * @method $where
- * @memberOf Model
- * @return {Query}
- * @see Query.$where https://mongoosejs.com/docs/api/query.html#Query.prototype.$where
- * @api public
- */
- Model.$where = function $where() {
- _checkContext(this, '$where');
- const mq = new this.Query({}, {}, this, this.$__collection).find({});
- return mq.$where.apply(mq, arguments);
- };
- /**
- * Issues a mongodb findOneAndUpdate command.
- *
- * Finds a matching document, updates it according to the `update` arg, passing any `options`. A Query object is returned.
- *
- * #### Example:
- *
- * A.findOneAndUpdate(filter, update, options); // returns Query
- * A.findOneAndUpdate(filter, update); // returns Query
- * A.findOneAndUpdate(filter); // returns Query
- * A.findOneAndUpdate(); // returns Query
- *
- * // Other supported syntaxes
- * // Note that calling `Query#findOneAndUpdate()` with 1 arg will treat the arg as `update`, NOT `filter`
- * A.find(filter).findOneAndUpdate(update);
- *
- * #### Note:
- *
- * All top level update keys which are not `atomic` operation names are treated as set operations:
- *
- * #### Example:
- *
- * const query = { name: 'borne' };
- * Model.findOneAndUpdate(query, { name: 'jason bourne' }, options);
- *
- * // is sent as
- * Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options);
- *
- * #### Note:
- *
- * `findOneAndX` and `findByIdAndX` functions support limited validation that
- * you can enable by setting the `runValidators` option.
- *
- * If you need full-fledged validation, use the traditional approach of first
- * retrieving the document.
- *
- * const doc = await Model.findById(id);
- * doc.name = 'jason bourne';
- * await doc.save();
- *
- * @param {Object} [conditions]
- * @param {Object} [update]
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
- * @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).
- * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
- * @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.
- * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
- * @param {Boolean} [options.new=false] if true, return the modified document rather than the original
- * @param {Object|String} [options.fields] Field selection. Equivalent to `.select(fields).findOneAndUpdate()`
- * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
- * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
- * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
- * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
- * @param {Boolean} [options.includeResultMetadata] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
- * @return {Query}
- * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
- * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
- * @api public
- */
- Model.findOneAndUpdate = function(conditions, update, options) {
- _checkContext(this, 'findOneAndUpdate');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
- throw new MongooseError('Model.findOneAndUpdate() no longer accepts a callback');
- }
- let fields;
- if (options) {
- fields = options.fields || options.projection;
- }
- update = clone(update, {
- depopulate: true,
- _isNested: true
- });
- decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey);
- const mq = new this.Query({}, {}, this, this.$__collection);
- mq.select(fields);
- return mq.findOneAndUpdate(conditions, update, options);
- };
- /**
- * Issues a mongodb findOneAndUpdate command by a document's _id field.
- * `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`.
- *
- * Finds a matching document, updates it according to the `update` arg,
- * passing any `options`, and returns the found document (if any).
- *
- * This function triggers the following middleware.
- *
- * - `findOneAndUpdate()`
- *
- * #### Example:
- *
- * A.findByIdAndUpdate(id, update, options) // returns Query
- * A.findByIdAndUpdate(id, update) // returns Query
- * A.findByIdAndUpdate() // returns Query
- *
- * #### Note:
- *
- * All top level update keys which are not `atomic` operation names are treated as set operations:
- *
- * #### Example:
- *
- * Model.findByIdAndUpdate(id, { name: 'jason bourne' }, options)
- *
- * // is sent as
- * Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options)
- *
- * #### Note:
- *
- * `findOneAndX` and `findByIdAndX` functions support limited validation. You can
- * enable validation by setting the `runValidators` option.
- *
- * If you need full-fledged validation, use the traditional approach of first
- * retrieving the document.
- *
- * const doc = await Model.findById(id)
- * doc.name = 'jason bourne';
- * await doc.save();
- *
- * @param {Object|Number|String} id value of `_id` to query by
- * @param {Object} [update]
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
- * @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).
- * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
- * @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.
- * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
- * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
- * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
- * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
- * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Boolean} [options.new=false] if true, return the modified document rather than the original
- * @param {Object|String} [options.select] sets the document fields to return.
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
- * @return {Query}
- * @see Model.findOneAndUpdate https://mongoosejs.com/docs/api/model.html#Model.findOneAndUpdate()
- * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
- * @api public
- */
- Model.findByIdAndUpdate = function(id, update, options) {
- _checkContext(this, 'findByIdAndUpdate');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
- throw new MongooseError('Model.findByIdAndUpdate() no longer accepts a callback');
- }
- // if a model is passed in instead of an id
- if (id instanceof Document) {
- id = id._doc._id;
- }
- return this.findOneAndUpdate.call(this, { _id: id }, update, options);
- };
- /**
- * Issue a MongoDB `findOneAndDelete()` command.
- *
- * Finds a matching document, removes it, and returns the found document (if any).
- *
- * This function triggers the following middleware.
- *
- * - `findOneAndDelete()`
- *
- * #### Example:
- *
- * A.findOneAndDelete(conditions, options) // return Query
- * A.findOneAndDelete(conditions) // returns Query
- * A.findOneAndDelete() // returns Query
- *
- * `findOneAndX` and `findByIdAndX` functions support limited validation. You can
- * enable validation by setting the `runValidators` option.
- *
- * If you need full-fledged validation, use the traditional approach of first
- * retrieving the document.
- *
- * const doc = await Model.findById(id)
- * doc.name = 'jason bourne';
- * await doc.save();
- *
- * @param {Object} conditions
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
- * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
- * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
- * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
- * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
- * @param {Object|String} [options.select] sets the document fields to return.
- * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @return {Query}
- * @api public
- */
- Model.findOneAndDelete = function(conditions, options) {
- _checkContext(this, 'findOneAndDelete');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.findOneAndDelete() no longer accepts a callback');
- }
- let fields;
- if (options) {
- fields = options.select;
- options.select = undefined;
- }
- const mq = new this.Query({}, {}, this, this.$__collection);
- mq.select(fields);
- return mq.findOneAndDelete(conditions, options);
- };
- /**
- * Issue a MongoDB `findOneAndDelete()` command by a document's _id field.
- * In other words, `findByIdAndDelete(id)` is a shorthand for
- * `findOneAndDelete({ _id: id })`.
- *
- * This function triggers the following middleware.
- *
- * - `findOneAndDelete()`
- *
- * @param {Object|Number|String} id value of `_id` to query by
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @return {Query}
- * @see Model.findOneAndDelete https://mongoosejs.com/docs/api/model.html#Model.findOneAndDelete()
- * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
- */
- Model.findByIdAndDelete = function(id, options) {
- _checkContext(this, 'findByIdAndDelete');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.findByIdAndDelete() no longer accepts a callback');
- }
- return this.findOneAndDelete({ _id: id }, options);
- };
- /**
- * Issue a MongoDB `findOneAndReplace()` command.
- *
- * Finds a matching document, replaces it with the provided doc, and returns the document.
- *
- * This function triggers the following query middleware.
- *
- * - `findOneAndReplace()`
- *
- * #### Example:
- *
- * A.findOneAndReplace(filter, replacement, options) // return Query
- * A.findOneAndReplace(filter, replacement) // returns Query
- * A.findOneAndReplace() // returns Query
- *
- * @param {Object} filter Replace the first document that matches this filter
- * @param {Object} [replacement] Replace with this document
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
- * @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).
- * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
- * @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.
- * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
- * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
- * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
- * @param {Object|String} [options.select] sets the document fields to return.
- * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @return {Query}
- * @api public
- */
- Model.findOneAndReplace = function(filter, replacement, options) {
- _checkContext(this, 'findOneAndReplace');
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
- throw new MongooseError('Model.findOneAndReplace() no longer accepts a callback');
- }
- let fields;
- if (options) {
- fields = options.select;
- options.select = undefined;
- }
- const mq = new this.Query({}, {}, this, this.$__collection);
- mq.select(fields);
- return mq.findOneAndReplace(filter, replacement, options);
- };
- /**
- * Shortcut for saving one or more documents to the database.
- * `MyModel.create(docs)` does `new MyModel(doc).save()` for every doc in
- * docs.
- *
- * This function triggers the following middleware.
- *
- * - `save()`
- *
- * #### Example:
- *
- * // Insert one new `Character` document
- * await Character.create({ name: 'Jean-Luc Picard' });
- *
- * // Insert multiple new `Character` documents
- * await Character.create([{ name: 'Will Riker' }, { name: 'Geordi LaForge' }]);
- *
- * // Create a new character within a transaction. Note that you **must**
- * // pass an array as the first parameter to `create()` if you want to
- * // specify options.
- * await Character.create([{ name: 'Jean-Luc Picard' }], { session });
- *
- * @param {Array|Object} docs Documents to insert, as a spread or array
- * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. See [Model.save](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) for available options.
- * @param {Boolean} [options.ordered] saves the docs in series rather than parallel.
- * @param {Boolean} [options.aggregateErrors] Aggregate Errors instead of throwing the first one that occurs. Default: false
- * @return {Promise}
- * @api public
- */
- Model.create = async function create(doc, options) {
- if (typeof options === 'function' ||
- typeof arguments[2] === 'function') {
- throw new MongooseError('Model.create() no longer accepts a callback');
- }
- _checkContext(this, 'create');
- let args;
- const discriminatorKey = this.schema.options.discriminatorKey;
- if (Array.isArray(doc)) {
- args = doc;
- options = options != null && typeof options === 'object' ? options : {};
- } else {
- const last = arguments[arguments.length - 1];
- options = {};
- const hasCallback = typeof last === 'function' ||
- typeof options === 'function' ||
- typeof arguments[2] === 'function';
- if (hasCallback) {
- throw new MongooseError('Model.create() no longer accepts a callback');
- } else {
- args = [...arguments];
- // For backwards compatibility with 6.x, because of gh-5061 Mongoose 6.x and
- // older would treat a falsy last arg as a callback. We don't want to throw
- // an error here, because it would look strange if `Test.create({}, void 0)`
- // threw a callback error. But we also don't want to create an unnecessary document.
- if (args.length > 1 && !last) {
- args.pop();
- }
- }
- if (args.length === 2 &&
- args[0] != null &&
- args[1] != null &&
- args[0].session == null &&
- last &&
- getConstructorName(last.session) === 'ClientSession' &&
- !this.schema.path('session')) {
- // Probably means the user is running into the common mistake of trying
- // to use a spread to specify options, see gh-7535
- utils.warn('WARNING: to pass a `session` to `Model.create()` in ' +
- 'Mongoose, you **must** pass an array as the first argument. See: ' +
- 'https://mongoosejs.com/docs/api/model.html#Model.create()');
- }
- }
- if (args.length === 0) {
- return Array.isArray(doc) ? [] : null;
- }
- let res = [];
- const immediateError = typeof options.aggregateErrors === 'boolean' ? !options.aggregateErrors : true;
- delete options.aggregateErrors; // dont pass on the option to "$save"
- if (options.session && !options.ordered && args.length > 1) {
- throw new MongooseError('Cannot call `create()` with a session and multiple documents unless `ordered: true` is set');
- }
- if (options.ordered) {
- for (let i = 0; i < args.length; i++) {
- try {
- const doc = args[i];
- const Model = this.discriminators && doc[discriminatorKey] != null ?
- this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
- this;
- if (Model == null) {
- throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
- `found for model "${this.modelName}"`);
- }
- let toSave = doc;
- if (!(toSave instanceof Model)) {
- toSave = new Model(toSave);
- }
- await toSave.$save(options);
- res.push(toSave);
- } catch (err) {
- if (!immediateError) {
- res.push(err);
- } else {
- throw err;
- }
- }
- }
- return res;
- } else if (!immediateError) {
- res = await Promise.allSettled(args.map(async doc => {
- const Model = this.discriminators && doc[discriminatorKey] != null ?
- this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
- this;
- if (Model == null) {
- throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
- `found for model "${this.modelName}"`);
- }
- let toSave = doc;
- if (!(toSave instanceof Model)) {
- toSave = new Model(toSave);
- }
- await toSave.$save(options);
- return toSave;
- }));
- res = res.map(result => result.status === 'fulfilled' ? result.value : result.reason);
- } else {
- let firstError = null;
- res = await Promise.all(args.map(async doc => {
- const Model = this.discriminators && doc[discriminatorKey] != null ?
- this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
- this;
- if (Model == null) {
- throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
- `found for model "${this.modelName}"`);
- }
- try {
- let toSave = doc;
- if (!(toSave instanceof Model)) {
- toSave = new Model(toSave);
- }
- await toSave.$save(options);
- return toSave;
- } catch (err) {
- if (!firstError) {
- firstError = err;
- }
- }
- }));
- if (firstError) {
- throw firstError;
- }
- }
- if (!Array.isArray(doc) && args.length === 1) {
- return res[0];
- }
- return res;
- };
- /**
- * Shortcut for saving one document to the database.
- * `MyModel.insertOne(obj, options)` is almost equivalent to `new MyModel(obj).save(options)`.
- * The difference is that `insertOne()` checks if `obj` is already a document, and checks for discriminators.
- *
- * This function triggers the following middleware.
- *
- * - `save()`
- *
- * #### Example:
- *
- * // Insert one new `Character` document
- * const character = await Character.insertOne({ name: 'Jean-Luc Picard' });
- * character.name; // 'Jean-Luc Picard'
- *
- * // Create a new character within a transaction.
- * await Character.insertOne({ name: 'Jean-Luc Picard' }, { session });
- *
- * @param {Object|Document} doc Document to insert, as a POJO or Mongoose document
- * @param {Object} [options] Options passed down to `save()`.
- * @return {Promise<Document>} resolves to the saved document
- * @api public
- */
- Model.insertOne = async function insertOne(doc, options) {
- _checkContext(this, 'insertOne');
- const discriminatorKey = this.schema.options.discriminatorKey;
- const Model = this.discriminators && doc[discriminatorKey] != null ?
- this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
- this;
- if (Model == null) {
- throw new MongooseError(
- `Discriminator "${doc[discriminatorKey]}" not found for model "${this.modelName}"`
- );
- }
- if (!(doc instanceof Model)) {
- doc = new Model(doc);
- }
- return await doc.$save(options);
- };
- /**
- * _Requires a replica set running MongoDB >= 3.6.0._ Watches the
- * underlying collection for changes using
- * [MongoDB change streams](https://www.mongodb.com/docs/manual/changeStreams/).
- *
- * This function does **not** trigger any middleware. In particular, it
- * does **not** trigger aggregate middleware.
- *
- * The ChangeStream object is an event emitter that emits the following events:
- *
- * - 'change': A change occurred, see below example
- * - 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow [this GitHub issue](https://github.com/Automattic/mongoose/issues/6799) for updates.
- * - 'end': Emitted if the underlying stream is closed
- * - 'close': Emitted if the underlying stream is closed
- *
- * #### Example:
- *
- * const doc = await Person.create({ name: 'Ned Stark' });
- * const changeStream = Person.watch().on('change', change => console.log(change));
- * // Will print from the above `console.log()`:
- * // { _id: { _data: ... },
- * // operationType: 'delete',
- * // ns: { db: 'mydb', coll: 'Person' },
- * // documentKey: { _id: 5a51b125c5500f5aa094c7bd } }
- * await doc.deleteOne();
- *
- * @param {Array} [pipeline]
- * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#watch)
- * @param {Boolean} [options.hydrate=false] if true and `fullDocument: 'updateLookup'` is set, Mongoose will automatically hydrate `fullDocument` into a fully fledged Mongoose document
- * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
- * @api public
- */
- Model.watch = function(pipeline, options) {
- _checkContext(this, 'watch');
- options = options || {};
- const watchOptions = options?.hydrate !== undefined ?
- utils.omit(options, ['hydrate']) :
- { ...options };
- options.model = this;
- const changeStreamThunk = cb => {
- pipeline = pipeline || [];
- prepareDiscriminatorPipeline(pipeline, this.schema, 'fullDocument');
- if (this.$__collection.buffer) {
- this.$__collection.addQueue(() => {
- if (this.closed) {
- return;
- }
- const driverChangeStream = this.$__collection.watch(pipeline, watchOptions);
- cb(null, driverChangeStream);
- });
- } else {
- const driverChangeStream = this.$__collection.watch(pipeline, watchOptions);
- cb(null, driverChangeStream);
- }
- };
- return new ChangeStream(changeStreamThunk, pipeline, options);
- };
- /**
- * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions)
- * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/),
- * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
- *
- * Calling `MyModel.startSession()` is equivalent to calling `MyModel.db.startSession()`.
- *
- * This function does not trigger any middleware.
- *
- * #### Example:
- *
- * const session = await Person.startSession();
- * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
- * await doc.deleteOne();
- * // `doc` will always be null, even if reading from a replica set
- * // secondary. Without causal consistency, it is possible to
- * // get a doc back from the below query if the query reads from a
- * // secondary that is experiencing replication lag.
- * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });
- *
- * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession)
- * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
- * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
- * @api public
- */
- Model.startSession = function() {
- _checkContext(this, 'startSession');
- return this.db.startSession.apply(this.db, arguments);
- };
- /**
- * Shortcut for validating an array of documents and inserting them into
- * MongoDB if they're all valid. This function is faster than `.create()`
- * because it only sends one operation to the server, rather than one for each
- * document.
- *
- * Mongoose always validates each document **before** sending `insertMany`
- * to MongoDB. So if one document has a validation error, no documents will
- * be saved, unless you set
- * [the `ordered` option to false](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertMany/#error-handling).
- *
- * This function does **not** trigger save middleware.
- *
- * This function triggers the following middleware.
- *
- * - `insertMany()`
- *
- * #### Example:
- *
- * const docs = await Movies.insertMany([
- * { name: 'Star Wars' },
- * { name: 'The Empire Strikes Back' }
- * ]);
- * docs[0].name; // 'Star Wars'
- *
- * // Return raw result from MongoDB
- * const result = await Movies.insertMany([
- * { name: 'Star Wars' },
- * { name: 'The Empire Strikes Back' }
- * ], { rawResult: true });
- *
- * @param {Array|Object|*} doc(s)
- * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany)
- * @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
- * @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`.
- * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast, validate, or apply defaults to any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](https://mongoosejs.com/docs/api/model.html#Model.castObject()) and [`applyDefaults()`](https://mongoosejs.com/docs/api/model.html#Model.applyDefaults()).
- * @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory.
- * @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set.
- * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully.
- * @return {Promise} resolving to the raw result from the MongoDB driver if `options.rawResult` was `true`, or the documents that passed validation, otherwise
- * @api public
- */
- Model.insertMany = async function insertMany(arr, options) {
- _checkContext(this, 'insertMany');
- if (typeof options === 'function' ||
- typeof arguments[2] === 'function') {
- throw new MongooseError('Model.insertMany() no longer accepts a callback');
- }
- try {
- [arr] = await this._middleware.execPre('insertMany', this, [arr]);
- } catch (error) {
- await this._middleware.execPost('insertMany', this, [arr], { error });
- }
- options = options || {};
- const ThisModel = this;
- const limit = options.limit || 1000;
- const rawResult = !!options.rawResult;
- const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
- const throwOnValidationError = typeof options.throwOnValidationError === 'boolean' ? options.throwOnValidationError : false;
- const lean = !!options.lean;
- const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore();
- if ((!options || !Object.hasOwn(options, 'session')) && asyncLocalStorage?.session != null) {
- options = { ...options, session: asyncLocalStorage.session };
- }
- if (!Array.isArray(arr)) {
- arr = [arr];
- }
- const validationErrors = [];
- const validationErrorsToOriginalOrder = new Map();
- const results = ordered ? null : new Array(arr.length);
- async function validateDoc(doc, index) {
- // If option `lean` is set to true bypass validation and hydration
- if (lean) {
- return doc;
- }
- let createdNewDoc = false;
- if (!(doc instanceof ThisModel)) {
- if (doc != null && typeof doc !== 'object') {
- throw new ObjectParameterError(doc, 'arr.' + index, 'insertMany');
- }
- doc = new ThisModel(doc);
- createdNewDoc = true;
- }
- if (options.session != null) {
- doc.$session(options.session);
- }
- return doc.$validate(createdNewDoc ? { _skipParallelValidateCheck: true } : null)
- .then(() => doc)
- .catch(error => {
- if (ordered === false) {
- error.index = index;
- validationErrors.push(error);
- validationErrorsToOriginalOrder.set(error, index);
- results[index] = error;
- return;
- }
- throw error;
- });
- }
- const docs = await parallelLimit(arr, validateDoc, limit);
- const originalDocIndex = new Map();
- const validDocIndexToOriginalIndex = new Map();
- for (let i = 0; i < docs.length; ++i) {
- originalDocIndex.set(docs[i], i);
- }
- // We filter all failed pre-validations by removing nulls
- const docAttributes = docs.filter(function(doc) {
- return doc != null;
- });
- for (let i = 0; i < docAttributes.length; ++i) {
- validDocIndexToOriginalIndex.set(i, originalDocIndex.get(docAttributes[i]));
- }
- // Make sure validation errors are in the same order as the
- // original documents, so if both doc1 and doc2 both fail validation,
- // `Model.insertMany([doc1, doc2])` will always have doc1's validation
- // error before doc2's. Re: gh-12791.
- if (validationErrors.length > 0) {
- validationErrors.sort((err1, err2) => {
- return validationErrorsToOriginalOrder.get(err1) - validationErrorsToOriginalOrder.get(err2);
- });
- }
- // Quickly escape while there aren't any valid docAttributes
- if (docAttributes.length === 0) {
- if (throwOnValidationError) {
- throw new MongooseBulkWriteError(
- validationErrors,
- results,
- null,
- 'insertMany'
- );
- }
- if (rawResult) {
- const res = {
- acknowledged: true,
- insertedCount: 0,
- insertedIds: {}
- };
- decorateBulkWriteResult(res, validationErrors, validationErrors);
- return res;
- }
- return [];
- }
- const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
- if (doc.$__schema.options.versionKey) {
- doc[doc.$__schema.options.versionKey] = 0;
- }
- const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
- if (shouldSetTimestamps) {
- doc.initializeTimestamps();
- }
- if (doc.$__hasOnlyPrimitiveValues()) {
- return doc.$__toObjectShallow();
- }
- return doc.toObject(internalToObjectOptions);
- });
- let res;
- try {
- res = await this.$__collection.insertMany(docObjects, options);
- } catch (error) {
- // `writeErrors` is a property reported by the MongoDB driver,
- // just not if there's only 1 error.
- if (error.writeErrors == null &&
- error.result?.result?.writeErrors != null) {
- error.writeErrors = error.result.result.writeErrors;
- }
- // `insertedDocs` is a Mongoose-specific property
- const hasWriteErrors = error?.writeErrors;
- const erroredIndexes = new Set((error?.writeErrors || []).map(err => err.index));
- if (error.writeErrors != null) {
- for (let i = 0; i < error.writeErrors.length; ++i) {
- const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
- error.writeErrors[i] = { ...error.writeErrors[i], index: originalIndex };
- if (!ordered) {
- results[originalIndex] = error.writeErrors[i];
- }
- }
- }
- if (!ordered) {
- for (let i = 0; i < results.length; ++i) {
- if (results[i] === void 0) {
- results[i] = docs[i];
- }
- }
- error.results = results;
- }
- let firstErroredIndex = -1;
- error.insertedDocs = docAttributes.
- filter((doc, i) => {
- const isErrored = !hasWriteErrors || erroredIndexes.has(i);
- if (ordered) {
- if (firstErroredIndex > -1) {
- return i < firstErroredIndex;
- }
- if (isErrored) {
- firstErroredIndex = i;
- }
- }
- return !isErrored;
- }).
- map(function setIsNewForInsertedDoc(doc) {
- if (lean) {
- return doc;
- }
- doc.$__reset();
- _setIsNew(doc, false);
- return doc;
- });
- if (rawResult && ordered === false) {
- decorateBulkWriteResult(error, validationErrors, results);
- }
- await this._middleware.execPost('insertMany', this, [arr], { error });
- }
- if (!lean) {
- for (const attribute of docAttributes) {
- attribute.$__reset();
- _setIsNew(attribute, false);
- }
- }
- if (ordered === false && throwOnValidationError && validationErrors.length > 0) {
- for (let i = 0; i < results.length; ++i) {
- if (results[i] === void 0) {
- results[i] = docs[i];
- }
- }
- throw new MongooseBulkWriteError(
- validationErrors,
- results,
- res,
- 'insertMany'
- );
- }
- if (rawResult) {
- if (ordered === false) {
- for (let i = 0; i < results.length; ++i) {
- if (results[i] === void 0) {
- results[i] = docs[i];
- }
- }
- // Decorate with mongoose validation errors in case of unordered,
- // because then still do `insertMany()`
- decorateBulkWriteResult(res, validationErrors, results);
- }
- return res;
- }
- if (options.populate != null) {
- return this.populate(docAttributes, options.populate).catch(err => {
- if (err != null) {
- err.insertedDocs = docAttributes;
- }
- throw err;
- });
- }
- return await this._middleware.execPost('insertMany', this, [docAttributes]).then(res => res[0]);
- };
- /*!
- * ignore
- */
- function _setIsNew(doc, val) {
- doc.$isNew = val;
- doc.$emit('isNew', val);
- doc.constructor.emit('isNew', val);
- const subdocs = doc.$getAllSubdocs({ useCache: true });
- for (const subdoc of subdocs) {
- subdoc.$isNew = val;
- subdoc.$emit('isNew', val);
- }
- }
- /**
- * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`,
- * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one
- * command. This is faster than sending multiple independent operations (e.g.
- * if you use `create()`) because with `bulkWrite()` there is only one round
- * trip to MongoDB.
- *
- * Mongoose will perform casting on all operations you provide.
- * The only exception is [setting the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines.
- *
- * This function does **not** trigger any middleware, neither `save()`, nor `update()`.
- * If you need to trigger
- * `save()` middleware for every document use [`create()`](https://mongoosejs.com/docs/api/model.html#Model.create()) instead.
- *
- * #### Example:
- *
- * Character.bulkWrite([
- * {
- * insertOne: {
- * document: {
- * name: 'Eddard Stark',
- * title: 'Warden of the North'
- * }
- * }
- * },
- * {
- * updateOne: {
- * filter: { name: 'Eddard Stark' },
- * // If you were using the MongoDB driver directly, you'd need to do
- * // `update: { $set: { title: ... } }` but mongoose adds $set for
- * // you.
- * update: { title: 'Hand of the King' }
- * }
- * },
- * {
- * deleteOne: {
- * filter: { name: 'Eddard Stark' }
- * }
- * }
- * ]).then(res => {
- * // Prints "1 1 1"
- * console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
- * });
- *
- * // Mongoose does **not** cast update pipelines, so no casting for the `update` option below.
- * // Mongoose does still cast `filter`
- * await Character.bulkWrite([{
- * updateOne: {
- * filter: { name: 'Annika Hansen' },
- * update: [{ $set: { name: 7 } }] // Array means update pipeline, so Mongoose skips casting
- * }
- * }]);
- *
- * The [supported operations](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are:
- *
- * - `insertOne`
- * - `updateOne`
- * - `updateMany`
- * - `deleteOne`
- * - `deleteMany`
- * - `replaceOne`
- *
- * @param {Array} ops
- * @param {Object} [ops.insertOne.document] The document to insert
- * @param {Object} [ops.insertOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
- * @param {Object} [ops.updateOne.filter] Update the first document that matches this filter
- * @param {Object} [ops.updateOne.update] An object containing [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/)
- * @param {Boolean} [ops.updateOne.upsert=false] If true, insert a doc if none match
- * @param {Boolean} [ops.updateOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
- * @param {Boolean} [ops.updateOne.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
- * @param {Object} [ops.updateOne.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
- * @param {Array} [ops.updateOne.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
- * @param {Object} [ops.updateMany.filter] Update all the documents that match this filter
- * @param {Object} [ops.updateMany.update] An object containing [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/)
- * @param {Boolean} [ops.updateMany.upsert=false] If true, insert a doc if no documents match `filter`
- * @param {Boolean} [ops.updateMany.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
- * @param {Boolean} [ops.updateMany.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
- * @param {Object} [ops.updateMany.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
- * @param {Array} [ops.updateMany.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
- * @param {Object} [ops.deleteOne.filter] Delete the first document that matches this filter
- * @param {Object} [ops.deleteMany.filter] Delete all documents that match this filter
- * @param {Object} [ops.replaceOne.filter] Replace the first document that matches this filter
- * @param {Object} [ops.replaceOne.replacement] The replacement document
- * @param {Boolean} [ops.replaceOne.upsert=false] If true, insert a doc if no documents match `filter`
- * @param {Object} [ops.replaceOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
- * @param {Object} [options]
- * @param {Boolean} [options.ordered=true] If true, execute writes in order and stop at the first error. If false, execute writes in parallel and continue until all writes have either succeeded or errored.
- * @param {Boolean} [options.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to any operations. Can be overridden at the operation-level.
- * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
- * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()) for more information.
- * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
- * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
- * @param {Boolean} [options.skipValidation=false] Set to true to skip Mongoose schema validation on bulk write operations. Mongoose currently runs validation on `insertOne` and `replaceOne` operations by default.
- * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://www.mongodb.com/docs/manual/core/schema-validation/) for all writes in this bulk.
- * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully. Note that Mongoose will still send all valid operations to the MongoDB server.
- * @param {Boolean|"throw"} [options.strict=null] Overwrites the [`strict` option](https://mongoosejs.com/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk.
- * @return {Promise} resolves to a [`BulkWriteOpResult`](https://mongodb.github.io/node-mongodb-native/4.9/classes/BulkWriteResult.html) if the operation succeeds
- * @api public
- */
- Model.bulkWrite = async function bulkWrite(ops, options) {
- _checkContext(this, 'bulkWrite');
- if (typeof options === 'function' ||
- typeof arguments[2] === 'function') {
- throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
- }
- options = options || {};
- try {
- [ops, options] = await this.hooks.execPre('bulkWrite', this, [ops, options]);
- } catch (err) {
- if (err instanceof Kareem.skipWrappedFunction) {
- ops = err;
- } else {
- await this.hooks.execPost('bulkWrite', this, [null], { error: err });
- }
- }
- if (ops instanceof Kareem.skipWrappedFunction) {
- return ops.args[0];
- }
- const ordered = options.ordered == null ? true : options.ordered;
- if (ops.length === 0) {
- const BulkWriteResult = this.base.driver.get().BulkWriteResult;
- const bulkWriteResult = new BulkWriteResult(getDefaultBulkwriteResult(), false);
- bulkWriteResult.n = 0;
- decorateBulkWriteResult(bulkWriteResult, [], []);
- return bulkWriteResult;
- }
- const validations = options?._skipCastBulkWrite ? [] : ops.map(op => castBulkWrite(this, op, options));
- const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore();
- if ((!options || !Object.hasOwn(options, 'session')) && asyncLocalStorage?.session != null) {
- options = { ...options, session: asyncLocalStorage.session };
- }
- let res = null;
- if (ordered) {
- await new Promise((resolve, reject) => {
- each(validations, (fn, cb) => fn(cb), error => {
- if (error) {
- return reject(error);
- }
- resolve();
- });
- });
- try {
- res = await this.$__collection.bulkWrite(ops, options);
- } catch (error) {
- await this.hooks.execPost('bulkWrite', this, [null], { error });
- }
- } else {
- let validOpIndexes = [];
- let validationErrors = [];
- const results = [];
- if (validations.length > 0) {
- validOpIndexes = await Promise.all(ops.map((op, i) => {
- if (i >= validations.length) {
- return i;
- }
- return new Promise((resolve) => {
- validations[i]((err) => {
- if (err == null) {
- resolve(i);
- } else {
- validationErrors.push({ index: i, error: err });
- results[i] = err;
- }
- resolve();
- });
- });
- }));
- validOpIndexes = validOpIndexes.filter(index => index != null);
- } else {
- validOpIndexes = ops.map((op, i) => i);
- }
- validationErrors = validationErrors.
- sort((v1, v2) => v1.index - v2.index).
- map(v => v.error);
- const validOps = validOpIndexes.sort().map(index => ops[index]);
- if (validOps.length === 0) {
- if (options.throwOnValidationError && validationErrors.length) {
- throw new MongooseBulkWriteError(
- validationErrors,
- results,
- res,
- 'bulkWrite'
- );
- }
- const BulkWriteResult = this.base.driver.get().BulkWriteResult;
- const bulkWriteResult = new BulkWriteResult(getDefaultBulkwriteResult(), false);
- bulkWriteResult.result = getDefaultBulkwriteResult();
- decorateBulkWriteResult(bulkWriteResult, validationErrors, results);
- return bulkWriteResult;
- }
- let error;
- [res, error] = await this.$__collection.bulkWrite(validOps, options).
- then(res => ([res, null])).
- catch(error => ([null, error]));
- const writeErrorsByIndex = {};
- if (error?.writeErrors) {
- for (const writeError of error.writeErrors) {
- writeErrorsByIndex[writeError.err.index] = writeError;
- }
- }
- for (let i = 0; i < validOpIndexes.length; ++i) {
- results[validOpIndexes[i]] = writeErrorsByIndex[i] ?? null;
- }
- if (error) {
- if (validationErrors.length > 0) {
- decorateBulkWriteResult(error, validationErrors, results);
- }
- await this.hooks.execPost('bulkWrite', this, [null], { error });
- }
- if (validationErrors.length > 0) {
- if (options.throwOnValidationError) {
- throw new MongooseBulkWriteError(
- validationErrors,
- results,
- res,
- 'bulkWrite'
- );
- } else {
- decorateBulkWriteResult(res, validationErrors, results);
- }
- }
- }
- await this.hooks.execPost('bulkWrite', this, [res]);
- return res;
- };
- /**
- * Takes an array of documents, gets the changes and inserts/updates documents in the database
- * according to whether or not the document is new, or whether it has changes or not.
- *
- * `bulkSave` uses `bulkWrite` under the hood, so it's mostly useful when dealing with many documents (10K+)
- *
- * `bulkSave()` throws errors under the following conditions:
- *
- * - one of the provided documents fails validation. In this case, `bulkSave()` does not send a `bulkWrite()`, and throws the first validation error.
- * - `bulkWrite()` fails (for example, due to being unable to connect to MongoDB or due to duplicate key error)
- * - `bulkWrite()` did not insert or update **any** documents. In this case, `bulkSave()` will throw a DocumentNotFound error.
- *
- * Note that `bulkSave()` will **not** throw an error if only some of the `save()` calls succeeded.
- *
- * @param {Array<Document>} documents
- * @param {Object} [options] options passed to the underlying `bulkWrite()`
- * @param {Boolean} [options.timestamps] defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
- * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
- * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()) for more information.
- * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
- * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
- * @param {Boolean} [options.validateBeforeSave=true] set to `false` to skip Mongoose validation on all documents
- * @return {BulkWriteResult} the return value from `bulkWrite()`
- */
- Model.bulkSave = async function bulkSave(documents, options) {
- options = options || {};
- if (options.timestamps != null) {
- for (const document of documents) {
- document.$__.saveOptions = document.$__.saveOptions || {};
- document.$__.saveOptions.timestamps = options.timestamps;
- }
- } else {
- for (const document of documents) {
- if (document.$__.timestamps != null) {
- document.$__.saveOptions = document.$__.saveOptions || {};
- document.$__.saveOptions.timestamps = document.$__.timestamps;
- }
- }
- }
- await Promise.all(documents.map(doc => buildPreSavePromise(doc, options)));
- const writeOperations = this.buildBulkWriteOperations(documents, options);
- const opts = { skipValidation: true, _skipCastBulkWrite: true, ...options };
- const { bulkWriteResult, bulkWriteError } = await this.bulkWrite(writeOperations, opts).then(
- (res) => ({ bulkWriteResult: res, bulkWriteError: null }),
- (err) => ({ bulkWriteResult: null, bulkWriteError: err })
- );
- // If not a MongoBulkWriteError, treat this as all documents failed to save.
- if (bulkWriteError != null && bulkWriteError.name !== 'MongoBulkWriteError') {
- throw bulkWriteError;
- }
- const matchedCount = bulkWriteResult?.matchedCount ?? 0;
- const insertedCount = bulkWriteResult?.insertedCount ?? 0;
- if (writeOperations.length > 0 && matchedCount + insertedCount < writeOperations.length && !bulkWriteError) {
- throw new MongooseBulkSaveIncompleteError(
- this.modelName,
- documents,
- bulkWriteResult
- );
- }
- const successfulDocuments = [];
- for (let i = 0; i < documents.length; i++) {
- const document = documents[i];
- const documentError = bulkWriteError?.writeErrors.find(writeError => {
- const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
- return writeErrorDocumentId.toString() === document._doc._id.toString();
- });
- if (documentError == null) {
- successfulDocuments.push(document);
- }
- }
- await Promise.all(successfulDocuments.map(document => handleSuccessfulWrite(document)));
- if (bulkWriteError != null) {
- throw bulkWriteError;
- }
- return bulkWriteResult;
- };
- async function buildPreSavePromise(document, options) {
- const [newOptions] = await document.schema.s.hooks.execPre('save', document, [options]);
- if (newOptions !== options) {
- throw new Error('Cannot overwrite options in pre("save") hook on bulkSave()');
- }
- }
- async function handleSuccessfulWrite(document) {
- if (document.$isNew) {
- _setIsNew(document, false);
- }
- document.$__reset();
- document._applyVersionIncrement();
- return document.schema.s.hooks.execPost('save', document, [document]);
- }
- /**
- * Apply defaults to the given document or POJO.
- *
- * @param {Object|Document} obj object or document to apply defaults on
- * @returns {Object|Document}
- * @api public
- */
- Model.applyDefaults = function applyDefaults(doc) {
- if (doc == null) {
- return doc;
- }
- if (doc.$__ != null) {
- applyDefaultsHelper(doc, doc.$__.fields, doc.$__.exclude);
- for (const subdoc of doc.$getAllSubdocs()) {
- applyDefaults(subdoc, subdoc.$__.fields, subdoc.$__.exclude);
- }
- return doc;
- }
- applyDefaultsToPOJO(doc, this.schema);
- return doc;
- };
- /**
- * Apply this model's virtuals to a given POJO. Virtuals execute with the POJO as the context `this`.
- *
- * #### Example:
- *
- * const userSchema = new Schema({ name: String });
- * userSchema.virtual('upper').get(function() { return this.name.toUpperCase(); });
- * const User = mongoose.model('User', userSchema);
- *
- * const obj = { name: 'John' };
- * User.applyVirtuals(obj);
- * obj.name; // 'John'
- * obj.upper; // 'JOHN', Mongoose applied the return value of the virtual to the given object
- *
- * @param {Object} obj object or document to apply virtuals on
- * @param {Array<string>} [virtualsToApply] optional whitelist of virtuals to apply
- * @returns {Object} obj
- * @api public
- */
- Model.applyVirtuals = function applyVirtuals(obj, virtualsToApply) {
- if (obj == null) {
- return obj;
- }
- // Nothing to do if this is already a hydrated document - it should already have virtuals
- if (obj.$__ != null) {
- return obj;
- }
- applyVirtualsHelper(this.schema, obj, virtualsToApply);
- return obj;
- };
- /**
- * Apply this model's timestamps to a given POJO, including subdocument timestamps
- *
- * #### Example:
- *
- * const userSchema = new Schema({ name: String }, { timestamps: true });
- * const User = mongoose.model('User', userSchema);
- *
- * const obj = { name: 'John' };
- * User.applyTimestamps(obj);
- * obj.createdAt; // 2024-06-01T18:00:00.000Z
- * obj.updatedAt; // 2024-06-01T18:00:00.000Z
- *
- * @param {Object} obj object or document to apply virtuals on
- * @param {Object} [options]
- * @param {Boolean} [options.isUpdate=false] if true, treat this as an update: just set updatedAt, skip setting createdAt. If false, set both createdAt and updatedAt
- * @param {Function} [options.currentTime] if set, Mongoose will call this function to get the current time.
- * @returns {Object} obj
- * @api public
- */
- Model.applyTimestamps = function applyTimestamps(obj, options) {
- if (obj == null) {
- return obj;
- }
- // Nothing to do if this is already a hydrated document - it should already have timestamps
- if (obj.$__ != null) {
- return obj;
- }
- applyTimestampsHelper(this.schema, obj, options);
- return obj;
- };
- /**
- * Cast the given POJO to the model's schema
- *
- * #### Example:
- *
- * const Test = mongoose.model('Test', Schema({ num: Number }));
- *
- * const obj = Test.castObject({ num: '42' });
- * obj.num; // 42 as a number
- *
- * Test.castObject({ num: 'not a number' }); // Throws a ValidationError
- *
- * @param {Object} obj object or document to cast
- * @param {Object} options options passed to castObject
- * @param {Boolean} options.ignoreCastErrors If set to `true` will not throw a ValidationError and only return values that were successfully cast.
- * @returns {Object} POJO casted to the model's schema
- * @throws {ValidationError} if casting failed for at least one path
- * @api public
- */
- Model.castObject = function castObject(obj, options) {
- options = options || {};
- const ret = {};
- let schema = this.schema;
- const discriminatorKey = schema.options.discriminatorKey;
- if (schema.discriminators != null && obj != null && obj[discriminatorKey] != null) {
- schema = getSchemaDiscriminatorByValue(schema, obj[discriminatorKey]) || schema;
- }
- const paths = Object.keys(schema.paths);
- for (const path of paths) {
- const schemaType = schema.path(path);
- if (!schemaType?.$isMongooseArray) {
- continue;
- }
- const val = get(obj, path);
- pushNestedArrayPaths(paths, val, path);
- }
- let error = null;
- for (const path of paths) {
- const schemaType = schema.path(path);
- if (schemaType == null) {
- continue;
- }
- let val = get(obj, path, void 0);
- if (val == null) {
- continue;
- }
- const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
- let cur = ret;
- for (let i = 0; i < pieces.length - 1; ++i) {
- if (cur[pieces[i]] == null) {
- cur[pieces[i]] = isNaN(pieces[i + 1]) ? {} : [];
- }
- cur = cur[pieces[i]];
- }
- if (schemaType.$isMongooseDocumentArray) {
- const castNonArraysOption = schemaType.options?.castNonArrays ?? schemaType.constructor.options.castNonArrays;
- if (!Array.isArray(val)) {
- if (!castNonArraysOption) {
- if (!options.ignoreCastErrors) {
- error = error || new ValidationError();
- error.addError(path, new ObjectExpectedError(path, val));
- }
- } else {
- cur[pieces[pieces.length - 1]] = [
- Model.castObject.call(schemaType.Constructor, val)
- ];
- }
- continue;
- }
- }
- if (schemaType.$isSingleNested || schemaType.$isMongooseDocumentArrayElement) {
- try {
- val = Model.castObject.call(schemaType.Constructor, val);
- } catch (err) {
- if (!options.ignoreCastErrors) {
- error = error || new ValidationError();
- error.addError(path, err);
- }
- continue;
- }
- cur[pieces[pieces.length - 1]] = val;
- continue;
- }
- try {
- val = schemaType.cast(val);
- cur[pieces[pieces.length - 1]] = val;
- } catch (err) {
- if (!options.ignoreCastErrors) {
- error = error || new ValidationError();
- error.addError(path, err);
- }
- continue;
- }
- }
- if (error != null) {
- throw error;
- }
- return ret;
- };
- /**
- * Build bulk write operations for `bulkSave()`.
- *
- * @param {Array<Document>} documents The array of documents to build write operations of
- * @param {Object} options
- * @param {Boolean} options.skipValidation defaults to `false`, when set to true, building the write operations will bypass validating the documents.
- * @param {Boolean} options.timestamps defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
- * @return {Array<Promise>} Returns a array of all Promises the function executes to be awaited.
- * @api private
- */
- Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, options) {
- if (!Array.isArray(documents)) {
- throw new Error(`bulkSave expects an array of documents to be passed, received \`${documents}\` instead`);
- }
- setDefaultOptions();
- const writeOperations = documents.map((document, i) => {
- if (!options.skipValidation) {
- if (!(document instanceof Document)) {
- throw new Error(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`);
- }
- if (options.validateBeforeSave == null || options.validateBeforeSave) {
- const err = document.validateSync();
- if (err != null) {
- throw err;
- }
- }
- }
- const isANewDocument = document.isNew;
- if (isANewDocument) {
- const writeOperation = { insertOne: { document } };
- utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
- return writeOperation;
- }
- const delta = document.$__delta();
- const isDocumentWithChanges = delta != null && !utils.isEmptyObject(delta[0]);
- if (isDocumentWithChanges) {
- const where = document.$__where(delta[0]);
- const changes = delta[1];
- _applyCustomWhere(document, where);
- // If shard key is set, add shard keys to _filter_ condition to right shard is targeted
- const shardKey = this.schema.options.shardKey;
- if (shardKey) {
- const paths = Object.keys(shardKey);
- const len = paths.length;
- for (let i = 0; i < len; ++i) {
- where[paths[i]] = document[paths[i]];
- }
- }
- document.$__version(where, delta);
- const writeOperation = { updateOne: { filter: where, update: changes } };
- utils.injectTimestampsOption(writeOperation.updateOne, options.timestamps);
- return writeOperation;
- }
- return null;
- }).filter(op => op !== null);
- return writeOperations;
- function setDefaultOptions() {
- options = options || {};
- if (options.skipValidation == null) {
- options.skipValidation = false;
- }
- }
- };
- /**
- * Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
- * The document returned has no paths marked as modified initially.
- *
- * #### Example:
- *
- * // hydrate previous data into a Mongoose document
- * const mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });
- *
- * @param {Object} obj
- * @param {Object|String|String[]} [projection] optional projection containing which fields should be selected for this document
- * @param {Object} [options] optional options
- * @param {Boolean} [options.setters=false] if true, apply schema setters when hydrating
- * @param {Boolean} [options.hydratedPopulatedDocs=false] if true, populates the docs if passing pre-populated data
- * @param {Boolean} [options.virtuals=false] if true, sets any virtuals present on `obj`
- * @return {Document} document instance
- * @api public
- */
- Model.hydrate = function(obj, projection, options) {
- _checkContext(this, 'hydrate');
- if (options?.virtuals && options?.hydratedPopulatedDocs === false) {
- throw new MongooseError('Cannot set `hydratedPopulatedDocs` option to false if `virtuals` option is truthy because `virtuals: true` also sets populated virtuals');
- }
- if (projection != null) {
- if (obj?.$__ != null) {
- obj = obj.toObject(internalToObjectOptions);
- }
- obj = applyProjection(obj, projection);
- }
- const document = require('./queryHelpers').createModel(this, obj, projection);
- document.$init(obj, options);
- return document;
- };
- /**
- * Same as `updateOne()`, except MongoDB will update _all_ documents that match
- * `filter` (as opposed to just the first one) regardless of the value of
- * the `multi` option.
- *
- * **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')`
- * and `post('updateMany')` instead.
- *
- * #### Example:
- *
- * const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
- * res.matchedCount; // Number of documents matched
- * res.modifiedCount; // Number of documents modified
- * res.acknowledged; // Boolean indicating the MongoDB server received the operation. This may be false if Mongoose did not send an update to the server because the update was empty.
- * res.upsertedId; // null or an id containing a document that had to be upserted.
- * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
- *
- * // Other supported syntaxes
- * await Person.find({ name: /Stark$/ }).updateMany({ isDeleted: true }); // Using chaining syntax
- * await Person.find().updateMany({ isDeleted: true }); // Set `isDeleted` on _all_ Person documents
- *
- * This function triggers the following middleware.
- *
- * - `updateMany()`
- *
- * @param {Object} filter
- * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
- * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
- * @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. Does nothing if schema-level timestamps are not set.
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
- * @return {Query}
- * @see Query docs https://mongoosejs.com/docs/queries.html
- * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
- * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
- * @api public
- */
- Model.updateMany = function updateMany(conditions, update, options) {
- _checkContext(this, 'updateMany');
- if (update == null) {
- throw new MongooseError('updateMany `update` parameter cannot be nullish');
- }
- return _update(this, 'updateMany', conditions, update, options);
- };
- /**
- * Update _only_ the first document that matches `filter`.
- *
- * - Use `replaceOne()` if you want to overwrite an entire document rather than using atomic operators like `$set`.
- *
- * #### Example:
- *
- * const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
- * res.matchedCount; // Number of documents matched
- * res.modifiedCount; // Number of documents modified
- * res.acknowledged; // Boolean indicating the MongoDB server received the operation. This may be false if Mongoose did not send an update to the server because the update was empty.
- * res.upsertedId; // null or an id containing a document that had to be upserted.
- * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
- *
- * // Other supported syntaxes
- * await Person.findOne({ name: 'Jean-Luc Picard' }).updateOne({ ship: 'USS Enterprise' }); // Using chaining syntax
- * await Person.updateOne({ ship: 'USS Enterprise' }); // Updates first doc's `ship` property
- *
- * This function triggers the following middleware.
- *
- * - `updateOne()`
- *
- * @param {Object} filter
- * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
- * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
- * @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.
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
- * @return {Query}
- * @see Query docs https://mongoosejs.com/docs/queries.html
- * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
- * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
- * @api public
- */
- Model.updateOne = function updateOne(conditions, doc, options) {
- _checkContext(this, 'updateOne');
- return _update(this, 'updateOne', conditions, doc, options);
- };
- /**
- * Replace the existing document with the given document (no atomic operators like `$set`).
- *
- * #### Example:
- *
- * const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
- * res.matchedCount; // Number of documents matched
- * res.modifiedCount; // Number of documents modified
- * res.acknowledged; // Boolean indicating the MongoDB server received the operation.
- * res.upsertedId; // null or an id containing a document that had to be upserted.
- * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
- *
- * This function triggers the following middleware.
- *
- * - `replaceOne()`
- *
- * @param {Object} filter
- * @param {Object} doc
- * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
- * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
- * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
- * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
- * @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. Does nothing if schema-level timestamps are not set.
- * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
- * @return {Query}
- * @see Query docs https://mongoosejs.com/docs/queries.html
- * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
- * @return {Query}
- * @api public
- */
- Model.replaceOne = function replaceOne(conditions, doc, options) {
- _checkContext(this, 'replaceOne');
- const versionKey = this?.schema?.options?.versionKey || null;
- if (versionKey && !doc[versionKey]) {
- doc[versionKey] = 0;
- }
- return _update(this, 'replaceOne', conditions, doc, options);
- };
- /**
- * Common code for `updateOne()`, `updateMany()`, `replaceOne()`, and `update()`
- * because they need to do the same thing
- * @api private
- */
- function _update(model, op, conditions, doc, options) {
- const mq = new model.Query({}, {}, model, model.collection);
- // gh-2406
- // make local deep copy of conditions
- if (conditions instanceof Document) {
- conditions = conditions.toObject();
- } else {
- conditions = clone(conditions);
- }
- options = typeof options === 'function' ? options : clone(options);
- const versionKey = model?.schema?.options?.versionKey || null;
- decorateUpdateWithVersionKey(doc, options, versionKey);
- return mq[op](conditions, doc, options);
- }
- /**
- * Performs [aggregations](https://www.mongodb.com/docs/manual/aggregation/) on the models collection.
- *
- * The `aggregate` itself is returned.
- *
- * This function triggers the following middleware.
- *
- * - `aggregate()`
- *
- * #### Example:
- *
- * // Find the max balance of all accounts
- * const res = await Users.aggregate([
- * { $group: { _id: null, maxBalance: { $max: '$balance' }}},
- * { $project: { _id: 0, maxBalance: 1 }}
- * ]);
- *
- * console.log(res); // [ { maxBalance: 98000 } ]
- *
- * // Or use the aggregation pipeline builder.
- * const res = await Users.aggregate().
- * group({ _id: null, maxBalance: { $max: '$balance' } }).
- * project('-id maxBalance').
- * exec();
- * console.log(res); // [ { maxBalance: 98 } ]
- *
- * #### Note:
- *
- * - Mongoose does **not** cast aggregation pipelines to the model's schema because `$project` and `$group` operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format. You can use the [mongoose-cast-aggregation plugin](https://github.com/AbdelrahmanHafez/mongoose-cast-aggregation) to enable minimal casting for aggregation pipelines.
- * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
- *
- * #### More About Aggregations:
- *
- * - [Mongoose `Aggregate`](https://mongoosejs.com/docs/api/aggregate.html)
- * - [An Introduction to Mongoose Aggregate](https://masteringjs.io/tutorials/mongoose/aggregate)
- * - [MongoDB Aggregation docs](https://www.mongodb.com/docs/manual/applications/aggregation/)
- *
- * @see Aggregate https://mongoosejs.com/docs/api/aggregate.html#Aggregate()
- * @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
- * @param {Array} [pipeline] aggregation pipeline as an array of objects
- * @param {Object} [options] aggregation options
- * @return {Aggregate}
- * @api public
- */
- Model.aggregate = function aggregate(pipeline, options) {
- _checkContext(this, 'aggregate');
- if (typeof options === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.aggregate() no longer accepts a callback');
- }
- const aggregate = new Aggregate(pipeline || []);
- aggregate.model(this);
- if (options != null) {
- aggregate.option(options);
- }
- return aggregate;
- };
- /**
- * Casts and validates the given object against this model's schema, passing the
- * given `context` to custom validators.
- *
- * #### Example:
- *
- * const Model = mongoose.model('Test', Schema({
- * name: { type: String, required: true },
- * age: { type: Number, required: true }
- * });
- *
- * try {
- * await Model.validate({ name: null }, ['name'])
- * } catch (err) {
- * err instanceof mongoose.Error.ValidationError; // true
- * Object.keys(err.errors); // ['name']
- * }
- *
- * @param {Object} obj
- * @param {Object|Array|String} pathsOrOptions
- * @param {Object} [context]
- * @return {Promise<Object>} casted and validated copy of `obj` if validation succeeded
- * @api public
- */
- Model.validate = async function validate(obj, pathsOrOptions, context) {
- if ((arguments.length < 3) || (arguments.length === 3 && typeof arguments[2] === 'function')) {
- // For convenience, if we're validating a document or an object, make `context` default to
- // the model so users don't have to always pass `context`, re: gh-10132, gh-10346
- context = obj;
- }
- if (typeof context === 'function' || typeof arguments[3] === 'function') {
- throw new MongooseError('Model.validate() no longer accepts a callback');
- }
- let schema = this.schema;
- const discriminatorKey = schema.options.discriminatorKey;
- if (schema.discriminators != null && obj != null && obj[discriminatorKey] != null) {
- schema = getSchemaDiscriminatorByValue(schema, obj[discriminatorKey]) || schema;
- }
- let paths = Object.keys(schema.paths);
- if (pathsOrOptions != null) {
- const _pathsToValidate = typeof pathsOrOptions === 'string' ? new Set(pathsOrOptions.split(' ')) : Array.isArray(pathsOrOptions) ? new Set(pathsOrOptions) : new Set(paths);
- paths = paths.filter(p => {
- if (pathsOrOptions.pathsToSkip) {
- if (Array.isArray(pathsOrOptions.pathsToSkip)) {
- if (pathsOrOptions.pathsToSkip.find(x => x == p)) {
- return false;
- }
- } else if (typeof pathsOrOptions.pathsToSkip == 'string') {
- if (pathsOrOptions.pathsToSkip.includes(p)) {
- return false;
- }
- }
- }
- const pieces = p.split('.');
- let cur = pieces[0];
- for (const piece of pieces) {
- if (_pathsToValidate.has(cur)) {
- return true;
- }
- cur += '.' + piece;
- }
- return _pathsToValidate.has(p);
- });
- }
- for (const path of paths) {
- const schemaType = schema.path(path);
- if (!schemaType?.$isMongooseArray || schemaType.$isMongooseDocumentArray) {
- continue;
- }
- const val = get(obj, path);
- pushNestedArrayPaths(paths, val, path);
- }
- let error = null;
- paths = new Set(paths);
- try {
- obj = this.castObject(obj);
- } catch (err) {
- error = err;
- for (const key of Object.keys(error.errors || {})) {
- paths.delete(key);
- }
- }
- const promises = [];
- for (const path of paths) {
- const schemaType = schema.path(path);
- if (schemaType == null) {
- continue;
- }
- const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
- let cur = obj;
- for (let i = 0; i < pieces.length - 1; ++i) {
- cur = cur[pieces[i]];
- }
- const val = get(obj, path, void 0);
- promises.push(
- schemaType.doValidate(val, context, { path: path }).catch(err => {
- error = error || new ValidationError();
- error.addError(path, err);
- })
- );
- }
- await Promise.all(promises);
- if (error != null) {
- throw error;
- }
- return obj;
- };
- /**
- * Populates document references.
- *
- * Changed in Mongoose 6: the model you call `populate()` on should be the
- * "local field" model, **not** the "foreign field" model.
- *
- * #### Available top-level options:
- *
- * - path: space delimited path(s) to populate
- * - select: optional fields to select
- * - match: optional query conditions to match
- * - model: optional name of the model to use for population
- * - options: optional query options like sort, limit, etc
- * - justOne: optional boolean, if true Mongoose will always set `path` to a document, or `null` if no document was found. If false, Mongoose will always set `path` to an array, which will be empty if no documents are found. Inferred from schema by default.
- * - strictPopulate: optional boolean, set to `false` to allow populating paths that aren't in the schema.
- * - forceRepopulate: optional boolean, defaults to `true`. Set to `false` to prevent Mongoose from repopulating paths that are already populated
- *
- * #### Example:
- *
- * const Dog = mongoose.model('Dog', new Schema({ name: String, breed: String }));
- * const Person = mongoose.model('Person', new Schema({
- * name: String,
- * pet: { type: mongoose.ObjectId, ref: 'Dog' }
- * }));
- *
- * const pets = await Pet.create([
- * { name: 'Daisy', breed: 'Beagle' },
- * { name: 'Einstein', breed: 'Catalan Sheepdog' }
- * ]);
- *
- * // populate many plain objects
- * const users = [
- * { name: 'John Wick', dog: pets[0]._id },
- * { name: 'Doc Brown', dog: pets[1]._id }
- * ];
- * await User.populate(users, { path: 'dog', select: 'name' });
- * users[0].dog.name; // 'Daisy'
- * users[0].dog.breed; // undefined because of `select`
- *
- * @param {Document|Array} docs Either a single document or array of documents to populate.
- * @param {Object|String} options Either the paths to populate or an object specifying all parameters
- * @param {string} [options.path=null] The path to populate.
- * @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).
- * @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.
- * @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).
- * @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.
- * @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.
- * @param {Boolean} [options.skipInvalidIds=false] By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type.
- * @param {Number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents.
- * @param {Boolean} [options.strictPopulate=true] Set to false to allow populating paths that aren't defined in the given model's schema.
- * @param {Object} [options.options=null] Additional options like `limit` and `lean`.
- * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
- * @param {Boolean} [options.forceRepopulate=true] Set to `false` to prevent Mongoose from repopulating paths that are already populated
- * @param {Boolean} [options.ordered=false] Set to `true` to execute any populate queries one at a time, as opposed to in parallel. Set this option to `true` if populating multiple paths or paths with multiple models in transactions.
- * @return {Promise}
- * @api public
- */
- Model.populate = async function populate(docs, paths) {
- _checkContext(this, 'populate');
- if (typeof paths === 'function' || typeof arguments[2] === 'function') {
- throw new MongooseError('Model.populate() no longer accepts a callback');
- }
- // normalized paths
- paths = utils.populate(paths);
- if (paths.length === 0) {
- return docs;
- }
- // each path has its own query options and must be executed separately
- if (paths.find(p => p.ordered)) {
- // Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
- // one transaction in parallel.
- // Note that if _any_ path has `ordered`, we make the top-level populate `ordered` as well.
- for (const path of paths) {
- await _populatePath(this, docs, path);
- }
- } else {
- // By default, populate in parallel
- const promises = [];
- for (const path of paths) {
- promises.push(_populatePath(this, docs, path));
- }
- await Promise.all(promises);
- }
- return docs;
- };
- /*!
- * Populates `docs` for a single `populateOptions` instance.
- */
- const excludeIdReg = /\s?-_id\s?/;
- const excludeIdRegGlobal = /\s?-_id\s?/g;
- async function _populatePath(model, docs, populateOptions) {
- if (populateOptions.strictPopulate == null) {
- if (populateOptions._localModel?.schema._userProvidedOptions.strictPopulate != null) {
- populateOptions.strictPopulate = populateOptions._localModel.schema._userProvidedOptions.strictPopulate;
- } else if (populateOptions._localModel != null && model.base.options.strictPopulate != null) {
- populateOptions.strictPopulate = model.base.options.strictPopulate;
- } else if (model.base.options.strictPopulate != null) {
- populateOptions.strictPopulate = model.base.options.strictPopulate;
- }
- }
- // normalize single / multiple docs passed
- if (!Array.isArray(docs)) {
- docs = [docs];
- }
- if (docs.length === 0 || docs.every(utils.isNullOrUndefined)) {
- return;
- }
- const modelsMap = getModelsMapForPopulate(model, docs, populateOptions);
- if (modelsMap instanceof MongooseError) {
- throw modelsMap;
- }
- const len = modelsMap.length;
- let vals = [];
- function flatten(item) {
- // no need to include undefined values in our query
- return undefined !== item;
- }
- let hasOne = false;
- const params = [];
- for (let i = 0; i < len; ++i) {
- const mod = modelsMap[i];
- let select = mod.options.select;
- let ids = utils.array.flatten(mod.ids, flatten);
- ids = utils.array.unique(ids);
- const assignmentOpts = {};
- assignmentOpts.sort = mod &&
- mod.options &&
- mod.options.options &&
- mod.options.options.sort || void 0;
- assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0);
- // Lean transform may delete `_id`, which would cause assignment
- // to fail. So delay running lean transform until _after_
- // `_assign()`
- if (mod.options &&
- mod.options.options &&
- mod.options.options.lean &&
- mod.options.options.lean.transform) {
- mod.options.options._leanTransform = mod.options.options.lean.transform;
- mod.options.options.lean = true;
- }
- if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) {
- // Ensure that we set to 0 or empty array even
- // if we don't actually execute a query to make sure there's a value
- // and we know this path was populated for future sets. See gh-7731, gh-8230
- _assign(model, [], mod, assignmentOpts);
- continue;
- }
- hasOne = true;
- if (typeof populateOptions.foreignField === 'string') {
- mod.foreignField.clear();
- mod.foreignField.add(populateOptions.foreignField);
- }
- const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
- if (assignmentOpts.excludeId) {
- // override the exclusion from the query so we can use the _id
- // for document matching during assignment. we'll delete the
- // _id back off before returning the result.
- if (typeof select === 'string') {
- select = select.replace(excludeIdRegGlobal, ' ');
- } else if (Array.isArray(select)) {
- select = select.filter(field => field !== '-_id');
- } else {
- // preserve original select conditions by copying
- select = { ...select };
- delete select._id;
- }
- }
- if (mod.options.options?.limit != null) {
- assignmentOpts.originalLimit = mod.options.options.limit;
- } else if (mod.options.limit != null) {
- assignmentOpts.originalLimit = mod.options.limit;
- }
- params.push([mod, match, select, assignmentOpts]);
- }
- if (!hasOne) {
- // If models but no docs, skip further deep populate.
- if (modelsMap.length !== 0) {
- return;
- }
- // If no models and no docs to populate but we have a nested populate,
- // probably a case of unnecessarily populating a non-ref path re: gh-8946
- if (populateOptions.populate != null) {
- const opts = utils.populate(populateOptions.populate).map(pop => Object.assign({}, pop, {
- path: populateOptions.path + '.' + pop.path
- }));
- return model.populate(docs, opts);
- }
- return;
- }
- if (populateOptions.ordered) {
- // Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
- // one transaction in parallel.
- for (const arr of params) {
- await _execPopulateQuery.apply(null, arr).then(valsFromDb => { vals = vals.concat(valsFromDb); });
- }
- } else {
- // By default, populate in parallel
- const promises = [];
- for (const arr of params) {
- promises.push(_execPopulateQuery.apply(null, arr).then(valsFromDb => { vals = vals.concat(valsFromDb); }));
- }
- await Promise.all(promises);
- }
- for (const arr of params) {
- const mod = arr[0];
- const assignmentOpts = arr[3];
- for (const val of vals) {
- mod.options._childDocs.push(val);
- }
- _assign(model, vals, mod, assignmentOpts);
- }
- for (const arr of params) {
- removeDeselectedForeignField(arr[0].foreignField, arr[0].options, vals);
- }
- for (const arr of params) {
- const mod = arr[0];
- if (mod.options?.options?._leanTransform) {
- for (const doc of vals) {
- mod.options.options._leanTransform(doc);
- }
- }
- }
- }
- /*!
- * ignore
- */
- function _execPopulateQuery(mod, match, select) {
- let subPopulate = clone(mod.options.populate);
- const queryOptions = {};
- if (mod.options.skip !== undefined) {
- queryOptions.skip = mod.options.skip;
- }
- if (mod.options.limit !== undefined) {
- queryOptions.limit = mod.options.limit;
- }
- if (mod.options.perDocumentLimit !== undefined) {
- queryOptions.perDocumentLimit = mod.options.perDocumentLimit;
- }
- Object.assign(queryOptions, mod.options.options);
- if (mod.count) {
- delete queryOptions.skip;
- }
- if (queryOptions.perDocumentLimit != null) {
- queryOptions.limit = queryOptions.perDocumentLimit;
- delete queryOptions.perDocumentLimit;
- } else if (queryOptions.limit != null) {
- queryOptions.limit = queryOptions.limit * mod.ids.length;
- }
- const query = mod.model.find(match, select, queryOptions);
- // If we're doing virtual populate and projection is inclusive and foreign
- // field is not selected, automatically select it because mongoose needs it.
- // If projection is exclusive and client explicitly unselected the foreign
- // field, that's the client's fault.
- for (const foreignField of mod.foreignField) {
- if (foreignField !== '_id' &&
- query.selectedInclusively() &&
- !isPathSelectedInclusive(query._fields, foreignField)) {
- query.select(foreignField);
- }
- }
- // If using count, still need the `foreignField` so we can match counts
- // to documents, otherwise we would need a separate `count()` for every doc.
- if (mod.count) {
- for (const foreignField of mod.foreignField) {
- query.select(foreignField);
- }
- }
- // If we need to sub-populate, call populate recursively
- if (subPopulate) {
- // If subpopulating on a discriminator, skip check for non-existent
- // paths. Because the discriminator may not have the path defined.
- if (mod.model.baseModelName != null) {
- if (Array.isArray(subPopulate)) {
- subPopulate.forEach(pop => { pop.strictPopulate = false; });
- } else if (typeof subPopulate === 'string') {
- subPopulate = { path: subPopulate, strictPopulate: false };
- } else {
- subPopulate.strictPopulate = false;
- }
- }
- const basePath = mod.options._fullPath || mod.options.path;
- if (Array.isArray(subPopulate)) {
- for (const pop of subPopulate) {
- pop._fullPath = basePath + '.' + pop.path;
- }
- } else if (typeof subPopulate === 'object') {
- subPopulate._fullPath = basePath + '.' + subPopulate.path;
- }
- query.populate(subPopulate);
- }
- return query.exec().then(
- docs => {
- for (const val of docs) {
- leanPopulateMap.set(val, mod.model);
- }
- return docs;
- }
- );
- }
- /*!
- * ignore
- */
- function _assign(model, vals, mod, assignmentOpts) {
- const options = mod.options;
- const isVirtual = mod.isVirtual;
- const justOne = mod.justOne;
- let _val;
- const lean = options &&
- options.options &&
- options.options.lean || false;
- const len = vals.length;
- const rawOrder = {};
- const rawDocs = {};
- let key;
- let val;
- // Clone because `assignRawDocsToIdStructure` will mutate the array
- const allIds = clone(mod.allIds);
- // optimization:
- // record the document positions as returned by
- // the query result.
- for (let i = 0; i < len; i++) {
- val = vals[i];
- if (val == null) {
- continue;
- }
- for (const foreignField of mod.foreignField) {
- _val = utils.getValue(foreignField, val);
- if (Array.isArray(_val)) {
- _val = utils.array.unique(utils.array.flatten(_val));
- for (let __val of _val) {
- if (__val instanceof Document) {
- __val = __val._doc._id;
- }
- key = String(__val);
- if (rawDocs[key]) {
- if (Array.isArray(rawDocs[key])) {
- rawDocs[key].push(val);
- rawOrder[key].push(i);
- } else {
- rawDocs[key] = [rawDocs[key], val];
- rawOrder[key] = [rawOrder[key], i];
- }
- } else {
- if (isVirtual && !justOne) {
- rawDocs[key] = [val];
- rawOrder[key] = [i];
- } else {
- rawDocs[key] = val;
- rawOrder[key] = i;
- }
- }
- }
- } else {
- if (_val instanceof Document) {
- _val = _val._doc._id;
- }
- key = String(_val);
- if (rawDocs[key]) {
- if (Array.isArray(rawDocs[key])) {
- rawDocs[key].push(val);
- rawOrder[key].push(i);
- } else if (isVirtual ||
- rawDocs[key].constructor !== val.constructor ||
- (rawDocs[key] instanceof Document ? String(rawDocs[key]._doc._id) : String(rawDocs[key]._id)) !== (val instanceof Document ? String(val._doc._id) : String(val._id))) {
- // May need to store multiple docs with the same id if there's multiple models
- // if we have discriminators or a ref function. But avoid converting to an array
- // if we have multiple queries on the same model because of `perDocumentLimit` re: gh-9906
- rawDocs[key] = [rawDocs[key], val];
- rawOrder[key] = [rawOrder[key], i];
- }
- } else {
- rawDocs[key] = val;
- rawOrder[key] = i;
- }
- }
- // flag each as result of population
- if (!lean) {
- val.$__.wasPopulated = val.$__.wasPopulated || { value: _val };
- }
- }
- }
- assignVals({
- originalModel: model,
- // If virtual, make sure to not mutate original field
- rawIds: mod.isVirtual ? allIds : mod.allIds,
- allIds: allIds,
- unpopulatedValues: mod.unpopulatedValues,
- foreignField: mod.foreignField,
- rawDocs: rawDocs,
- rawOrder: rawOrder,
- docs: mod.docs,
- path: options.path,
- options: assignmentOpts,
- justOne: mod.justOne,
- isVirtual: mod.isVirtual,
- allOptions: mod,
- populatedModel: mod.model,
- lean: lean,
- virtual: mod.virtual,
- count: mod.count,
- match: mod.match
- });
- }
- /**
- * Compiler utility.
- *
- * @param {String|Function} name model name or class extending Model
- * @param {Schema} schema
- * @param {String} collectionName
- * @param {Connection} connection
- * @param {Mongoose} base mongoose instance
- * @api private
- */
- Model.compile = function compile(name, schema, collectionName, connection, base) {
- const versioningEnabled = schema.options.versionKey !== false;
- if (versioningEnabled && !schema.paths[schema.options.versionKey]) {
- // add versioning to top level documents only
- const o = {};
- o[schema.options.versionKey] = Number;
- schema.add(o);
- }
- let model;
- if (typeof name === 'function' && name.prototype instanceof Model) {
- model = name;
- name = model.name;
- schema.loadClass(model, false);
- model.prototype.$isMongooseModelPrototype = true;
- } else {
- // generate new class
- model = function model(doc, fields, skipId) {
- model.hooks.execPreSync('createModel', doc);
- if (!(this instanceof model)) {
- return new model(doc, fields, skipId);
- }
- const discriminatorKey = model.schema.options.discriminatorKey;
- if (model.discriminators == null || doc == null || doc[discriminatorKey] == null) {
- Model.call(this, doc, fields, skipId);
- return;
- }
- // If discriminator key is set, use the discriminator instead (gh-7586)
- const Discriminator = model.discriminators[doc[discriminatorKey]] ||
- getDiscriminatorByValue(model.discriminators, doc[discriminatorKey]);
- if (Discriminator != null) {
- return new Discriminator(doc, fields, skipId);
- }
- // Otherwise, just use the top-level model
- Model.call(this, doc, fields, skipId);
- };
- }
- model.hooks = schema.s.hooks.clone();
- model.base = base;
- model.modelName = name;
- if (!(model.prototype instanceof Model)) {
- Object.setPrototypeOf(model, Model);
- Object.setPrototypeOf(model.prototype, Model.prototype);
- }
- model.model = function model(name) {
- return this.db.model(name);
- };
- model.db = connection;
- model.prototype.db = connection;
- model.prototype[modelDbSymbol] = connection;
- model.discriminators = model.prototype.discriminators = undefined;
- model[modelSymbol] = true;
- model.events = new EventEmitter();
- schema._preCompile();
- const _userProvidedOptions = schema._userProvidedOptions || {};
- const collectionOptions = {
- schemaUserProvidedOptions: _userProvidedOptions,
- capped: schema.options.capped,
- Promise: model.base.Promise,
- modelName: name
- };
- if (schema.options.autoCreate !== void 0) {
- collectionOptions.autoCreate = schema.options.autoCreate;
- }
- const collection = connection.collection(
- collectionName,
- collectionOptions
- );
- model.prototype.collection = collection;
- model.prototype.$collection = collection;
- model.prototype[modelCollectionSymbol] = collection;
- model.prototype.$__setSchema(schema);
- // apply methods and statics
- applyMethods(model, schema);
- applyStatics(model, schema);
- applyHooks(model, schema);
- applyStaticHooks(model, schema.s.hooks, schema.statics);
- model.schema = model.prototype.$__schema;
- model.collection = collection;
- model.$__collection = collection;
- // Create custom query constructor
- model.Query = function() {
- Query.apply(this, arguments);
- };
- Object.setPrototypeOf(model.Query.prototype, Query.prototype);
- model.Query.base = Query.base;
- model.Query.prototype.constructor = Query;
- model._applyQueryMiddleware();
- applyQueryMethods(model, schema.query);
- return model;
- };
- /**
- * If auto encryption is enabled, returns a ClientEncryption instance that is configured with the same settings that
- * Mongoose's underlying MongoClient is using. If the client has not yet been configured, returns null.
- *
- * @returns {ClientEncryption | null}
- */
- Model.clientEncryption = function clientEncryption() {
- const ClientEncryption = this.base.driver.get().ClientEncryption;
- if (!ClientEncryption) {
- throw new Error('The mongodb driver must be used to obtain a ClientEncryption object.');
- }
- const client = this.collection?.conn?.client;
- if (!client) return null;
- const autoEncryptionOptions = client.options.autoEncryption;
- if (!autoEncryptionOptions) return null;
- const {
- keyVaultNamespace,
- keyVaultClient,
- kmsProviders,
- credentialProviders,
- proxyOptions,
- tlsOptions
- } = autoEncryptionOptions;
- return new ClientEncryption(keyVaultClient ?? client,
- { keyVaultNamespace, kmsProviders, credentialProviders, proxyOptions, tlsOptions }
- );
- };
- /**
- * Update this model to use the new connection, including updating all internal
- * references and creating a new `Collection` instance using the new connection.
- * Not for external use, only used by `setDriver()` to ensure that you can still
- * call `setDriver()` after creating a model using `mongoose.model()`.
- *
- * @param {Connection} newConnection the new connection to use
- * @api private
- */
- Model.$__updateConnection = function $__updateConnection(newConnection) {
- this.db = newConnection;
- this.prototype.db = newConnection;
- this.prototype[modelDbSymbol] = newConnection;
- const collection = newConnection.collection(
- this.collection.collectionName,
- this.collection.opts
- );
- this.prototype.collection = collection;
- this.prototype.$collection = collection;
- this.prototype[modelCollectionSymbol] = collection;
- this.collection = collection;
- this.$__collection = collection;
- };
- /**
- * Register custom query methods for this model
- *
- * @param {Model} model
- * @param {Schema} schema
- * @api private
- */
- function applyQueryMethods(model, methods) {
- for (const i in methods) {
- model.Query.prototype[i] = methods[i];
- }
- }
- /**
- * Subclass this model with `conn`, `schema`, and `collection` settings.
- *
- * @param {Connection} conn
- * @param {Schema} [schema]
- * @param {String} [collection]
- * @return {Model}
- * @api private
- * @memberOf Model
- * @static
- * @method __subclass
- */
- Model.__subclass = function subclass(conn, schema, collection) {
- // subclass model using this connection and collection name
- const _this = this;
- const Model = function Model(doc, fields, skipId) {
- if (!(this instanceof Model)) {
- return new Model(doc, fields, skipId);
- }
- _this.call(this, doc, fields, skipId);
- };
- Object.setPrototypeOf(Model, _this);
- Object.setPrototypeOf(Model.prototype, _this.prototype);
- Model.db = conn;
- Model.prototype.db = conn;
- Model.prototype[modelDbSymbol] = conn;
- _this[subclassedSymbol] = _this[subclassedSymbol] || [];
- _this[subclassedSymbol].push(Model);
- if (_this.discriminators != null) {
- Model.discriminators = {};
- for (const key of Object.keys(_this.discriminators)) {
- Model.discriminators[key] = _this.discriminators[key].
- __subclass(_this.db, _this.discriminators[key].schema, collection);
- }
- }
- const s = schema && typeof schema !== 'string'
- ? schema
- : _this.prototype.$__schema;
- const options = s.options || {};
- const _userProvidedOptions = s._userProvidedOptions || {};
- if (!collection) {
- collection = _this.prototype.$__schema.get('collection') ||
- utils.toCollectionName(_this.modelName, this.base.pluralize());
- }
- const collectionOptions = {
- schemaUserProvidedOptions: _userProvidedOptions,
- capped: s && options.capped
- };
- Model.prototype.collection = conn.collection(collection, collectionOptions);
- Model.prototype.$collection = Model.prototype.collection;
- Model.prototype[modelCollectionSymbol] = Model.prototype.collection;
- Model.collection = Model.prototype.collection;
- Model.$__collection = Model.collection;
- // Errors handled internally, so ignore
- Model.init().catch(() => {});
- return Model;
- };
- /**
- * Apply changes made to this model's schema after this model was compiled.
- * By default, adding virtuals and other properties to a schema after the model is compiled does nothing.
- * Call this function to apply virtuals and properties that were added later.
- *
- * #### Example:
- *
- * const schema = new mongoose.Schema({ field: String });
- * const TestModel = mongoose.model('Test', schema);
- * TestModel.schema.virtual('myVirtual').get(function() {
- * return this.field + ' from myVirtual';
- * });
- * const doc = new TestModel({ field: 'Hello' });
- * doc.myVirtual; // undefined
- *
- * TestModel.recompileSchema();
- * doc.myVirtual; // 'Hello from myVirtual'
- *
- * @return {undefined}
- * @api public
- * @memberOf Model
- * @static
- * @method recompileSchema
- */
- Model.recompileSchema = function recompileSchema() {
- this.prototype.$__setSchema(this.schema);
- if (this.schema._applyDiscriminators != null) {
- for (const disc of this.schema._applyDiscriminators.keys()) {
- this.discriminator(disc, this.schema._applyDiscriminators.get(disc));
- }
- }
- delete this.schema._defaultToObjectOptionsMap;
- applyEmbeddedDiscriminators(this.schema, new WeakSet(), true);
- };
- /**
- * Helper for console.log. Given a model named 'MyModel', returns the string
- * `'Model { MyModel }'`.
- *
- * #### Example:
- *
- * const MyModel = mongoose.model('Test', Schema({ name: String }));
- * MyModel.inspect(); // 'Model { Test }'
- * console.log(MyModel); // Prints 'Model { Test }'
- *
- * @api public
- */
- Model.inspect = function() {
- return `Model { ${this.modelName} }`;
- };
- /**
- * Return the MongoDB namespace for this model as a string. The namespace is the database name, followed by '.', followed by the collection name.
- *
- * #### Example:
- *
- * const conn = mongoose.createConnection('mongodb://127.0.0.1:27017/mydb');
- * const TestModel = conn.model('Test', mongoose.Schema({ name: String }));
- *
- * TestModel.namespace(); // 'mydb.tests'
- *
- * @api public
- */
- Model.namespace = function namespace() {
- return this.db.name + '.' + this.collection.collectionName;
- };
- if (util.inspect.custom) {
- // Avoid Node deprecation warning DEP0079
- Model[util.inspect.custom] = Model.inspect;
- }
- /*!
- * Applies query middleware from this model's schema to this model's
- * Query constructor.
- */
- Model._applyQueryMiddleware = function _applyQueryMiddleware() {
- const Query = this.Query;
- const queryMiddleware = this.schema.s.hooks.filter(hook => {
- const contexts = _getContexts(hook);
- if (hook.name === 'validate') {
- return !!contexts.query;
- }
- if (hook.name === 'deleteOne' || hook.name === 'updateOne') {
- return !!contexts.query || utils.hasOwnKeys(contexts) === false;
- }
- if (hook.query != null || hook.document != null) {
- return !!hook.query;
- }
- return true;
- });
- Query.prototype._queryMiddleware = queryMiddleware;
- };
- function _getContexts(hook) {
- const ret = {};
- if (Object.hasOwn(hook, 'query')) {
- ret.query = hook.query;
- }
- if (Object.hasOwn(hook, 'document')) {
- ret.document = hook.document;
- }
- return ret;
- }
- /*!
- * Module exports.
- */
- module.exports = exports = Model;
|