schema.js 103 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226
  1. 'use strict';
  2. /*!
  3. * Module dependencies.
  4. */
  5. const EventEmitter = require('events').EventEmitter;
  6. const Kareem = require('kareem');
  7. const MongooseError = require('./error/mongooseError');
  8. const SchemaType = require('./schemaType');
  9. const SchemaTypeOptions = require('./options/schemaTypeOptions');
  10. const VirtualOptions = require('./options/virtualOptions');
  11. const VirtualType = require('./virtualType');
  12. const addAutoId = require('./helpers/schema/addAutoId');
  13. const clone = require('./helpers/clone');
  14. const get = require('./helpers/get');
  15. const getConstructorName = require('./helpers/getConstructorName');
  16. const getIndexes = require('./helpers/schema/getIndexes');
  17. const handleReadPreferenceAliases = require('./helpers/query/handleReadPreferenceAliases');
  18. const idGetter = require('./helpers/schema/idGetter');
  19. const isIndexSpecEqual = require('./helpers/indexes/isIndexSpecEqual');
  20. const merge = require('./helpers/schema/merge');
  21. const mpath = require('mpath');
  22. const setPopulatedVirtualValue = require('./helpers/populate/setPopulatedVirtualValue');
  23. const setupTimestamps = require('./helpers/timestamps/setupTimestamps');
  24. const symbols = require('./schema/symbols');
  25. const utils = require('./utils');
  26. const validateRef = require('./helpers/populate/validateRef');
  27. const hasNumericSubpathRegex = /\.\d+(\.|$)/;
  28. let MongooseTypes;
  29. const queryHooks = require('./constants').queryMiddlewareFunctions;
  30. const documentHooks = require('./constants').documentMiddlewareFunctions;
  31. const hookNames = queryHooks.concat(documentHooks).
  32. reduce((s, hook) => s.add(hook), new Set());
  33. const isPOJO = utils.isPOJO;
  34. let id = 0;
  35. const numberRE = /^\d+$/;
  36. /**
  37. * Schema constructor.
  38. *
  39. * #### Example:
  40. *
  41. * const child = new Schema({ name: String });
  42. * const schema = new Schema({ name: String, age: Number, children: [child] });
  43. * const Tree = mongoose.model('Tree', schema);
  44. *
  45. * // setting schema options
  46. * new Schema({ name: String }, { id: false, autoIndex: false })
  47. *
  48. * #### Options:
  49. *
  50. * - [autoIndex](https://mongoosejs.com/docs/guide.html#autoIndex): bool - defaults to null (which means use the connection's autoIndex option)
  51. * - [autoCreate](https://mongoosejs.com/docs/guide.html#autoCreate): bool - defaults to null (which means use the connection's autoCreate option)
  52. * - [bufferCommands](https://mongoosejs.com/docs/guide.html#bufferCommands): bool - defaults to true
  53. * - [bufferTimeoutMS](https://mongoosejs.com/docs/guide.html#bufferTimeoutMS): number - defaults to 10000 (10 seconds). If `bufferCommands` is enabled, the amount of time Mongoose will wait for connectivity to be restablished before erroring out.
  54. * - [capped](https://mongoosejs.com/docs/guide.html#capped): bool | number | object - defaults to false
  55. * - [collection](https://mongoosejs.com/docs/guide.html#collection): string - no default
  56. * - [discriminatorKey](https://mongoosejs.com/docs/guide.html#discriminatorKey): string - defaults to `__t`
  57. * - [id](https://mongoosejs.com/docs/guide.html#id): bool - defaults to true
  58. * - [_id](https://mongoosejs.com/docs/guide.html#_id): bool - defaults to true
  59. * - [minimize](https://mongoosejs.com/docs/guide.html#minimize): bool - controls [document#toObject](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()) behavior when called manually - defaults to true
  60. * - [read](https://mongoosejs.com/docs/guide.html#read): string
  61. * - [readConcern](https://mongoosejs.com/docs/guide.html#readConcern): object - defaults to null, use to set a default [read concern](https://www.mongodb.com/docs/manual/reference/read-concern/) for all queries.
  62. * - [writeConcern](https://mongoosejs.com/docs/guide.html#writeConcern): object - defaults to null, use to override [the MongoDB server's default write concern settings](https://www.mongodb.com/docs/manual/reference/write-concern/)
  63. * - [shardKey](https://mongoosejs.com/docs/guide.html#shardKey): object - defaults to `null`
  64. * - [strict](https://mongoosejs.com/docs/guide.html#strict): bool - defaults to true
  65. * - [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery): bool - defaults to false
  66. * - [toJSON](https://mongoosejs.com/docs/guide.html#toJSON) - object - no default
  67. * - [toObject](https://mongoosejs.com/docs/guide.html#toObject) - object - no default
  68. * - [typeKey](https://mongoosejs.com/docs/guide.html#typeKey) - string - defaults to 'type'
  69. * - [validateBeforeSave](https://mongoosejs.com/docs/guide.html#validateBeforeSave) - bool - defaults to `true`
  70. * - [validateModifiedOnly](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) - bool - defaults to `false`
  71. * - [versionKey](https://mongoosejs.com/docs/guide.html#versionKey): string or object - defaults to "__v"
  72. * - [optimisticConcurrency](https://mongoosejs.com/docs/guide.html#optimisticConcurrency): bool or string[] or { exclude: string[] } - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html) for all fields. Set to a string array to enable optimistic concurrency only for the specified fields; note that this **replaces** the default array versioning behavior. Set to `{ exclude: string[] }` to enable optimistic concurrency for all fields except the specified ones; this also replaces the default array versioning.
  73. * - [collation](https://mongoosejs.com/docs/guide.html#collation): object - defaults to null (which means use no collation)
  74. * - [timeseries](https://mongoosejs.com/docs/guide.html#timeseries): object - defaults to null (which means this schema's collection won't be a timeseries collection)
  75. * - [selectPopulatedPaths](https://mongoosejs.com/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true`
  76. * - [skipVersioning](https://mongoosejs.com/docs/guide.html#skipVersioning): object - paths to exclude from versioning
  77. * - [timestamps](https://mongoosejs.com/docs/guide.html#timestamps): object or boolean - defaults to `false`. If true, Mongoose adds `createdAt` and `updatedAt` properties to your schema and manages those properties for you.
  78. * - [pluginTags](https://mongoosejs.com/docs/guide.html#pluginTags): array of strings - defaults to `undefined`. If set and plugin called with `tags` option, will only apply that plugin to schemas with a matching tag.
  79. * - [virtuals](https://mongoosejs.com/docs/tutorials/virtuals.html#virtuals-via-schema-options): object - virtuals to define, alias for [`.virtual`](https://mongoosejs.com/docs/api/schema.html#Schema.prototype.virtual())
  80. * - [collectionOptions]: object with options passed to [`createCollection()`](https://www.mongodb.com/docs/manual/reference/method/db.createCollection/) when calling `Model.createCollection()` or `autoCreate` set to true.
  81. * - [encryptionType]: the encryption type for the schema. Valid options are `csfle` or `queryableEncryption`. See https://mongoosejs.com/docs/field-level-encryption.
  82. * - [lean]: boolean - set to true to make all queries use [lean](https://mongoosejs.com/docs/tutorials/lean.html) by default. Defaults to false.
  83. *
  84. * #### Options for Nested Schemas:
  85. *
  86. * - `excludeIndexes`: bool - defaults to `false`. If `true`, skip building indexes on this schema's paths.
  87. *
  88. * #### Note:
  89. *
  90. * _When nesting schemas, (`children` in the example above), always declare the child schema first before passing it into its parent._
  91. *
  92. * @param {Object|Schema|Array} [definition] Can be one of: object describing schema paths, or schema to copy, or array of objects and schemas
  93. * @param {Object} [options]
  94. * @inherits NodeJS EventEmitter https://nodejs.org/api/events.html#class-eventemitter
  95. * @event `init`: Emitted after the schema is compiled into a `Model`.
  96. * @api public
  97. */
  98. function Schema(obj, options) {
  99. if (!(this instanceof Schema)) {
  100. return new Schema(obj, options);
  101. }
  102. this.obj = obj;
  103. this.paths = {};
  104. this.aliases = {};
  105. this.subpaths = {};
  106. this.virtuals = {};
  107. this.singleNestedPaths = {};
  108. this.nested = {};
  109. this.inherits = {};
  110. this.callQueue = [];
  111. this._indexes = [];
  112. this._searchIndexes = [];
  113. this.methods = options?.methods || {};
  114. this.methodOptions = {};
  115. this.statics = options?.statics || {};
  116. this.tree = {};
  117. this.query = options?.query || {};
  118. this.childSchemas = [];
  119. this.plugins = [];
  120. // For internal debugging. Do not use this to try to save a schema in MDB.
  121. this.$id = ++id;
  122. this.mapPaths = [];
  123. this.encryptedFields = {};
  124. this.s = {
  125. hooks: new Kareem()
  126. };
  127. this.options = this.defaultOptions(options);
  128. // build paths
  129. if (Array.isArray(obj)) {
  130. for (const definition of obj) {
  131. this.add(definition);
  132. }
  133. } else if (obj) {
  134. this.add(obj);
  135. }
  136. // build virtual paths
  137. if (options?.virtuals) {
  138. const virtuals = options.virtuals;
  139. const pathNames = Object.keys(virtuals);
  140. for (const pathName of pathNames) {
  141. const pathOptions = virtuals[pathName].options ? virtuals[pathName].options : undefined;
  142. const virtual = this.virtual(pathName, pathOptions);
  143. if (virtuals[pathName].get) {
  144. virtual.get(virtuals[pathName].get);
  145. }
  146. if (virtuals[pathName].set) {
  147. virtual.set(virtuals[pathName].set);
  148. }
  149. }
  150. }
  151. // check if _id's value is a subdocument (gh-2276)
  152. const _idSubDoc = obj?._id && utils.isObject(obj._id);
  153. // ensure the documents get an auto _id unless disabled
  154. const auto_id = !this.paths['_id'] &&
  155. (this.options._id) && !_idSubDoc;
  156. if (auto_id) {
  157. addAutoId(this);
  158. }
  159. this.setupTimestamp(this.options.timestamps);
  160. }
  161. /**
  162. * Create virtual properties with alias field
  163. * @api private
  164. */
  165. function aliasFields(schema, paths) {
  166. for (const path of Object.keys(paths)) {
  167. let alias = null;
  168. if (paths[path] != null) {
  169. alias = paths[path];
  170. } else {
  171. const options = get(schema.paths[path], 'options');
  172. if (options == null) {
  173. continue;
  174. }
  175. alias = options.alias;
  176. }
  177. if (!alias) {
  178. continue;
  179. }
  180. const prop = schema.paths[path].path;
  181. if (Array.isArray(alias)) {
  182. for (const a of alias) {
  183. if (typeof a !== 'string') {
  184. throw new Error('Invalid value for alias option on ' + prop + ', got ' + a);
  185. }
  186. schema.aliases[a] = prop;
  187. schema.
  188. virtual(a).
  189. get((function(p) {
  190. return function() {
  191. if (typeof this.get === 'function') {
  192. return this.get(p);
  193. }
  194. return this[p];
  195. };
  196. })(prop)).
  197. set((function(p) {
  198. return function(v) {
  199. return this.$set(p, v);
  200. };
  201. })(prop));
  202. }
  203. continue;
  204. }
  205. if (typeof alias !== 'string') {
  206. throw new Error('Invalid value for alias option on ' + prop + ', got ' + alias);
  207. }
  208. schema.aliases[alias] = prop;
  209. schema.
  210. virtual(alias).
  211. get((function(p) {
  212. return function() {
  213. if (typeof this.get === 'function') {
  214. return this.get(p);
  215. }
  216. return this[p];
  217. };
  218. })(prop)).
  219. set((function(p) {
  220. return function(v) {
  221. return this.$set(p, v);
  222. };
  223. })(prop));
  224. }
  225. }
  226. /*!
  227. * Inherit from EventEmitter.
  228. */
  229. Schema.prototype = Object.create(EventEmitter.prototype);
  230. Schema.prototype.constructor = Schema;
  231. Schema.prototype.instanceOfSchema = true;
  232. /*!
  233. * ignore
  234. */
  235. Object.defineProperty(Schema.prototype, '$schemaType', {
  236. configurable: false,
  237. enumerable: false,
  238. writable: true
  239. });
  240. /**
  241. * Array of child schemas (from document arrays and single nested subdocs)
  242. * and their corresponding compiled models. Each element of the array is
  243. * an object with 2 properties: `schema` and `model`.
  244. *
  245. * This property is typically only useful for plugin authors and advanced users.
  246. * You do not need to interact with this property at all to use mongoose.
  247. *
  248. * @api public
  249. * @property childSchemas
  250. * @memberOf Schema
  251. * @instance
  252. */
  253. Object.defineProperty(Schema.prototype, 'childSchemas', {
  254. configurable: false,
  255. enumerable: true,
  256. writable: true
  257. });
  258. /**
  259. * Object containing all virtuals defined on this schema.
  260. * The objects' keys are the virtual paths and values are instances of `VirtualType`.
  261. *
  262. * This property is typically only useful for plugin authors and advanced users.
  263. * You do not need to interact with this property at all to use mongoose.
  264. *
  265. * #### Example:
  266. *
  267. * const schema = new Schema({});
  268. * schema.virtual('answer').get(() => 42);
  269. *
  270. * console.log(schema.virtuals); // { answer: VirtualType { path: 'answer', ... } }
  271. * console.log(schema.virtuals['answer'].getters[0].call()); // 42
  272. *
  273. * @api public
  274. * @property virtuals
  275. * @memberOf Schema
  276. * @instance
  277. */
  278. Object.defineProperty(Schema.prototype, 'virtuals', {
  279. configurable: false,
  280. enumerable: true,
  281. writable: true
  282. });
  283. /**
  284. * The original object passed to the schema constructor
  285. *
  286. * #### Example:
  287. *
  288. * const schema = new Schema({ a: String }).add({ b: String });
  289. * schema.obj; // { a: String }
  290. *
  291. * @api public
  292. * @property obj
  293. * @memberOf Schema
  294. * @instance
  295. */
  296. Schema.prototype.obj;
  297. /**
  298. * The paths defined on this schema. The keys are the top-level paths
  299. * in this schema, and the values are instances of the SchemaType class.
  300. *
  301. * #### Example:
  302. *
  303. * const schema = new Schema({ name: String }, { _id: false });
  304. * schema.paths; // { name: SchemaString { ... } }
  305. *
  306. * schema.add({ age: Number });
  307. * schema.paths; // { name: SchemaString { ... }, age: SchemaNumber { ... } }
  308. *
  309. * @api public
  310. * @property paths
  311. * @memberOf Schema
  312. * @instance
  313. */
  314. Schema.prototype.paths;
  315. /**
  316. * Schema as a tree
  317. *
  318. * #### Example:
  319. *
  320. * {
  321. * '_id' : ObjectId
  322. * , 'nested' : {
  323. * 'key' : String
  324. * }
  325. * }
  326. *
  327. * @api private
  328. * @property tree
  329. * @memberOf Schema
  330. * @instance
  331. */
  332. Schema.prototype.tree;
  333. /**
  334. * Creates a new schema with the given definition and options. Equivalent to `new Schema(definition, options)`.
  335. *
  336. * `Schema.create()` is primarily useful for automatic schema type inference in TypeScript.
  337. *
  338. * #### Example:
  339. *
  340. * const schema = Schema.create({ name: String }, { toObject: { virtuals: true } });
  341. * // Equivalent:
  342. * const schema2 = new Schema({ name: String }, { toObject: { virtuals: true } });
  343. *
  344. * @param {Object} definition
  345. * @param {Object} [options]
  346. * @return {Schema} the new schema
  347. * @api public
  348. * @memberOf Schema
  349. * @static
  350. */
  351. Schema.create = function create(definition, options) {
  352. return new Schema(definition, options);
  353. };
  354. /**
  355. * Returns a deep copy of the schema
  356. *
  357. * #### Example:
  358. *
  359. * const schema = new Schema({ name: String });
  360. * const clone = schema.clone();
  361. * clone === schema; // false
  362. * clone.path('name'); // SchemaString { ... }
  363. *
  364. * @return {Schema} the cloned schema
  365. * @api public
  366. * @memberOf Schema
  367. * @instance
  368. */
  369. Schema.prototype.clone = function() {
  370. const s = this._clone();
  371. // Bubble up `init` for backwards compat
  372. s.on('init', v => this.emit('init', v));
  373. return s;
  374. };
  375. /*!
  376. * ignore
  377. */
  378. Schema.prototype._clone = function _clone(Constructor) {
  379. Constructor = Constructor || (this.base == null ? Schema : this.base.Schema);
  380. const s = new Constructor({}, this._userProvidedOptions);
  381. s.base = this.base;
  382. s.obj = this.obj;
  383. s.options = clone(this.options);
  384. s.callQueue = this.callQueue.map(function(f) { return f; });
  385. s.methods = clone(this.methods);
  386. s.methodOptions = clone(this.methodOptions);
  387. s.statics = clone(this.statics);
  388. s.query = clone(this.query);
  389. s.plugins = Array.prototype.slice.call(this.plugins);
  390. s._indexes = clone(this._indexes);
  391. s._searchIndexes = clone(this._searchIndexes);
  392. s.s.hooks = this.s.hooks.clone();
  393. s.tree = clone(this.tree);
  394. s.paths = Object.fromEntries(
  395. Object.entries(this.paths).map(([key, value]) => ([key, value.clone()]))
  396. );
  397. s.nested = clone(this.nested);
  398. s.subpaths = clone(this.subpaths);
  399. for (const schemaType of Object.values(s.paths)) {
  400. if (schemaType.$isSingleNested) {
  401. const path = schemaType.path;
  402. for (const key of Object.keys(schemaType.schema.paths)) {
  403. s.singleNestedPaths[path + '.' + key] = schemaType.schema.paths[key];
  404. }
  405. for (const key of Object.keys(schemaType.schema.singleNestedPaths)) {
  406. s.singleNestedPaths[path + '.' + key] =
  407. schemaType.schema.singleNestedPaths[key];
  408. }
  409. for (const key of Object.keys(schemaType.schema.subpaths)) {
  410. s.singleNestedPaths[path + '.' + key] =
  411. schemaType.schema.subpaths[key];
  412. }
  413. for (const key of Object.keys(schemaType.schema.nested)) {
  414. s.singleNestedPaths[path + '.' + key] = 'nested';
  415. }
  416. }
  417. }
  418. s._gatherChildSchemas();
  419. s.virtuals = clone(this.virtuals);
  420. s.$globalPluginsApplied = this.$globalPluginsApplied;
  421. s.$isRootDiscriminator = this.$isRootDiscriminator;
  422. s.$implicitlyCreated = this.$implicitlyCreated;
  423. s.$id = ++id;
  424. s.$originalSchemaId = this.$id;
  425. s.mapPaths = [].concat(this.mapPaths);
  426. if (this.discriminatorMapping != null) {
  427. s.discriminatorMapping = Object.assign({}, this.discriminatorMapping);
  428. }
  429. if (this.discriminators != null) {
  430. s.discriminators = Object.assign({}, this.discriminators);
  431. }
  432. if (this._applyDiscriminators != null) {
  433. s._applyDiscriminators = new Map(this._applyDiscriminators);
  434. }
  435. s.aliases = Object.assign({}, this.aliases);
  436. s.encryptedFields = clone(this.encryptedFields);
  437. return s;
  438. };
  439. /**
  440. * Returns a new schema that has the picked `paths` from this schema.
  441. *
  442. * This method is analagous to [Lodash's `pick()` function](https://lodash.com/docs/4.17.15#pick) for Mongoose schemas.
  443. *
  444. * #### Example:
  445. *
  446. * const schema = Schema({ name: String, age: Number });
  447. * // Creates a new schema with the same `name` path as `schema`,
  448. * // but no `age` path.
  449. * const newSchema = schema.pick(['name']);
  450. *
  451. * newSchema.path('name'); // SchemaString { ... }
  452. * newSchema.path('age'); // undefined
  453. *
  454. * @param {String[]} paths List of Paths to pick for the new Schema
  455. * @param {Object} [options] Options to pass to the new Schema Constructor (same as `new Schema(.., Options)`). Defaults to `this.options` if not set.
  456. * @return {Schema}
  457. * @api public
  458. */
  459. Schema.prototype.pick = function(paths, options) {
  460. const newSchema = new Schema({}, options || this.options);
  461. if (!Array.isArray(paths)) {
  462. throw new MongooseError('Schema#pick() only accepts an array argument, ' +
  463. 'got "' + typeof paths + '"');
  464. }
  465. for (const path of paths) {
  466. if (this._hasEncryptedField(path)) {
  467. const encrypt = this.encryptedFields[path];
  468. const schemaType = this.path(path);
  469. newSchema.add({
  470. [path]: {
  471. encrypt,
  472. [this.options.typeKey]: schemaType
  473. }
  474. });
  475. } else if (this.nested[path]) {
  476. newSchema.add({ [path]: get(this.tree, path) });
  477. } else {
  478. const schematype = this.path(path);
  479. if (schematype == null) {
  480. throw new MongooseError('Path `' + path + '` is not in the schema');
  481. }
  482. newSchema.add({ [path]: schematype });
  483. }
  484. }
  485. if (!this._hasEncryptedFields()) {
  486. newSchema.options.encryptionType = null;
  487. }
  488. return newSchema;
  489. };
  490. /**
  491. * Returns a new schema that has the `paths` from the original schema, minus the omitted ones.
  492. *
  493. * This method is analagous to [Lodash's `omit()` function](https://lodash.com/docs/#omit) for Mongoose schemas.
  494. *
  495. * #### Example:
  496. *
  497. * const schema = Schema({ name: String, age: Number });
  498. * // Creates a new schema omitting the `age` path
  499. * const newSchema = schema.omit(['age']);
  500. *
  501. * newSchema.path('name'); // SchemaString { ... }
  502. * newSchema.path('age'); // undefined
  503. *
  504. * @param {String[]} paths List of Paths to omit for the new Schema
  505. * @param {Object} [options] Options to pass to the new Schema Constructor (same as `new Schema(.., Options)`). Defaults to `this.options` if not set.
  506. * @return {Schema}
  507. * @api public
  508. */
  509. Schema.prototype.omit = function(paths, options) {
  510. const newSchema = new Schema(this, options || this.options);
  511. if (!Array.isArray(paths)) {
  512. throw new MongooseError(
  513. 'Schema#omit() only accepts an array argument, ' +
  514. 'got "' +
  515. typeof paths +
  516. '"'
  517. );
  518. }
  519. newSchema.remove(paths);
  520. for (const nested in newSchema.singleNestedPaths) {
  521. if (paths.includes(nested)) {
  522. delete newSchema.singleNestedPaths[nested];
  523. }
  524. }
  525. return newSchema;
  526. };
  527. /**
  528. * Returns default options for this schema, merged with `options`.
  529. *
  530. * @param {Object} [options] Options to overwrite the default options
  531. * @return {Object} The merged options of `options` and the default options
  532. * @api private
  533. */
  534. Schema.prototype.defaultOptions = function(options) {
  535. this._userProvidedOptions = options == null ? {} : clone(options);
  536. const baseOptions = this.base?.options || {};
  537. const strict = 'strict' in baseOptions ? baseOptions.strict : true;
  538. const strictQuery = 'strictQuery' in baseOptions ? baseOptions.strictQuery : false;
  539. const id = 'id' in baseOptions ? baseOptions.id : true;
  540. options = {
  541. strict,
  542. strictQuery,
  543. bufferCommands: true,
  544. capped: false, // { size, max, autoIndexId }
  545. versionKey: '__v',
  546. optimisticConcurrency: false,
  547. minimize: true,
  548. autoIndex: null,
  549. discriminatorKey: '__t',
  550. shardKey: null,
  551. read: null,
  552. validateBeforeSave: true,
  553. validateModifiedOnly: false,
  554. // the following are only applied at construction time
  555. _id: true,
  556. id: id,
  557. typeKey: 'type',
  558. ...options
  559. };
  560. if (options.versionKey && typeof options.versionKey !== 'string') {
  561. throw new MongooseError('`versionKey` must be falsy or string, got `' + (typeof options.versionKey) + '`');
  562. }
  563. if (typeof options.read === 'string') {
  564. options.read = handleReadPreferenceAliases(options.read);
  565. } else if (Array.isArray(options.read) && typeof options.read[0] === 'string') {
  566. options.read = {
  567. mode: handleReadPreferenceAliases(options.read[0]),
  568. tags: options.read[1]
  569. };
  570. }
  571. if (options.optimisticConcurrency && !options.versionKey) {
  572. throw new MongooseError('Must set `versionKey` if using `optimisticConcurrency`');
  573. }
  574. return options;
  575. };
  576. /**
  577. * Inherit a Schema by applying a discriminator on an existing Schema.
  578. *
  579. *
  580. * #### Example:
  581. *
  582. * const eventSchema = new mongoose.Schema({ timestamp: Date }, { discriminatorKey: 'kind' });
  583. *
  584. * const clickedEventSchema = new mongoose.Schema({ element: String }, { discriminatorKey: 'kind' });
  585. * const ClickedModel = eventSchema.discriminator('clicked', clickedEventSchema);
  586. *
  587. * const Event = mongoose.model('Event', eventSchema);
  588. *
  589. * Event.discriminators['clicked']; // Model { clicked }
  590. *
  591. * const doc = await Event.create({ kind: 'clicked', element: '#hero' });
  592. * doc.element; // '#hero'
  593. * doc instanceof ClickedModel; // true
  594. *
  595. * @param {String} name the name of the discriminator
  596. * @param {Schema} schema the discriminated Schema
  597. * @param {Object} [options] discriminator options
  598. * @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
  599. * @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
  600. * @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.
  601. * @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.
  602. * @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.
  603. * @return {Schema} the Schema instance
  604. * @api public
  605. */
  606. Schema.prototype.discriminator = function(name, schema, options) {
  607. this._applyDiscriminators = this._applyDiscriminators || new Map();
  608. this._applyDiscriminators.set(name, { schema, options });
  609. return this;
  610. };
  611. /*!
  612. * Get the document middleware for this schema, filtering out any hooks that are specific to queries.
  613. */
  614. Schema.prototype._getDocumentMiddleware = function _getDocumentMiddleware() {
  615. return this.s.hooks.
  616. filter(hook => {
  617. if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
  618. return !!hook['document'];
  619. }
  620. if (hook.name === 'remove' || hook.name === 'init') {
  621. return hook['document'] == null || !!hook['document'];
  622. }
  623. if (hook.query != null || hook.document != null) {
  624. return hook.document !== false;
  625. }
  626. return true;
  627. }).
  628. filter(hook => {
  629. // If user has overwritten the method, don't apply built-in middleware
  630. if (this.methods[hook.name]) {
  631. return !hook.fn[symbols.builtInMiddleware];
  632. }
  633. return true;
  634. });
  635. };
  636. /*!
  637. * Get this schema's default toObject/toJSON options, including Mongoose global
  638. * options.
  639. */
  640. Schema.prototype._defaultToObjectOptions = function(json) {
  641. const path = json ? 'toJSON' : 'toObject';
  642. if (this._defaultToObjectOptionsMap && this._defaultToObjectOptionsMap[path]) {
  643. return this._defaultToObjectOptionsMap[path];
  644. }
  645. const baseOptions = this.base?.options?.[path] || {};
  646. const schemaOptions = this.options[path] || {};
  647. // merge base default options with Schema's set default options if available.
  648. // `clone` is necessary here because `utils.options` directly modifies the second input.
  649. const defaultOptions = Object.assign({}, baseOptions, schemaOptions);
  650. this._defaultToObjectOptionsMap = this._defaultToObjectOptionsMap || {};
  651. this._defaultToObjectOptionsMap[path] = defaultOptions;
  652. return defaultOptions;
  653. };
  654. /**
  655. * Sets the encryption type of the schema, if a value is provided, otherwise
  656. * returns the encryption type.
  657. *
  658. * @param {'csfle' | 'queryableEncryption' | null | undefined} encryptionType plain object with paths to add, or another schema
  659. */
  660. Schema.prototype.encryptionType = function encryptionType(encryptionType) {
  661. if (arguments.length === 0) {
  662. return this.options.encryptionType;
  663. }
  664. if (!(typeof encryptionType === 'string' || encryptionType === null)) {
  665. throw new Error('invalid `encryptionType`: ${encryptionType}');
  666. }
  667. this.options.encryptionType = encryptionType;
  668. };
  669. /**
  670. * Adds key path / schema type pairs to this schema.
  671. *
  672. * #### Example:
  673. *
  674. * const ToySchema = new Schema();
  675. * ToySchema.add({ name: 'string', color: 'string', price: 'number' });
  676. *
  677. * const TurboManSchema = new Schema();
  678. * // You can also `add()` another schema and copy over all paths, virtuals,
  679. * // getters, setters, indexes, methods, and statics.
  680. * TurboManSchema.add(ToySchema).add({ year: Number });
  681. *
  682. * @param {Object|Schema} obj plain object with paths to add, or another schema
  683. * @param {String} [prefix] path to prefix the newly added paths with
  684. * @return {Schema} the Schema instance
  685. * @api public
  686. */
  687. Schema.prototype.add = function add(obj, prefix) {
  688. if (obj instanceof Schema || obj?.instanceOfSchema) {
  689. merge(this, obj);
  690. return this;
  691. }
  692. // Special case: setting top-level `_id` to false should convert to disabling
  693. // the `_id` option. This behavior never worked before 5.4.11 but numerous
  694. // codebases use it (see gh-7516, gh-7512).
  695. if (obj._id === false && prefix == null) {
  696. this.options._id = false;
  697. }
  698. prefix = prefix || '';
  699. // avoid prototype pollution
  700. if (prefix === '__proto__.' || prefix === 'constructor.' || prefix === 'prototype.') {
  701. return this;
  702. }
  703. const keys = Object.keys(obj);
  704. const typeKey = this.options.typeKey;
  705. for (const key of keys) {
  706. if (utils.specialProperties.has(key)) {
  707. continue;
  708. }
  709. const fullPath = prefix + key;
  710. const val = obj[key];
  711. if (val == null) {
  712. throw new TypeError('Invalid value for schema path `' + fullPath +
  713. '`, got value "' + val + '"');
  714. }
  715. // Retain `_id: false` but don't set it as a path, re: gh-8274.
  716. if (key === '_id' && val === false) {
  717. continue;
  718. }
  719. // Deprecate setting schema paths to primitive types (gh-7558)
  720. let isMongooseTypeString = false;
  721. if (typeof val === 'string') {
  722. // Handle the case in which the type is specified as a string (eg. 'date', 'oid', ...)
  723. const MongooseTypes = this.base?.Schema.Types ?? Schema.Types;
  724. const upperVal = val.charAt(0).toUpperCase() + val.substring(1);
  725. isMongooseTypeString = MongooseTypes[upperVal] != null;
  726. }
  727. if (
  728. key !== '_id' &&
  729. ((typeof val !== 'object' && typeof val !== 'function' && !isMongooseTypeString) ||
  730. val == null)
  731. ) {
  732. throw new TypeError(`Invalid schema configuration: \`${val}\` is not ` +
  733. `a valid type at path \`${key}\`. See ` +
  734. 'https://bit.ly/mongoose-schematypes for a list of valid schema types.');
  735. }
  736. if (val instanceof VirtualType || (val.constructor?.name ?? null) === 'VirtualType') {
  737. this.virtual(val);
  738. continue;
  739. }
  740. if (Array.isArray(val) && val.length === 1 && val[0] == null) {
  741. throw new TypeError('Invalid value for schema Array path `' + fullPath +
  742. '`, got value "' + val[0] + '"');
  743. }
  744. if (!(isPOJO(val) || val instanceof SchemaTypeOptions)) {
  745. // Special-case: Non-options definitely a path so leaf at this node
  746. // Examples: Schema instances, SchemaType instances
  747. if (prefix) {
  748. this.nested[prefix.substring(0, prefix.length - 1)] = true;
  749. }
  750. this.path(prefix + key, val);
  751. if (!val[0]?.instanceOfSchema && utils.isPOJO(val[0]?.discriminators)) {
  752. const schemaType = this.path(prefix + key);
  753. for (const key in val[0].discriminators) {
  754. schemaType.discriminator(key, val[0].discriminators[key]);
  755. }
  756. }
  757. } else if (utils.hasOwnKeys(val) === false) {
  758. // Special-case: {} always interpreted as Mixed path so leaf at this node
  759. if (prefix) {
  760. this.nested[prefix.substring(0, prefix.length - 1)] = true;
  761. }
  762. this.path(fullPath, val); // mixed type
  763. } else if (!val[typeKey] || (typeKey === 'type' && isPOJO(val.type) && val.type.type)) {
  764. // Special-case: POJO with no bona-fide type key - interpret as tree of deep paths so recurse
  765. // nested object `{ last: { name: String } }`. Avoid functions with `.type` re: #10807 because
  766. // NestJS sometimes adds `Date.type`.
  767. this.nested[fullPath] = true;
  768. this.add(val, fullPath + '.');
  769. } else {
  770. // There IS a bona-fide type key that may also be a POJO
  771. const _typeDef = val[typeKey];
  772. if (isPOJO(_typeDef) && utils.hasOwnKeys(_typeDef)) {
  773. // If a POJO is the value of a type key, make it a subdocument
  774. if (prefix) {
  775. this.nested[prefix.substring(0, prefix.length - 1)] = true;
  776. }
  777. const childSchemaOptions = {};
  778. if (this._userProvidedOptions.typeKey) {
  779. childSchemaOptions.typeKey = this._userProvidedOptions.typeKey;
  780. }
  781. // propagate 'strict' option to child schema
  782. if (this._userProvidedOptions.strict != null) {
  783. childSchemaOptions.strict = this._userProvidedOptions.strict;
  784. }
  785. if (this._userProvidedOptions.toObject != null) {
  786. childSchemaOptions.toObject = utils.omit(this._userProvidedOptions.toObject, ['transform']);
  787. }
  788. if (this._userProvidedOptions.toJSON != null) {
  789. childSchemaOptions.toJSON = utils.omit(this._userProvidedOptions.toJSON, ['transform']);
  790. }
  791. const _schema = new Schema(_typeDef, childSchemaOptions);
  792. _schema.$implicitlyCreated = true;
  793. const schemaWrappedPath = Object.assign({}, val, { [typeKey]: _schema });
  794. this.path(prefix + key, schemaWrappedPath);
  795. } else {
  796. // Either the type is non-POJO or we interpret it as Mixed anyway
  797. if (prefix) {
  798. this.nested[prefix.substring(0, prefix.length - 1)] = true;
  799. }
  800. this.path(prefix + key, val);
  801. if (!val?.instanceOfSchema && utils.isPOJO(val?.discriminators)) {
  802. const schemaType = this.path(prefix + key);
  803. for (const key in val.discriminators) {
  804. schemaType.discriminator(key, val.discriminators[key]);
  805. }
  806. }
  807. }
  808. }
  809. if (val.instanceOfSchema && val.encryptionType() != null) {
  810. // schema.add({ field: <instance of encrypted schema> })
  811. if (this.encryptionType() != val.encryptionType()) {
  812. throw new Error('encryptionType of a nested schema must match the encryption type of the parent schema.');
  813. }
  814. for (const [encryptedField, encryptedFieldConfig] of Object.entries(val.encryptedFields)) {
  815. const path = fullPath + '.' + encryptedField;
  816. this._addEncryptedField(path, encryptedFieldConfig);
  817. }
  818. } else if (typeof val === 'object' && 'encrypt' in val) {
  819. // schema.add({ field: { type: <schema type>, encrypt: { ... }}})
  820. const { encrypt } = val;
  821. if (this.encryptionType() == null) {
  822. throw new Error('encryptionType must be provided');
  823. }
  824. this._addEncryptedField(fullPath, encrypt);
  825. } else {
  826. // if the field was already encrypted and we re-configure it to be unencrypted, remove
  827. // the encrypted field configuration
  828. this._removeEncryptedField(fullPath);
  829. }
  830. }
  831. const aliasObj = Object.fromEntries(
  832. Object.entries(obj).map(([key]) => ([prefix + key, null]))
  833. );
  834. aliasFields(this, aliasObj);
  835. return this;
  836. };
  837. /**
  838. * @param {string} path
  839. * @param {object} fieldConfig
  840. *
  841. * @api private
  842. */
  843. Schema.prototype._addEncryptedField = function _addEncryptedField(path, fieldConfig) {
  844. const type = this.path(path).autoEncryptionType();
  845. if (type == null) {
  846. throw new Error(`Invalid BSON type for FLE field: '${path}'`);
  847. }
  848. this.encryptedFields[path] = clone(fieldConfig);
  849. };
  850. /**
  851. * @param {string} path
  852. *
  853. * @api private
  854. */
  855. Schema.prototype._removeEncryptedField = function _removeEncryptedField(path) {
  856. delete this.encryptedFields[path];
  857. };
  858. /**
  859. * @api private
  860. *
  861. * @returns {boolean}
  862. */
  863. Schema.prototype._hasEncryptedFields = function _hasEncryptedFields() {
  864. return utils.hasOwnKeys(this.encryptedFields);
  865. };
  866. /**
  867. * @param {string} path
  868. * @returns {boolean}
  869. *
  870. * @api private
  871. */
  872. Schema.prototype._hasEncryptedField = function _hasEncryptedField(path) {
  873. return path in this.encryptedFields;
  874. };
  875. /**
  876. * Builds an encryptedFieldsMap for the schema.
  877. *
  878. * @api private
  879. */
  880. Schema.prototype._buildEncryptedFields = function() {
  881. const fields = Object.entries(this.encryptedFields).map(
  882. ([path, config]) => {
  883. const bsonType = this.path(path).autoEncryptionType();
  884. // { path, bsonType, keyId, queries? }
  885. return { path, bsonType, ...config };
  886. });
  887. return { fields };
  888. };
  889. /**
  890. * Builds a schemaMap for the schema, if the schema is configured for client-side field level encryption.
  891. *
  892. * @api private
  893. */
  894. Schema.prototype._buildSchemaMap = function() {
  895. /**
  896. * `schemaMap`s are JSON schemas, which use the following structure to represent objects:
  897. * { field: { bsonType: 'object', properties: { ... } } }
  898. *
  899. * for example, a schema that looks like this `{ a: { b: int32 } }` would be encoded as
  900. * `{ a: { bsonType: 'object', properties: { b: < encryption configuration > } } }`
  901. *
  902. * This function takes an array of path segments, an output object (that gets mutated) and
  903. * a value to be associated with the full path, and constructs a valid CSFLE JSON schema path for
  904. * the object. This works for deeply nested properties as well.
  905. *
  906. * @param {string[]} path array of path components
  907. * @param {object} object the object in which to build a JSON schema of `path`'s properties
  908. * @param {object} value the value to associate with the path in object
  909. */
  910. function buildNestedPath(path, object, value) {
  911. let i = 0, component = path[i];
  912. for (; i < path.length - 1; ++i, component = path[i]) {
  913. object[component] = object[component] == null ? {
  914. bsonType: 'object',
  915. properties: {}
  916. } : object[component];
  917. object = object[component].properties;
  918. }
  919. object[component] = value;
  920. }
  921. const schemaMapPropertyReducer = (accum, [path, propertyConfig]) => {
  922. const bsonType = this.path(path).autoEncryptionType();
  923. const pathComponents = path.split('.');
  924. const configuration = { encrypt: { ...propertyConfig, bsonType } };
  925. buildNestedPath(pathComponents, accum, configuration);
  926. return accum;
  927. };
  928. const properties = Object.entries(this.encryptedFields).reduce(
  929. schemaMapPropertyReducer,
  930. {});
  931. return {
  932. bsonType: 'object',
  933. properties
  934. };
  935. };
  936. /**
  937. * Add an alias for `path`. This means getting or setting the `alias`
  938. * is equivalent to getting or setting the `path`.
  939. *
  940. * #### Example:
  941. *
  942. * const toySchema = new Schema({ n: String });
  943. *
  944. * // Make 'name' an alias for 'n'
  945. * toySchema.alias('n', 'name');
  946. *
  947. * const Toy = mongoose.model('Toy', toySchema);
  948. * const turboMan = new Toy({ n: 'Turbo Man' });
  949. *
  950. * turboMan.name; // 'Turbo Man'
  951. * turboMan.n; // 'Turbo Man'
  952. *
  953. * turboMan.name = 'Turbo Man Action Figure';
  954. * turboMan.n; // 'Turbo Man Action Figure'
  955. *
  956. * await turboMan.save(); // Saves { _id: ..., n: 'Turbo Man Action Figure' }
  957. *
  958. *
  959. * @param {String} path real path to alias
  960. * @param {String|String[]} alias the path(s) to use as an alias for `path`
  961. * @return {Schema} the Schema instance
  962. * @api public
  963. */
  964. Schema.prototype.alias = function alias(path, alias) {
  965. aliasFields(this, { [path]: alias });
  966. return this;
  967. };
  968. /**
  969. * Remove an index by name or index specification.
  970. *
  971. * removeIndex only removes indexes from your schema object. Does **not** affect the indexes
  972. * in MongoDB.
  973. *
  974. * #### Example:
  975. *
  976. * const ToySchema = new Schema({ name: String, color: String, price: Number });
  977. *
  978. * // Add a new index on { name, color }
  979. * ToySchema.index({ name: 1, color: 1 });
  980. *
  981. * // Remove index on { name, color }
  982. * // Keep in mind that order matters! `removeIndex({ color: 1, name: 1 })` won't remove the index
  983. * ToySchema.removeIndex({ name: 1, color: 1 });
  984. *
  985. * // Add an index with a custom name
  986. * ToySchema.index({ color: 1 }, { name: 'my custom index name' });
  987. * // Remove index by name
  988. * ToySchema.removeIndex('my custom index name');
  989. *
  990. * @param {Object|string} index name or index specification
  991. * @return {Schema} the Schema instance
  992. * @api public
  993. */
  994. Schema.prototype.removeIndex = function removeIndex(index) {
  995. if (arguments.length > 1) {
  996. throw new Error('removeIndex() takes only 1 argument');
  997. }
  998. if (typeof index !== 'object' && typeof index !== 'string') {
  999. throw new Error('removeIndex() may only take either an object or a string as an argument');
  1000. }
  1001. if (typeof index === 'object') {
  1002. for (let i = this._indexes.length - 1; i >= 0; --i) {
  1003. if (isIndexSpecEqual(this._indexes[i][0], index)) {
  1004. this._indexes.splice(i, 1);
  1005. }
  1006. }
  1007. } else {
  1008. for (let i = this._indexes.length - 1; i >= 0; --i) {
  1009. if (this._indexes[i][1] != null && this._indexes[i][1].name === index) {
  1010. this._indexes.splice(i, 1);
  1011. }
  1012. }
  1013. }
  1014. return this;
  1015. };
  1016. /**
  1017. * Remove all indexes from this schema.
  1018. *
  1019. * clearIndexes only removes indexes from your schema object. Does **not** affect the indexes
  1020. * in MongoDB.
  1021. *
  1022. * #### Example:
  1023. *
  1024. * const ToySchema = new Schema({ name: String, color: String, price: Number });
  1025. * ToySchema.index({ name: 1 });
  1026. * ToySchema.index({ color: 1 });
  1027. *
  1028. * // Remove all indexes on this schema
  1029. * ToySchema.clearIndexes();
  1030. *
  1031. * ToySchema.indexes(); // []
  1032. *
  1033. * @return {Schema} the Schema instance
  1034. * @api public
  1035. */
  1036. Schema.prototype.clearIndexes = function clearIndexes() {
  1037. this._indexes.length = 0;
  1038. return this;
  1039. };
  1040. /**
  1041. * Add an [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) that Mongoose will create using `Model.createSearchIndex()`.
  1042. * This function only works when connected to MongoDB Atlas.
  1043. *
  1044. * #### Example:
  1045. *
  1046. * const ToySchema = new Schema({ name: String, color: String, price: Number });
  1047. * ToySchema.searchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
  1048. *
  1049. * @param {Object} description index options, including `name` and `definition`
  1050. * @param {String} description.name
  1051. * @param {Object} description.definition
  1052. * @return {Schema} the Schema instance
  1053. * @api public
  1054. */
  1055. Schema.prototype.searchIndex = function searchIndex(description) {
  1056. this._searchIndexes.push(description);
  1057. return this;
  1058. };
  1059. /**
  1060. * Reserved document keys.
  1061. *
  1062. * Keys in this object are names that are warned in schema declarations
  1063. * because they have the potential to break Mongoose/ Mongoose plugins functionality. If you create a schema
  1064. * using `new Schema()` with one of these property names, Mongoose will log a warning.
  1065. *
  1066. * - _posts
  1067. * - _pres
  1068. * - collection
  1069. * - emit
  1070. * - errors
  1071. * - get
  1072. * - init
  1073. * - isModified
  1074. * - isNew
  1075. * - listeners
  1076. * - modelName
  1077. * - on
  1078. * - once
  1079. * - populated
  1080. * - prototype
  1081. * - remove
  1082. * - removeListener
  1083. * - save
  1084. * - schema
  1085. * - toObject
  1086. * - validate
  1087. *
  1088. * _NOTE:_ Use of these terms as method names is permitted, but play at your own risk, as they may be existing mongoose document methods you are stomping on.
  1089. *
  1090. * const schema = new Schema(..);
  1091. * schema.methods.init = function () {} // potentially breaking
  1092. *
  1093. * @property reserved
  1094. * @memberOf Schema
  1095. * @static
  1096. */
  1097. Schema.reserved = Object.create(null);
  1098. Schema.prototype.reserved = Schema.reserved;
  1099. const reserved = Schema.reserved;
  1100. // Core object
  1101. reserved['prototype'] =
  1102. // EventEmitter
  1103. reserved.emit =
  1104. reserved.listeners =
  1105. reserved.removeListener =
  1106. // document properties and functions
  1107. reserved.collection =
  1108. reserved.errors =
  1109. reserved.get =
  1110. reserved.init =
  1111. reserved.isModified =
  1112. reserved.isNew =
  1113. reserved.populated =
  1114. reserved.remove =
  1115. reserved.save =
  1116. reserved.toObject =
  1117. reserved.validate = 1;
  1118. reserved.collection = 1;
  1119. /**
  1120. * Gets/sets schema paths.
  1121. *
  1122. * Sets a path (if arity 2)
  1123. * Gets a path (if arity 1)
  1124. *
  1125. * #### Example:
  1126. *
  1127. * schema.path('name') // returns a SchemaType
  1128. * schema.path('name', Number) // changes the schemaType of `name` to Number
  1129. *
  1130. * @param {String} path The name of the Path to get / set
  1131. * @param {Object} [obj] The Type to set the path to, if provided the path will be SET, otherwise the path will be GET
  1132. * @api public
  1133. */
  1134. Schema.prototype.path = function(path, obj) {
  1135. if (obj === undefined) {
  1136. if (this.paths[path] != null) {
  1137. return this.paths[path];
  1138. }
  1139. // Convert to '.$' to check subpaths re: gh-6405
  1140. const cleanPath = _pathToPositionalSyntax(path);
  1141. let schematype = _getPath(this, path, cleanPath);
  1142. if (schematype != null) {
  1143. return schematype;
  1144. }
  1145. // Look for maps
  1146. const mapPath = getMapPath(this, path);
  1147. if (mapPath != null) {
  1148. return mapPath;
  1149. }
  1150. // Look if a parent of this path is mixed
  1151. schematype = this.hasMixedParent(cleanPath);
  1152. if (schematype != null) {
  1153. return schematype;
  1154. }
  1155. // subpaths?
  1156. return hasNumericSubpathRegex.test(path)
  1157. ? getPositionalPath(this, path, cleanPath)
  1158. : undefined;
  1159. }
  1160. // some path names conflict with document methods
  1161. const firstPieceOfPath = path.split('.')[0];
  1162. if (reserved[firstPieceOfPath] && !this.options.suppressReservedKeysWarning) {
  1163. const errorMessage = `\`${firstPieceOfPath}\` is a reserved schema pathname and may break some functionality. ` +
  1164. 'You are allowed to use it, but use at your own risk. ' +
  1165. 'To disable this warning pass `suppressReservedKeysWarning` as a schema option.';
  1166. utils.warn(errorMessage);
  1167. }
  1168. if (typeof obj === 'object' && utils.hasUserDefinedProperty(obj, 'ref')) {
  1169. validateRef(obj.ref, path);
  1170. }
  1171. // update the tree
  1172. const subpaths = path.split(/\./);
  1173. const last = subpaths.pop();
  1174. let branch = this.tree;
  1175. let fullPath = '';
  1176. for (const sub of subpaths) {
  1177. if (utils.specialProperties.has(sub)) {
  1178. throw new Error('Cannot set special property `' + sub + '` on a schema');
  1179. }
  1180. fullPath = fullPath += (fullPath.length > 0 ? '.' : '') + sub;
  1181. if (!branch[sub]) {
  1182. this.nested[fullPath] = true;
  1183. branch[sub] = {};
  1184. }
  1185. if (typeof branch[sub] !== 'object') {
  1186. const msg = 'Cannot set nested path `' + path + '`. '
  1187. + 'Parent path `'
  1188. + fullPath
  1189. + '` already set to type ' + branch[sub].name
  1190. + '.';
  1191. throw new Error(msg);
  1192. }
  1193. branch = branch[sub];
  1194. }
  1195. branch[last] = clone(obj);
  1196. this.paths[path] = this.interpretAsType(path, obj, this.options);
  1197. const schemaType = this.paths[path];
  1198. // If overwriting an existing path, make sure to clear the childSchemas
  1199. this.childSchemas = this.childSchemas.filter(childSchema => childSchema.path !== path);
  1200. if (schemaType.$isSchemaMap) {
  1201. // Maps can have arbitrary keys, so `$*` is internal shorthand for "any key"
  1202. // The '$' is to imply this path should never be stored in MongoDB so we
  1203. // can easily build a regexp out of this path, and '*' to imply "any key."
  1204. const mapPath = path + '.$*';
  1205. this.paths[mapPath] = schemaType.$__schemaType;
  1206. this.mapPaths.push(this.paths[mapPath]);
  1207. if (schemaType.$__schemaType.$isSingleNested) {
  1208. this.childSchemas.push({
  1209. schema: schemaType.$__schemaType.schema,
  1210. model: schemaType.$__schemaType.Constructor,
  1211. path: path
  1212. });
  1213. }
  1214. }
  1215. if (schemaType.$isSingleNested) {
  1216. for (const key of Object.keys(schemaType.schema.paths)) {
  1217. this.singleNestedPaths[path + '.' + key] = schemaType.schema.paths[key];
  1218. }
  1219. for (const key of Object.keys(schemaType.schema.singleNestedPaths)) {
  1220. this.singleNestedPaths[path + '.' + key] =
  1221. schemaType.schema.singleNestedPaths[key];
  1222. }
  1223. for (const key of Object.keys(schemaType.schema.subpaths)) {
  1224. this.singleNestedPaths[path + '.' + key] =
  1225. schemaType.schema.subpaths[key];
  1226. }
  1227. for (const key of Object.keys(schemaType.schema.nested)) {
  1228. this.singleNestedPaths[path + '.' + key] = 'nested';
  1229. }
  1230. Object.defineProperty(schemaType.schema, 'base', {
  1231. configurable: true,
  1232. enumerable: false,
  1233. writable: false,
  1234. value: this.base
  1235. });
  1236. schemaType.Constructor.base = this.base;
  1237. this.childSchemas.push({
  1238. schema: schemaType.schema,
  1239. model: schemaType.Constructor,
  1240. path: path
  1241. });
  1242. } else if (schemaType.$isMongooseDocumentArray) {
  1243. Object.defineProperty(schemaType.schema, 'base', {
  1244. configurable: true,
  1245. enumerable: false,
  1246. writable: false,
  1247. value: this.base
  1248. });
  1249. schemaType.Constructor.base = this.base;
  1250. this.childSchemas.push({
  1251. schema: schemaType.schema,
  1252. model: schemaType.Constructor,
  1253. path: path
  1254. });
  1255. }
  1256. if (schemaType.$isMongooseArray && !schemaType.$isMongooseDocumentArray) {
  1257. let arrayPath = path;
  1258. let _schemaType = schemaType;
  1259. const toAdd = [];
  1260. while (_schemaType.$isMongooseArray) {
  1261. arrayPath = arrayPath + '.$';
  1262. _schemaType.embeddedSchemaType._arrayPath = arrayPath;
  1263. _schemaType.embeddedSchemaType._arrayParentPath = path;
  1264. _schemaType = _schemaType.embeddedSchemaType;
  1265. this.subpaths[arrayPath] = _schemaType;
  1266. }
  1267. for (const _schemaType of toAdd) {
  1268. this.subpaths[_schemaType.path] = _schemaType;
  1269. }
  1270. }
  1271. if (schemaType.$isMongooseDocumentArray) {
  1272. for (const key of Object.keys(schemaType.schema.paths)) {
  1273. const _schemaType = schemaType.schema.paths[key];
  1274. this.subpaths[path + '.' + key] = _schemaType;
  1275. if (typeof _schemaType === 'object' && _schemaType != null && _schemaType.$parentSchemaDocArray == null) {
  1276. _schemaType.$parentSchemaDocArray = schemaType;
  1277. }
  1278. }
  1279. for (const key of Object.keys(schemaType.schema.subpaths)) {
  1280. const _schemaType = schemaType.schema.subpaths[key];
  1281. this.subpaths[path + '.' + key] = _schemaType;
  1282. if (typeof _schemaType === 'object' && _schemaType != null && _schemaType.$parentSchemaDocArray == null) {
  1283. _schemaType.$parentSchemaDocArray = schemaType;
  1284. }
  1285. }
  1286. for (const key of Object.keys(schemaType.schema.singleNestedPaths)) {
  1287. const _schemaType = schemaType.schema.singleNestedPaths[key];
  1288. this.subpaths[path + '.' + key] = _schemaType;
  1289. if (typeof _schemaType === 'object' && _schemaType != null && _schemaType.$parentSchemaDocArray == null) {
  1290. _schemaType.$parentSchemaDocArray = schemaType;
  1291. }
  1292. }
  1293. }
  1294. return this;
  1295. };
  1296. /*!
  1297. * ignore
  1298. */
  1299. Schema.prototype._gatherChildSchemas = function _gatherChildSchemas() {
  1300. const childSchemas = [];
  1301. for (const path of Object.keys(this.paths)) {
  1302. if (typeof path !== 'string') {
  1303. continue;
  1304. }
  1305. const schematype = this.paths[path];
  1306. if (schematype.$isMongooseDocumentArray || schematype.$isSingleNested) {
  1307. childSchemas.push({
  1308. schema: schematype.schema,
  1309. model: schematype.Constructor,
  1310. path: path
  1311. });
  1312. } else if (schematype.$isSchemaMap && schematype.$__schemaType.$isSingleNested) {
  1313. childSchemas.push({
  1314. schema: schematype.$__schemaType.schema,
  1315. model: schematype.$__schemaType.Constructor,
  1316. path: path
  1317. });
  1318. }
  1319. }
  1320. this.childSchemas = childSchemas;
  1321. return childSchemas;
  1322. };
  1323. /*!
  1324. * ignore
  1325. */
  1326. function _getPath(schema, path, cleanPath) {
  1327. if (Object.hasOwn(schema.paths, path)) {
  1328. return schema.paths[path];
  1329. }
  1330. if (Object.hasOwn(schema.subpaths, cleanPath)) {
  1331. const subpath = schema.subpaths[cleanPath];
  1332. if (subpath === 'nested') {
  1333. return undefined;
  1334. }
  1335. return subpath;
  1336. }
  1337. if (Object.hasOwn(schema.singleNestedPaths, cleanPath) && typeof schema.singleNestedPaths[cleanPath] === 'object') {
  1338. const singleNestedPath = schema.singleNestedPaths[cleanPath];
  1339. if (singleNestedPath === 'nested') {
  1340. return undefined;
  1341. }
  1342. return singleNestedPath;
  1343. }
  1344. return null;
  1345. }
  1346. /*!
  1347. * ignore
  1348. */
  1349. function _pathToPositionalSyntax(path) {
  1350. if (!/\.\d+/.test(path)) {
  1351. return path;
  1352. }
  1353. return path.replace(/\.\d+\./g, '.$.').replace(/\.\d+$/, '.$');
  1354. }
  1355. /*!
  1356. * ignore
  1357. */
  1358. function getMapPath(schema, path) {
  1359. if (schema.mapPaths.length === 0) {
  1360. return null;
  1361. }
  1362. for (const val of schema.mapPaths) {
  1363. const cleanPath = val.path.replace(/\.\$\*/g, '');
  1364. if (path === cleanPath || (path.startsWith(cleanPath + '.') && path.slice(cleanPath.length + 1).indexOf('.') === -1)) {
  1365. return val;
  1366. } else if (val.schema && path.startsWith(cleanPath + '.')) {
  1367. let remnant = path.slice(cleanPath.length + 1);
  1368. remnant = remnant.slice(remnant.indexOf('.') + 1);
  1369. return val.schema.paths[remnant];
  1370. } else if (val.$isSchemaMap && path.startsWith(cleanPath + '.')) {
  1371. let remnant = path.slice(cleanPath.length + 1);
  1372. remnant = remnant.slice(remnant.indexOf('.') + 1);
  1373. const presplitPath = val.$__schemaType._presplitPath;
  1374. if (remnant.indexOf('.') === -1 && presplitPath[presplitPath.length - 1] === '$*') {
  1375. // Handle map of map of primitives
  1376. return val.$__schemaType;
  1377. } else if (remnant.indexOf('.') !== -1 && val.$__schemaType.schema && presplitPath[presplitPath.length - 1] === '$*') {
  1378. // map of map of subdocs (recursive)
  1379. return val.$__schemaType.schema.path(remnant.slice(remnant.indexOf('.') + 1));
  1380. }
  1381. }
  1382. }
  1383. return null;
  1384. }
  1385. /**
  1386. * The Mongoose instance this schema is associated with
  1387. *
  1388. * @property base
  1389. * @api private
  1390. */
  1391. Object.defineProperty(Schema.prototype, 'base', {
  1392. configurable: true,
  1393. enumerable: false,
  1394. writable: true,
  1395. value: null
  1396. });
  1397. /**
  1398. * Converts type arguments into Mongoose Types.
  1399. *
  1400. * @param {String} path
  1401. * @param {Object} obj constructor
  1402. * @param {Object} options schema options
  1403. * @api private
  1404. */
  1405. Schema.prototype.interpretAsType = function(path, obj, options) {
  1406. if (obj instanceof SchemaType) {
  1407. if (obj.path === path) {
  1408. return obj;
  1409. }
  1410. const clone = obj.clone();
  1411. clone.path = path;
  1412. return clone;
  1413. }
  1414. // If this schema has an associated Mongoose object, use the Mongoose object's
  1415. // copy of SchemaTypes re: gh-7158 gh-6933
  1416. const MongooseTypes = this.base?.Schema.Types ?? Schema.Types;
  1417. const Types = this.base?.Types ?? require('./types');
  1418. if (!utils.isPOJO(obj) && !(obj instanceof SchemaTypeOptions)) {
  1419. const constructorName = utils.getFunctionName(obj.constructor);
  1420. if (constructorName !== 'Object') {
  1421. const oldObj = obj;
  1422. obj = {};
  1423. obj[options.typeKey] = oldObj;
  1424. }
  1425. }
  1426. // Get the type making sure to allow keys named "type"
  1427. // and default to mixed if not specified.
  1428. // { type: { type: String, default: 'freshcut' } }
  1429. let type = obj[options.typeKey] && (obj[options.typeKey] instanceof Function || options.typeKey !== 'type' || !obj.type.type)
  1430. ? obj[options.typeKey]
  1431. : {};
  1432. if (type instanceof SchemaType) {
  1433. if (type.path === path) {
  1434. return type;
  1435. }
  1436. const clone = type.clone();
  1437. clone.path = path;
  1438. return clone;
  1439. }
  1440. let name;
  1441. if (utils.isPOJO(type) || type === 'mixed') {
  1442. return new MongooseTypes.Mixed(path, obj, null, this);
  1443. }
  1444. if (Array.isArray(type) || type === Array || type === 'array' || type === MongooseTypes.Array) {
  1445. // if it was specified through { type } look for `cast`
  1446. let cast = (type === Array || type === 'array')
  1447. ? obj.cast || obj.of
  1448. : type[0];
  1449. // new Schema({ path: [new Schema({ ... })] })
  1450. if (cast?.instanceOfSchema) {
  1451. if (!(cast instanceof Schema)) {
  1452. if (this.options._isMerging) {
  1453. cast = new Schema(cast);
  1454. } else {
  1455. throw new TypeError('Schema for array path `' + path +
  1456. '` is from a different copy of the Mongoose module. ' +
  1457. 'Please make sure you\'re using the same version ' +
  1458. 'of Mongoose everywhere with `npm list mongoose`. If you are still ' +
  1459. 'getting this error, please add `new Schema()` around the path: ' +
  1460. `${path}: new Schema(...)`);
  1461. }
  1462. }
  1463. return new MongooseTypes.DocumentArray(path, cast, obj, null, this);
  1464. }
  1465. if (cast &&
  1466. cast[options.typeKey] &&
  1467. cast[options.typeKey].instanceOfSchema) {
  1468. if (!(cast[options.typeKey] instanceof Schema)) {
  1469. if (this.options._isMerging) {
  1470. cast[options.typeKey] = new Schema(cast[options.typeKey]);
  1471. } else {
  1472. throw new TypeError('Schema for array path `' + path +
  1473. '` is from a different copy of the Mongoose module. ' +
  1474. 'Please make sure you\'re using the same version ' +
  1475. 'of Mongoose everywhere with `npm list mongoose`. If you are still ' +
  1476. 'getting this error, please add `new Schema()` around the path: ' +
  1477. `${path}: new Schema(...)`);
  1478. }
  1479. }
  1480. return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast, this);
  1481. }
  1482. if (typeof cast !== 'undefined') {
  1483. if (Array.isArray(cast) || cast.type === Array || cast.type == 'Array') {
  1484. if (cast?.type == 'Array') {
  1485. cast.type = Array;
  1486. }
  1487. return new MongooseTypes.Array(path, this.interpretAsType(path, cast, options), obj, null, this);
  1488. }
  1489. }
  1490. // Handle both `new Schema({ arr: [{ subpath: String }] })` and `new Schema({ arr: [{ type: { subpath: string } }] })`
  1491. const castFromTypeKey = (cast != null && cast[options.typeKey] && (options.typeKey !== 'type' || !cast.type.type)) ?
  1492. cast[options.typeKey] :
  1493. cast;
  1494. if (typeof cast === 'string') {
  1495. cast = MongooseTypes[cast.charAt(0).toUpperCase() + cast.substring(1)];
  1496. } else if (utils.isPOJO(castFromTypeKey)) {
  1497. if (utils.hasOwnKeys(castFromTypeKey)) {
  1498. // The `minimize` and `typeKey` options propagate to child schemas
  1499. // declared inline, like `{ arr: [{ val: { $type: String } }] }`.
  1500. // See gh-3560
  1501. const childSchemaOptions = { minimize: options.minimize };
  1502. if (options.typeKey) {
  1503. childSchemaOptions.typeKey = options.typeKey;
  1504. }
  1505. // propagate 'strict' option to child schema
  1506. if (Object.hasOwn(options, 'strict')) {
  1507. childSchemaOptions.strict = options.strict;
  1508. }
  1509. if (Object.hasOwn(options, 'strictQuery')) {
  1510. childSchemaOptions.strictQuery = options.strictQuery;
  1511. }
  1512. if (Object.hasOwn(options, 'toObject')) {
  1513. childSchemaOptions.toObject = utils.omit(options.toObject, ['transform']);
  1514. }
  1515. if (Object.hasOwn(options, 'toJSON')) {
  1516. childSchemaOptions.toJSON = utils.omit(options.toJSON, ['transform']);
  1517. }
  1518. if (Object.hasOwn(this._userProvidedOptions, '_id')) {
  1519. childSchemaOptions._id = this._userProvidedOptions._id;
  1520. } else if (Schema.Types.DocumentArray.defaultOptions._id != null) {
  1521. childSchemaOptions._id = Schema.Types.DocumentArray.defaultOptions._id;
  1522. }
  1523. const childSchema = new Schema(castFromTypeKey, childSchemaOptions);
  1524. childSchema.$implicitlyCreated = true;
  1525. return new MongooseTypes.DocumentArray(path, childSchema, obj, null, this);
  1526. } else {
  1527. // Special case: empty object becomes mixed
  1528. return new MongooseTypes.Array(path, MongooseTypes.Mixed, obj, null, this);
  1529. }
  1530. }
  1531. if (cast) {
  1532. type = cast[options.typeKey] && (options.typeKey !== 'type' || !cast.type.type)
  1533. ? cast[options.typeKey]
  1534. : cast;
  1535. if (Array.isArray(type)) {
  1536. return new MongooseTypes.Array(path, this.interpretAsType(path, type, options), obj, null, this);
  1537. }
  1538. name = typeof type === 'string'
  1539. ? type
  1540. : type.schemaName || utils.getFunctionName(type);
  1541. // For Jest 26+, see #10296
  1542. if (name === 'ClockDate') {
  1543. name = 'Date';
  1544. }
  1545. if (name === void 0) {
  1546. throw new TypeError('Invalid schema configuration: ' +
  1547. `Could not determine the embedded type for array \`${path}\`. ` +
  1548. 'See https://mongoosejs.com/docs/guide.html#definition for more info on supported schema syntaxes.');
  1549. }
  1550. if (!Object.hasOwn(MongooseTypes, name)) {
  1551. throw new TypeError('Invalid schema configuration: ' +
  1552. `\`${name}\` is not a valid type within the array \`${path}\`.` +
  1553. 'See https://bit.ly/mongoose-schematypes for a list of valid schema types.');
  1554. }
  1555. if (name === 'Union' && typeof cast === 'object') {
  1556. cast.parentSchema = this;
  1557. }
  1558. }
  1559. return new MongooseTypes.Array(path, cast || MongooseTypes.Mixed, obj, options, this);
  1560. }
  1561. if (type?.instanceOfSchema) {
  1562. return new MongooseTypes.Subdocument(type, path, obj, this);
  1563. }
  1564. if (Buffer.isBuffer(type)) {
  1565. name = 'Buffer';
  1566. } else if (typeof type === 'function' || typeof type === 'object') {
  1567. name = type.schemaName || utils.getFunctionName(type);
  1568. } else if (type === Types.ObjectId) {
  1569. name = 'ObjectId';
  1570. } else if (type === Types.Decimal128) {
  1571. name = 'Decimal128';
  1572. } else {
  1573. name = type == null ? '' + type : type.toString();
  1574. }
  1575. if (name) {
  1576. name = name.charAt(0).toUpperCase() + name.substring(1);
  1577. }
  1578. // Special case re: gh-7049 because the bson `ObjectID` class' capitalization
  1579. // doesn't line up with Mongoose's.
  1580. if (name === 'ObjectID') {
  1581. name = 'ObjectId';
  1582. }
  1583. // For Jest 26+, see #10296
  1584. if (name === 'ClockDate') {
  1585. name = 'Date';
  1586. }
  1587. if (name === void 0) {
  1588. throw new TypeError(`Invalid schema configuration: \`${path}\` schematype definition is ` +
  1589. 'invalid. See ' +
  1590. 'https://mongoosejs.com/docs/guide.html#definition for more info on supported schema syntaxes.');
  1591. }
  1592. if (MongooseTypes[name] == null) {
  1593. throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` +
  1594. `a valid type at path \`${path}\`. See ` +
  1595. 'https://bit.ly/mongoose-schematypes for a list of valid schema types.');
  1596. }
  1597. const schemaType = new MongooseTypes[name](path, obj, options, this);
  1598. return schemaType;
  1599. };
  1600. /**
  1601. * Iterates the schemas paths similar to Array#forEach.
  1602. *
  1603. * The callback is passed the pathname and the schemaType instance.
  1604. *
  1605. * #### Example:
  1606. *
  1607. * const userSchema = new Schema({ name: String, registeredAt: Date });
  1608. * userSchema.eachPath((pathname, schematype) => {
  1609. * // Prints twice:
  1610. * // name SchemaString { ... }
  1611. * // registeredAt SchemaDate { ... }
  1612. * console.log(pathname, schematype);
  1613. * });
  1614. *
  1615. * @param {Function} fn callback function
  1616. * @return {Schema} this
  1617. * @api public
  1618. */
  1619. Schema.prototype.eachPath = function(fn) {
  1620. const keys = Object.keys(this.paths);
  1621. const len = keys.length;
  1622. for (let i = 0; i < len; ++i) {
  1623. fn(keys[i], this.paths[keys[i]]);
  1624. }
  1625. return this;
  1626. };
  1627. /**
  1628. * Returns an Array of path strings that are required by this schema.
  1629. *
  1630. * #### Example:
  1631. *
  1632. * const s = new Schema({
  1633. * name: { type: String, required: true },
  1634. * age: { type: String, required: true },
  1635. * notes: String
  1636. * });
  1637. * s.requiredPaths(); // [ 'age', 'name' ]
  1638. *
  1639. * @api public
  1640. * @param {Boolean} invalidate Refresh the cache
  1641. * @return {Array}
  1642. */
  1643. Schema.prototype.requiredPaths = function requiredPaths(invalidate) {
  1644. if (this._requiredpaths && !invalidate) {
  1645. return this._requiredpaths;
  1646. }
  1647. const paths = Object.keys(this.paths);
  1648. let i = paths.length;
  1649. const ret = [];
  1650. while (i--) {
  1651. const path = paths[i];
  1652. if (this.paths[path].isRequired) {
  1653. ret.push(path);
  1654. }
  1655. }
  1656. this._requiredpaths = ret;
  1657. return this._requiredpaths;
  1658. };
  1659. /**
  1660. * Returns indexes from fields and schema-level indexes (cached).
  1661. *
  1662. * @api private
  1663. * @return {Array}
  1664. */
  1665. Schema.prototype.indexedPaths = function indexedPaths() {
  1666. if (this._indexedpaths) {
  1667. return this._indexedpaths;
  1668. }
  1669. this._indexedpaths = this.indexes();
  1670. return this._indexedpaths;
  1671. };
  1672. /**
  1673. * Returns the pathType of `path` for this schema.
  1674. *
  1675. * Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.
  1676. *
  1677. * #### Example:
  1678. *
  1679. * const s = new Schema({ name: String, nested: { foo: String } });
  1680. * s.virtual('foo').get(() => 42);
  1681. * s.pathType('name'); // "real"
  1682. * s.pathType('nested'); // "nested"
  1683. * s.pathType('foo'); // "virtual"
  1684. * s.pathType('fail'); // "adhocOrUndefined"
  1685. *
  1686. * @param {String} path
  1687. * @return {String}
  1688. * @api public
  1689. */
  1690. Schema.prototype.pathType = function(path) {
  1691. if (Object.hasOwn(this.paths, path)) {
  1692. return 'real';
  1693. }
  1694. if (Object.hasOwn(this.virtuals, path)) {
  1695. return 'virtual';
  1696. }
  1697. if (Object.hasOwn(this.nested, path)) {
  1698. return 'nested';
  1699. }
  1700. // Convert to '.$' to check subpaths re: gh-6405
  1701. const cleanPath = _pathToPositionalSyntax(path);
  1702. if (Object.hasOwn(this.subpaths, cleanPath) || Object.hasOwn(this.subpaths, path)) {
  1703. return 'real';
  1704. }
  1705. const singleNestedPath = Object.hasOwn(this.singleNestedPaths, cleanPath) || Object.hasOwn(this.singleNestedPaths, path);
  1706. if (singleNestedPath) {
  1707. return singleNestedPath === 'nested' ? 'nested' : 'real';
  1708. }
  1709. // Look for maps
  1710. const mapPath = getMapPath(this, path);
  1711. if (mapPath != null) {
  1712. return 'real';
  1713. }
  1714. if (/\.\d+\.|\.\d+$/.test(path)) {
  1715. return getPositionalPathType(this, path, cleanPath);
  1716. }
  1717. return 'adhocOrUndefined';
  1718. };
  1719. /**
  1720. * Returns true iff this path is a child of a mixed schema.
  1721. *
  1722. * @param {String} path
  1723. * @return {Boolean}
  1724. * @api private
  1725. */
  1726. Schema.prototype.hasMixedParent = function(path) {
  1727. const subpaths = path.split(/\./g);
  1728. path = '';
  1729. for (let i = 0; i < subpaths.length; ++i) {
  1730. path = i > 0 ? path + '.' + subpaths[i] : subpaths[i];
  1731. if (Object.hasOwn(this.paths, path) &&
  1732. this.paths[path] instanceof MongooseTypes.Mixed) {
  1733. return this.paths[path];
  1734. }
  1735. }
  1736. return null;
  1737. };
  1738. /**
  1739. * Setup updatedAt and createdAt timestamps to documents if enabled
  1740. *
  1741. * @param {Boolean|Object} timestamps timestamps options
  1742. * @api private
  1743. */
  1744. Schema.prototype.setupTimestamp = function(timestamps) {
  1745. return setupTimestamps(this, timestamps);
  1746. };
  1747. /**
  1748. * ignore. Deprecated re: #6405
  1749. * @param {Any} self
  1750. * @param {String} path
  1751. * @api private
  1752. */
  1753. function getPositionalPathType(self, path, cleanPath) {
  1754. const subpaths = path.split(/\.(\d+)\.|\.(\d+)$/).filter(Boolean);
  1755. if (subpaths.length < 2) {
  1756. return Object.hasOwn(self.paths, subpaths[0]) ?
  1757. self.paths[subpaths[0]] :
  1758. 'adhocOrUndefined';
  1759. }
  1760. let val = self.path(subpaths[0]);
  1761. let isNested = false;
  1762. if (!val) {
  1763. return 'adhocOrUndefined';
  1764. }
  1765. const last = subpaths.length - 1;
  1766. for (let i = 1; i < subpaths.length; ++i) {
  1767. isNested = false;
  1768. const subpath = subpaths[i];
  1769. if (i === last && val && !/\D/.test(subpath)) {
  1770. if (val.$isMongooseDocumentArray) {
  1771. val = val.embeddedSchemaType;
  1772. } else if (val instanceof MongooseTypes.Array) {
  1773. // StringSchema, NumberSchema, etc
  1774. val = val.embeddedSchemaType;
  1775. } else {
  1776. val = undefined;
  1777. }
  1778. break;
  1779. }
  1780. // ignore if its just a position segment: path.0.subpath
  1781. if (!/\D/.test(subpath)) {
  1782. // Nested array
  1783. if (val instanceof MongooseTypes.Array && i !== last) {
  1784. val = val.embeddedSchemaType;
  1785. }
  1786. continue;
  1787. }
  1788. if (!val?.schema) {
  1789. val = undefined;
  1790. break;
  1791. }
  1792. const type = val.schema.pathType(subpath);
  1793. isNested = (type === 'nested');
  1794. val = val.schema.path(subpath);
  1795. }
  1796. self.subpaths[cleanPath] = val;
  1797. if (val) {
  1798. return 'real';
  1799. }
  1800. if (isNested) {
  1801. return 'nested';
  1802. }
  1803. return 'adhocOrUndefined';
  1804. }
  1805. /*!
  1806. * ignore
  1807. */
  1808. function getPositionalPath(self, path, cleanPath) {
  1809. getPositionalPathType(self, path, cleanPath);
  1810. return self.subpaths[cleanPath];
  1811. }
  1812. /**
  1813. * Adds a method call to the queue.
  1814. *
  1815. * #### Example:
  1816. *
  1817. * schema.methods.print = function() { console.log(this); };
  1818. * schema.queue('print', []); // Print the doc every one is instantiated
  1819. *
  1820. * const Model = mongoose.model('Test', schema);
  1821. * new Model({ name: 'test' }); // Prints '{"_id": ..., "name": "test" }'
  1822. *
  1823. * @param {String} name name of the document method to call later
  1824. * @param {Array} args arguments to pass to the method
  1825. * @api public
  1826. */
  1827. Schema.prototype.queue = function(name, args) {
  1828. this.callQueue.push([name, args]);
  1829. return this;
  1830. };
  1831. /**
  1832. * Defines a pre hook for the model.
  1833. *
  1834. * #### Example:
  1835. *
  1836. * const toySchema = new Schema({ name: String, created: Date });
  1837. *
  1838. * toySchema.pre('save', function() {
  1839. * if (!this.created) this.created = new Date;
  1840. * });
  1841. *
  1842. * toySchema.pre('validate', function() {
  1843. * if (this.name !== 'Woody') this.name = 'Woody';
  1844. * });
  1845. *
  1846. * // Equivalent to calling `pre()` on `find`, `findOne`, `findOneAndUpdate`.
  1847. * toySchema.pre(/^find/, function() {
  1848. * console.log(this.getFilter());
  1849. * });
  1850. *
  1851. * // Equivalent to calling `pre()` on `updateOne`, `findOneAndUpdate`.
  1852. * toySchema.pre(['updateOne', 'findOneAndUpdate'], function() {
  1853. * console.log(this.getFilter());
  1854. * });
  1855. *
  1856. * toySchema.pre('deleteOne', function() {
  1857. * // Runs when you call `Toy.deleteOne()`
  1858. * });
  1859. *
  1860. * toySchema.pre('deleteOne', { document: true }, function() {
  1861. * // Runs when you call `doc.deleteOne()`
  1862. * });
  1863. *
  1864. * @param {String|RegExp|String[]} methodName The method name or regular expression to match method name
  1865. * @param {Object} [options]
  1866. * @param {Boolean} [options.document] If `name` is a hook for both document and query middleware, set to `true` to run on document middleware. For example, set `options.document` to `true` to apply this hook to `Document#deleteOne()` rather than `Query#deleteOne()`.
  1867. * @param {Boolean} [options.query] If `name` is a hook for both document and query middleware, set to `true` to run on query middleware.
  1868. * @param {Function} callback
  1869. * @api public
  1870. */
  1871. Schema.prototype.pre = function(name) {
  1872. if (name instanceof RegExp) {
  1873. const remainingArgs = Array.prototype.slice.call(arguments, 1);
  1874. for (const fn of hookNames) {
  1875. if (name.test(fn)) {
  1876. this.pre.apply(this, [fn].concat(remainingArgs));
  1877. }
  1878. }
  1879. return this;
  1880. }
  1881. if (Array.isArray(name)) {
  1882. const remainingArgs = Array.prototype.slice.call(arguments, 1);
  1883. for (const el of name) {
  1884. this.pre.apply(this, [el].concat(remainingArgs));
  1885. }
  1886. return this;
  1887. }
  1888. this.s.hooks.pre.apply(this.s.hooks, arguments);
  1889. return this;
  1890. };
  1891. /**
  1892. * Defines a post hook for the document
  1893. *
  1894. * const schema = new Schema(..);
  1895. * schema.post('save', function (doc) {
  1896. * console.log('this fired after a document was saved');
  1897. * });
  1898. *
  1899. * schema.post('find', function(docs) {
  1900. * console.log('this fired after you ran a find query');
  1901. * });
  1902. *
  1903. * schema.post(/Many$/, function(res) {
  1904. * console.log('this fired after you ran `updateMany()` or `deleteMany()`');
  1905. * });
  1906. *
  1907. * const Model = mongoose.model('Model', schema);
  1908. *
  1909. * const m = new Model(..);
  1910. * await m.save();
  1911. * console.log('this fires after the `post` hook');
  1912. *
  1913. * await m.find();
  1914. * console.log('this fires after the post find hook');
  1915. *
  1916. * @param {String|RegExp|String[]} methodName The method name or regular expression to match method name
  1917. * @param {Object} [options]
  1918. * @param {Boolean} [options.document] If `name` is a hook for both document and query middleware, set to `true` to run on document middleware.
  1919. * @param {Boolean} [options.query] If `name` is a hook for both document and query middleware, set to `true` to run on query middleware.
  1920. * @param {Function} fn callback
  1921. * @see middleware https://mongoosejs.com/docs/middleware.html
  1922. * @see kareem https://npmjs.org/package/kareem
  1923. * @api public
  1924. */
  1925. Schema.prototype.post = function(name) {
  1926. if (name instanceof RegExp) {
  1927. const remainingArgs = Array.prototype.slice.call(arguments, 1);
  1928. for (const fn of hookNames) {
  1929. if (name.test(fn)) {
  1930. this.post.apply(this, [fn].concat(remainingArgs));
  1931. }
  1932. }
  1933. return this;
  1934. }
  1935. if (Array.isArray(name)) {
  1936. const remainingArgs = Array.prototype.slice.call(arguments, 1);
  1937. for (const el of name) {
  1938. this.post.apply(this, [el].concat(remainingArgs));
  1939. }
  1940. return this;
  1941. }
  1942. this.s.hooks.post.apply(this.s.hooks, arguments);
  1943. return this;
  1944. };
  1945. /**
  1946. * Registers a plugin for this schema.
  1947. *
  1948. * #### Example:
  1949. *
  1950. * const s = new Schema({ name: String });
  1951. * s.plugin(schema => console.log(schema.path('name').path));
  1952. * mongoose.model('Test', s); // Prints 'name'
  1953. *
  1954. * Or with Options:
  1955. *
  1956. * const s = new Schema({ name: String });
  1957. * s.plugin((schema, opts) => console.log(opts.text, schema.path('name').path), { text: "Schema Path Name:" });
  1958. * mongoose.model('Test', s); // Prints 'Schema Path Name: name'
  1959. *
  1960. * @param {Function} plugin The Plugin's callback
  1961. * @param {Object} [opts] Options to pass to the plugin
  1962. * @param {Boolean} [opts.deduplicate=false] If true, ignore duplicate plugins (same `fn` argument using `===`)
  1963. * @see plugins https://mongoosejs.com/docs/plugins.html
  1964. * @api public
  1965. */
  1966. Schema.prototype.plugin = function(fn, opts) {
  1967. if (typeof fn !== 'function') {
  1968. throw new Error('First param to `schema.plugin()` must be a function, ' +
  1969. 'got "' + (typeof fn) + '"');
  1970. }
  1971. if (opts?.deduplicate) {
  1972. for (const plugin of this.plugins) {
  1973. if (plugin.fn === fn) {
  1974. return this;
  1975. }
  1976. }
  1977. }
  1978. this.plugins.push({ fn: fn, opts: opts });
  1979. fn(this, opts);
  1980. return this;
  1981. };
  1982. /**
  1983. * Adds an instance method to documents constructed from Models compiled from this schema.
  1984. *
  1985. * #### Example:
  1986. *
  1987. * const schema = kittySchema = new Schema(..);
  1988. *
  1989. * schema.method('meow', function () {
  1990. * console.log('meeeeeoooooooooooow');
  1991. * })
  1992. *
  1993. * const Kitty = mongoose.model('Kitty', schema);
  1994. *
  1995. * const fizz = new Kitty;
  1996. * fizz.meow(); // meeeeeooooooooooooow
  1997. *
  1998. * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
  1999. *
  2000. * schema.method({
  2001. * purr: function () {}
  2002. * , scratch: function () {}
  2003. * });
  2004. *
  2005. * // later
  2006. * const fizz = new Kitty;
  2007. * fizz.purr();
  2008. * fizz.scratch();
  2009. *
  2010. * NOTE: `Schema.method()` adds instance methods to the `Schema.methods` object. You can also add instance methods directly to the `Schema.methods` object as seen in the [guide](https://mongoosejs.com/docs/guide.html#methods)
  2011. *
  2012. * @param {String|Object} name The Method Name for a single function, or a Object of "string-function" pairs.
  2013. * @param {Function} [fn] The Function in a single-function definition.
  2014. * @api public
  2015. */
  2016. Schema.prototype.method = function(name, fn, options) {
  2017. if (typeof name !== 'string') {
  2018. for (const i in name) {
  2019. this.methods[i] = name[i];
  2020. this.methodOptions[i] = clone(options);
  2021. }
  2022. } else {
  2023. this.methods[name] = fn;
  2024. this.methodOptions[name] = clone(options);
  2025. }
  2026. return this;
  2027. };
  2028. /**
  2029. * Adds static "class" methods to Models compiled from this schema.
  2030. *
  2031. * #### Example:
  2032. *
  2033. * const schema = new Schema(..);
  2034. * // Equivalent to `schema.statics.findByName = function(name) {}`;
  2035. * schema.static('findByName', function(name) {
  2036. * return this.find({ name: name });
  2037. * });
  2038. *
  2039. * const Drink = mongoose.model('Drink', schema);
  2040. * await Drink.findByName('LaCroix');
  2041. *
  2042. * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
  2043. *
  2044. * schema.static({
  2045. * findByName: function () {..}
  2046. * , findByCost: function () {..}
  2047. * });
  2048. *
  2049. * const Drink = mongoose.model('Drink', schema);
  2050. * await Drink.findByName('LaCroix');
  2051. * await Drink.findByCost(3);
  2052. *
  2053. * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
  2054. *
  2055. * @param {String|Object} name The Method Name for a single function, or a Object of "string-function" pairs.
  2056. * @param {Function} [fn] The Function in a single-function definition.
  2057. * @api public
  2058. * @see Statics https://mongoosejs.com/docs/guide.html#statics
  2059. */
  2060. Schema.prototype.static = function(name, fn) {
  2061. if (typeof name !== 'string') {
  2062. for (const i in name) {
  2063. this.statics[i] = name[i];
  2064. }
  2065. } else {
  2066. this.statics[name] = fn;
  2067. }
  2068. return this;
  2069. };
  2070. /**
  2071. * Defines an index (most likely compound) for this schema.
  2072. *
  2073. * #### Example:
  2074. *
  2075. * schema.index({ first: 1, last: -1 })
  2076. *
  2077. * @param {Object} fields The Fields to index, with the order, available values: `1 | -1 | '2d' | '2dsphere' | 'geoHaystack' | 'hashed' | 'text'`
  2078. * @param {Object} [options] Options to pass to [MongoDB driver's `createIndex()` function](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#createIndex)
  2079. * @param {String | number} [options.expires=null] Mongoose-specific syntactic sugar, uses [ms](https://www.npmjs.com/package/ms) to convert `expires` option into seconds for the `expireAfterSeconds` in the above link.
  2080. * @param {String} [options.language_override=null] Tells mongodb to use the specified field instead of `language` for parsing text indexes.
  2081. * @api public
  2082. */
  2083. Schema.prototype.index = function(fields, options) {
  2084. fields || (fields = {});
  2085. options || (options = {});
  2086. if (options.expires) {
  2087. utils.expires(options);
  2088. }
  2089. for (const key in fields) {
  2090. if (this.aliases[key]) {
  2091. fields = utils.renameObjKey(fields, key, this.aliases[key]);
  2092. }
  2093. }
  2094. for (const field of Object.keys(fields)) {
  2095. if (fields[field] === 'ascending' || fields[field] === 'asc') {
  2096. fields[field] = 1;
  2097. } else if (fields[field] === 'descending' || fields[field] === 'desc') {
  2098. fields[field] = -1;
  2099. }
  2100. }
  2101. for (const existingIndex of this.indexes()) {
  2102. if (options.name == null && existingIndex[1].name == null && isIndexSpecEqual(existingIndex[0], fields)) {
  2103. utils.warn(`Duplicate schema index on ${JSON.stringify(fields)} found. This is often due to declaring an index using both "index: true" and "schema.index()". Please remove the duplicate index definition.`);
  2104. }
  2105. }
  2106. this._indexes.push([fields, options]);
  2107. return this;
  2108. };
  2109. /**
  2110. * Sets a schema option.
  2111. *
  2112. * #### Example:
  2113. *
  2114. * schema.set('strict'); // 'true' by default
  2115. * schema.set('strict', false); // Sets 'strict' to false
  2116. * schema.set('strict'); // 'false'
  2117. *
  2118. * @param {String} key The name of the option to set the value to
  2119. * @param {Object} [value] The value to set the option to, if not passed, the option will be reset to default
  2120. * @param {Array<string>} [tags] tags to add to read preference if key === 'read'
  2121. * @see Schema https://mongoosejs.com/docs/api/schema.html#Schema()
  2122. * @api public
  2123. */
  2124. Schema.prototype.set = function(key, value, tags) {
  2125. if (arguments.length === 1) {
  2126. return this.options[key];
  2127. }
  2128. switch (key) {
  2129. case 'read':
  2130. if (typeof value === 'string') {
  2131. this.options[key] = { mode: handleReadPreferenceAliases(value), tags };
  2132. } else if (Array.isArray(value) && typeof value[0] === 'string') {
  2133. this.options[key] = {
  2134. mode: handleReadPreferenceAliases(value[0]),
  2135. tags: value[1]
  2136. };
  2137. } else {
  2138. this.options[key] = value;
  2139. }
  2140. this._userProvidedOptions[key] = this.options[key];
  2141. break;
  2142. case 'timestamps':
  2143. this.setupTimestamp(value);
  2144. this.options[key] = value;
  2145. this._userProvidedOptions[key] = this.options[key];
  2146. break;
  2147. case '_id':
  2148. this.options[key] = value;
  2149. this._userProvidedOptions[key] = this.options[key];
  2150. if (value && !this.paths['_id']) {
  2151. addAutoId(this);
  2152. } else if (!value && this.paths['_id'] != null && this.paths['_id'].auto) {
  2153. this.remove('_id');
  2154. }
  2155. break;
  2156. default:
  2157. this.options[key] = value;
  2158. this._userProvidedOptions[key] = this.options[key];
  2159. break;
  2160. }
  2161. // Propagate `strict` and `strictQuery` changes down to implicitly created schemas
  2162. if (key === 'strict') {
  2163. _propagateOptionsToImplicitlyCreatedSchemas(this, { strict: value });
  2164. }
  2165. if (key === 'strictQuery') {
  2166. _propagateOptionsToImplicitlyCreatedSchemas(this, { strictQuery: value });
  2167. }
  2168. if (key === 'toObject') {
  2169. value = { ...value };
  2170. // Avoid propagating transform to implicitly created schemas re: gh-3279
  2171. delete value.transform;
  2172. _propagateOptionsToImplicitlyCreatedSchemas(this, { toObject: value });
  2173. }
  2174. if (key === 'toJSON') {
  2175. value = { ...value };
  2176. // Avoid propagating transform to implicitly created schemas re: gh-3279
  2177. delete value.transform;
  2178. _propagateOptionsToImplicitlyCreatedSchemas(this, { toJSON: value });
  2179. }
  2180. return this;
  2181. };
  2182. /*!
  2183. * Recursively set options on implicitly created schemas
  2184. */
  2185. function _propagateOptionsToImplicitlyCreatedSchemas(baseSchema, options) {
  2186. for (const { schema } of baseSchema.childSchemas) {
  2187. if (!schema.$implicitlyCreated) {
  2188. continue;
  2189. }
  2190. Object.assign(schema.options, options);
  2191. _propagateOptionsToImplicitlyCreatedSchemas(schema, options);
  2192. }
  2193. }
  2194. /**
  2195. * Gets a schema option.
  2196. *
  2197. * #### Example:
  2198. *
  2199. * schema.get('strict'); // true
  2200. * schema.set('strict', false);
  2201. * schema.get('strict'); // false
  2202. *
  2203. * @param {String} key The name of the Option to get the current value for
  2204. * @api public
  2205. * @return {Any} the option's value
  2206. */
  2207. Schema.prototype.get = function(key) {
  2208. return this.options[key];
  2209. };
  2210. const indexTypes = '2d 2dsphere hashed text'.split(' ');
  2211. /**
  2212. * The allowed index types
  2213. *
  2214. * @property {String[]} indexTypes
  2215. * @memberOf Schema
  2216. * @static
  2217. * @api public
  2218. */
  2219. Object.defineProperty(Schema, 'indexTypes', {
  2220. get: function() {
  2221. return indexTypes;
  2222. },
  2223. set: function() {
  2224. throw new Error('Cannot overwrite Schema.indexTypes');
  2225. }
  2226. });
  2227. /**
  2228. * Returns a list of indexes that this schema declares, via `schema.index()` or by `index: true` in a path's options.
  2229. * Indexes are expressed as an array `[spec, options]`.
  2230. *
  2231. * #### Example:
  2232. *
  2233. * const userSchema = new Schema({
  2234. * email: { type: String, required: true, unique: true },
  2235. * registeredAt: { type: Date, index: true }
  2236. * });
  2237. *
  2238. * // [ [ { email: 1 }, { unique: true } ],
  2239. * // [ { registeredAt: 1 }, {} ] ]
  2240. * userSchema.indexes();
  2241. *
  2242. * [Plugins](https://mongoosejs.com/docs/plugins.html) can use the return value of this function to modify a schema's indexes.
  2243. * For example, the below plugin makes every index unique by default.
  2244. *
  2245. * function myPlugin(schema) {
  2246. * for (const index of schema.indexes()) {
  2247. * if (index[1].unique === undefined) {
  2248. * index[1].unique = true;
  2249. * }
  2250. * }
  2251. * }
  2252. *
  2253. * @api public
  2254. * @return {Array} list of indexes defined in the schema
  2255. */
  2256. Schema.prototype.indexes = function() {
  2257. return getIndexes(this);
  2258. };
  2259. /**
  2260. * Creates a virtual type with the given name.
  2261. *
  2262. * @param {String} name The name of the Virtual
  2263. * @param {Object} [options]
  2264. * @param {String|Model} [options.ref] model name or model instance. Marks this as a [populate virtual](https://mongoosejs.com/docs/populate.html#populate-virtuals).
  2265. * @param {String|Function} [options.localField] Required for populate virtuals. See [populate virtual docs](https://mongoosejs.com/docs/populate.html#populate-virtuals) for more information.
  2266. * @param {String|Function} [options.foreignField] Required for populate virtuals. See [populate virtual docs](https://mongoosejs.com/docs/populate.html#populate-virtuals) for more information.
  2267. * @param {Boolean|Function} [options.justOne=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), will be a single doc or `null`. Otherwise, the populate virtual will be an array.
  2268. * @param {Boolean} [options.count=false] Only works with populate virtuals. If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), this populate virtual will contain the number of documents rather than the documents themselves when you `populate()`.
  2269. * @param {Function|null} [options.get=null] Adds a [getter](https://mongoosejs.com/docs/tutorials/getters-setters.html) to this virtual to transform the populated doc.
  2270. * @param {Object|Function} [options.match=null] Apply a default [`match` option to populate](https://mongoosejs.com/docs/populate.html#match), adding an additional filter to the populate query.
  2271. * @param {Boolean} [options.applyToArray=false] If true and the given `name` is a direct child of an array, apply the virtual to the array rather than the elements.
  2272. * @return {VirtualType}
  2273. */
  2274. Schema.prototype.virtual = function(name, options) {
  2275. if (name instanceof VirtualType || getConstructorName(name) === 'VirtualType') {
  2276. return this.virtual(name.path, name.options);
  2277. }
  2278. options = new VirtualOptions(options);
  2279. if (utils.hasUserDefinedProperty(options, ['ref', 'refPath'])) {
  2280. if (options.localField == null) {
  2281. throw new Error('Reference virtuals require `localField` option');
  2282. }
  2283. if (options.foreignField == null) {
  2284. throw new Error('Reference virtuals require `foreignField` option');
  2285. }
  2286. const virtual = this.virtual(name);
  2287. virtual.options = options;
  2288. this.pre('init', function virtualPreInit(obj, opts) {
  2289. if (mpath.has(name, obj)) {
  2290. const _v = mpath.get(name, obj);
  2291. if (!this.$$populatedVirtuals) {
  2292. this.$$populatedVirtuals = {};
  2293. }
  2294. if (options.justOne || options.count) {
  2295. this.$$populatedVirtuals[name] = Array.isArray(_v) ?
  2296. _v[0] :
  2297. _v;
  2298. } else {
  2299. this.$$populatedVirtuals[name] = Array.isArray(_v) ?
  2300. _v :
  2301. _v == null ? [] : [_v];
  2302. }
  2303. if (opts?.hydratedPopulatedDocs && !options.count) {
  2304. const modelNames = virtual._getModelNamesForPopulate(this);
  2305. const populatedVal = this.$$populatedVirtuals[name];
  2306. if (!Array.isArray(populatedVal) && !populatedVal.$__ && modelNames?.length === 1) {
  2307. const PopulateModel = this.db.model(modelNames[0]);
  2308. this.$$populatedVirtuals[name] = PopulateModel.hydrate(populatedVal);
  2309. } else if (Array.isArray(populatedVal) && modelNames?.length === 1) {
  2310. const PopulateModel = this.db.model(modelNames[0]);
  2311. for (let i = 0; i < populatedVal.length; ++i) {
  2312. if (!populatedVal[i].$__) {
  2313. populatedVal[i] = PopulateModel.hydrate(populatedVal[i], null, { hydratedPopulatedDocs: true });
  2314. }
  2315. }
  2316. const foreignField = options.foreignField;
  2317. this.$populated(
  2318. name,
  2319. populatedVal.map(doc => doc == null ? doc : doc.get(typeof foreignField === 'function' ? foreignField.call(doc, doc) : foreignField)),
  2320. { populateModelSymbol: PopulateModel }
  2321. );
  2322. }
  2323. }
  2324. mpath.unset(name, obj);
  2325. }
  2326. });
  2327. virtual.
  2328. set(function(v) {
  2329. if (!this.$$populatedVirtuals) {
  2330. this.$$populatedVirtuals = {};
  2331. }
  2332. return setPopulatedVirtualValue(
  2333. this.$$populatedVirtuals,
  2334. name,
  2335. v,
  2336. options
  2337. );
  2338. });
  2339. if (typeof options.get === 'function') {
  2340. virtual.get(options.get);
  2341. }
  2342. // Workaround for gh-8198: if virtual is under document array, make a fake
  2343. // virtual. See gh-8210, gh-13189
  2344. const parts = name.split('.');
  2345. let cur = parts[0];
  2346. for (let i = 0; i < parts.length - 1; ++i) {
  2347. if (this.paths[cur] == null) {
  2348. continue;
  2349. }
  2350. if (this.paths[cur].$isMongooseDocumentArray || this.paths[cur].$isSingleNested) {
  2351. const remnant = parts.slice(i + 1).join('.');
  2352. this.paths[cur].schema.virtual(remnant, options);
  2353. break;
  2354. } else if (this.paths[cur].$isSchemaMap) {
  2355. const remnant = parts.slice(i + 2).join('.');
  2356. this.paths[cur].$__schemaType.schema.virtual(remnant, options);
  2357. break;
  2358. }
  2359. cur += '.' + parts[i + 1];
  2360. }
  2361. return virtual;
  2362. }
  2363. const virtuals = this.virtuals;
  2364. const parts = name.split('.');
  2365. if (this.pathType(name) === 'real') {
  2366. throw new Error('Virtual path "' + name + '"' +
  2367. ' conflicts with a real path in the schema');
  2368. }
  2369. virtuals[name] = parts.reduce(function(mem, part, i) {
  2370. mem[part] || (mem[part] = (i === parts.length - 1)
  2371. ? new VirtualType(options, name)
  2372. : {});
  2373. return mem[part];
  2374. }, this.tree);
  2375. if (options?.applyToArray && parts.length > 1) {
  2376. const path = this.path(parts.slice(0, -1).join('.'));
  2377. if (path?.$isMongooseArray) {
  2378. return path.virtual(parts[parts.length - 1], options);
  2379. } else {
  2380. throw new MongooseError(`Path "${path}" is not an array`);
  2381. }
  2382. }
  2383. return virtuals[name];
  2384. };
  2385. /**
  2386. * Returns the virtual type with the given `name`.
  2387. *
  2388. * @param {String} name The name of the Virtual to get
  2389. * @return {VirtualType|null}
  2390. */
  2391. Schema.prototype.virtualpath = function(name) {
  2392. return Object.hasOwn(this.virtuals, name) ? this.virtuals[name] : null;
  2393. };
  2394. /**
  2395. * Removes the given `path` (or [`paths`]).
  2396. *
  2397. * #### Example:
  2398. *
  2399. * const schema = new Schema({ name: String, age: Number });
  2400. * schema.remove('name');
  2401. * schema.path('name'); // Undefined
  2402. * schema.path('age'); // SchemaNumber { ... }
  2403. *
  2404. * Or as a Array:
  2405. *
  2406. * schema.remove(['name', 'age']);
  2407. * schema.path('name'); // Undefined
  2408. * schema.path('age'); // Undefined
  2409. *
  2410. * @param {String|Array} path The Path(s) to remove
  2411. * @return {Schema} the Schema instance
  2412. * @api public
  2413. */
  2414. Schema.prototype.remove = function(path) {
  2415. if (typeof path === 'string') {
  2416. path = [path];
  2417. }
  2418. if (Array.isArray(path)) {
  2419. path.forEach(function(name) {
  2420. if (this.path(name) == null && !this.nested[name]) {
  2421. return;
  2422. }
  2423. if (this.nested[name]) {
  2424. const allKeys = Object.keys(this.paths).
  2425. concat(Object.keys(this.nested));
  2426. for (const path of allKeys) {
  2427. if (path.startsWith(name + '.')) {
  2428. delete this.paths[path];
  2429. delete this.nested[path];
  2430. _deletePath(this, path);
  2431. }
  2432. }
  2433. delete this.nested[name];
  2434. _deletePath(this, name);
  2435. return;
  2436. }
  2437. delete this.paths[name];
  2438. _deletePath(this, name);
  2439. this._removeEncryptedField(name);
  2440. }, this);
  2441. }
  2442. return this;
  2443. };
  2444. /*!
  2445. * ignore
  2446. */
  2447. function _deletePath(schema, name) {
  2448. const pieces = name.split('.');
  2449. const last = pieces.pop();
  2450. let branch = schema.tree;
  2451. for (const piece of pieces) {
  2452. branch = branch[piece];
  2453. }
  2454. delete branch[last];
  2455. }
  2456. /**
  2457. * Removes the given virtual or virtuals from the schema.
  2458. *
  2459. * @param {String|Array} path The virutal path(s) to remove.
  2460. * @returns {Schema} the Schema instance, or a mongoose error if the virtual does not exist.
  2461. * @api public
  2462. */
  2463. Schema.prototype.removeVirtual = function(path) {
  2464. if (typeof path === 'string') {
  2465. path = [path];
  2466. }
  2467. if (Array.isArray(path)) {
  2468. for (const virtual of path) {
  2469. if (this.virtuals[virtual] == null) {
  2470. throw new MongooseError(`Attempting to remove virtual "${virtual}" that does not exist.`);
  2471. }
  2472. }
  2473. for (const virtual of path) {
  2474. delete this.paths[virtual];
  2475. delete this.virtuals[virtual];
  2476. if (virtual.indexOf('.') !== -1) {
  2477. mpath.unset(virtual, this.tree);
  2478. } else {
  2479. delete this.tree[virtual];
  2480. }
  2481. }
  2482. }
  2483. return this;
  2484. };
  2485. /**
  2486. * Loads an ES6 class into a schema. Maps [setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) + [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get), [static methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static),
  2487. * and [instance methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Class_body_and_method_definitions)
  2488. * to schema [virtuals](https://mongoosejs.com/docs/guide.html#virtuals),
  2489. * [statics](https://mongoosejs.com/docs/guide.html#statics), and
  2490. * [methods](https://mongoosejs.com/docs/guide.html#methods).
  2491. *
  2492. * #### Example:
  2493. *
  2494. * ```javascript
  2495. * const md5 = require('md5');
  2496. * const userSchema = new Schema({ email: String });
  2497. * class UserClass {
  2498. * // `gravatarImage` becomes a virtual
  2499. * get gravatarImage() {
  2500. * const hash = md5(this.email.toLowerCase());
  2501. * return `https://www.gravatar.com/avatar/${hash}`;
  2502. * }
  2503. *
  2504. * // `getProfileUrl()` becomes a document method
  2505. * getProfileUrl() {
  2506. * return `https://mysite.com/${this.email}`;
  2507. * }
  2508. *
  2509. * // `findByEmail()` becomes a static
  2510. * static findByEmail(email) {
  2511. * return this.findOne({ email });
  2512. * }
  2513. * }
  2514. *
  2515. * // `schema` will now have a `gravatarImage` virtual, a `getProfileUrl()` method,
  2516. * // and a `findByEmail()` static
  2517. * userSchema.loadClass(UserClass);
  2518. * ```
  2519. *
  2520. * @param {Function} model The Class to load
  2521. * @param {Boolean} [virtualsOnly] if truthy, only pulls virtuals from the class, not methods or statics
  2522. */
  2523. Schema.prototype.loadClass = function(model, virtualsOnly) {
  2524. // Stop copying when hit certain base classes
  2525. if (model === Object.prototype ||
  2526. model === Function.prototype ||
  2527. Object.hasOwn(model.prototype, '$isMongooseModelPrototype') ||
  2528. Object.hasOwn(model.prototype, '$isMongooseDocumentPrototype')) {
  2529. return this;
  2530. }
  2531. this.loadClass(Object.getPrototypeOf(model), virtualsOnly);
  2532. // Add static methods
  2533. if (!virtualsOnly) {
  2534. Object.getOwnPropertyNames(model).forEach(function(name) {
  2535. if (name.match(/^(length|name|prototype|constructor|__proto__)$/)) {
  2536. return;
  2537. }
  2538. const prop = Object.getOwnPropertyDescriptor(model, name);
  2539. if (Object.hasOwn(prop, 'value')) {
  2540. this.static(name, prop.value);
  2541. }
  2542. }, this);
  2543. }
  2544. // Add methods and virtuals
  2545. Object.getOwnPropertyNames(model.prototype).forEach(function(name) {
  2546. if (name.match(/^(constructor)$/)) {
  2547. return;
  2548. }
  2549. const method = Object.getOwnPropertyDescriptor(model.prototype, name);
  2550. if (!virtualsOnly) {
  2551. if (typeof method.value === 'function') {
  2552. this.method(name, method.value);
  2553. }
  2554. }
  2555. if (typeof method.get === 'function') {
  2556. if (this.virtuals[name]) {
  2557. this.virtuals[name].getters = [];
  2558. }
  2559. this.virtual(name).get(method.get);
  2560. }
  2561. if (typeof method.set === 'function') {
  2562. if (this.virtuals[name]) {
  2563. this.virtuals[name].setters = [];
  2564. }
  2565. this.virtual(name).set(method.set);
  2566. }
  2567. }, this);
  2568. return this;
  2569. };
  2570. /*!
  2571. * ignore
  2572. */
  2573. Schema.prototype._getSchema = function(path) {
  2574. const _this = this;
  2575. const pathschema = _this.path(path);
  2576. const resultPath = [];
  2577. if (pathschema) {
  2578. pathschema.$fullPath = path;
  2579. return pathschema;
  2580. }
  2581. function search(parts, schema) {
  2582. let p = parts.length + 1;
  2583. let foundschema;
  2584. let trypath;
  2585. while (p--) {
  2586. trypath = parts.slice(0, p).join('.');
  2587. foundschema = schema.path(trypath);
  2588. if (foundschema) {
  2589. resultPath.push(trypath);
  2590. if (foundschema.embeddedSchemaType || foundschema.Constructor) {
  2591. // array of Mixed?
  2592. if (foundschema.embeddedSchemaType instanceof MongooseTypes.Mixed) {
  2593. foundschema.embeddedSchemaType.$fullPath = resultPath.join('.');
  2594. return foundschema.embeddedSchemaType;
  2595. }
  2596. // Now that we found the array, we need to check if there
  2597. // are remaining document paths to look up for casting.
  2598. // Also we need to handle array.$.path since schema.path
  2599. // doesn't work for that.
  2600. // If there is no foundschema.schema we are dealing with
  2601. // a path like array.$
  2602. if (p !== parts.length) {
  2603. if (p + 1 === parts.length && foundschema.embeddedSchemaType && (parts[p] === '$' || isArrayFilter(parts[p]))) {
  2604. return foundschema.embeddedSchemaType;
  2605. }
  2606. if (foundschema.schema) {
  2607. let ret;
  2608. if (parts[p] === '$' || isArrayFilter(parts[p])) {
  2609. if (p + 1 === parts.length) {
  2610. // comments.$
  2611. return foundschema.embeddedSchemaType;
  2612. }
  2613. // comments.$.comments.$.title
  2614. ret = search(parts.slice(p + 1), foundschema.schema);
  2615. if (ret) {
  2616. ret.$parentSchemaDocArray = ret.$parentSchemaDocArray ||
  2617. (foundschema.schema.$isSingleNested ? null : foundschema);
  2618. }
  2619. return ret;
  2620. }
  2621. // this is the last path of the selector
  2622. ret = search(parts.slice(p), foundschema.schema);
  2623. if (ret) {
  2624. ret.$parentSchemaDocArray = ret.$parentSchemaDocArray ||
  2625. (foundschema.schema.$isSingleNested ? null : foundschema);
  2626. }
  2627. return ret;
  2628. }
  2629. }
  2630. } else if (foundschema.$isSchemaMap) {
  2631. if (p >= parts.length) {
  2632. return foundschema;
  2633. }
  2634. // Any path in the map will be an instance of the map's embedded schematype
  2635. if (p + 1 >= parts.length) {
  2636. return foundschema.$__schemaType;
  2637. }
  2638. if (foundschema.$__schemaType instanceof MongooseTypes.Mixed) {
  2639. return foundschema.$__schemaType;
  2640. }
  2641. if (foundschema.$__schemaType.schema != null) {
  2642. // Map of docs
  2643. const ret = search(parts.slice(p + 1), foundschema.$__schemaType.schema);
  2644. return ret;
  2645. }
  2646. }
  2647. foundschema.$fullPath = resultPath.join('.');
  2648. return foundschema;
  2649. }
  2650. }
  2651. }
  2652. // look for arrays
  2653. const parts = path.split('.');
  2654. for (let i = 0; i < parts.length; ++i) {
  2655. if (parts[i] === '$' || isArrayFilter(parts[i])) {
  2656. // Re: gh-5628, because `schema.path()` doesn't take $ into account.
  2657. parts[i] = '0';
  2658. }
  2659. if (numberRE.test(parts[i])) {
  2660. parts[i] = '$';
  2661. }
  2662. }
  2663. return search(parts, _this);
  2664. };
  2665. /*!
  2666. * ignore
  2667. */
  2668. Schema.prototype._getPathType = function(path) {
  2669. const _this = this;
  2670. const pathschema = _this.path(path);
  2671. if (pathschema) {
  2672. return 'real';
  2673. }
  2674. function search(parts, schema) {
  2675. let p = parts.length + 1,
  2676. foundschema,
  2677. trypath;
  2678. while (p--) {
  2679. trypath = parts.slice(0, p).join('.');
  2680. foundschema = schema.path(trypath);
  2681. if (foundschema) {
  2682. if (foundschema.embeddedSchemaType || foundschema.Constructor) {
  2683. // array of Mixed?
  2684. if (foundschema.embeddedSchemaType instanceof MongooseTypes.Mixed) {
  2685. return { schema: foundschema, pathType: 'mixed' };
  2686. }
  2687. // Now that we found the array, we need to check if there
  2688. // are remaining document paths to look up for casting.
  2689. // Also we need to handle array.$.path since schema.path
  2690. // doesn't work for that.
  2691. // If there is no foundschema.schema we are dealing with
  2692. // a path like array.$
  2693. if (p !== parts.length && foundschema.schema) {
  2694. if (parts[p] === '$' || isArrayFilter(parts[p])) {
  2695. if (p === parts.length - 1) {
  2696. return { schema: foundschema, pathType: 'nested' };
  2697. }
  2698. // comments.$.comments.$.title
  2699. return search(parts.slice(p + 1), foundschema.schema);
  2700. }
  2701. // this is the last path of the selector
  2702. return search(parts.slice(p), foundschema.schema);
  2703. }
  2704. return {
  2705. schema: foundschema,
  2706. pathType: foundschema.$isSingleNested ? 'nested' : 'array'
  2707. };
  2708. }
  2709. return { schema: foundschema, pathType: 'real' };
  2710. } else if (p === parts.length && schema.nested[trypath]) {
  2711. return { schema: schema, pathType: 'nested' };
  2712. }
  2713. }
  2714. return { schema: foundschema || schema, pathType: 'undefined' };
  2715. }
  2716. // look for arrays
  2717. return search(path.split('.'), _this);
  2718. };
  2719. /**
  2720. * Transforms the duplicate key error by checking for duplicate key error messages by path.
  2721. * If no duplicate key error messages are found, returns the original error.
  2722. *
  2723. * @param {Error} error The error to transform
  2724. * @returns {Error} The transformed error
  2725. * @api private
  2726. */
  2727. Schema.prototype._transformDuplicateKeyError = function _transformDuplicateKeyError(error) {
  2728. if (!this._duplicateKeyErrorMessagesByPath) {
  2729. return error;
  2730. }
  2731. if (error.code !== 11000 && error.code !== 11001) {
  2732. return error;
  2733. }
  2734. if (error.keyPattern != null) {
  2735. const keyPattern = error.keyPattern;
  2736. const keys = Object.keys(keyPattern);
  2737. if (keys.length !== 1) {
  2738. return error;
  2739. }
  2740. const firstKey = keys[0];
  2741. if (!Object.hasOwn(this._duplicateKeyErrorMessagesByPath, firstKey)) {
  2742. return error;
  2743. }
  2744. return new MongooseError(this._duplicateKeyErrorMessagesByPath[firstKey], { cause: error });
  2745. }
  2746. return error;
  2747. };
  2748. /*!
  2749. * ignore
  2750. */
  2751. function isArrayFilter(piece) {
  2752. return piece.startsWith('$[') && piece.endsWith(']');
  2753. }
  2754. /**
  2755. * Called by `compile()` _right before_ compiling. Good for making any changes to
  2756. * the schema that should respect options set by plugins, like `id`
  2757. * @method _preCompile
  2758. * @memberOf Schema
  2759. * @instance
  2760. * @api private
  2761. */
  2762. Schema.prototype._preCompile = function _preCompile() {
  2763. this.plugin(idGetter, { deduplicate: true });
  2764. };
  2765. /**
  2766. * Returns a JSON schema representation of this Schema.
  2767. *
  2768. * By default, returns normal [JSON schema representation](https://json-schema.org/learn/getting-started-step-by-step), which is not typically what you want to use with
  2769. * [MongoDB's `$jsonSchema` collection option](https://www.mongodb.com/docs/manual/core/schema-validation/specify-json-schema/).
  2770. * Use the `useBsonType: true` option to return MongoDB `$jsonSchema` syntax instead.
  2771. *
  2772. * In addition to types, `jsonSchema()` supports the following Mongoose validators:
  2773. * - `enum` for strings and numbers
  2774. *
  2775. * #### Example:
  2776. * const schema = new Schema({ name: String });
  2777. * // { required: ['_id'], properties: { name: { type: ['string', 'null'] }, _id: { type: 'string' } } }
  2778. * schema.toJSONSchema();
  2779. *
  2780. * // { required: ['_id'], properties: { name: { bsonType: ['string', 'null'] }, _id: { bsonType: 'objectId' } } }
  2781. * schema.toJSONSchema({ useBsonType: true });
  2782. *
  2783. * @param {Object} [options]
  2784. * @param [Boolean] [options.useBsonType=false] if true, specify each path's type using `bsonType` rather than `type` for MongoDB $jsonSchema support
  2785. */
  2786. Schema.prototype.toJSONSchema = function toJSONSchema(options) {
  2787. const useBsonType = options?.useBsonType ?? false;
  2788. const result = useBsonType ? { required: [], properties: {} } : { type: 'object', required: [], properties: {} };
  2789. for (const path of Object.keys(this.paths)) {
  2790. const schemaType = this.paths[path];
  2791. // Skip Map embedded paths, maps will be handled seperately.
  2792. if (schemaType._presplitPath.indexOf('$*') !== -1) {
  2793. continue;
  2794. }
  2795. // Nested paths are stored as `nested.path` in the schema type, so create nested paths in the json schema
  2796. // when necessary.
  2797. const isNested = schemaType._presplitPath.length > 1;
  2798. let jsonSchemaForPath = result;
  2799. if (isNested) {
  2800. for (let i = 0; i < schemaType._presplitPath.length - 1; ++i) {
  2801. const subpath = schemaType._presplitPath[i];
  2802. if (jsonSchemaForPath.properties[subpath] == null) {
  2803. jsonSchemaForPath.properties[subpath] = useBsonType
  2804. ? {
  2805. bsonType: ['object', 'null'],
  2806. properties: {}
  2807. }
  2808. : {
  2809. type: ['object', 'null'],
  2810. properties: {}
  2811. };
  2812. }
  2813. jsonSchemaForPath = jsonSchemaForPath.properties[subpath];
  2814. }
  2815. }
  2816. const lastSubpath = schemaType._presplitPath[schemaType._presplitPath.length - 1];
  2817. let isRequired = false;
  2818. if (path === '_id') {
  2819. if (!jsonSchemaForPath.required) {
  2820. jsonSchemaForPath.required = [];
  2821. }
  2822. jsonSchemaForPath.required.push('_id');
  2823. isRequired = true;
  2824. } else if (schemaType.options.required && typeof schemaType.options.required !== 'function') {
  2825. if (!jsonSchemaForPath.required) {
  2826. jsonSchemaForPath.required = [];
  2827. }
  2828. // Only `required: true` paths are required, conditional required is not required
  2829. jsonSchemaForPath.required.push(lastSubpath);
  2830. isRequired = true;
  2831. }
  2832. jsonSchemaForPath.properties[lastSubpath] = schemaType.toJSONSchema(options);
  2833. if (schemaType.options.enum) {
  2834. jsonSchemaForPath.properties[lastSubpath].enum = isRequired
  2835. ? schemaType.options.enum
  2836. : [...schemaType.options.enum, null];
  2837. }
  2838. }
  2839. // Otherwise MongoDB errors with "$jsonSchema keyword 'required' cannot be an empty array"
  2840. if (result.required.length === 0) {
  2841. delete result.required;
  2842. }
  2843. return result;
  2844. };
  2845. /*!
  2846. * Module exports.
  2847. */
  2848. module.exports = exports = Schema;
  2849. // require down here because of reference issues
  2850. /**
  2851. * The various built-in Mongoose Schema Types.
  2852. *
  2853. * #### Example:
  2854. *
  2855. * const mongoose = require('mongoose');
  2856. * const ObjectId = mongoose.Schema.Types.ObjectId;
  2857. *
  2858. * #### Types:
  2859. *
  2860. * - [String](https://mongoosejs.com/docs/schematypes.html#strings)
  2861. * - [Number](https://mongoosejs.com/docs/schematypes.html#numbers)
  2862. * - [Boolean](https://mongoosejs.com/docs/schematypes.html#booleans) | Bool
  2863. * - [Array](https://mongoosejs.com/docs/schematypes.html#arrays)
  2864. * - [Buffer](https://mongoosejs.com/docs/schematypes.html#buffers)
  2865. * - [Date](https://mongoosejs.com/docs/schematypes.html#dates)
  2866. * - [ObjectId](https://mongoosejs.com/docs/schematypes.html#objectids) | Oid
  2867. * - [Mixed](https://mongoosejs.com/docs/schematypes.html#mixed)
  2868. * - [UUID](https://mongoosejs.com/docs/schematypes.html#uuid)
  2869. * - [BigInt](https://mongoosejs.com/docs/schematypes.html#bigint)
  2870. * - [Double] (https://mongoosejs.com/docs/schematypes.html#double)
  2871. * - [Int32](https://mongoosejs.com/docs/schematypes.html#int32)
  2872. *
  2873. * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema.
  2874. *
  2875. * const Mixed = mongoose.Schema.Types.Mixed;
  2876. * new mongoose.Schema({ _user: Mixed })
  2877. *
  2878. * @api public
  2879. */
  2880. Schema.Types = MongooseTypes = require('./schema/index');
  2881. /*!
  2882. * ignore
  2883. */
  2884. exports.ObjectId = MongooseTypes.ObjectId;