Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

schema.js 49KB

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