schema.js 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195
  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 applyTimestampsToChildren = require('./helpers/update/applyTimestampsToChildren');
  14. const applyTimestampsToUpdate = require('./helpers/update/applyTimestampsToUpdate');
  15. const arrayParentSymbol = require('./helpers/symbols').arrayParentSymbol;
  16. const get = require('./helpers/get');
  17. const getIndexes = require('./helpers/schema/getIndexes');
  18. const handleTimestampOption = require('./helpers/schema/handleTimestampOption');
  19. const merge = require('./helpers/schema/merge');
  20. const mpath = require('mpath');
  21. const readPref = require('./driver').get().ReadPreference;
  22. const symbols = require('./schema/symbols');
  23. const util = require('util');
  24. const utils = require('./utils');
  25. const validateRef = require('./helpers/populate/validateRef');
  26. let MongooseTypes;
  27. const queryHooks = require('./helpers/query/applyQueryMiddleware').
  28. middlewareFunctions;
  29. const documentHooks = require('./helpers/model/applyHooks').middlewareFunctions;
  30. const hookNames = queryHooks.concat(documentHooks).
  31. reduce((s, hook) => s.add(hook), new Set());
  32. let id = 0;
  33. /**
  34. * Schema constructor.
  35. *
  36. * ####Example:
  37. *
  38. * var child = new Schema({ name: String });
  39. * var schema = new Schema({ name: String, age: Number, children: [child] });
  40. * var Tree = mongoose.model('Tree', schema);
  41. *
  42. * // setting schema options
  43. * new Schema({ name: String }, { _id: false, autoIndex: false })
  44. *
  45. * ####Options:
  46. *
  47. * - [autoIndex](/docs/guide.html#autoIndex): bool - defaults to null (which means use the connection's autoIndex option)
  48. * - [autoCreate](/docs/guide.html#autoCreate): bool - defaults to null (which means use the connection's autoCreate option)
  49. * - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true
  50. * - [capped](/docs/guide.html#capped): bool - defaults to false
  51. * - [collection](/docs/guide.html#collection): string - no default
  52. * - [id](/docs/guide.html#id): bool - defaults to true
  53. * - [_id](/docs/guide.html#_id): bool - defaults to true
  54. * - [minimize](/docs/guide.html#minimize): bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true
  55. * - [read](/docs/guide.html#read): string
  56. * - [writeConcern](/docs/guide.html#writeConcern): object - defaults to null, use to override [the MongoDB server's default write concern settings](https://docs.mongodb.com/manual/reference/write-concern/)
  57. * - [shardKey](/docs/guide.html#shardKey): object - defaults to `null`
  58. * - [strict](/docs/guide.html#strict): bool - defaults to true
  59. * - [strictQuery](/docs/guide.html#strictQuery): bool - defaults to false
  60. * - [toJSON](/docs/guide.html#toJSON) - object - no default
  61. * - [toObject](/docs/guide.html#toObject) - object - no default
  62. * - [typeKey](/docs/guide.html#typeKey) - string - defaults to 'type'
  63. * - [typePojoToMixed](/docs/guide.html#typePojoToMixed) - boolean - defaults to true. Determines whether a type set to a POJO becomes a Mixed path or a Subdocument
  64. * - [useNestedStrict](/docs/guide.html#useNestedStrict) - boolean - defaults to false
  65. * - [validateBeforeSave](/docs/guide.html#validateBeforeSave) - bool - defaults to `true`
  66. * - [versionKey](/docs/guide.html#versionKey): string - defaults to "__v"
  67. * - [collation](/docs/guide.html#collation): object - defaults to null (which means use no collation)
  68. * - [selectPopulatedPaths](/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true`
  69. * - [skipVersioning](/docs/guide.html#skipVersioning): object - paths to exclude from versioning
  70. * - [timestamps](/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.
  71. * - [storeSubdocValidationError](/docs/guide.html#storeSubdocValidationError): boolean - Defaults to true. If false, Mongoose will wrap validation errors in single nested document subpaths into a single validation error on the single nested subdoc's path.
  72. *
  73. * ####Options for Nested Schemas:
  74. * - `excludeIndexes`: bool - defaults to `false`. If `true`, skip building indexes on this schema's paths.
  75. *
  76. * ####Note:
  77. *
  78. * _When nesting schemas, (`children` in the example above), always declare the child schema first before passing it into its parent._
  79. *
  80. * @param {Object|Schema|Array} [definition] Can be one of: object describing schema paths, or schema to copy, or array of objects and schemas
  81. * @param {Object} [options]
  82. * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
  83. * @event `init`: Emitted after the schema is compiled into a `Model`.
  84. * @api public
  85. */
  86. function Schema(obj, options) {
  87. if (!(this instanceof Schema)) {
  88. return new Schema(obj, options);
  89. }
  90. this.obj = obj;
  91. this.paths = {};
  92. this.aliases = {};
  93. this.subpaths = {};
  94. this.virtuals = {};
  95. this.singleNestedPaths = {};
  96. this.nested = {};
  97. this.inherits = {};
  98. this.callQueue = [];
  99. this._indexes = [];
  100. this.methods = {};
  101. this.methodOptions = {};
  102. this.statics = {};
  103. this.tree = {};
  104. this.query = {};
  105. this.childSchemas = [];
  106. this.plugins = [];
  107. // For internal debugging. Do not use this to try to save a schema in MDB.
  108. this.$id = ++id;
  109. this.s = {
  110. hooks: new Kareem()
  111. };
  112. this.options = this.defaultOptions(options);
  113. // build paths
  114. if (Array.isArray(obj)) {
  115. for (const definition of obj) {
  116. this.add(definition);
  117. }
  118. } else if (obj) {
  119. this.add(obj);
  120. }
  121. // check if _id's value is a subdocument (gh-2276)
  122. const _idSubDoc = obj && obj._id && utils.isObject(obj._id);
  123. // ensure the documents get an auto _id unless disabled
  124. const auto_id = !this.paths['_id'] &&
  125. (!this.options.noId && this.options._id) && !_idSubDoc;
  126. if (auto_id) {
  127. addAutoId(this);
  128. }
  129. this.setupTimestamp(this.options.timestamps);
  130. }
  131. /*!
  132. * Create virtual properties with alias field
  133. */
  134. function aliasFields(schema, paths) {
  135. paths = paths || Object.keys(schema.paths);
  136. for (const path of paths) {
  137. const options = get(schema.paths[path], 'options');
  138. if (options == null) {
  139. continue;
  140. }
  141. const prop = schema.paths[path].path;
  142. const alias = options.alias;
  143. if (!alias) {
  144. continue;
  145. }
  146. if (typeof alias !== 'string') {
  147. throw new Error('Invalid value for alias option on ' + prop + ', got ' + alias);
  148. }
  149. schema.aliases[alias] = prop;
  150. schema.
  151. virtual(alias).
  152. get((function(p) {
  153. return function() {
  154. if (typeof this.get === 'function') {
  155. return this.get(p);
  156. }
  157. return this[p];
  158. };
  159. })(prop)).
  160. set((function(p) {
  161. return function(v) {
  162. return this.set(p, v);
  163. };
  164. })(prop));
  165. }
  166. }
  167. /*!
  168. * Inherit from EventEmitter.
  169. */
  170. Schema.prototype = Object.create(EventEmitter.prototype);
  171. Schema.prototype.constructor = Schema;
  172. Schema.prototype.instanceOfSchema = true;
  173. /*!
  174. * ignore
  175. */
  176. Object.defineProperty(Schema.prototype, '$schemaType', {
  177. configurable: false,
  178. enumerable: false,
  179. writable: true
  180. });
  181. /**
  182. * Array of child schemas (from document arrays and single nested subdocs)
  183. * and their corresponding compiled models. Each element of the array is
  184. * an object with 2 properties: `schema` and `model`.
  185. *
  186. * This property is typically only useful for plugin authors and advanced users.
  187. * You do not need to interact with this property at all to use mongoose.
  188. *
  189. * @api public
  190. * @property childSchemas
  191. * @memberOf Schema
  192. * @instance
  193. */
  194. Object.defineProperty(Schema.prototype, 'childSchemas', {
  195. configurable: false,
  196. enumerable: true,
  197. writable: true
  198. });
  199. /**
  200. * The original object passed to the schema constructor
  201. *
  202. * ####Example:
  203. *
  204. * var schema = new Schema({ a: String }).add({ b: String });
  205. * schema.obj; // { a: String }
  206. *
  207. * @api public
  208. * @property obj
  209. * @memberOf Schema
  210. * @instance
  211. */
  212. Schema.prototype.obj;
  213. /**
  214. * The paths defined on this schema. The keys are the top-level paths
  215. * in this schema, and the values are instances of the SchemaType class.
  216. *
  217. * ####Example:
  218. * const schema = new Schema({ name: String }, { _id: false });
  219. * schema.paths; // { name: SchemaString { ... } }
  220. *
  221. * schema.add({ age: Number });
  222. * schema.paths; // { name: SchemaString { ... }, age: SchemaNumber { ... } }
  223. *
  224. * @api public
  225. * @property paths
  226. * @memberOf Schema
  227. * @instance
  228. */
  229. Schema.prototype.paths;
  230. /**
  231. * Schema as a tree
  232. *
  233. * ####Example:
  234. * {
  235. * '_id' : ObjectId
  236. * , 'nested' : {
  237. * 'key' : String
  238. * }
  239. * }
  240. *
  241. * @api private
  242. * @property tree
  243. * @memberOf Schema
  244. * @instance
  245. */
  246. Schema.prototype.tree;
  247. /**
  248. * Returns a deep copy of the schema
  249. *
  250. * ####Example:
  251. *
  252. * const schema = new Schema({ name: String });
  253. * const clone = schema.clone();
  254. * clone === schema; // false
  255. * clone.path('name'); // SchemaString { ... }
  256. *
  257. * @return {Schema} the cloned schema
  258. * @api public
  259. * @memberOf Schema
  260. * @instance
  261. */
  262. Schema.prototype.clone = function() {
  263. const s = new Schema({}, this._userProvidedOptions);
  264. s.base = this.base;
  265. s.obj = this.obj;
  266. s.options = utils.clone(this.options);
  267. s.callQueue = this.callQueue.map(function(f) { return f; });
  268. s.methods = utils.clone(this.methods);
  269. s.methodOptions = utils.clone(this.methodOptions);
  270. s.statics = utils.clone(this.statics);
  271. s.query = utils.clone(this.query);
  272. s.plugins = Array.prototype.slice.call(this.plugins);
  273. s._indexes = utils.clone(this._indexes);
  274. s.s.hooks = this.s.hooks.clone();
  275. s.tree = utils.clone(this.tree);
  276. s.paths = utils.clone(this.paths);
  277. s.nested = utils.clone(this.nested);
  278. s.subpaths = utils.clone(this.subpaths);
  279. s.singleNestedPaths = utils.clone(this.singleNestedPaths);
  280. s.childSchemas = gatherChildSchemas(s);
  281. s.virtuals = utils.clone(this.virtuals);
  282. s.$globalPluginsApplied = this.$globalPluginsApplied;
  283. s.$isRootDiscriminator = this.$isRootDiscriminator;
  284. s.$implicitlyCreated = this.$implicitlyCreated;
  285. if (this.discriminatorMapping != null) {
  286. s.discriminatorMapping = Object.assign({}, this.discriminatorMapping);
  287. }
  288. if (this.discriminators != null) {
  289. s.discriminators = Object.assign({}, this.discriminators);
  290. }
  291. s.aliases = Object.assign({}, this.aliases);
  292. // Bubble up `init` for backwards compat
  293. s.on('init', v => this.emit('init', v));
  294. return s;
  295. };
  296. /**
  297. * Returns a new schema that has the picked `paths` from this schema.
  298. *
  299. * This method is analagous to [Lodash's `pick()` function](https://lodash.com/docs/4.17.15#pick) for Mongoose schemas.
  300. *
  301. * ####Example:
  302. *
  303. * const schema = Schema({ name: String, age: Number });
  304. * // Creates a new schema with the same `name` path as `schema`,
  305. * // but no `age` path.
  306. * const newSchema = schema.pick(['name']);
  307. *
  308. * newSchema.path('name'); // SchemaString { ... }
  309. * newSchema.path('age'); // undefined
  310. *
  311. * @param {Array} paths list of paths to pick
  312. * @param {Object} [options] options to pass to the schema constructor. Defaults to `this.options` if not set.
  313. * @return {Schema}
  314. * @api public
  315. */
  316. Schema.prototype.pick = function(paths, options) {
  317. const newSchema = new Schema({}, options || this.options);
  318. if (!Array.isArray(paths)) {
  319. throw new MongooseError('Schema#pick() only accepts an array argument, ' +
  320. 'got "' + typeof paths + '"');
  321. }
  322. for (const path of paths) {
  323. if (this.nested[path]) {
  324. newSchema.add({ [path]: get(this.tree, path) });
  325. } else {
  326. const schematype = this.path(path);
  327. if (schematype == null) {
  328. throw new MongooseError('Path `' + path + '` is not in the schema');
  329. }
  330. newSchema.add({ [path]: schematype });
  331. }
  332. }
  333. return newSchema;
  334. };
  335. /**
  336. * Returns default options for this schema, merged with `options`.
  337. *
  338. * @param {Object} options
  339. * @return {Object}
  340. * @api private
  341. */
  342. Schema.prototype.defaultOptions = function(options) {
  343. if (options && options.safe === false) {
  344. options.safe = { w: 0 };
  345. }
  346. if (options && options.safe && options.safe.w === 0) {
  347. // if you turn off safe writes, then versioning goes off as well
  348. options.versionKey = false;
  349. }
  350. this._userProvidedOptions = options == null ? {} : utils.clone(options);
  351. const baseOptions = get(this, 'base.options', {});
  352. options = utils.options({
  353. strict: 'strict' in baseOptions ? baseOptions.strict : true,
  354. bufferCommands: true,
  355. capped: false, // { size, max, autoIndexId }
  356. versionKey: '__v',
  357. discriminatorKey: '__t',
  358. minimize: true,
  359. autoIndex: null,
  360. shardKey: null,
  361. read: null,
  362. validateBeforeSave: true,
  363. // the following are only applied at construction time
  364. noId: false, // deprecated, use { _id: false }
  365. _id: true,
  366. noVirtualId: false, // deprecated, use { id: false }
  367. id: true,
  368. typeKey: 'type',
  369. typePojoToMixed: 'typePojoToMixed' in baseOptions ? baseOptions.typePojoToMixed : true
  370. }, utils.clone(options));
  371. if (options.read) {
  372. options.read = readPref(options.read);
  373. }
  374. return options;
  375. };
  376. /**
  377. * Adds key path / schema type pairs to this schema.
  378. *
  379. * ####Example:
  380. *
  381. * const ToySchema = new Schema();
  382. * ToySchema.add({ name: 'string', color: 'string', price: 'number' });
  383. *
  384. * const TurboManSchema = new Schema();
  385. * // You can also `add()` another schema and copy over all paths, virtuals,
  386. * // getters, setters, indexes, methods, and statics.
  387. * TurboManSchema.add(ToySchema).add({ year: Number });
  388. *
  389. * @param {Object|Schema} obj plain object with paths to add, or another schema
  390. * @param {String} [prefix] path to prefix the newly added paths with
  391. * @return {Schema} the Schema instance
  392. * @api public
  393. */
  394. Schema.prototype.add = function add(obj, prefix) {
  395. if (obj instanceof Schema) {
  396. merge(this, obj);
  397. return this;
  398. }
  399. // Special case: setting top-level `_id` to false should convert to disabling
  400. // the `_id` option. This behavior never worked before 5.4.11 but numerous
  401. // codebases use it (see gh-7516, gh-7512).
  402. if (obj._id === false && prefix == null) {
  403. this.options._id = false;
  404. }
  405. prefix = prefix || '';
  406. const keys = Object.keys(obj);
  407. for (const key of keys) {
  408. const fullPath = prefix + key;
  409. if (obj[key] == null) {
  410. throw new TypeError('Invalid value for schema path `' + fullPath +
  411. '`, got value "' + obj[key] + '"');
  412. }
  413. // Retain `_id: false` but don't set it as a path, re: gh-8274.
  414. if (key === '_id' && obj[key] === false) {
  415. continue;
  416. }
  417. if (obj[key] instanceof VirtualType) {
  418. this.virtual(obj[key]);
  419. continue;
  420. }
  421. if (Array.isArray(obj[key]) && obj[key].length === 1 && obj[key][0] == null) {
  422. throw new TypeError('Invalid value for schema Array path `' + fullPath +
  423. '`, got value "' + obj[key][0] + '"');
  424. }
  425. if (!(utils.isPOJO(obj[key]) || obj[key] instanceof SchemaTypeOptions)) {
  426. // Special-case: Non-options definitely a path so leaf at this node
  427. // Examples: Schema instances, SchemaType instances
  428. if (prefix) {
  429. this.nested[prefix.substr(0, prefix.length - 1)] = true;
  430. }
  431. this.path(prefix + key, obj[key]);
  432. } else if (Object.keys(obj[key]).length < 1) {
  433. // Special-case: {} always interpreted as Mixed path so leaf at this node
  434. if (prefix) {
  435. this.nested[prefix.substr(0, prefix.length - 1)] = true;
  436. }
  437. this.path(fullPath, obj[key]); // mixed type
  438. } else if (!obj[key][this.options.typeKey] || (this.options.typeKey === 'type' && obj[key].type.type)) {
  439. // Special-case: POJO with no bona-fide type key - interpret as tree of deep paths so recurse
  440. // nested object { last: { name: String }}
  441. this.nested[fullPath] = true;
  442. this.add(obj[key], fullPath + '.');
  443. } else {
  444. // There IS a bona-fide type key that may also be a POJO
  445. if (!this.options.typePojoToMixed && utils.isPOJO(obj[key][this.options.typeKey])) {
  446. // If a POJO is the value of a type key, make it a subdocument
  447. if (prefix) {
  448. this.nested[prefix.substr(0, prefix.length - 1)] = true;
  449. }
  450. // Propage `typePojoToMixed` to implicitly created schemas
  451. const opts = { typePojoToMixed: false };
  452. const _schema = new Schema(obj[key][this.options.typeKey], opts);
  453. const schemaWrappedPath = Object.assign({}, obj[key], { type: _schema });
  454. this.path(prefix + key, schemaWrappedPath);
  455. } else {
  456. // Either the type is non-POJO or we interpret it as Mixed anyway
  457. if (prefix) {
  458. this.nested[prefix.substr(0, prefix.length - 1)] = true;
  459. }
  460. this.path(prefix + key, obj[key]);
  461. }
  462. }
  463. }
  464. const addedKeys = Object.keys(obj).
  465. map(key => prefix ? prefix + key : key);
  466. aliasFields(this, addedKeys);
  467. return this;
  468. };
  469. /**
  470. * Reserved document keys.
  471. *
  472. * Keys in this object are names that are rejected in schema declarations
  473. * because they conflict with Mongoose functionality. If you create a schema
  474. * using `new Schema()` with one of these property names, Mongoose will throw
  475. * an error.
  476. *
  477. * - _posts
  478. * - _pres
  479. * - collection
  480. * - emit
  481. * - errors
  482. * - get
  483. * - init
  484. * - isModified
  485. * - isNew
  486. * - listeners
  487. * - modelName
  488. * - on
  489. * - once
  490. * - populated
  491. * - prototype
  492. * - remove
  493. * - removeListener
  494. * - save
  495. * - schema
  496. * - toObject
  497. * - validate
  498. *
  499. * _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.
  500. *
  501. * var schema = new Schema(..);
  502. * schema.methods.init = function () {} // potentially breaking
  503. */
  504. Schema.reserved = Object.create(null);
  505. Schema.prototype.reserved = Schema.reserved;
  506. const reserved = Schema.reserved;
  507. // Core object
  508. reserved['prototype'] =
  509. // EventEmitter
  510. reserved.emit =
  511. reserved.listeners =
  512. reserved.on =
  513. reserved.removeListener =
  514. // document properties and functions
  515. reserved.collection =
  516. reserved.errors =
  517. reserved.get =
  518. reserved.init =
  519. reserved.isModified =
  520. reserved.isNew =
  521. reserved.populated =
  522. reserved.remove =
  523. reserved.save =
  524. reserved.schema =
  525. reserved.toObject =
  526. reserved.validate = 1;
  527. /*!
  528. * Document keys to print warnings for
  529. */
  530. const warnings = {};
  531. warnings.increment = '`increment` should not be used as a schema path name ' +
  532. 'unless you have disabled versioning.';
  533. /**
  534. * Gets/sets schema paths.
  535. *
  536. * Sets a path (if arity 2)
  537. * Gets a path (if arity 1)
  538. *
  539. * ####Example
  540. *
  541. * schema.path('name') // returns a SchemaType
  542. * schema.path('name', Number) // changes the schemaType of `name` to Number
  543. *
  544. * @param {String} path
  545. * @param {Object} constructor
  546. * @api public
  547. */
  548. Schema.prototype.path = function(path, obj) {
  549. // Convert to '.$' to check subpaths re: gh-6405
  550. const cleanPath = _pathToPositionalSyntax(path);
  551. if (obj === undefined) {
  552. let schematype = _getPath(this, path, cleanPath);
  553. if (schematype != null) {
  554. return schematype;
  555. }
  556. // Look for maps
  557. const mapPath = getMapPath(this, path);
  558. if (mapPath != null) {
  559. return mapPath;
  560. }
  561. // Look if a parent of this path is mixed
  562. schematype = this.hasMixedParent(cleanPath);
  563. if (schematype != null) {
  564. return schematype;
  565. }
  566. // subpaths?
  567. return /\.\d+\.?.*$/.test(path)
  568. ? getPositionalPath(this, path)
  569. : undefined;
  570. }
  571. // some path names conflict with document methods
  572. const firstPieceOfPath = path.split('.')[0];
  573. if (reserved[firstPieceOfPath]) {
  574. throw new Error('`' + firstPieceOfPath + '` may not be used as a schema pathname');
  575. }
  576. if (warnings[path]) {
  577. console.log('WARN: ' + warnings[path]);
  578. }
  579. if (typeof obj === 'object' && utils.hasUserDefinedProperty(obj, 'ref')) {
  580. validateRef(obj.ref, path);
  581. }
  582. // update the tree
  583. const subpaths = path.split(/\./);
  584. const last = subpaths.pop();
  585. let branch = this.tree;
  586. let fullPath = '';
  587. for (const sub of subpaths) {
  588. fullPath = fullPath += (fullPath.length > 0 ? '.' : '') + sub;
  589. if (!branch[sub]) {
  590. this.nested[fullPath] = true;
  591. branch[sub] = {};
  592. }
  593. if (typeof branch[sub] !== 'object') {
  594. const msg = 'Cannot set nested path `' + path + '`. '
  595. + 'Parent path `'
  596. + fullPath
  597. + '` already set to type ' + branch[sub].name
  598. + '.';
  599. throw new Error(msg);
  600. }
  601. branch = branch[sub];
  602. }
  603. branch[last] = utils.clone(obj);
  604. this.paths[path] = this.interpretAsType(path, obj, this.options);
  605. const schemaType = this.paths[path];
  606. if (schemaType.$isSchemaMap) {
  607. // Maps can have arbitrary keys, so `$*` is internal shorthand for "any key"
  608. // The '$' is to imply this path should never be stored in MongoDB so we
  609. // can easily build a regexp out of this path, and '*' to imply "any key."
  610. const mapPath = path + '.$*';
  611. let _mapType = { type: {} };
  612. if (utils.hasUserDefinedProperty(obj, 'of')) {
  613. const isInlineSchema = utils.isPOJO(obj.of) &&
  614. Object.keys(obj.of).length > 0 &&
  615. !utils.hasUserDefinedProperty(obj.of, this.options.typeKey);
  616. _mapType = isInlineSchema ? new Schema(obj.of) : obj.of;
  617. }
  618. this.paths[mapPath] = this.interpretAsType(mapPath,
  619. _mapType, this.options);
  620. schemaType.$__schemaType = this.paths[mapPath];
  621. }
  622. if (schemaType.$isSingleNested) {
  623. for (const key in schemaType.schema.paths) {
  624. this.singleNestedPaths[path + '.' + key] = schemaType.schema.paths[key];
  625. }
  626. for (const key in schemaType.schema.singleNestedPaths) {
  627. this.singleNestedPaths[path + '.' + key] =
  628. schemaType.schema.singleNestedPaths[key];
  629. }
  630. for (const key in schemaType.schema.subpaths) {
  631. this.singleNestedPaths[path + '.' + key] =
  632. schemaType.schema.subpaths[key];
  633. }
  634. Object.defineProperty(schemaType.schema, 'base', {
  635. configurable: true,
  636. enumerable: false,
  637. writable: false,
  638. value: this.base
  639. });
  640. schemaType.caster.base = this.base;
  641. this.childSchemas.push({
  642. schema: schemaType.schema,
  643. model: schemaType.caster
  644. });
  645. } else if (schemaType.$isMongooseDocumentArray) {
  646. Object.defineProperty(schemaType.schema, 'base', {
  647. configurable: true,
  648. enumerable: false,
  649. writable: false,
  650. value: this.base
  651. });
  652. schemaType.casterConstructor.base = this.base;
  653. this.childSchemas.push({
  654. schema: schemaType.schema,
  655. model: schemaType.casterConstructor
  656. });
  657. }
  658. if (schemaType.$isMongooseArray && schemaType.caster instanceof SchemaType) {
  659. let arrayPath = path;
  660. let _schemaType = schemaType;
  661. const toAdd = [];
  662. while (_schemaType.$isMongooseArray) {
  663. arrayPath = arrayPath + '.$';
  664. // Skip arrays of document arrays
  665. if (_schemaType.$isMongooseDocumentArray) {
  666. _schemaType.$embeddedSchemaType._arrayPath = arrayPath;
  667. _schemaType = _schemaType.$embeddedSchemaType.clone();
  668. } else {
  669. _schemaType.caster._arrayPath = arrayPath;
  670. _schemaType = _schemaType.caster.clone();
  671. }
  672. _schemaType.path = arrayPath;
  673. toAdd.push(_schemaType);
  674. }
  675. for (const _schemaType of toAdd) {
  676. this.subpaths[_schemaType.path] = _schemaType;
  677. }
  678. }
  679. if (schemaType.$isMongooseDocumentArray) {
  680. for (const key of Object.keys(schemaType.schema.paths)) {
  681. this.subpaths[path + '.' + key] = schemaType.schema.paths[key];
  682. schemaType.schema.paths[key].$isUnderneathDocArray = true;
  683. }
  684. for (const key of Object.keys(schemaType.schema.subpaths)) {
  685. this.subpaths[path + '.' + key] = schemaType.schema.subpaths[key];
  686. schemaType.schema.subpaths[key].$isUnderneathDocArray = true;
  687. }
  688. for (const key of Object.keys(schemaType.schema.singleNestedPaths)) {
  689. this.subpaths[path + '.' + key] = schemaType.schema.singleNestedPaths[key];
  690. schemaType.schema.singleNestedPaths[key].$isUnderneathDocArray = true;
  691. }
  692. }
  693. return this;
  694. };
  695. /*!
  696. * ignore
  697. */
  698. function gatherChildSchemas(schema) {
  699. const childSchemas = [];
  700. for (const path of Object.keys(schema.paths)) {
  701. const schematype = schema.paths[path];
  702. if (schematype.$isMongooseDocumentArray || schematype.$isSingleNested) {
  703. childSchemas.push({ schema: schematype.schema, model: schematype.caster });
  704. }
  705. }
  706. return childSchemas;
  707. }
  708. /*!
  709. * ignore
  710. */
  711. function _getPath(schema, path, cleanPath) {
  712. if (schema.paths.hasOwnProperty(path)) {
  713. return schema.paths[path];
  714. }
  715. if (schema.subpaths.hasOwnProperty(cleanPath)) {
  716. return schema.subpaths[cleanPath];
  717. }
  718. if (schema.singleNestedPaths.hasOwnProperty(cleanPath)) {
  719. return schema.singleNestedPaths[cleanPath];
  720. }
  721. return null;
  722. }
  723. /*!
  724. * ignore
  725. */
  726. function _pathToPositionalSyntax(path) {
  727. if (!/\.\d+/.test(path)) {
  728. return path;
  729. }
  730. return path.replace(/\.\d+\./g, '.$.').replace(/\.\d+$/, '.$');
  731. }
  732. /*!
  733. * ignore
  734. */
  735. function getMapPath(schema, path) {
  736. for (const _path of Object.keys(schema.paths)) {
  737. if (!_path.includes('.$*')) {
  738. continue;
  739. }
  740. const re = new RegExp('^' + _path.replace(/\.\$\*/g, '\\.[^.]+') + '$');
  741. if (re.test(path)) {
  742. return schema.paths[_path];
  743. }
  744. }
  745. return null;
  746. }
  747. /**
  748. * The Mongoose instance this schema is associated with
  749. *
  750. * @property base
  751. * @api private
  752. */
  753. Object.defineProperty(Schema.prototype, 'base', {
  754. configurable: true,
  755. enumerable: false,
  756. writable: true,
  757. value: null
  758. });
  759. /**
  760. * Converts type arguments into Mongoose Types.
  761. *
  762. * @param {String} path
  763. * @param {Object} obj constructor
  764. * @api private
  765. */
  766. Schema.prototype.interpretAsType = function(path, obj, options) {
  767. if (obj instanceof SchemaType) {
  768. return obj;
  769. }
  770. // If this schema has an associated Mongoose object, use the Mongoose object's
  771. // copy of SchemaTypes re: gh-7158 gh-6933
  772. const MongooseTypes = this.base != null ? this.base.Schema.Types : Schema.Types;
  773. if (!utils.isPOJO(obj) && !(obj instanceof SchemaTypeOptions)) {
  774. const constructorName = utils.getFunctionName(obj.constructor);
  775. if (constructorName !== 'Object') {
  776. const oldObj = obj;
  777. obj = {};
  778. obj[options.typeKey] = oldObj;
  779. }
  780. }
  781. // Get the type making sure to allow keys named "type"
  782. // and default to mixed if not specified.
  783. // { type: { type: String, default: 'freshcut' } }
  784. let type = obj[options.typeKey] && (options.typeKey !== 'type' || !obj.type.type)
  785. ? obj[options.typeKey]
  786. : {};
  787. let name;
  788. if (utils.isPOJO(type) || type === 'mixed') {
  789. return new MongooseTypes.Mixed(path, obj);
  790. }
  791. if (Array.isArray(type) || Array === type || type === 'array') {
  792. // if it was specified through { type } look for `cast`
  793. let cast = (Array === type || type === 'array')
  794. ? obj.cast
  795. : type[0];
  796. if (cast && cast.instanceOfSchema) {
  797. return new MongooseTypes.DocumentArray(path, cast, obj);
  798. }
  799. if (cast &&
  800. cast[options.typeKey] &&
  801. cast[options.typeKey].instanceOfSchema) {
  802. return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast);
  803. }
  804. if (Array.isArray(cast)) {
  805. return new MongooseTypes.Array(path, this.interpretAsType(path, cast, options), obj);
  806. }
  807. if (typeof cast === 'string') {
  808. cast = MongooseTypes[cast.charAt(0).toUpperCase() + cast.substring(1)];
  809. } else if (cast && (!cast[options.typeKey] || (options.typeKey === 'type' && cast.type.type))
  810. && utils.isPOJO(cast)) {
  811. if (Object.keys(cast).length) {
  812. // The `minimize` and `typeKey` options propagate to child schemas
  813. // declared inline, like `{ arr: [{ val: { $type: String } }] }`.
  814. // See gh-3560
  815. const childSchemaOptions = { minimize: options.minimize };
  816. if (options.typeKey) {
  817. childSchemaOptions.typeKey = options.typeKey;
  818. }
  819. // propagate 'strict' option to child schema
  820. if (options.hasOwnProperty('strict')) {
  821. childSchemaOptions.strict = options.strict;
  822. }
  823. if (options.hasOwnProperty('typePojoToMixed')) {
  824. childSchemaOptions.typePojoToMixed = options.typePojoToMixed;
  825. }
  826. const childSchema = new Schema(cast, childSchemaOptions);
  827. childSchema.$implicitlyCreated = true;
  828. return new MongooseTypes.DocumentArray(path, childSchema, obj);
  829. } else {
  830. // Special case: empty object becomes mixed
  831. return new MongooseTypes.Array(path, MongooseTypes.Mixed, obj);
  832. }
  833. }
  834. if (cast) {
  835. type = cast[options.typeKey] && (options.typeKey !== 'type' || !cast.type.type)
  836. ? cast[options.typeKey]
  837. : cast;
  838. name = typeof type === 'string'
  839. ? type
  840. : type.schemaName || utils.getFunctionName(type);
  841. if (!MongooseTypes.hasOwnProperty(name)) {
  842. throw new TypeError('Invalid schema configuration: ' +
  843. `\`${name}\` is not a valid type within the array \`${path}\`.` +
  844. 'See http://bit.ly/mongoose-schematypes for a list of valid schema types.');
  845. }
  846. }
  847. return new MongooseTypes.Array(path, cast || MongooseTypes.Mixed, obj, options);
  848. }
  849. if (type && type.instanceOfSchema) {
  850. return new MongooseTypes.Embedded(type, path, obj);
  851. }
  852. if (Buffer.isBuffer(type)) {
  853. name = 'Buffer';
  854. } else if (typeof type === 'function' || typeof type === 'object') {
  855. name = type.schemaName || utils.getFunctionName(type);
  856. } else {
  857. name = type == null ? '' + type : type.toString();
  858. }
  859. if (name) {
  860. name = name.charAt(0).toUpperCase() + name.substring(1);
  861. }
  862. // Special case re: gh-7049 because the bson `ObjectID` class' capitalization
  863. // doesn't line up with Mongoose's.
  864. if (name === 'ObjectID') {
  865. name = 'ObjectId';
  866. }
  867. if (MongooseTypes[name] == null) {
  868. throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` +
  869. `a valid type at path \`${path}\`. See ` +
  870. 'http://bit.ly/mongoose-schematypes for a list of valid schema types.');
  871. }
  872. return new MongooseTypes[name](path, obj);
  873. };
  874. /**
  875. * Iterates the schemas paths similar to Array#forEach.
  876. *
  877. * The callback is passed the pathname and the schemaType instance.
  878. *
  879. * ####Example:
  880. *
  881. * const userSchema = new Schema({ name: String, registeredAt: Date });
  882. * userSchema.eachPath((pathname, schematype) => {
  883. * // Prints twice:
  884. * // name SchemaString { ... }
  885. * // registeredAt SchemaDate { ... }
  886. * console.log(pathname, schematype);
  887. * });
  888. *
  889. * @param {Function} fn callback function
  890. * @return {Schema} this
  891. * @api public
  892. */
  893. Schema.prototype.eachPath = function(fn) {
  894. const keys = Object.keys(this.paths);
  895. const len = keys.length;
  896. for (let i = 0; i < len; ++i) {
  897. fn(keys[i], this.paths[keys[i]]);
  898. }
  899. return this;
  900. };
  901. /**
  902. * Returns an Array of path strings that are required by this schema.
  903. *
  904. * ####Example:
  905. * const s = new Schema({
  906. * name: { type: String, required: true },
  907. * age: { type: String, required: true },
  908. * notes: String
  909. * });
  910. * s.requiredPaths(); // [ 'age', 'name' ]
  911. *
  912. * @api public
  913. * @param {Boolean} invalidate refresh the cache
  914. * @return {Array}
  915. */
  916. Schema.prototype.requiredPaths = function requiredPaths(invalidate) {
  917. if (this._requiredpaths && !invalidate) {
  918. return this._requiredpaths;
  919. }
  920. const paths = Object.keys(this.paths);
  921. let i = paths.length;
  922. const ret = [];
  923. while (i--) {
  924. const path = paths[i];
  925. if (this.paths[path].isRequired) {
  926. ret.push(path);
  927. }
  928. }
  929. this._requiredpaths = ret;
  930. return this._requiredpaths;
  931. };
  932. /**
  933. * Returns indexes from fields and schema-level indexes (cached).
  934. *
  935. * @api private
  936. * @return {Array}
  937. */
  938. Schema.prototype.indexedPaths = function indexedPaths() {
  939. if (this._indexedpaths) {
  940. return this._indexedpaths;
  941. }
  942. this._indexedpaths = this.indexes();
  943. return this._indexedpaths;
  944. };
  945. /**
  946. * Returns the pathType of `path` for this schema.
  947. *
  948. * Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.
  949. *
  950. * ####Example:
  951. * const s = new Schema({ name: String, nested: { foo: String } });
  952. * s.virtual('foo').get(() => 42);
  953. * s.pathType('name'); // "real"
  954. * s.pathType('nested'); // "nested"
  955. * s.pathType('foo'); // "virtual"
  956. * s.pathType('fail'); // "adhocOrUndefined"
  957. *
  958. * @param {String} path
  959. * @return {String}
  960. * @api public
  961. */
  962. Schema.prototype.pathType = function(path) {
  963. // Convert to '.$' to check subpaths re: gh-6405
  964. const cleanPath = _pathToPositionalSyntax(path);
  965. if (this.paths.hasOwnProperty(path)) {
  966. return 'real';
  967. }
  968. if (this.virtuals.hasOwnProperty(path)) {
  969. return 'virtual';
  970. }
  971. if (this.nested.hasOwnProperty(path)) {
  972. return 'nested';
  973. }
  974. if (this.subpaths.hasOwnProperty(cleanPath) || this.subpaths.hasOwnProperty(path)) {
  975. return 'real';
  976. }
  977. if (this.singleNestedPaths.hasOwnProperty(cleanPath) || this.singleNestedPaths.hasOwnProperty(path)) {
  978. return 'real';
  979. }
  980. // Look for maps
  981. const mapPath = getMapPath(this, path);
  982. if (mapPath != null) {
  983. return 'real';
  984. }
  985. if (/\.\d+\.|\.\d+$/.test(path)) {
  986. return getPositionalPathType(this, path);
  987. }
  988. return 'adhocOrUndefined';
  989. };
  990. /**
  991. * Returns true iff this path is a child of a mixed schema.
  992. *
  993. * @param {String} path
  994. * @return {Boolean}
  995. * @api private
  996. */
  997. Schema.prototype.hasMixedParent = function(path) {
  998. const subpaths = path.split(/\./g);
  999. path = '';
  1000. for (let i = 0; i < subpaths.length; ++i) {
  1001. path = i > 0 ? path + '.' + subpaths[i] : subpaths[i];
  1002. if (path in this.paths &&
  1003. this.paths[path] instanceof MongooseTypes.Mixed) {
  1004. return this.paths[path];
  1005. }
  1006. }
  1007. return null;
  1008. };
  1009. /**
  1010. * Setup updatedAt and createdAt timestamps to documents if enabled
  1011. *
  1012. * @param {Boolean|Object} timestamps timestamps options
  1013. * @api private
  1014. */
  1015. Schema.prototype.setupTimestamp = function(timestamps) {
  1016. const childHasTimestamp = this.childSchemas.find(withTimestamp);
  1017. function withTimestamp(s) {
  1018. const ts = s.schema.options.timestamps;
  1019. return !!ts;
  1020. }
  1021. if (!timestamps && !childHasTimestamp) {
  1022. return;
  1023. }
  1024. const createdAt = handleTimestampOption(timestamps, 'createdAt');
  1025. const updatedAt = handleTimestampOption(timestamps, 'updatedAt');
  1026. const currentTime = timestamps != null && timestamps.hasOwnProperty('currentTime') ?
  1027. timestamps.currentTime :
  1028. null;
  1029. const schemaAdditions = {};
  1030. this.$timestamps = { createdAt: createdAt, updatedAt: updatedAt };
  1031. if (updatedAt && !this.paths[updatedAt]) {
  1032. schemaAdditions[updatedAt] = Date;
  1033. }
  1034. if (createdAt && !this.paths[createdAt]) {
  1035. schemaAdditions[createdAt] = Date;
  1036. }
  1037. this.add(schemaAdditions);
  1038. this.pre('save', function(next) {
  1039. const timestampOption = get(this, '$__.saveOptions.timestamps');
  1040. if (timestampOption === false) {
  1041. return next();
  1042. }
  1043. const skipUpdatedAt = timestampOption != null && timestampOption.updatedAt === false;
  1044. const skipCreatedAt = timestampOption != null && timestampOption.createdAt === false;
  1045. const defaultTimestamp = currentTime != null ?
  1046. currentTime() :
  1047. (this.ownerDocument ? this.ownerDocument() : this).constructor.base.now();
  1048. const auto_id = this._id && this._id.auto;
  1049. if (!skipCreatedAt && createdAt && !this.get(createdAt) && this.isSelected(createdAt)) {
  1050. this.set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp);
  1051. }
  1052. if (!skipUpdatedAt && updatedAt && (this.isNew || this.isModified())) {
  1053. let ts = defaultTimestamp;
  1054. if (this.isNew) {
  1055. if (createdAt != null) {
  1056. ts = this.$__getValue(createdAt);
  1057. } else if (auto_id) {
  1058. ts = this._id.getTimestamp();
  1059. }
  1060. }
  1061. this.set(updatedAt, ts);
  1062. }
  1063. next();
  1064. });
  1065. this.methods.initializeTimestamps = function() {
  1066. const ts = currentTime != null ?
  1067. currentTime() :
  1068. this.constructor.base.now();
  1069. if (createdAt && !this.get(createdAt)) {
  1070. this.set(createdAt, ts);
  1071. }
  1072. if (updatedAt && !this.get(updatedAt)) {
  1073. this.set(updatedAt, ts);
  1074. }
  1075. return this;
  1076. };
  1077. _setTimestampsOnUpdate[symbols.builtInMiddleware] = true;
  1078. const opts = { query: true, model: false };
  1079. this.pre('findOneAndUpdate', opts, _setTimestampsOnUpdate);
  1080. this.pre('replaceOne', opts, _setTimestampsOnUpdate);
  1081. this.pre('update', opts, _setTimestampsOnUpdate);
  1082. this.pre('updateOne', opts, _setTimestampsOnUpdate);
  1083. this.pre('updateMany', opts, _setTimestampsOnUpdate);
  1084. function _setTimestampsOnUpdate(next) {
  1085. const now = currentTime != null ?
  1086. currentTime() :
  1087. this.model.base.now();
  1088. applyTimestampsToUpdate(now, createdAt, updatedAt, this.getUpdate(),
  1089. this.options, this.schema);
  1090. applyTimestampsToChildren(now, this.getUpdate(), this.model.schema);
  1091. next();
  1092. }
  1093. };
  1094. /*!
  1095. * ignore. Deprecated re: #6405
  1096. */
  1097. function getPositionalPathType(self, path) {
  1098. const subpaths = path.split(/\.(\d+)\.|\.(\d+)$/).filter(Boolean);
  1099. if (subpaths.length < 2) {
  1100. return self.paths.hasOwnProperty(subpaths[0]) ?
  1101. self.paths[subpaths[0]] :
  1102. 'adhocOrUndefined';
  1103. }
  1104. let val = self.path(subpaths[0]);
  1105. let isNested = false;
  1106. if (!val) {
  1107. return 'adhocOrUndefined';
  1108. }
  1109. const last = subpaths.length - 1;
  1110. for (let i = 1; i < subpaths.length; ++i) {
  1111. isNested = false;
  1112. const subpath = subpaths[i];
  1113. if (i === last && val && !/\D/.test(subpath)) {
  1114. if (val.$isMongooseDocumentArray) {
  1115. val = val.$embeddedSchemaType;
  1116. } else if (val instanceof MongooseTypes.Array) {
  1117. // StringSchema, NumberSchema, etc
  1118. val = val.caster;
  1119. } else {
  1120. val = undefined;
  1121. }
  1122. break;
  1123. }
  1124. // ignore if its just a position segment: path.0.subpath
  1125. if (!/\D/.test(subpath)) {
  1126. // Nested array
  1127. if (val instanceof MongooseTypes.Array && i !== last) {
  1128. val = val.caster;
  1129. }
  1130. continue;
  1131. }
  1132. if (!(val && val.schema)) {
  1133. val = undefined;
  1134. break;
  1135. }
  1136. const type = val.schema.pathType(subpath);
  1137. isNested = (type === 'nested');
  1138. val = val.schema.path(subpath);
  1139. }
  1140. self.subpaths[path] = val;
  1141. if (val) {
  1142. return 'real';
  1143. }
  1144. if (isNested) {
  1145. return 'nested';
  1146. }
  1147. return 'adhocOrUndefined';
  1148. }
  1149. /*!
  1150. * ignore
  1151. */
  1152. function getPositionalPath(self, path) {
  1153. getPositionalPathType(self, path);
  1154. return self.subpaths[path];
  1155. }
  1156. /**
  1157. * Adds a method call to the queue.
  1158. *
  1159. * ####Example:
  1160. *
  1161. * schema.methods.print = function() { console.log(this); };
  1162. * schema.queue('print', []); // Print the doc every one is instantiated
  1163. *
  1164. * const Model = mongoose.model('Test', schema);
  1165. * new Model({ name: 'test' }); // Prints '{"_id": ..., "name": "test" }'
  1166. *
  1167. * @param {String} name name of the document method to call later
  1168. * @param {Array} args arguments to pass to the method
  1169. * @api public
  1170. */
  1171. Schema.prototype.queue = function(name, args) {
  1172. this.callQueue.push([name, args]);
  1173. return this;
  1174. };
  1175. /**
  1176. * Defines a pre hook for the document.
  1177. *
  1178. * ####Example
  1179. *
  1180. * var toySchema = new Schema({ name: String, created: Date });
  1181. *
  1182. * toySchema.pre('save', function(next) {
  1183. * if (!this.created) this.created = new Date;
  1184. * next();
  1185. * });
  1186. *
  1187. * toySchema.pre('validate', function(next) {
  1188. * if (this.name !== 'Woody') this.name = 'Woody';
  1189. * next();
  1190. * });
  1191. *
  1192. * // Equivalent to calling `pre()` on `find`, `findOne`, `findOneAndUpdate`.
  1193. * toySchema.pre(/^find/, function(next) {
  1194. * console.log(this.getFilter());
  1195. * });
  1196. *
  1197. * // Equivalent to calling `pre()` on `updateOne`, `findOneAndUpdate`.
  1198. * toySchema.pre(['updateOne', 'findOneAndUpdate'], function(next) {
  1199. * console.log(this.getFilter());
  1200. * });
  1201. *
  1202. * toySchema.pre('deleteOne', function() {
  1203. * // Runs when you call `Toy.deleteOne()`
  1204. * });
  1205. *
  1206. * toySchema.pre('deleteOne', { document: true }, function() {
  1207. * // Runs when you call `doc.deleteOne()`
  1208. * });
  1209. *
  1210. * @param {String|RegExp} The method name or regular expression to match method name
  1211. * @param {Object} [options]
  1212. * @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()`.
  1213. * @param {Boolean} [options.query] If `name` is a hook for both document and query middleware, set to `true` to run on query middleware.
  1214. * @param {Function} callback
  1215. * @api public
  1216. */
  1217. Schema.prototype.pre = function(name) {
  1218. if (name instanceof RegExp) {
  1219. const remainingArgs = Array.prototype.slice.call(arguments, 1);
  1220. for (const fn of hookNames) {
  1221. if (name.test(fn)) {
  1222. this.pre.apply(this, [fn].concat(remainingArgs));
  1223. }
  1224. }
  1225. return this;
  1226. }
  1227. if (Array.isArray(name)) {
  1228. const remainingArgs = Array.prototype.slice.call(arguments, 1);
  1229. for (const el of name) {
  1230. this.pre.apply(this, [el].concat(remainingArgs));
  1231. }
  1232. return this;
  1233. }
  1234. this.s.hooks.pre.apply(this.s.hooks, arguments);
  1235. return this;
  1236. };
  1237. /**
  1238. * Defines a post hook for the document
  1239. *
  1240. * var schema = new Schema(..);
  1241. * schema.post('save', function (doc) {
  1242. * console.log('this fired after a document was saved');
  1243. * });
  1244. *
  1245. * schema.post('find', function(docs) {
  1246. * console.log('this fired after you ran a find query');
  1247. * });
  1248. *
  1249. * schema.post(/Many$/, function(res) {
  1250. * console.log('this fired after you ran `updateMany()` or `deleteMany()`);
  1251. * });
  1252. *
  1253. * var Model = mongoose.model('Model', schema);
  1254. *
  1255. * var m = new Model(..);
  1256. * m.save(function(err) {
  1257. * console.log('this fires after the `post` hook');
  1258. * });
  1259. *
  1260. * m.find(function(err, docs) {
  1261. * console.log('this fires after the post find hook');
  1262. * });
  1263. *
  1264. * @param {String|RegExp} The method name or regular expression to match method name
  1265. * @param {Object} [options]
  1266. * @param {Boolean} [options.document] If `name` is a hook for both document and query middleware, set to `true` to run on document middleware.
  1267. * @param {Boolean} [options.query] If `name` is a hook for both document and query middleware, set to `true` to run on query middleware.
  1268. * @param {Function} fn callback
  1269. * @see middleware http://mongoosejs.com/docs/middleware.html
  1270. * @see kareem http://npmjs.org/package/kareem
  1271. * @api public
  1272. */
  1273. Schema.prototype.post = function(name) {
  1274. if (name instanceof RegExp) {
  1275. const remainingArgs = Array.prototype.slice.call(arguments, 1);
  1276. for (const fn of hookNames) {
  1277. if (name.test(fn)) {
  1278. this.post.apply(this, [fn].concat(remainingArgs));
  1279. }
  1280. }
  1281. return this;
  1282. }
  1283. if (Array.isArray(name)) {
  1284. const remainingArgs = Array.prototype.slice.call(arguments, 1);
  1285. for (const el of name) {
  1286. this.post.apply(this, [el].concat(remainingArgs));
  1287. }
  1288. return this;
  1289. }
  1290. this.s.hooks.post.apply(this.s.hooks, arguments);
  1291. return this;
  1292. };
  1293. /**
  1294. * Registers a plugin for this schema.
  1295. *
  1296. * ####Example:
  1297. *
  1298. * const s = new Schema({ name: String });
  1299. * s.plugin(schema => console.log(schema.path('name').path));
  1300. * mongoose.model('Test', s); // Prints 'name'
  1301. *
  1302. * @param {Function} plugin callback
  1303. * @param {Object} [opts]
  1304. * @see plugins
  1305. * @api public
  1306. */
  1307. Schema.prototype.plugin = function(fn, opts) {
  1308. if (typeof fn !== 'function') {
  1309. throw new Error('First param to `schema.plugin()` must be a function, ' +
  1310. 'got "' + (typeof fn) + '"');
  1311. }
  1312. if (opts && opts.deduplicate) {
  1313. for (const plugin of this.plugins) {
  1314. if (plugin.fn === fn) {
  1315. return this;
  1316. }
  1317. }
  1318. }
  1319. this.plugins.push({ fn: fn, opts: opts });
  1320. fn(this, opts);
  1321. return this;
  1322. };
  1323. /**
  1324. * Adds an instance method to documents constructed from Models compiled from this schema.
  1325. *
  1326. * ####Example
  1327. *
  1328. * var schema = kittySchema = new Schema(..);
  1329. *
  1330. * schema.method('meow', function () {
  1331. * console.log('meeeeeoooooooooooow');
  1332. * })
  1333. *
  1334. * var Kitty = mongoose.model('Kitty', schema);
  1335. *
  1336. * var fizz = new Kitty;
  1337. * fizz.meow(); // meeeeeooooooooooooow
  1338. *
  1339. * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
  1340. *
  1341. * schema.method({
  1342. * purr: function () {}
  1343. * , scratch: function () {}
  1344. * });
  1345. *
  1346. * // later
  1347. * fizz.purr();
  1348. * fizz.scratch();
  1349. *
  1350. * 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](./guide.html#methods)
  1351. *
  1352. * @param {String|Object} method name
  1353. * @param {Function} [fn]
  1354. * @api public
  1355. */
  1356. Schema.prototype.method = function(name, fn, options) {
  1357. if (typeof name !== 'string') {
  1358. for (const i in name) {
  1359. this.methods[i] = name[i];
  1360. this.methodOptions[i] = utils.clone(options);
  1361. }
  1362. } else {
  1363. this.methods[name] = fn;
  1364. this.methodOptions[name] = utils.clone(options);
  1365. }
  1366. return this;
  1367. };
  1368. /**
  1369. * Adds static "class" methods to Models compiled from this schema.
  1370. *
  1371. * ####Example
  1372. *
  1373. * const schema = new Schema(..);
  1374. * // Equivalent to `schema.statics.findByName = function(name) {}`;
  1375. * schema.static('findByName', function(name) {
  1376. * return this.find({ name: name });
  1377. * });
  1378. *
  1379. * const Drink = mongoose.model('Drink', schema);
  1380. * await Drink.findByName('LaCroix');
  1381. *
  1382. * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
  1383. *
  1384. * @param {String|Object} name
  1385. * @param {Function} [fn]
  1386. * @api public
  1387. * @see Statics /docs/guide.html#statics
  1388. */
  1389. Schema.prototype.static = function(name, fn) {
  1390. if (typeof name !== 'string') {
  1391. for (const i in name) {
  1392. this.statics[i] = name[i];
  1393. }
  1394. } else {
  1395. this.statics[name] = fn;
  1396. }
  1397. return this;
  1398. };
  1399. /**
  1400. * Defines an index (most likely compound) for this schema.
  1401. *
  1402. * ####Example
  1403. *
  1404. * schema.index({ first: 1, last: -1 })
  1405. *
  1406. * @param {Object} fields
  1407. * @param {Object} [options] Options to pass to [MongoDB driver's `createIndex()` function](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#createIndex)
  1408. * @param {String} [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.
  1409. * @api public
  1410. */
  1411. Schema.prototype.index = function(fields, options) {
  1412. fields || (fields = {});
  1413. options || (options = {});
  1414. if (options.expires) {
  1415. utils.expires(options);
  1416. }
  1417. this._indexes.push([fields, options]);
  1418. return this;
  1419. };
  1420. /**
  1421. * Sets/gets a schema option.
  1422. *
  1423. * ####Example
  1424. *
  1425. * schema.set('strict'); // 'true' by default
  1426. * schema.set('strict', false); // Sets 'strict' to false
  1427. * schema.set('strict'); // 'false'
  1428. *
  1429. * @param {String} key option name
  1430. * @param {Object} [value] if not passed, the current option value is returned
  1431. * @see Schema ./
  1432. * @api public
  1433. */
  1434. Schema.prototype.set = function(key, value, _tags) {
  1435. if (arguments.length === 1) {
  1436. return this.options[key];
  1437. }
  1438. switch (key) {
  1439. case 'read':
  1440. this.options[key] = readPref(value, _tags);
  1441. this._userProvidedOptions[key] = this.options[key];
  1442. break;
  1443. case 'safe':
  1444. setSafe(this.options, value);
  1445. this._userProvidedOptions[key] = this.options[key];
  1446. break;
  1447. case 'timestamps':
  1448. this.setupTimestamp(value);
  1449. this.options[key] = value;
  1450. this._userProvidedOptions[key] = this.options[key];
  1451. break;
  1452. default:
  1453. this.options[key] = value;
  1454. this._userProvidedOptions[key] = this.options[key];
  1455. break;
  1456. }
  1457. return this;
  1458. };
  1459. /*!
  1460. * ignore
  1461. */
  1462. const safeDeprecationWarning = 'Mongoose: The `safe` option for schemas is ' +
  1463. 'deprecated. Use the `writeConcern` option instead: ' +
  1464. 'http://bit.ly/mongoose-write-concern';
  1465. const setSafe = util.deprecate(function setSafe(options, value) {
  1466. options.safe = value === false ?
  1467. { w: 0 } :
  1468. value;
  1469. }, safeDeprecationWarning);
  1470. /**
  1471. * Gets a schema option.
  1472. *
  1473. * ####Example:
  1474. *
  1475. * schema.get('strict'); // true
  1476. * schema.set('strict', false);
  1477. * schema.get('strict'); // false
  1478. *
  1479. * @param {String} key option name
  1480. * @api public
  1481. * @return {Any} the option's value
  1482. */
  1483. Schema.prototype.get = function(key) {
  1484. return this.options[key];
  1485. };
  1486. /**
  1487. * The allowed index types
  1488. *
  1489. * @receiver Schema
  1490. * @static indexTypes
  1491. * @api public
  1492. */
  1493. const indexTypes = '2d 2dsphere hashed text'.split(' ');
  1494. Object.defineProperty(Schema, 'indexTypes', {
  1495. get: function() {
  1496. return indexTypes;
  1497. },
  1498. set: function() {
  1499. throw new Error('Cannot overwrite Schema.indexTypes');
  1500. }
  1501. });
  1502. /**
  1503. * Returns a list of indexes that this schema declares, via `schema.index()`
  1504. * or by `index: true` in a path's options.
  1505. *
  1506. * ####Example:
  1507. *
  1508. * const userSchema = new Schema({
  1509. * email: { type: String, required: true, unique: true },
  1510. * registeredAt: { type: Date, index: true }
  1511. * });
  1512. *
  1513. * // [ [ { email: 1 }, { unique: true, background: true } ],
  1514. * // [ { registeredAt: 1 }, { background: true } ] ]
  1515. * userSchema.indexes();
  1516. *
  1517. * @api public
  1518. * @return {Array} list of indexes defined in the schema
  1519. */
  1520. Schema.prototype.indexes = function() {
  1521. return getIndexes(this);
  1522. };
  1523. /**
  1524. * Creates a virtual type with the given name.
  1525. *
  1526. * @param {String} name
  1527. * @param {Object} [options]
  1528. * @param {String|Model} [options.ref] model name or model instance. Marks this as a [populate virtual](populate.html#populate-virtuals).
  1529. * @param {String|Function} [options.localField] Required for populate virtuals. See [populate virtual docs](populate.html#populate-virtuals) for more information.
  1530. * @param {String|Function} [options.foreignField] Required for populate virtuals. See [populate virtual docs](populate.html#populate-virtuals) for more information.
  1531. * @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.
  1532. * @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()`.
  1533. * @return {VirtualType}
  1534. */
  1535. Schema.prototype.virtual = function(name, options) {
  1536. if (name instanceof VirtualType) {
  1537. return this.virtual(name.path, name.options);
  1538. }
  1539. options = new VirtualOptions(options);
  1540. if (utils.hasUserDefinedProperty(options, ['ref', 'refPath'])) {
  1541. if (options.localField == null) {
  1542. throw new Error('Reference virtuals require `localField` option');
  1543. }
  1544. if (options.foreignField == null) {
  1545. throw new Error('Reference virtuals require `foreignField` option');
  1546. }
  1547. this.pre('init', function(obj) {
  1548. if (mpath.has(name, obj)) {
  1549. const _v = mpath.get(name, obj);
  1550. if (!this.$$populatedVirtuals) {
  1551. this.$$populatedVirtuals = {};
  1552. }
  1553. if (options.justOne || options.count) {
  1554. this.$$populatedVirtuals[name] = Array.isArray(_v) ?
  1555. _v[0] :
  1556. _v;
  1557. } else {
  1558. this.$$populatedVirtuals[name] = Array.isArray(_v) ?
  1559. _v :
  1560. _v == null ? [] : [_v];
  1561. }
  1562. mpath.unset(name, obj);
  1563. }
  1564. });
  1565. const virtual = this.virtual(name);
  1566. virtual.options = options;
  1567. return virtual.
  1568. get(function(_v) {
  1569. if (this.$$populatedVirtuals &&
  1570. this.$$populatedVirtuals.hasOwnProperty(name)) {
  1571. return this.$$populatedVirtuals[name];
  1572. }
  1573. if (_v == null) return undefined;
  1574. return _v;
  1575. }).
  1576. set(function(_v) {
  1577. if (!this.$$populatedVirtuals) {
  1578. this.$$populatedVirtuals = {};
  1579. }
  1580. if (options.justOne || options.count) {
  1581. this.$$populatedVirtuals[name] = Array.isArray(_v) ?
  1582. _v[0] :
  1583. _v;
  1584. if (typeof this.$$populatedVirtuals[name] !== 'object') {
  1585. this.$$populatedVirtuals[name] = options.count ? _v : null;
  1586. }
  1587. } else {
  1588. this.$$populatedVirtuals[name] = Array.isArray(_v) ?
  1589. _v :
  1590. _v == null ? [] : [_v];
  1591. this.$$populatedVirtuals[name] = this.$$populatedVirtuals[name].filter(function(doc) {
  1592. return doc && typeof doc === 'object';
  1593. });
  1594. }
  1595. });
  1596. }
  1597. const virtuals = this.virtuals;
  1598. const parts = name.split('.');
  1599. if (this.pathType(name) === 'real') {
  1600. throw new Error('Virtual path "' + name + '"' +
  1601. ' conflicts with a real path in the schema');
  1602. }
  1603. virtuals[name] = parts.reduce(function(mem, part, i) {
  1604. mem[part] || (mem[part] = (i === parts.length - 1)
  1605. ? new VirtualType(options, name)
  1606. : {});
  1607. return mem[part];
  1608. }, this.tree);
  1609. // Workaround for gh-8198: if virtual is under document array, make a fake
  1610. // virtual. See gh-8210
  1611. let cur = parts[0];
  1612. for (let i = 0; i < parts.length - 1; ++i) {
  1613. if (this.paths[cur] != null && this.paths[cur].$isMongooseDocumentArray) {
  1614. const remnant = parts.slice(i + 1).join('.');
  1615. const v = this.paths[cur].schema.virtual(remnant);
  1616. v.get((v, virtual, doc) => {
  1617. const parent = doc.__parentArray[arrayParentSymbol];
  1618. const path = cur + '.' + doc.__index + '.' + remnant;
  1619. return parent.get(path);
  1620. });
  1621. break;
  1622. }
  1623. cur += '.' + parts[i + 1];
  1624. }
  1625. return virtuals[name];
  1626. };
  1627. /**
  1628. * Returns the virtual type with the given `name`.
  1629. *
  1630. * @param {String} name
  1631. * @return {VirtualType}
  1632. */
  1633. Schema.prototype.virtualpath = function(name) {
  1634. return this.virtuals.hasOwnProperty(name) ? this.virtuals[name] : null;
  1635. };
  1636. /**
  1637. * Removes the given `path` (or [`paths`]).
  1638. *
  1639. * ####Example:
  1640. *
  1641. * const schema = new Schema({ name: String, age: Number });
  1642. * schema.remove('name');
  1643. * schema.path('name'); // Undefined
  1644. * schema.path('age'); // SchemaNumber { ... }
  1645. *
  1646. * @param {String|Array} path
  1647. * @return {Schema} the Schema instance
  1648. * @api public
  1649. */
  1650. Schema.prototype.remove = function(path) {
  1651. if (typeof path === 'string') {
  1652. path = [path];
  1653. }
  1654. if (Array.isArray(path)) {
  1655. path.forEach(function(name) {
  1656. if (this.path(name) == null && !this.nested[name]) {
  1657. return;
  1658. }
  1659. if (this.nested[name]) {
  1660. const allKeys = Object.keys(this.paths).
  1661. concat(Object.keys(this.nested));
  1662. for (const path of allKeys) {
  1663. if (path.startsWith(name + '.')) {
  1664. delete this.paths[path];
  1665. delete this.nested[path];
  1666. _deletePath(this, path);
  1667. }
  1668. }
  1669. delete this.nested[name];
  1670. _deletePath(this, name);
  1671. return;
  1672. }
  1673. delete this.paths[name];
  1674. _deletePath(this, name);
  1675. }, this);
  1676. }
  1677. return this;
  1678. };
  1679. /*!
  1680. * ignore
  1681. */
  1682. function _deletePath(schema, name) {
  1683. const pieces = name.split('.');
  1684. const last = pieces.pop();
  1685. let branch = schema.tree;
  1686. for (const piece of pieces) {
  1687. branch = branch[piece];
  1688. }
  1689. delete branch[last];
  1690. }
  1691. /**
  1692. * 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),
  1693. * and [instance methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Class_body_and_method_definitions)
  1694. * to schema [virtuals](http://mongoosejs.com/docs/guide.html#virtuals),
  1695. * [statics](http://mongoosejs.com/docs/guide.html#statics), and
  1696. * [methods](http://mongoosejs.com/docs/guide.html#methods).
  1697. *
  1698. * ####Example:
  1699. *
  1700. * ```javascript
  1701. * const md5 = require('md5');
  1702. * const userSchema = new Schema({ email: String });
  1703. * class UserClass {
  1704. * // `gravatarImage` becomes a virtual
  1705. * get gravatarImage() {
  1706. * const hash = md5(this.email.toLowerCase());
  1707. * return `https://www.gravatar.com/avatar/${hash}`;
  1708. * }
  1709. *
  1710. * // `getProfileUrl()` becomes a document method
  1711. * getProfileUrl() {
  1712. * return `https://mysite.com/${this.email}`;
  1713. * }
  1714. *
  1715. * // `findByEmail()` becomes a static
  1716. * static findByEmail(email) {
  1717. * return this.findOne({ email });
  1718. * }
  1719. * }
  1720. *
  1721. * // `schema` will now have a `gravatarImage` virtual, a `getProfileUrl()` method,
  1722. * // and a `findByEmail()` static
  1723. * userSchema.loadClass(UserClass);
  1724. * ```
  1725. *
  1726. * @param {Function} model
  1727. * @param {Boolean} [virtualsOnly] if truthy, only pulls virtuals from the class, not methods or statics
  1728. */
  1729. Schema.prototype.loadClass = function(model, virtualsOnly) {
  1730. if (model === Object.prototype ||
  1731. model === Function.prototype ||
  1732. model.prototype.hasOwnProperty('$isMongooseModelPrototype')) {
  1733. return this;
  1734. }
  1735. this.loadClass(Object.getPrototypeOf(model));
  1736. // Add static methods
  1737. if (!virtualsOnly) {
  1738. Object.getOwnPropertyNames(model).forEach(function(name) {
  1739. if (name.match(/^(length|name|prototype)$/)) {
  1740. return;
  1741. }
  1742. const method = Object.getOwnPropertyDescriptor(model, name);
  1743. if (typeof method.value === 'function') {
  1744. this.static(name, method.value);
  1745. }
  1746. }, this);
  1747. }
  1748. // Add methods and virtuals
  1749. Object.getOwnPropertyNames(model.prototype).forEach(function(name) {
  1750. if (name.match(/^(constructor)$/)) {
  1751. return;
  1752. }
  1753. const method = Object.getOwnPropertyDescriptor(model.prototype, name);
  1754. if (!virtualsOnly) {
  1755. if (typeof method.value === 'function') {
  1756. this.method(name, method.value);
  1757. }
  1758. }
  1759. if (typeof method.get === 'function') {
  1760. this.virtual(name).get(method.get);
  1761. }
  1762. if (typeof method.set === 'function') {
  1763. this.virtual(name).set(method.set);
  1764. }
  1765. }, this);
  1766. return this;
  1767. };
  1768. /*!
  1769. * ignore
  1770. */
  1771. Schema.prototype._getSchema = function(path) {
  1772. const _this = this;
  1773. const pathschema = _this.path(path);
  1774. const resultPath = [];
  1775. if (pathschema) {
  1776. pathschema.$fullPath = path;
  1777. return pathschema;
  1778. }
  1779. function search(parts, schema) {
  1780. let p = parts.length + 1;
  1781. let foundschema;
  1782. let trypath;
  1783. while (p--) {
  1784. trypath = parts.slice(0, p).join('.');
  1785. foundschema = schema.path(trypath);
  1786. if (foundschema) {
  1787. resultPath.push(trypath);
  1788. if (foundschema.caster) {
  1789. // array of Mixed?
  1790. if (foundschema.caster instanceof MongooseTypes.Mixed) {
  1791. foundschema.caster.$fullPath = resultPath.join('.');
  1792. return foundschema.caster;
  1793. }
  1794. // Now that we found the array, we need to check if there
  1795. // are remaining document paths to look up for casting.
  1796. // Also we need to handle array.$.path since schema.path
  1797. // doesn't work for that.
  1798. // If there is no foundschema.schema we are dealing with
  1799. // a path like array.$
  1800. if (p !== parts.length && foundschema.schema) {
  1801. let ret;
  1802. if (parts[p] === '$' || isArrayFilter(parts[p])) {
  1803. if (p + 1 === parts.length) {
  1804. // comments.$
  1805. return foundschema;
  1806. }
  1807. // comments.$.comments.$.title
  1808. ret = search(parts.slice(p + 1), foundschema.schema);
  1809. if (ret) {
  1810. ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
  1811. !foundschema.schema.$isSingleNested;
  1812. }
  1813. return ret;
  1814. }
  1815. // this is the last path of the selector
  1816. ret = search(parts.slice(p), foundschema.schema);
  1817. if (ret) {
  1818. ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
  1819. !foundschema.schema.$isSingleNested;
  1820. }
  1821. return ret;
  1822. }
  1823. }
  1824. foundschema.$fullPath = resultPath.join('.');
  1825. return foundschema;
  1826. }
  1827. }
  1828. }
  1829. // look for arrays
  1830. const parts = path.split('.');
  1831. for (let i = 0; i < parts.length; ++i) {
  1832. if (parts[i] === '$' || isArrayFilter(parts[i])) {
  1833. // Re: gh-5628, because `schema.path()` doesn't take $ into account.
  1834. parts[i] = '0';
  1835. }
  1836. }
  1837. return search(parts, _this);
  1838. };
  1839. /*!
  1840. * ignore
  1841. */
  1842. Schema.prototype._getPathType = function(path) {
  1843. const _this = this;
  1844. const pathschema = _this.path(path);
  1845. if (pathschema) {
  1846. return 'real';
  1847. }
  1848. function search(parts, schema) {
  1849. let p = parts.length + 1,
  1850. foundschema,
  1851. trypath;
  1852. while (p--) {
  1853. trypath = parts.slice(0, p).join('.');
  1854. foundschema = schema.path(trypath);
  1855. if (foundschema) {
  1856. if (foundschema.caster) {
  1857. // array of Mixed?
  1858. if (foundschema.caster instanceof MongooseTypes.Mixed) {
  1859. return { schema: foundschema, pathType: 'mixed' };
  1860. }
  1861. // Now that we found the array, we need to check if there
  1862. // are remaining document paths to look up for casting.
  1863. // Also we need to handle array.$.path since schema.path
  1864. // doesn't work for that.
  1865. // If there is no foundschema.schema we are dealing with
  1866. // a path like array.$
  1867. if (p !== parts.length && foundschema.schema) {
  1868. if (parts[p] === '$' || isArrayFilter(parts[p])) {
  1869. if (p === parts.length - 1) {
  1870. return { schema: foundschema, pathType: 'nested' };
  1871. }
  1872. // comments.$.comments.$.title
  1873. return search(parts.slice(p + 1), foundschema.schema);
  1874. }
  1875. // this is the last path of the selector
  1876. return search(parts.slice(p), foundschema.schema);
  1877. }
  1878. return {
  1879. schema: foundschema,
  1880. pathType: foundschema.$isSingleNested ? 'nested' : 'array'
  1881. };
  1882. }
  1883. return { schema: foundschema, pathType: 'real' };
  1884. } else if (p === parts.length && schema.nested[trypath]) {
  1885. return { schema: schema, pathType: 'nested' };
  1886. }
  1887. }
  1888. return { schema: foundschema || schema, pathType: 'undefined' };
  1889. }
  1890. // look for arrays
  1891. return search(path.split('.'), _this);
  1892. };
  1893. /*!
  1894. * ignore
  1895. */
  1896. function isArrayFilter(piece) {
  1897. return piece.startsWith('$[') && piece.endsWith(']');
  1898. }
  1899. /*!
  1900. * Module exports.
  1901. */
  1902. module.exports = exports = Schema;
  1903. // require down here because of reference issues
  1904. /**
  1905. * The various built-in Mongoose Schema Types.
  1906. *
  1907. * ####Example:
  1908. *
  1909. * var mongoose = require('mongoose');
  1910. * var ObjectId = mongoose.Schema.Types.ObjectId;
  1911. *
  1912. * ####Types:
  1913. *
  1914. * - [String](#schema-string-js)
  1915. * - [Number](#schema-number-js)
  1916. * - [Boolean](#schema-boolean-js) | Bool
  1917. * - [Array](#schema-array-js)
  1918. * - [Buffer](#schema-buffer-js)
  1919. * - [Date](#schema-date-js)
  1920. * - [ObjectId](#schema-objectid-js) | Oid
  1921. * - [Mixed](#schema-mixed-js)
  1922. *
  1923. * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema.
  1924. *
  1925. * var Mixed = mongoose.Schema.Types.Mixed;
  1926. * new mongoose.Schema({ _user: Mixed })
  1927. *
  1928. * @api public
  1929. */
  1930. Schema.Types = MongooseTypes = require('./schema/index');
  1931. /*!
  1932. * ignore
  1933. */
  1934. exports.ObjectId = MongooseTypes.ObjectId;