diff options
author | 2020-11-16 00:10:28 +0100 | |
---|---|---|
committer | 2020-11-16 00:10:28 +0100 | |
commit | e06ec920f7a5d784e674c4c4b4e6d1da3dc7391d (patch) | |
tree | 55713f725f77b44ebfec86e4eec3ce33e71458ca /node_modules/mongoose/lib | |
download | website_creator-e06ec920f7a5d784e674c4c4b4e6d1da3dc7391d.tar.gz website_creator-e06ec920f7a5d784e674c4c4b4e6d1da3dc7391d.tar.bz2 website_creator-e06ec920f7a5d784e674c4c4b4e6d1da3dc7391d.zip |
api, login, auth
Diffstat (limited to 'node_modules/mongoose/lib')
201 files changed, 41669 insertions, 0 deletions
diff --git a/node_modules/mongoose/lib/aggregate.js b/node_modules/mongoose/lib/aggregate.js new file mode 100644 index 0000000..f562e57 --- /dev/null +++ b/node_modules/mongoose/lib/aggregate.js @@ -0,0 +1,1162 @@ +'use strict'; + +/*! + * Module dependencies + */ + +const AggregationCursor = require('./cursor/AggregationCursor'); +const Query = require('./query'); +const applyGlobalMaxTimeMS = require('./helpers/query/applyGlobalMaxTimeMS'); +const promiseOrCallback = require('./helpers/promiseOrCallback'); +const stringifyAccumulatorOptions = require('./helpers/aggregate/stringifyAccumulatorOptions'); +const util = require('util'); +const utils = require('./utils'); +const read = Query.prototype.read; +const readConcern = Query.prototype.readConcern; + +/** + * Aggregate constructor used for building aggregation pipelines. Do not + * instantiate this class directly, use [Model.aggregate()](/docs/api.html#model_Model.aggregate) instead. + * + * ####Example: + * + * const aggregate = Model.aggregate([ + * { $project: { a: 1, b: 1 } }, + * { $skip: 5 } + * ]); + * + * Model. + * aggregate([{ $match: { age: { $gte: 21 }}}]). + * unwind('tags'). + * exec(callback); + * + * ####Note: + * + * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned). + * - Mongoose does **not** cast pipeline stages. The below will **not** work unless `_id` is a string in the database + * + * ```javascript + * new Aggregate([{ $match: { _id: '00000000000000000000000a' } }]); + * // Do this instead to cast to an ObjectId + * new Aggregate([{ $match: { _id: mongoose.Types.ObjectId('00000000000000000000000a') } }]); + * ``` + * + * @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/ + * @see driver http://mongodb.github.com/node-mongodb-native/api-generated/collection.html#aggregate + * @param {Array} [pipeline] aggregation pipeline as an array of objects + * @api public + */ + +function Aggregate(pipeline) { + this._pipeline = []; + this._model = undefined; + this.options = {}; + + if (arguments.length === 1 && util.isArray(pipeline)) { + this.append.apply(this, pipeline); + } +} + +/** + * Contains options passed down to the [aggregate command](https://docs.mongodb.com/manual/reference/command/aggregate/). + * Supported options are: + * + * - `readPreference` + * - [`cursor`](./api.html#aggregate_Aggregate-cursor) + * - [`explain`](./api.html#aggregate_Aggregate-explain) + * - [`allowDiskUse`](./api.html#aggregate_Aggregate-allowDiskUse) + * - `maxTimeMS` + * - `bypassDocumentValidation` + * - `raw` + * - `promoteLongs` + * - `promoteValues` + * - `promoteBuffers` + * - [`collation`](./api.html#aggregate_Aggregate-collation) + * - `comment` + * - [`session`](./api.html#aggregate_Aggregate-session) + * + * @property options + * @memberOf Aggregate + * @api public + */ + +Aggregate.prototype.options; + +/** + * Get/set the model that this aggregation will execute on. + * + * ####Example: + * const aggregate = MyModel.aggregate([{ $match: { answer: 42 } }]); + * aggregate.model() === MyModel; // true + * + * // Change the model. There's rarely any reason to do this. + * aggregate.model(SomeOtherModel); + * aggregate.model() === SomeOtherModel; // true + * + * @param {Model} [model] the model to which the aggregate is to be bound + * @return {Aggregate|Model} if model is passed, will return `this`, otherwise will return the model + * @api public + */ + +Aggregate.prototype.model = function(model) { + if (arguments.length === 0) { + return this._model; + } + + this._model = model; + if (model.schema != null) { + if (this.options.readPreference == null && + model.schema.options.read != null) { + this.options.readPreference = model.schema.options.read; + } + if (this.options.collation == null && + model.schema.options.collation != null) { + this.options.collation = model.schema.options.collation; + } + } + return this; +}; + +/** + * Appends new operators to this aggregate pipeline + * + * ####Examples: + * + * aggregate.append({ $project: { field: 1 }}, { $limit: 2 }); + * + * // or pass an array + * const pipeline = [{ $match: { daw: 'Logic Audio X' }} ]; + * aggregate.append(pipeline); + * + * @param {Object} ops operator(s) to append + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.append = function() { + const args = (arguments.length === 1 && util.isArray(arguments[0])) + ? arguments[0] + : utils.args(arguments); + + if (!args.every(isOperator)) { + throw new Error('Arguments must be aggregate pipeline operators'); + } + + this._pipeline = this._pipeline.concat(args); + + return this; +}; + +/** + * Appends a new $addFields operator to this aggregate pipeline. + * Requires MongoDB v3.4+ to work + * + * ####Examples: + * + * // adding new fields based on existing fields + * aggregate.addFields({ + * newField: '$b.nested' + * , plusTen: { $add: ['$val', 10]} + * , sub: { + * name: '$a' + * } + * }) + * + * // etc + * aggregate.addFields({ salary_k: { $divide: [ "$salary", 1000 ] } }); + * + * @param {Object} arg field specification + * @see $addFields https://docs.mongodb.com/manual/reference/operator/aggregation/addFields/ + * @return {Aggregate} + * @api public + */ +Aggregate.prototype.addFields = function(arg) { + const fields = {}; + if (typeof arg === 'object' && !util.isArray(arg)) { + Object.keys(arg).forEach(function(field) { + fields[field] = arg[field]; + }); + } else { + throw new Error('Invalid addFields() argument. Must be an object'); + } + return this.append({ $addFields: fields }); +}; + +/** + * Appends a new $project operator to this aggregate pipeline. + * + * Mongoose query [selection syntax](#query_Query-select) is also supported. + * + * ####Examples: + * + * // include a, include b, exclude _id + * aggregate.project("a b -_id"); + * + * // or you may use object notation, useful when + * // you have keys already prefixed with a "-" + * aggregate.project({a: 1, b: 1, _id: 0}); + * + * // reshaping documents + * aggregate.project({ + * newField: '$b.nested' + * , plusTen: { $add: ['$val', 10]} + * , sub: { + * name: '$a' + * } + * }) + * + * // etc + * aggregate.project({ salary_k: { $divide: [ "$salary", 1000 ] } }); + * + * @param {Object|String} arg field specification + * @see projection http://docs.mongodb.org/manual/reference/aggregation/project/ + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.project = function(arg) { + const fields = {}; + + if (typeof arg === 'object' && !util.isArray(arg)) { + Object.keys(arg).forEach(function(field) { + fields[field] = arg[field]; + }); + } else if (arguments.length === 1 && typeof arg === 'string') { + arg.split(/\s+/).forEach(function(field) { + if (!field) { + return; + } + const include = field[0] === '-' ? 0 : 1; + if (include === 0) { + field = field.substring(1); + } + fields[field] = include; + }); + } else { + throw new Error('Invalid project() argument. Must be string or object'); + } + + return this.append({ $project: fields }); +}; + +/** + * Appends a new custom $group operator to this aggregate pipeline. + * + * ####Examples: + * + * aggregate.group({ _id: "$department" }); + * + * @see $group http://docs.mongodb.org/manual/reference/aggregation/group/ + * @method group + * @memberOf Aggregate + * @instance + * @param {Object} arg $group operator contents + * @return {Aggregate} + * @api public + */ + +/** + * Appends a new custom $match operator to this aggregate pipeline. + * + * ####Examples: + * + * aggregate.match({ department: { $in: [ "sales", "engineering" ] } }); + * + * @see $match http://docs.mongodb.org/manual/reference/aggregation/match/ + * @method match + * @memberOf Aggregate + * @instance + * @param {Object} arg $match operator contents + * @return {Aggregate} + * @api public + */ + +/** + * Appends a new $skip operator to this aggregate pipeline. + * + * ####Examples: + * + * aggregate.skip(10); + * + * @see $skip http://docs.mongodb.org/manual/reference/aggregation/skip/ + * @method skip + * @memberOf Aggregate + * @instance + * @param {Number} num number of records to skip before next stage + * @return {Aggregate} + * @api public + */ + +/** + * Appends a new $limit operator to this aggregate pipeline. + * + * ####Examples: + * + * aggregate.limit(10); + * + * @see $limit http://docs.mongodb.org/manual/reference/aggregation/limit/ + * @method limit + * @memberOf Aggregate + * @instance + * @param {Number} num maximum number of records to pass to the next stage + * @return {Aggregate} + * @api public + */ + +/** + * Appends a new $geoNear operator to this aggregate pipeline. + * + * ####NOTE: + * + * **MUST** be used as the first operator in the pipeline. + * + * ####Examples: + * + * aggregate.near({ + * near: [40.724, -73.997], + * distanceField: "dist.calculated", // required + * maxDistance: 0.008, + * query: { type: "public" }, + * includeLocs: "dist.location", + * uniqueDocs: true, + * num: 5 + * }); + * + * @see $geoNear http://docs.mongodb.org/manual/reference/aggregation/geoNear/ + * @method near + * @memberOf Aggregate + * @instance + * @param {Object} arg + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.near = function(arg) { + const op = {}; + op.$geoNear = arg; + return this.append(op); +}; + +/*! + * define methods + */ + +'group match skip limit out'.split(' ').forEach(function($operator) { + Aggregate.prototype[$operator] = function(arg) { + const op = {}; + op['$' + $operator] = arg; + return this.append(op); + }; +}); + +/** + * Appends new custom $unwind operator(s) to this aggregate pipeline. + * + * Note that the `$unwind` operator requires the path name to start with '$'. + * Mongoose will prepend '$' if the specified field doesn't start '$'. + * + * ####Examples: + * + * aggregate.unwind("tags"); + * aggregate.unwind("a", "b", "c"); + * aggregate.unwind({ path: '$tags', preserveNullAndEmptyArrays: true }); + * + * @see $unwind http://docs.mongodb.org/manual/reference/aggregation/unwind/ + * @param {String|Object} fields the field(s) to unwind, either as field names or as [objects with options](https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/#document-operand-with-options). If passing a string, prefixing the field name with '$' is optional. If passing an object, `path` must start with '$'. + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.unwind = function() { + const args = utils.args(arguments); + + const res = []; + for (const arg of args) { + if (arg && typeof arg === 'object') { + res.push({ $unwind: arg }); + } else if (typeof arg === 'string') { + res.push({ + $unwind: (arg && arg.startsWith('$')) ? arg : '$' + arg + }); + } else { + throw new Error('Invalid arg "' + arg + '" to unwind(), ' + + 'must be string or object'); + } + } + + return this.append.apply(this, res); +}; + +/** + * Appends a new $replaceRoot operator to this aggregate pipeline. + * + * Note that the `$replaceRoot` operator requires field strings to start with '$'. + * If you are passing in a string Mongoose will prepend '$' if the specified field doesn't start '$'. + * If you are passing in an object the strings in your expression will not be altered. + * + * ####Examples: + * + * aggregate.replaceRoot("user"); + * + * aggregate.replaceRoot({ x: { $concat: ['$this', '$that'] } }); + * + * @see $replaceRoot https://docs.mongodb.org/manual/reference/operator/aggregation/replaceRoot + * @param {String|Object} the field or document which will become the new root document + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.replaceRoot = function(newRoot) { + let ret; + + if (typeof newRoot === 'string') { + ret = newRoot.startsWith('$') ? newRoot : '$' + newRoot; + } else { + ret = newRoot; + } + + return this.append({ + $replaceRoot: { + newRoot: ret + } + }); +}; + +/** + * Appends a new $count operator to this aggregate pipeline. + * + * ####Examples: + * + * aggregate.count("userCount"); + * + * @see $count https://docs.mongodb.org/manual/reference/operator/aggregation/count + * @param {String} the name of the count field + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.count = function(countName) { + return this.append({ $count: countName }); +}; + +/** + * Appends a new $sortByCount operator to this aggregate pipeline. Accepts either a string field name + * or a pipeline object. + * + * Note that the `$sortByCount` operator requires the new root to start with '$'. + * Mongoose will prepend '$' if the specified field name doesn't start with '$'. + * + * ####Examples: + * + * aggregate.sortByCount('users'); + * aggregate.sortByCount({ $mergeObjects: [ "$employee", "$business" ] }) + * + * @see $sortByCount https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/ + * @param {Object|String} arg + * @return {Aggregate} this + * @api public + */ + +Aggregate.prototype.sortByCount = function(arg) { + if (arg && typeof arg === 'object') { + return this.append({ $sortByCount: arg }); + } else if (typeof arg === 'string') { + return this.append({ + $sortByCount: (arg && arg.startsWith('$')) ? arg : '$' + arg + }); + } else { + throw new TypeError('Invalid arg "' + arg + '" to sortByCount(), ' + + 'must be string or object'); + } +}; + +/** + * Appends new custom $lookup operator(s) to this aggregate pipeline. + * + * ####Examples: + * + * aggregate.lookup({ from: 'users', localField: 'userId', foreignField: '_id', as: 'users' }); + * + * @see $lookup https://docs.mongodb.org/manual/reference/operator/aggregation/lookup/#pipe._S_lookup + * @param {Object} options to $lookup as described in the above link + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.lookup = function(options) { + return this.append({ $lookup: options }); +}; + +/** + * Appends new custom $graphLookup operator(s) to this aggregate pipeline, performing a recursive search on a collection. + * + * Note that graphLookup can only consume at most 100MB of memory, and does not allow disk use even if `{ allowDiskUse: true }` is specified. + * + * #### Examples: + * // Suppose we have a collection of courses, where a document might look like `{ _id: 0, name: 'Calculus', prerequisite: 'Trigonometry'}` and `{ _id: 0, name: 'Trigonometry', prerequisite: 'Algebra' }` + * aggregate.graphLookup({ from: 'courses', startWith: '$prerequisite', connectFromField: 'prerequisite', connectToField: 'name', as: 'prerequisites', maxDepth: 3 }) // this will recursively search the 'courses' collection up to 3 prerequisites + * + * @see $graphLookup https://docs.mongodb.com/manual/reference/operator/aggregation/graphLookup/#pipe._S_graphLookup + * @param {Object} options to $graphLookup as described in the above link + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.graphLookup = function(options) { + const cloneOptions = {}; + if (options) { + if (!utils.isObject(options)) { + throw new TypeError('Invalid graphLookup() argument. Must be an object.'); + } + + utils.mergeClone(cloneOptions, options); + const startWith = cloneOptions.startWith; + + if (startWith && typeof startWith === 'string') { + cloneOptions.startWith = cloneOptions.startWith.startsWith('$') ? + cloneOptions.startWith : + '$' + cloneOptions.startWith; + } + + } + return this.append({ $graphLookup: cloneOptions }); +}; + +/** + * Appends new custom $sample operator(s) to this aggregate pipeline. + * + * ####Examples: + * + * aggregate.sample(3); // Add a pipeline that picks 3 random documents + * + * @see $sample https://docs.mongodb.org/manual/reference/operator/aggregation/sample/#pipe._S_sample + * @param {Number} size number of random documents to pick + * @return {Aggregate} + * @api public + */ + +Aggregate.prototype.sample = function(size) { + return this.append({ $sample: { size: size } }); +}; + +/** + * Appends a new $sort operator to this aggregate pipeline. + * + * If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`. + * + * If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with `-` which will be treated as descending. + * + * ####Examples: + * + * // these are equivalent + * aggregate.sort({ field: 'asc', test: -1 }); + * aggregate.sort('field -test'); + * + * @see $sort http://docs.mongodb.org/manual/reference/aggregation/sort/ + * @param {Object|String} arg + * @return {Aggregate} this + * @api public + */ + +Aggregate.prototype.sort = function(arg) { + // TODO refactor to reuse the query builder logic + + const sort = {}; + + if (arg.constructor.name === 'Object') { + const desc = ['desc', 'descending', -1]; + Object.keys(arg).forEach(function(field) { + // If sorting by text score, skip coercing into 1/-1 + if (arg[field] instanceof Object && arg[field].$meta) { + sort[field] = arg[field]; + return; + } + sort[field] = desc.indexOf(arg[field]) === -1 ? 1 : -1; + }); + } else if (arguments.length === 1 && typeof arg === 'string') { + arg.split(/\s+/).forEach(function(field) { + if (!field) { + return; + } + const ascend = field[0] === '-' ? -1 : 1; + if (ascend === -1) { + field = field.substring(1); + } + sort[field] = ascend; + }); + } else { + throw new TypeError('Invalid sort() argument. Must be a string or object.'); + } + + return this.append({ $sort: sort }); +}; + +/** + * Sets the readPreference option for the aggregation query. + * + * ####Example: + * + * Model.aggregate(..).read('primaryPreferred').exec(callback) + * + * @param {String} pref one of the listed preference options or their aliases + * @param {Array} [tags] optional tags for this query + * @return {Aggregate} this + * @api public + * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference + * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences + */ + +Aggregate.prototype.read = function(pref, tags) { + if (!this.options) { + this.options = {}; + } + read.call(this, pref, tags); + return this; +}; + +/** + * Sets the readConcern level for the aggregation query. + * + * ####Example: + * + * Model.aggregate(..).readConcern('majority').exec(callback) + * + * @param {String} level one of the listed read concern level or their aliases + * @see mongodb https://docs.mongodb.com/manual/reference/read-concern/ + * @return {Aggregate} this + * @api public + */ + +Aggregate.prototype.readConcern = function(level) { + if (!this.options) { + this.options = {}; + } + readConcern.call(this, level); + return this; +}; + +/** + * Appends a new $redact operator to this aggregate pipeline. + * + * If 3 arguments are supplied, Mongoose will wrap them with if-then-else of $cond operator respectively + * If `thenExpr` or `elseExpr` is string, make sure it starts with $$, like `$$DESCEND`, `$$PRUNE` or `$$KEEP`. + * + * ####Example: + * + * Model.aggregate(...) + * .redact({ + * $cond: { + * if: { $eq: [ '$level', 5 ] }, + * then: '$$PRUNE', + * else: '$$DESCEND' + * } + * }) + * .exec(); + * + * // $redact often comes with $cond operator, you can also use the following syntax provided by mongoose + * Model.aggregate(...) + * .redact({ $eq: [ '$level', 5 ] }, '$$PRUNE', '$$DESCEND') + * .exec(); + * + * @param {Object} expression redact options or conditional expression + * @param {String|Object} [thenExpr] true case for the condition + * @param {String|Object} [elseExpr] false case for the condition + * @return {Aggregate} this + * @see $redact https://docs.mongodb.com/manual/reference/operator/aggregation/redact/ + * @api public + */ + +Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) { + if (arguments.length === 3) { + if ((typeof thenExpr === 'string' && !thenExpr.startsWith('$$')) || + (typeof elseExpr === 'string' && !elseExpr.startsWith('$$'))) { + throw new Error('If thenExpr or elseExpr is string, it must start with $$. e.g. $$DESCEND, $$PRUNE, $$KEEP'); + } + + expression = { + $cond: { + if: expression, + then: thenExpr, + else: elseExpr + } + }; + } else if (arguments.length !== 1) { + throw new TypeError('Invalid arguments'); + } + + return this.append({ $redact: expression }); +}; + +/** + * Execute the aggregation with explain + * + * ####Example: + * + * Model.aggregate(..).explain(callback) + * + * @param {Function} callback + * @return {Promise} + */ + +Aggregate.prototype.explain = function(callback) { + const model = this._model; + + return promiseOrCallback(callback, cb => { + if (!this._pipeline.length) { + const err = new Error('Aggregate has empty pipeline'); + return cb(err); + } + + prepareDiscriminatorPipeline(this); + + model.hooks.execPre('aggregate', this, error => { + if (error) { + const _opts = { error: error }; + return model.hooks.execPost('aggregate', this, [null], _opts, error => { + cb(error); + }); + } + + this.options.explain = true; + + model.collection. + aggregate(this._pipeline, this.options || {}). + explain((error, result) => { + const _opts = { error: error }; + return model.hooks.execPost('aggregate', this, [result], _opts, error => { + if (error) { + return cb(error); + } + return cb(null, result); + }); + }); + }); + }, model.events); +}; + +/** + * Sets the allowDiskUse option for the aggregation query (ignored for < 2.6.0) + * + * ####Example: + * + * await Model.aggregate([{ $match: { foo: 'bar' } }]).allowDiskUse(true); + * + * @param {Boolean} value Should tell server it can use hard drive to store data during aggregation. + * @param {Array} [tags] optional tags for this query + * @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/ + */ + +Aggregate.prototype.allowDiskUse = function(value) { + this.options.allowDiskUse = value; + return this; +}; + +/** + * Sets the hint option for the aggregation query (ignored for < 3.6.0) + * + * ####Example: + * + * Model.aggregate(..).hint({ qty: 1, category: 1 }).exec(callback) + * + * @param {Object|String} value a hint object or the index name + * @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/ + */ + +Aggregate.prototype.hint = function(value) { + this.options.hint = value; + return this; +}; + +/** + * Sets the session for this aggregation. Useful for [transactions](/docs/transactions.html). + * + * ####Example: + * + * const session = await Model.startSession(); + * await Model.aggregate(..).session(session); + * + * @param {ClientSession} session + * @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/ + */ + +Aggregate.prototype.session = function(session) { + if (session == null) { + delete this.options.session; + } else { + this.options.session = session; + } + return this; +}; + +/** + * Lets you set arbitrary options, for middleware or plugins. + * + * ####Example: + * + * const agg = Model.aggregate(..).option({ allowDiskUse: true }); // Set the `allowDiskUse` option + * agg.options; // `{ allowDiskUse: true }` + * + * @param {Object} options keys to merge into current options + * @param [options.maxTimeMS] number limits the time this aggregation will run, see [MongoDB docs on `maxTimeMS`](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) + * @param [options.allowDiskUse] boolean if true, the MongoDB server will use the hard drive to store data during this aggregation + * @param [options.collation] object see [`Aggregate.prototype.collation()`](./docs/api.html#aggregate_Aggregate-collation) + * @param [options.session] ClientSession see [`Aggregate.prototype.session()`](./docs/api.html#aggregate_Aggregate-session) + * @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/ + * @return {Aggregate} this + * @api public + */ + +Aggregate.prototype.option = function(value) { + for (const key in value) { + this.options[key] = value[key]; + } + return this; +}; + +/** + * Sets the cursor option option for the aggregation query (ignored for < 2.6.0). + * Note the different syntax below: .exec() returns a cursor object, and no callback + * is necessary. + * + * ####Example: + * + * const cursor = Model.aggregate(..).cursor({ batchSize: 1000 }).exec(); + * cursor.eachAsync(function(doc, i) { + * // use doc + * }); + * + * @param {Object} options + * @param {Number} options.batchSize set the cursor batch size + * @param {Boolean} [options.useMongooseAggCursor] use experimental mongoose-specific aggregation cursor (for `eachAsync()` and other query cursor semantics) + * @return {Aggregate} this + * @api public + * @see mongodb http://mongodb.github.io/node-mongodb-native/2.0/api/AggregationCursor.html + */ + +Aggregate.prototype.cursor = function(options) { + if (!this.options) { + this.options = {}; + } + this.options.cursor = options || {}; + return this; +}; + +/** + * Sets an option on this aggregation. This function will be deprecated in a + * future release. Use the [`cursor()`](./api.html#aggregate_Aggregate-cursor), + * [`collation()`](./api.html#aggregate_Aggregate-collation), etc. helpers to + * set individual options, or access `agg.options` directly. + * + * Note that MongoDB aggregations [do **not** support the `noCursorTimeout` flag](https://jira.mongodb.org/browse/SERVER-6036), + * if you try setting that flag with this function you will get a "unrecognized field 'noCursorTimeout'" error. + * + * @param {String} flag + * @param {Boolean} value + * @return {Aggregate} this + * @api public + * @deprecated Use [`.option()`](api.html#aggregate_Aggregate-option) instead. Note that MongoDB aggregations do **not** support a `noCursorTimeout` option. + */ + +Aggregate.prototype.addCursorFlag = util.deprecate(function(flag, value) { + if (!this.options) { + this.options = {}; + } + this.options[flag] = value; + return this; +}, 'Mongoose: `Aggregate#addCursorFlag()` is deprecated, use `option()` instead'); + +/** + * Adds a collation + * + * ####Example: + * + * Model.aggregate(..).collation({ locale: 'en_US', strength: 1 }).exec(); + * + * @param {Object} collation options + * @return {Aggregate} this + * @api public + * @see mongodb http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#aggregate + */ + +Aggregate.prototype.collation = function(collation) { + if (!this.options) { + this.options = {}; + } + this.options.collation = collation; + return this; +}; + +/** + * Combines multiple aggregation pipelines. + * + * ####Example: + * + * Model.aggregate(...) + * .facet({ + * books: [{ groupBy: '$author' }], + * price: [{ $bucketAuto: { groupBy: '$price', buckets: 2 } }] + * }) + * .exec(); + * + * // Output: { books: [...], price: [{...}, {...}] } + * + * @param {Object} facet options + * @return {Aggregate} this + * @see $facet https://docs.mongodb.com/v3.4/reference/operator/aggregation/facet/ + * @api public + */ + +Aggregate.prototype.facet = function(options) { + return this.append({ $facet: options }); +}; + +/** + * Helper for [Atlas Text Search](https://docs.atlas.mongodb.com/reference/atlas-search/tutorial/)'s + * `$search` stage. + * + * ####Example: + * + * Model.aggregate(). + * search({ + * text: { + * query: 'baseball', + * path: 'plot' + * } + * }); + * + * // Output: [{ plot: '...', title: '...' }] + * + * @param {Object} $search options + * @return {Aggregate} this + * @see $search https://docs.atlas.mongodb.com/reference/atlas-search/tutorial/ + * @api public + */ + +Aggregate.prototype.search = function(options) { + return this.append({ $search: options }); +}; + +/** + * Returns the current pipeline + * + * ####Example: + * + * MyModel.aggregate().match({ test: 1 }).pipeline(); // [{ $match: { test: 1 } }] + * + * @return {Array} + * @api public + */ + + +Aggregate.prototype.pipeline = function() { + return this._pipeline; +}; + +/** + * Executes the aggregate pipeline on the currently bound Model. + * + * ####Example: + * + * aggregate.exec(callback); + * + * // Because a promise is returned, the `callback` is optional. + * const promise = aggregate.exec(); + * promise.then(..); + * + * @see Promise #promise_Promise + * @param {Function} [callback] + * @return {Promise} + * @api public + */ + +Aggregate.prototype.exec = function(callback) { + if (!this._model) { + throw new Error('Aggregate not bound to any Model'); + } + const model = this._model; + const collection = this._model.collection; + + applyGlobalMaxTimeMS(this.options, model); + + if (this.options && this.options.cursor) { + return new AggregationCursor(this); + } + + return promiseOrCallback(callback, cb => { + prepareDiscriminatorPipeline(this); + stringifyAccumulatorOptions(this._pipeline); + + model.hooks.execPre('aggregate', this, error => { + if (error) { + const _opts = { error: error }; + return model.hooks.execPost('aggregate', this, [null], _opts, error => { + cb(error); + }); + } + if (!this._pipeline.length) { + return cb(new Error('Aggregate has empty pipeline')); + } + + const options = utils.clone(this.options || {}); + collection.aggregate(this._pipeline, options, (error, cursor) => { + if (error) { + const _opts = { error: error }; + return model.hooks.execPost('aggregate', this, [null], _opts, error => { + if (error) { + return cb(error); + } + return cb(null); + }); + } + cursor.toArray((error, result) => { + const _opts = { error: error }; + model.hooks.execPost('aggregate', this, [result], _opts, (error, result) => { + if (error) { + return cb(error); + } + + cb(null, result); + }); + }); + }); + }); + }, model.events); +}; + +/** + * Provides promise for aggregate. + * + * ####Example: + * + * Model.aggregate(..).then(successCallback, errorCallback); + * + * @see Promise #promise_Promise + * @param {Function} [resolve] successCallback + * @param {Function} [reject] errorCallback + * @return {Promise} + */ +Aggregate.prototype.then = function(resolve, reject) { + return this.exec().then(resolve, reject); +}; + +/** + * Executes the query returning a `Promise` which will be + * resolved with either the doc(s) or rejected with the error. + * Like [`.then()`](#query_Query-then), but only takes a rejection handler. + * + * @param {Function} [reject] + * @return {Promise} + * @api public + */ + +Aggregate.prototype.catch = function(reject) { + return this.exec().then(null, reject); +}; + +/** + * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js + * You do not need to call this function explicitly, the JavaScript runtime + * will call it for you. + * + * ####Example + * + * const agg = Model.aggregate([{ $match: { age: { $gte: 25 } } }]); + * for await (const doc of agg) { + * console.log(doc.name); + * } + * + * Node.js 10.x supports async iterators natively without any flags. You can + * enable async iterators in Node.js 8.x using the [`--harmony_async_iteration` flag](https://github.com/tc39/proposal-async-iteration/issues/117#issuecomment-346695187). + * + * **Note:** This function is not set if `Symbol.asyncIterator` is undefined. If + * `Symbol.asyncIterator` is undefined, that means your Node.js version does not + * support async iterators. + * + * @method Symbol.asyncIterator + * @memberOf Aggregate + * @instance + * @api public + */ + +if (Symbol.asyncIterator != null) { + Aggregate.prototype[Symbol.asyncIterator] = function() { + return this.cursor({ useMongooseAggCursor: true }). + exec(). + transformNull(). + _transformForAsyncIterator(); + }; +} + +/*! + * Helpers + */ + +/** + * Checks whether an object is likely a pipeline operator + * + * @param {Object} obj object to check + * @return {Boolean} + * @api private + */ + +function isOperator(obj) { + if (typeof obj !== 'object') { + return false; + } + + const k = Object.keys(obj); + + return k.length === 1 && k.some(key => { return key[0] === '$'; }); +} + +/*! + * Adds the appropriate `$match` pipeline step to the top of an aggregate's + * pipeline, should it's model is a non-root discriminator type. This is + * analogous to the `prepareDiscriminatorCriteria` function in `lib/query.js`. + * + * @param {Aggregate} aggregate Aggregate to prepare + */ + +Aggregate._prepareDiscriminatorPipeline = prepareDiscriminatorPipeline; + +function prepareDiscriminatorPipeline(aggregate) { + const schema = aggregate._model.schema; + const discriminatorMapping = schema && schema.discriminatorMapping; + + if (discriminatorMapping && !discriminatorMapping.isRoot) { + const originalPipeline = aggregate._pipeline; + const discriminatorKey = discriminatorMapping.key; + const discriminatorValue = discriminatorMapping.value; + + // If the first pipeline stage is a match and it doesn't specify a `__t` + // key, add the discriminator key to it. This allows for potential + // aggregation query optimizations not to be disturbed by this feature. + if (originalPipeline[0] && originalPipeline[0].$match && !originalPipeline[0].$match[discriminatorKey]) { + originalPipeline[0].$match[discriminatorKey] = discriminatorValue; + // `originalPipeline` is a ref, so there's no need for + // aggregate._pipeline = originalPipeline + } else if (originalPipeline[0] && originalPipeline[0].$geoNear) { + originalPipeline[0].$geoNear.query = + originalPipeline[0].$geoNear.query || {}; + originalPipeline[0].$geoNear.query[discriminatorKey] = discriminatorValue; + } else if (originalPipeline[0] && originalPipeline[0].$search) { + if (originalPipeline[1] && originalPipeline[1].$match != null) { + originalPipeline[1].$match[discriminatorKey] = originalPipeline[1].$match[discriminatorKey] || discriminatorValue; + } else { + const match = {}; + match[discriminatorKey] = discriminatorValue; + originalPipeline.splice(1, 0, { $match: match }); + } + } else { + const match = {}; + match[discriminatorKey] = discriminatorValue; + aggregate._pipeline.unshift({ $match: match }); + } + } +} + +/*! + * Exports + */ + +module.exports = Aggregate; diff --git a/node_modules/mongoose/lib/browser.js b/node_modules/mongoose/lib/browser.js new file mode 100644 index 0000000..f716f2a --- /dev/null +++ b/node_modules/mongoose/lib/browser.js @@ -0,0 +1,155 @@ +/* eslint-env browser */ + +'use strict'; + +require('./driver').set(require('./drivers/browser')); + +const DocumentProvider = require('./document_provider.js'); +const PromiseProvider = require('./promise_provider'); + +DocumentProvider.setBrowser(true); + +/** + * The Mongoose [Promise](#promise_Promise) constructor. + * + * @method Promise + * @api public + */ + +Object.defineProperty(exports, 'Promise', { + get: function() { + return PromiseProvider.get(); + }, + set: function(lib) { + PromiseProvider.set(lib); + } +}); + +/** + * Storage layer for mongoose promises + * + * @method PromiseProvider + * @api public + */ + +exports.PromiseProvider = PromiseProvider; + +/** + * The [MongooseError](#error_MongooseError) constructor. + * + * @method Error + * @api public + */ + +exports.Error = require('./error/index'); + +/** + * The Mongoose [Schema](#schema_Schema) constructor + * + * ####Example: + * + * const mongoose = require('mongoose'); + * const Schema = mongoose.Schema; + * const CatSchema = new Schema(..); + * + * @method Schema + * @api public + */ + +exports.Schema = require('./schema'); + +/** + * The various Mongoose Types. + * + * ####Example: + * + * const mongoose = require('mongoose'); + * const array = mongoose.Types.Array; + * + * ####Types: + * + * - [ObjectId](#types-objectid-js) + * - [Buffer](#types-buffer-js) + * - [SubDocument](#types-embedded-js) + * - [Array](#types-array-js) + * - [DocumentArray](#types-documentarray-js) + * + * Using this exposed access to the `ObjectId` type, we can construct ids on demand. + * + * const ObjectId = mongoose.Types.ObjectId; + * const id1 = new ObjectId; + * + * @property Types + * @api public + */ +exports.Types = require('./types'); + +/** + * The Mongoose [VirtualType](#virtualtype_VirtualType) constructor + * + * @method VirtualType + * @api public + */ +exports.VirtualType = require('./virtualtype'); + +/** + * The various Mongoose SchemaTypes. + * + * ####Note: + * + * _Alias of mongoose.Schema.Types for backwards compatibility._ + * + * @property SchemaTypes + * @see Schema.SchemaTypes #schema_Schema.Types + * @api public + */ + +exports.SchemaType = require('./schematype.js'); + +/** + * Internal utils + * + * @property utils + * @api private + */ + +exports.utils = require('./utils.js'); + +/** + * The Mongoose browser [Document](/api/document.html) constructor. + * + * @method Document + * @api public + */ +exports.Document = DocumentProvider(); + +/** + * Return a new browser model. In the browser, a model is just + * a simplified document with a schema - it does **not** have + * functions like `findOne()`, etc. + * + * @method model + * @api public + * @param {String} name + * @param {Schema} schema + * @return Class + */ +exports.model = function(name, schema) { + class Model extends exports.Document { + constructor(obj, fields) { + super(obj, schema, fields); + } + } + Model.modelName = name; + + return Model; +}; + +/*! + * Module exports. + */ + +if (typeof window !== 'undefined') { + window.mongoose = module.exports; + window.Buffer = Buffer; +} diff --git a/node_modules/mongoose/lib/browserDocument.js b/node_modules/mongoose/lib/browserDocument.js new file mode 100644 index 0000000..a44f307 --- /dev/null +++ b/node_modules/mongoose/lib/browserDocument.js @@ -0,0 +1,100 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const NodeJSDocument = require('./document'); +const EventEmitter = require('events').EventEmitter; +const MongooseError = require('./error/index'); +const Schema = require('./schema'); +const ObjectId = require('./types/objectid'); +const ValidationError = MongooseError.ValidationError; +const applyHooks = require('./helpers/model/applyHooks'); +const isObject = require('./helpers/isObject'); + +/** + * Document constructor. + * + * @param {Object} obj the values to set + * @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data + * @param {Boolean} [skipId] bool, should we auto create an ObjectId _id + * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter + * @event `init`: Emitted on a document after it has was retrieved from the db and fully hydrated by Mongoose. + * @event `save`: Emitted when the document is successfully saved + * @api private + */ + +function Document(obj, schema, fields, skipId, skipInit) { + if (!(this instanceof Document)) { + return new Document(obj, schema, fields, skipId, skipInit); + } + + if (isObject(schema) && !schema.instanceOfSchema) { + schema = new Schema(schema); + } + + // When creating EmbeddedDocument, it already has the schema and he doesn't need the _id + schema = this.schema || schema; + + // Generate ObjectId if it is missing, but it requires a scheme + if (!this.schema && schema.options._id) { + obj = obj || {}; + + if (obj._id === undefined) { + obj._id = new ObjectId(); + } + } + + if (!schema) { + throw new MongooseError.MissingSchemaError(); + } + + this.$__setSchema(schema); + + NodeJSDocument.call(this, obj, fields, skipId, skipInit); + + applyHooks(this, schema, { decorateDoc: true }); + + // apply methods + for (const m in schema.methods) { + this[m] = schema.methods[m]; + } + // apply statics + for (const s in schema.statics) { + this[s] = schema.statics[s]; + } +} + +/*! + * Inherit from the NodeJS document + */ + +Document.prototype = Object.create(NodeJSDocument.prototype); +Document.prototype.constructor = Document; + +/*! + * ignore + */ + +Document.events = new EventEmitter(); + +/*! + * Browser doc exposes the event emitter API + */ + +Document.$emitter = new EventEmitter(); + +['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners', + 'removeAllListeners', 'addListener'].forEach(function(emitterFn) { + Document[emitterFn] = function() { + return Document.$emitter[emitterFn].apply(Document.$emitter, arguments); + }; +}); + +/*! + * Module exports. + */ + +Document.ValidationError = ValidationError; +module.exports = exports = Document; diff --git a/node_modules/mongoose/lib/cast.js b/node_modules/mongoose/lib/cast.js new file mode 100644 index 0000000..1c24bc7 --- /dev/null +++ b/node_modules/mongoose/lib/cast.js @@ -0,0 +1,364 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const CastError = require('./error/cast'); +const StrictModeError = require('./error/strict'); +const Types = require('./schema/index'); +const castTextSearch = require('./schema/operators/text'); +const get = require('./helpers/get'); +const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue'); +const isOperator = require('./helpers/query/isOperator'); +const util = require('util'); +const isObject = require('./helpers/isObject'); +const isMongooseObject = require('./helpers/isMongooseObject'); + +const ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon']; + +/** + * Handles internal casting for query filters. + * + * @param {Schema} schema + * @param {Object} obj Object to cast + * @param {Object} options the query options + * @param {Query} context passed to setters + * @api private + */ +module.exports = function cast(schema, obj, options, context) { + if (Array.isArray(obj)) { + throw new Error('Query filter must be an object, got an array ', util.inspect(obj)); + } + + if (obj == null) { + return obj; + } + + // bson 1.x has the unfortunate tendency to remove filters that have a top-level + // `_bsontype` property. But we should still allow ObjectIds because + // `Collection#find()` has a special case to support `find(objectid)`. + // Should remove this when we upgrade to bson 4.x. See gh-8222, gh-8268 + if (obj.hasOwnProperty('_bsontype') && obj._bsontype !== 'ObjectID') { + delete obj._bsontype; + } + + if (schema != null && schema.discriminators != null && obj[schema.options.discriminatorKey] != null) { + schema = getSchemaDiscriminatorByValue(schema, obj[schema.options.discriminatorKey]) || schema; + } + + const paths = Object.keys(obj); + let i = paths.length; + let _keys; + let any$conditionals; + let schematype; + let nested; + let path; + let type; + let val; + + options = options || {}; + + while (i--) { + path = paths[i]; + val = obj[path]; + + if (path === '$or' || path === '$nor' || path === '$and') { + if (!Array.isArray(val)) { + throw new CastError('Array', val, path); + } + for (let k = 0; k < val.length; ++k) { + if (val[k] == null || typeof val[k] !== 'object') { + throw new CastError('Object', val[k], path + '.' + k); + } + val[k] = cast(schema, val[k], options, context); + } + } else if (path === '$where') { + type = typeof val; + + if (type !== 'string' && type !== 'function') { + throw new Error('Must have a string or function for $where'); + } + + if (type === 'function') { + obj[path] = val.toString(); + } + + continue; + } else if (path === '$elemMatch') { + val = cast(schema, val, options, context); + } else if (path === '$text') { + val = castTextSearch(val, path); + } else { + if (!schema) { + // no casting for Mixed types + continue; + } + + schematype = schema.path(path); + + // Check for embedded discriminator paths + if (!schematype) { + const split = path.split('.'); + let j = split.length; + while (j--) { + const pathFirstHalf = split.slice(0, j).join('.'); + const pathLastHalf = split.slice(j).join('.'); + const _schematype = schema.path(pathFirstHalf); + const discriminatorKey = get(_schematype, 'schema.options.discriminatorKey'); + + // gh-6027: if we haven't found the schematype but this path is + // underneath an embedded discriminator and the embedded discriminator + // key is in the query, use the embedded discriminator schema + if (_schematype != null && + get(_schematype, 'schema.discriminators') != null && + discriminatorKey != null && + pathLastHalf !== discriminatorKey) { + const discriminatorVal = get(obj, pathFirstHalf + '.' + discriminatorKey); + if (discriminatorVal != null) { + schematype = _schematype.schema.discriminators[discriminatorVal]. + path(pathLastHalf); + } + } + } + } + + if (!schematype) { + // Handle potential embedded array queries + const split = path.split('.'); + let j = split.length; + let pathFirstHalf; + let pathLastHalf; + let remainingConds; + + // Find the part of the var path that is a path of the Schema + while (j--) { + pathFirstHalf = split.slice(0, j).join('.'); + schematype = schema.path(pathFirstHalf); + if (schematype) { + break; + } + } + + // If a substring of the input path resolves to an actual real path... + if (schematype) { + // Apply the casting; similar code for $elemMatch in schema/array.js + if (schematype.caster && schematype.caster.schema) { + remainingConds = {}; + pathLastHalf = split.slice(j).join('.'); + remainingConds[pathLastHalf] = val; + obj[path] = cast(schematype.caster.schema, remainingConds, options, context)[pathLastHalf]; + } else { + obj[path] = val; + } + continue; + } + + if (isObject(val)) { + // handle geo schemas that use object notation + // { loc: { long: Number, lat: Number } + + let geo = ''; + if (val.$near) { + geo = '$near'; + } else if (val.$nearSphere) { + geo = '$nearSphere'; + } else if (val.$within) { + geo = '$within'; + } else if (val.$geoIntersects) { + geo = '$geoIntersects'; + } else if (val.$geoWithin) { + geo = '$geoWithin'; + } + + if (geo) { + const numbertype = new Types.Number('__QueryCasting__'); + let value = val[geo]; + + if (val.$maxDistance != null) { + val.$maxDistance = numbertype.castForQueryWrapper({ + val: val.$maxDistance, + context: context + }); + } + if (val.$minDistance != null) { + val.$minDistance = numbertype.castForQueryWrapper({ + val: val.$minDistance, + context: context + }); + } + + if (geo === '$within') { + const withinType = value.$center + || value.$centerSphere + || value.$box + || value.$polygon; + + if (!withinType) { + throw new Error('Bad $within parameter: ' + JSON.stringify(val)); + } + + value = withinType; + } else if (geo === '$near' && + typeof value.type === 'string' && Array.isArray(value.coordinates)) { + // geojson; cast the coordinates + value = value.coordinates; + } else if ((geo === '$near' || geo === '$nearSphere' || geo === '$geoIntersects') && + value.$geometry && typeof value.$geometry.type === 'string' && + Array.isArray(value.$geometry.coordinates)) { + if (value.$maxDistance != null) { + value.$maxDistance = numbertype.castForQueryWrapper({ + val: value.$maxDistance, + context: context + }); + } + if (value.$minDistance != null) { + value.$minDistance = numbertype.castForQueryWrapper({ + val: value.$minDistance, + context: context + }); + } + if (isMongooseObject(value.$geometry)) { + value.$geometry = value.$geometry.toObject({ + transform: false, + virtuals: false + }); + } + value = value.$geometry.coordinates; + } else if (geo === '$geoWithin') { + if (value.$geometry) { + if (isMongooseObject(value.$geometry)) { + value.$geometry = value.$geometry.toObject({ virtuals: false }); + } + const geoWithinType = value.$geometry.type; + if (ALLOWED_GEOWITHIN_GEOJSON_TYPES.indexOf(geoWithinType) === -1) { + throw new Error('Invalid geoJSON type for $geoWithin "' + + geoWithinType + '", must be "Polygon" or "MultiPolygon"'); + } + value = value.$geometry.coordinates; + } else { + value = value.$box || value.$polygon || value.$center || + value.$centerSphere; + if (isMongooseObject(value)) { + value = value.toObject({ virtuals: false }); + } + } + } + + _cast(value, numbertype, context); + continue; + } + } + + if (schema.nested[path]) { + continue; + } + if (options.upsert && options.strict) { + if (options.strict === 'throw') { + throw new StrictModeError(path); + } + throw new StrictModeError(path, 'Path "' + path + '" is not in ' + + 'schema, strict mode is `true`, and upsert is `true`.'); + } else if (options.strictQuery === 'throw') { + throw new StrictModeError(path, 'Path "' + path + '" is not in ' + + 'schema and strictQuery is \'throw\'.'); + } else if (options.strictQuery) { + delete obj[path]; + } + } else if (val == null) { + continue; + } else if (val.constructor.name === 'Object') { + any$conditionals = Object.keys(val).some(isOperator); + + if (!any$conditionals) { + obj[path] = schematype.castForQueryWrapper({ + val: val, + context: context + }); + } else { + const ks = Object.keys(val); + let $cond; + + let k = ks.length; + + while (k--) { + $cond = ks[k]; + nested = val[$cond]; + + if ($cond === '$not') { + if (nested && schematype && !schematype.caster) { + _keys = Object.keys(nested); + if (_keys.length && isOperator(_keys[0])) { + for (const key in nested) { + nested[key] = schematype.castForQueryWrapper({ + $conditional: key, + val: nested[key], + context: context + }); + } + } else { + val[$cond] = schematype.castForQueryWrapper({ + $conditional: $cond, + val: nested, + context: context + }); + } + continue; + } + cast(schematype.caster ? schematype.caster.schema : schema, nested, options, context); + } else { + val[$cond] = schematype.castForQueryWrapper({ + $conditional: $cond, + val: nested, + context: context + }); + } + } + } + } else if (Array.isArray(val) && ['Buffer', 'Array'].indexOf(schematype.instance) === -1) { + const casted = []; + const valuesArray = val; + + for (const _val of valuesArray) { + casted.push(schematype.castForQueryWrapper({ + val: _val, + context: context + })); + } + + obj[path] = { $in: casted }; + } else { + obj[path] = schematype.castForQueryWrapper({ + val: val, + context: context + }); + } + } + } + + return obj; +}; + +function _cast(val, numbertype, context) { + if (Array.isArray(val)) { + val.forEach(function(item, i) { + if (Array.isArray(item) || isObject(item)) { + return _cast(item, numbertype, context); + } + val[i] = numbertype.castForQueryWrapper({ val: item, context: context }); + }); + } else { + const nearKeys = Object.keys(val); + let nearLen = nearKeys.length; + while (nearLen--) { + const nkey = nearKeys[nearLen]; + const item = val[nkey]; + if (Array.isArray(item) || isObject(item)) { + _cast(item, numbertype, context); + val[nkey] = item; + } else { + val[nkey] = numbertype.castForQuery({ val: item, context: context }); + } + } + } +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/cast/boolean.js b/node_modules/mongoose/lib/cast/boolean.js new file mode 100644 index 0000000..92551d4 --- /dev/null +++ b/node_modules/mongoose/lib/cast/boolean.js @@ -0,0 +1,32 @@ +'use strict'; + +const CastError = require('../error/cast'); + +/*! + * Given a value, cast it to a boolean, or throw a `CastError` if the value + * cannot be casted. `null` and `undefined` are considered valid. + * + * @param {Any} value + * @param {String} [path] optional the path to set on the CastError + * @return {Boolean|null|undefined} + * @throws {CastError} if `value` is not one of the allowed values + * @api private + */ + +module.exports = function castBoolean(value, path) { + if (module.exports.convertToTrue.has(value)) { + return true; + } + if (module.exports.convertToFalse.has(value)) { + return false; + } + + if (value == null) { + return value; + } + + throw new CastError('boolean', value, path); +}; + +module.exports.convertToTrue = new Set([true, 'true', 1, '1', 'yes']); +module.exports.convertToFalse = new Set([false, 'false', 0, '0', 'no']); diff --git a/node_modules/mongoose/lib/cast/date.js b/node_modules/mongoose/lib/cast/date.js new file mode 100644 index 0000000..ac17006 --- /dev/null +++ b/node_modules/mongoose/lib/cast/date.js @@ -0,0 +1,41 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = function castDate(value) { + // Support empty string because of empty form values. Originally introduced + // in https://github.com/Automattic/mongoose/commit/efc72a1898fc3c33a319d915b8c5463a22938dfe + if (value == null || value === '') { + return null; + } + + if (value instanceof Date) { + assert.ok(!isNaN(value.valueOf())); + + return value; + } + + let date; + + assert.ok(typeof value !== 'boolean'); + + if (value instanceof Number || typeof value === 'number') { + date = new Date(value); + } else if (typeof value === 'string' && !isNaN(Number(value)) && (Number(value) >= 275761 || Number(value) < -271820)) { + // string representation of milliseconds take this path + date = new Date(Number(value)); + } else if (typeof value.valueOf === 'function') { + // support for moment.js. This is also the path strings will take because + // strings have a `valueOf()` + date = new Date(value.valueOf()); + } else { + // fallback + date = new Date(value); + } + + if (!isNaN(date.valueOf())) { + return date; + } + + assert.ok(false); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/cast/decimal128.js b/node_modules/mongoose/lib/cast/decimal128.js new file mode 100644 index 0000000..bfb1578 --- /dev/null +++ b/node_modules/mongoose/lib/cast/decimal128.js @@ -0,0 +1,36 @@ +'use strict'; + +const Decimal128Type = require('../types/decimal128'); +const assert = require('assert'); + +module.exports = function castDecimal128(value) { + if (value == null) { + return value; + } + + if (typeof value === 'object' && typeof value.$numberDecimal === 'string') { + return Decimal128Type.fromString(value.$numberDecimal); + } + + if (value instanceof Decimal128Type) { + return value; + } + + if (typeof value === 'string') { + return Decimal128Type.fromString(value); + } + + if (Buffer.isBuffer(value)) { + return new Decimal128Type(value); + } + + if (typeof value === 'number') { + return Decimal128Type.fromString(String(value)); + } + + if (typeof value.valueOf === 'function' && typeof value.valueOf() === 'string') { + return Decimal128Type.fromString(value.valueOf()); + } + + assert.ok(false); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/cast/number.js b/node_modules/mongoose/lib/cast/number.js new file mode 100644 index 0000000..7cd7f84 --- /dev/null +++ b/node_modules/mongoose/lib/cast/number.js @@ -0,0 +1,43 @@ +'use strict'; + +const assert = require('assert'); + +/*! + * Given a value, cast it to a number, or throw a `CastError` if the value + * cannot be casted. `null` and `undefined` are considered valid. + * + * @param {Any} value + * @param {String} [path] optional the path to set on the CastError + * @return {Boolean|null|undefined} + * @throws {Error} if `value` is not one of the allowed values + * @api private + */ + +module.exports = function castNumber(val) { + if (val == null) { + return val; + } + if (val === '') { + return null; + } + + if (typeof val === 'string' || typeof val === 'boolean') { + val = Number(val); + } + + assert.ok(!isNaN(val)); + if (val instanceof Number) { + return val.valueOf(); + } + if (typeof val === 'number') { + return val; + } + if (!Array.isArray(val) && typeof val.valueOf === 'function') { + return Number(val.valueOf()); + } + if (val.toString && !Array.isArray(val) && val.toString() == Number(val)) { + return Number(val); + } + + assert.ok(false); +}; diff --git a/node_modules/mongoose/lib/cast/objectid.js b/node_modules/mongoose/lib/cast/objectid.js new file mode 100644 index 0000000..67cffb5 --- /dev/null +++ b/node_modules/mongoose/lib/cast/objectid.js @@ -0,0 +1,29 @@ +'use strict'; + +const ObjectId = require('../driver').get().ObjectId; +const assert = require('assert'); + +module.exports = function castObjectId(value) { + if (value == null) { + return value; + } + + if (value instanceof ObjectId) { + return value; + } + + if (value._id) { + if (value._id instanceof ObjectId) { + return value._id; + } + if (value._id.toString instanceof Function) { + return new ObjectId(value._id.toString()); + } + } + + if (value.toString instanceof Function) { + return new ObjectId(value.toString()); + } + + assert.ok(false); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/cast/string.js b/node_modules/mongoose/lib/cast/string.js new file mode 100644 index 0000000..4d89f8e --- /dev/null +++ b/node_modules/mongoose/lib/cast/string.js @@ -0,0 +1,37 @@ +'use strict'; + +const CastError = require('../error/cast'); + +/*! + * Given a value, cast it to a string, or throw a `CastError` if the value + * cannot be casted. `null` and `undefined` are considered valid. + * + * @param {Any} value + * @param {String} [path] optional the path to set on the CastError + * @return {string|null|undefined} + * @throws {CastError} + * @api private + */ + +module.exports = function castString(value, path) { + // If null or undefined + if (value == null) { + return value; + } + + // handle documents being passed + if (value._id && typeof value._id === 'string') { + return value._id; + } + + // Re: gh-647 and gh-3030, we're ok with casting using `toString()` + // **unless** its the default Object.toString, because "[object Object]" + // doesn't really qualify as useful data + if (value.toString && + value.toString !== Object.prototype.toString && + !Array.isArray(value)) { + return value.toString(); + } + + throw new CastError('string', value, path); +}; diff --git a/node_modules/mongoose/lib/collection.js b/node_modules/mongoose/lib/collection.js new file mode 100644 index 0000000..1f60a63 --- /dev/null +++ b/node_modules/mongoose/lib/collection.js @@ -0,0 +1,282 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const EventEmitter = require('events').EventEmitter; +const STATES = require('./connectionstate'); +const immediate = require('./helpers/immediate'); + +/** + * Abstract Collection constructor + * + * This is the base class that drivers inherit from and implement. + * + * @param {String} name name of the collection + * @param {Connection} conn A MongooseConnection instance + * @param {Object} opts optional collection options + * @api public + */ + +function Collection(name, conn, opts) { + if (opts === void 0) { + opts = {}; + } + if (opts.capped === void 0) { + opts.capped = {}; + } + + if (typeof opts.capped === 'number') { + opts.capped = { size: opts.capped }; + } + + this.opts = opts; + this.name = name; + this.collectionName = name; + this.conn = conn; + this.queue = []; + this.buffer = true; + this.emitter = new EventEmitter(); + + if (STATES.connected === this.conn.readyState) { + this.onOpen(); + } +} + +/** + * The collection name + * + * @api public + * @property name + */ + +Collection.prototype.name; + +/** + * The collection name + * + * @api public + * @property collectionName + */ + +Collection.prototype.collectionName; + +/** + * The Connection instance + * + * @api public + * @property conn + */ + +Collection.prototype.conn; + +/** + * Called when the database connects + * + * @api private + */ + +Collection.prototype.onOpen = function() { + this.buffer = false; + immediate(() => this.doQueue()); +}; + +/** + * Called when the database disconnects + * + * @api private + */ + +Collection.prototype.onClose = function(force) { + if (this._shouldBufferCommands() && !force) { + this.buffer = true; + } +}; + +/** + * Queues a method for later execution when its + * database connection opens. + * + * @param {String} name name of the method to queue + * @param {Array} args arguments to pass to the method when executed + * @api private + */ + +Collection.prototype.addQueue = function(name, args) { + this.queue.push([name, args]); + return this; +}; + +/** + * Executes all queued methods and clears the queue. + * + * @api private + */ + +Collection.prototype.doQueue = function() { + for (const method of this.queue) { + if (typeof method[0] === 'function') { + method[0].apply(this, method[1]); + } else { + this[method[0]].apply(this, method[1]); + } + } + this.queue = []; + const _this = this; + process.nextTick(function() { + _this.emitter.emit('queue'); + }); + return this; +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.ensureIndex = function() { + throw new Error('Collection#ensureIndex unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.createIndex = function() { + throw new Error('Collection#createIndex unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.findAndModify = function() { + throw new Error('Collection#findAndModify unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.findOneAndUpdate = function() { + throw new Error('Collection#findOneAndUpdate unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.findOneAndDelete = function() { + throw new Error('Collection#findOneAndDelete unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.findOneAndReplace = function() { + throw new Error('Collection#findOneAndReplace unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.findOne = function() { + throw new Error('Collection#findOne unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.find = function() { + throw new Error('Collection#find unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.insert = function() { + throw new Error('Collection#insert unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.insertOne = function() { + throw new Error('Collection#insertOne unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.insertMany = function() { + throw new Error('Collection#insertMany unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.save = function() { + throw new Error('Collection#save unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.update = function() { + throw new Error('Collection#update unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.getIndexes = function() { + throw new Error('Collection#getIndexes unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.mapReduce = function() { + throw new Error('Collection#mapReduce unimplemented by driver'); +}; + +/** + * Abstract method that drivers must implement. + */ + +Collection.prototype.watch = function() { + throw new Error('Collection#watch unimplemented by driver'); +}; + +/*! + * ignore + */ + +Collection.prototype._shouldBufferCommands = function _shouldBufferCommands() { + const opts = this.opts; + + if (opts.bufferCommands != null) { + return opts.bufferCommands; + } + if (opts && opts.schemaUserProvidedOptions != null && opts.schemaUserProvidedOptions.bufferCommands != null) { + return opts.schemaUserProvidedOptions.bufferCommands; + } + + return this.conn._shouldBufferCommands(); +}; + +/*! + * Module exports. + */ + +module.exports = Collection; diff --git a/node_modules/mongoose/lib/connection.js b/node_modules/mongoose/lib/connection.js new file mode 100644 index 0000000..2d2e7fd --- /dev/null +++ b/node_modules/mongoose/lib/connection.js @@ -0,0 +1,1459 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const ChangeStream = require('./cursor/ChangeStream'); +const EventEmitter = require('events').EventEmitter; +const Schema = require('./schema'); +const Collection = require('./driver').get().Collection; +const STATES = require('./connectionstate'); +const MongooseError = require('./error/index'); +const PromiseProvider = require('./promise_provider'); +const ServerSelectionError = require('./error/serverSelection'); +const applyPlugins = require('./helpers/schema/applyPlugins'); +const promiseOrCallback = require('./helpers/promiseOrCallback'); +const get = require('./helpers/get'); +const immediate = require('./helpers/immediate'); +const mongodb = require('mongodb'); +const pkg = require('../package.json'); +const utils = require('./utils'); + +const parseConnectionString = require('mongodb/lib/core').parseConnectionString; + +const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol; +const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments; + +let id = 0; + +/*! + * A list of authentication mechanisms that don't require a password for authentication. + * This is used by the authMechanismDoesNotRequirePassword method. + * + * @api private + */ +const noPasswordAuthMechanisms = [ + 'MONGODB-X509' +]; + +/** + * Connection constructor + * + * For practical reasons, a Connection equals a Db. + * + * @param {Mongoose} base a mongoose instance + * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter + * @event `connecting`: Emitted when `connection.openUri()` is executed on this connection. + * @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios. + * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connections models. + * @event `disconnecting`: Emitted when `connection.close()` was executed. + * @event `disconnected`: Emitted after getting disconnected from the db. + * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connections models. + * @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successful connection. + * @event `error`: Emitted when an error occurs on this connection. + * @event `fullsetup`: Emitted after the driver has connected to primary and all secondaries if specified in the connection string. + * @api public + */ + +function Connection(base) { + this.base = base; + this.collections = {}; + this.models = {}; + this.config = { autoIndex: true }; + this.replica = false; + this.options = null; + this.otherDbs = []; // FIXME: To be replaced with relatedDbs + this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection + this.states = STATES; + this._readyState = STATES.disconnected; + this._closeCalled = false; + this._hasOpened = false; + this.plugins = []; + this.id = id++; +} + +/*! + * Inherit from EventEmitter + */ + +Connection.prototype.__proto__ = EventEmitter.prototype; + +/** + * Connection ready state + * + * - 0 = disconnected + * - 1 = connected + * - 2 = connecting + * - 3 = disconnecting + * + * Each state change emits its associated event name. + * + * ####Example + * + * conn.on('connected', callback); + * conn.on('disconnected', callback); + * + * @property readyState + * @memberOf Connection + * @instance + * @api public + */ + +Object.defineProperty(Connection.prototype, 'readyState', { + get: function() { + return this._readyState; + }, + set: function(val) { + if (!(val in STATES)) { + throw new Error('Invalid connection state: ' + val); + } + + if (this._readyState !== val) { + this._readyState = val; + // [legacy] loop over the otherDbs on this connection and change their state + for (const db of this.otherDbs) { + db.readyState = val; + } + + // loop over relatedDbs on this connection and change their state + for (const k in this.relatedDbs) { + this.relatedDbs[k].readyState = val; + } + + if (STATES.connected === val) { + this._hasOpened = true; + } + + this.emit(STATES[val]); + } + } +}); + +/** + * Gets the value of the option `key`. Equivalent to `conn.options[key]` + * + * ####Example: + * + * conn.get('test'); // returns the 'test' value + * + * @param {String} key + * @method get + * @api public + */ + +Connection.prototype.get = function(key) { + return get(this.options, key); +}; + +/** + * Sets the value of the option `key`. Equivalent to `conn.options[key] = val` + * + * Supported options include: + * + * - `maxTimeMS`: Set [`maxTimeMS`](/docs/api.html#query_Query-maxTimeMS) for all queries on this connection. + * - `useFindAndModify`: Set to `false` to work around the [`findAndModify()` deprecation warning](/docs/deprecations.html#findandmodify) + * + * ####Example: + * + * conn.set('test', 'foo'); + * conn.get('test'); // 'foo' + * conn.options.test; // 'foo' + * + * @param {String} key + * @param {Any} val + * @method set + * @api public + */ + +Connection.prototype.set = function(key, val) { + this.options = this.options || {}; + this.options[key] = val; + return val; +}; + +/** + * A hash of the collections associated with this connection + * + * @property collections + * @memberOf Connection + * @instance + * @api public + */ + +Connection.prototype.collections; + +/** + * The name of the database this connection points to. + * + * ####Example + * + * mongoose.createConnection('mongodb://localhost:27017/mydb').name; // "mydb" + * + * @property name + * @memberOf Connection + * @instance + * @api public + */ + +Connection.prototype.name; + +/** + * A [POJO](https://masteringjs.io/tutorials/fundamentals/pojo) containing + * a map from model names to models. Contains all models that have been + * added to this connection using [`Connection#model()`](/docs/api/connection.html#connection_Connection-model). + * + * ####Example + * + * const conn = mongoose.createConnection(); + * const Test = conn.model('Test', mongoose.Schema({ name: String })); + * + * Object.keys(conn.models).length; // 1 + * conn.models.Test === Test; // true + * + * @property models + * @memberOf Connection + * @instance + * @api public + */ + +Connection.prototype.models; + +/** + * A number identifier for this connection. Used for debugging when + * you have [multiple connections](/docs/connections.html#multiple_connections). + * + * ####Example + * + * // The default connection has `id = 0` + * mongoose.connection.id; // 0 + * + * // If you create a new connection, Mongoose increments id + * const conn = mongoose.createConnection(); + * conn.id; // 1 + * + * @property id + * @memberOf Connection + * @instance + * @api public + */ + +Connection.prototype.id; + +/** + * The plugins that will be applied to all models created on this connection. + * + * ####Example: + * + * const db = mongoose.createConnection('mongodb://localhost:27017/mydb'); + * db.plugin(() => console.log('Applied')); + * db.plugins.length; // 1 + * + * db.model('Test', new Schema({})); // Prints "Applied" + * + * @property plugins + * @memberOf Connection + * @instance + * @api public + */ + +Object.defineProperty(Connection.prototype, 'plugins', { + configurable: false, + enumerable: true, + writable: true +}); + +/** + * The host name portion of the URI. If multiple hosts, such as a replica set, + * this will contain the first host name in the URI + * + * ####Example + * + * mongoose.createConnection('mongodb://localhost:27017/mydb').host; // "localhost" + * + * @property host + * @memberOf Connection + * @instance + * @api public + */ + +Object.defineProperty(Connection.prototype, 'host', { + configurable: true, + enumerable: true, + writable: true +}); + +/** + * The port portion of the URI. If multiple hosts, such as a replica set, + * this will contain the port from the first host name in the URI. + * + * ####Example + * + * mongoose.createConnection('mongodb://localhost:27017/mydb').port; // 27017 + * + * @property port + * @memberOf Connection + * @instance + * @api public + */ + +Object.defineProperty(Connection.prototype, 'port', { + configurable: true, + enumerable: true, + writable: true +}); + +/** + * The username specified in the URI + * + * ####Example + * + * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').user; // "val" + * + * @property user + * @memberOf Connection + * @instance + * @api public + */ + +Object.defineProperty(Connection.prototype, 'user', { + configurable: true, + enumerable: true, + writable: true +}); + +/** + * The password specified in the URI + * + * ####Example + * + * mongoose.createConnection('mongodb://val:psw@localhost:27017/mydb').pass; // "psw" + * + * @property pass + * @memberOf Connection + * @instance + * @api public + */ + +Object.defineProperty(Connection.prototype, 'pass', { + configurable: true, + enumerable: true, + writable: true +}); + +/** + * The mongodb.Db instance, set when the connection is opened + * + * @property db + * @memberOf Connection + * @instance + * @api public + */ + +Connection.prototype.db; + +/** + * A hash of the global options that are associated with this connection + * + * @property config + * @memberOf Connection + * @instance + * @api public + */ + +Connection.prototype.config; + +/** + * Helper for `createCollection()`. Will explicitly create the given collection + * with specified options. Used to create [capped collections](https://docs.mongodb.com/manual/core/capped-collections/) + * and [views](https://docs.mongodb.com/manual/core/views/) from mongoose. + * + * Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection) + * + * @method createCollection + * @param {string} collection The collection to create + * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection) + * @param {Function} [callback] + * @return {Promise} + * @api public + */ + +Connection.prototype.createCollection = _wrapConnHelper(function createCollection(collection, options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; + } + this.db.createCollection(collection, options, cb); +}); + +/** + * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions) + * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/), + * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). + * + * ####Example: + * + * const session = await conn.startSession(); + * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session }); + * await doc.remove(); + * // `doc` will always be null, even if reading from a replica set + * // secondary. Without causal consistency, it is possible to + * // get a doc back from the below query if the query reads from a + * // secondary that is experiencing replication lag. + * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' }); + * + * + * @method startSession + * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession) + * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency + * @param {Function} [callback] + * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession` + * @api public + */ + +Connection.prototype.startSession = _wrapConnHelper(function startSession(options, cb) { + if (typeof options === 'function') { + cb = options; + options = null; + } + const session = this.client.startSession(options); + cb(null, session); +}); + +/** + * _Requires MongoDB >= 3.6.0._ Executes the wrapped async function + * in a transaction. Mongoose will commit the transaction if the + * async function executes successfully and attempt to retry if + * there was a retriable error. + * + * Calls the MongoDB driver's [`session.withTransaction()`](http://mongodb.github.io/node-mongodb-native/3.5/api/ClientSession.html#withTransaction), + * but also handles resetting Mongoose document state as shown below. + * + * ####Example: + * + * const doc = new Person({ name: 'Will Riker' }); + * await db.transaction(async function setRank(session) { + * doc.rank = 'Captain'; + * await doc.save({ session }); + * doc.isNew; // false + * + * // Throw an error to abort the transaction + * throw new Error('Oops!'); + * }).catch(() => {}); + * + * // true, `transaction()` reset the document's state because the + * // transaction was aborted. + * doc.isNew; + * + * @method transaction + * @param {Function} fn Function to execute in a transaction + * @return {Promise<Any>} promise that resolves to the returned value of `fn` + * @api public + */ + +Connection.prototype.transaction = function transaction(fn) { + return this.startSession().then(session => { + session[sessionNewDocuments] = new Map(); + return session.withTransaction(() => fn(session)). + then(res => { + delete session[sessionNewDocuments]; + return res; + }). + catch(err => { + // If transaction was aborted, we need to reset newly + // inserted documents' `isNew`. + for (const doc of session[sessionNewDocuments].keys()) { + const state = session[sessionNewDocuments].get(doc); + if (state.hasOwnProperty('isNew')) { + doc.isNew = state.isNew; + } + if (state.hasOwnProperty('versionKey')) { + doc.set(doc.schema.options.versionKey, state.versionKey); + } + + for (const path of state.modifiedPaths) { + doc.$__.activePaths.paths[path] = 'modify'; + doc.$__.activePaths.states.modify[path] = true; + } + + for (const path of state.atomics.keys()) { + const val = doc.$__getValue(path); + if (val == null) { + continue; + } + val[arrayAtomicsSymbol] = state.atomics.get(path); + } + } + delete session[sessionNewDocuments]; + throw err; + }); + }); +}; + +/** + * Helper for `dropCollection()`. Will delete the given collection, including + * all documents and indexes. + * + * @method dropCollection + * @param {string} collection The collection to delete + * @param {Function} [callback] + * @return {Promise} + * @api public + */ + +Connection.prototype.dropCollection = _wrapConnHelper(function dropCollection(collection, cb) { + this.db.dropCollection(collection, cb); +}); + +/** + * Helper for `dropDatabase()`. Deletes the given database, including all + * collections, documents, and indexes. + * + * ####Example: + * + * const conn = mongoose.createConnection('mongodb://localhost:27017/mydb'); + * // Deletes the entire 'mydb' database + * await conn.dropDatabase(); + * + * @method dropDatabase + * @param {Function} [callback] + * @return {Promise} + * @api public + */ + +Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) { + // If `dropDatabase()` is called, this model's collection will not be + // init-ed. It is sufficiently common to call `dropDatabase()` after + // `mongoose.connect()` but before creating models that we want to + // support this. See gh-6967 + for (const name of Object.keys(this.models)) { + delete this.models[name].$init; + } + this.db.dropDatabase(cb); +}); + +/*! + * ignore + */ + +function _wrapConnHelper(fn) { + return function() { + const cb = arguments.length > 0 ? arguments[arguments.length - 1] : null; + const argsWithoutCb = typeof cb === 'function' ? + Array.prototype.slice.call(arguments, 0, arguments.length - 1) : + Array.prototype.slice.call(arguments); + const disconnectedError = new MongooseError('Connection ' + this.id + + ' was disconnected when calling `' + fn.name + '`'); + return promiseOrCallback(cb, cb => { + // Make it ok to call collection helpers before `mongoose.connect()` + // as long as `mongoose.connect()` is called on the same tick. + // Re: gh-8534 + immediate(() => { + if (this.readyState === STATES.connecting && this._shouldBufferCommands()) { + this.once('open', function() { + fn.apply(this, argsWithoutCb.concat([cb])); + }); + } else if (this.readyState === STATES.disconnected && this.db == null) { + cb(disconnectedError); + } else { + try { + fn.apply(this, argsWithoutCb.concat([cb])); + } catch (err) { + return cb(err); + } + } + }); + }); + }; +} + +/*! + * ignore + */ + +Connection.prototype._shouldBufferCommands = function _shouldBufferCommands() { + if (this.config.bufferCommands != null) { + return this.config.bufferCommands; + } + if (this.base != null && this.base.get('bufferCommands') != null) { + return this.base.get('bufferCommands'); + } + return true; +}; + +/** + * error + * + * Graceful error handling, passes error to callback + * if available, else emits error on the connection. + * + * @param {Error} err + * @param {Function} callback optional + * @api private + */ + +Connection.prototype.error = function(err, callback) { + if (callback) { + callback(err); + return null; + } + if (this.listeners('error').length > 0) { + this.emit('error', err); + } + return Promise.reject(err); +}; + +/** + * Called when the connection is opened + * + * @api private + */ + +Connection.prototype.onOpen = function() { + this.readyState = STATES.connected; + + // avoid having the collection subscribe to our event emitter + // to prevent 0.3 warning + for (const i in this.collections) { + if (utils.object.hasOwnProperty(this.collections, i)) { + this.collections[i].onOpen(); + } + } + + this.emit('open'); +}; + +/** + * Opens the connection with a URI using `MongoClient.connect()`. + * + * @param {String} uri The URI to connect with. + * @param {Object} [options] Passed on to http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect + * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. + * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. + * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. + * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. + * @param {Number} [options.poolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Boolean} [options.useUnifiedTopology=false] False by default. Set to `true` to opt in to the MongoDB driver's replica set and sharded cluster monitoring engine. + * @param {Number} [options.serverSelectionTimeoutMS] If `useUnifiedTopology = true`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds before erroring out. If not set, the MongoDB driver defaults to using `30000` (30 seconds). + * @param {Number} [options.heartbeatFrequencyMS] If `useUnifiedTopology = true`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation. + * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. + * @param {Boolean} [options.useNewUrlParser=false] False by default. Set to `true` to opt in to the MongoDB driver's new URL parser logic. + * @param {Boolean} [options.useCreateIndex=true] Mongoose-specific option. If `true`, this connection will use [`createIndex()` instead of `ensureIndex()`](/docs/deprecations.html#ensureindex) for automatic index builds via [`Model.init()`](/docs/api.html#model_Model.init). + * @param {Boolean} [options.useFindAndModify=true] True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. + * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections. + * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above. + * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html). + * @param {Number} [options.bufferMaxEntries] This option does nothing if `useUnifiedTopology` is set. The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set `bufferCommands` to `false` on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection. + * @param {Number} [options.connectTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback). + * @param {Number} [options.socketTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. + * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. + * @param {Function} [callback] + * @returns {Connection} this + * @api public + */ + +Connection.prototype.openUri = function(uri, options, callback) { + if (typeof options === 'function') { + callback = options; + options = null; + } + + if (['string', 'number'].indexOf(typeof options) !== -1) { + throw new MongooseError('Mongoose 5.x no longer supports ' + + '`mongoose.connect(host, dbname, port)` or ' + + '`mongoose.createConnection(host, dbname, port)`. See ' + + 'http://mongoosejs.com/docs/connections.html for supported connection syntax'); + } + + if (typeof uri !== 'string') { + throw new MongooseError('The `uri` parameter to `openUri()` must be a ' + + `string, got "${typeof uri}". Make sure the first parameter to ` + + '`mongoose.connect()` or `mongoose.createConnection()` is a string.'); + } + + if (callback != null && typeof callback !== 'function') { + throw new MongooseError('3rd parameter to `mongoose.connect()` or ' + + '`mongoose.createConnection()` must be a function, got "' + + typeof callback + '"'); + } + + if (this.readyState === STATES.connecting || this.readyState === STATES.connected) { + if (this._connectionString !== uri) { + throw new MongooseError('Can\'t call `openUri()` on an active connection with ' + + 'different connection strings. Make sure you aren\'t calling `mongoose.connect()` ' + + 'multiple times. See: https://mongoosejs.com/docs/connections.html#multiple_connections'); + } + + if (typeof callback === 'function') { + this.$initialConnection = this.$initialConnection.then( + () => callback(null, this), + err => callback(err) + ); + } + return this; + } + + this._connectionString = uri; + this.readyState = STATES.connecting; + this._closeCalled = false; + + const Promise = PromiseProvider.get(); + const _this = this; + + if (options) { + options = utils.clone(options); + const autoIndex = options.config && options.config.autoIndex != null ? + options.config.autoIndex : + options.autoIndex; + if (autoIndex != null) { + this.config.autoIndex = autoIndex !== false; + delete options.config; + delete options.autoIndex; + } + + if ('autoCreate' in options) { + this.config.autoCreate = !!options.autoCreate; + delete options.autoCreate; + } + if ('useCreateIndex' in options) { + this.config.useCreateIndex = !!options.useCreateIndex; + delete options.useCreateIndex; + } + + if ('useFindAndModify' in options) { + this.config.useFindAndModify = !!options.useFindAndModify; + delete options.useFindAndModify; + } + + // Backwards compat + if (options.user || options.pass) { + options.auth = options.auth || {}; + options.auth.user = options.user; + options.auth.password = options.pass; + + this.user = options.user; + this.pass = options.pass; + } + delete options.user; + delete options.pass; + + if (options.bufferCommands != null) { + if (options.bufferMaxEntries == null) { + options.bufferMaxEntries = 0; + } + this.config.bufferCommands = options.bufferCommands; + delete options.bufferCommands; + } + + if (options.useMongoClient != null) { + handleUseMongoClient(options); + } + } else { + options = {}; + } + + this._connectionOptions = options; + const dbName = options.dbName; + if (dbName != null) { + this.$dbName = dbName; + } + delete options.dbName; + + if (!('promiseLibrary' in options)) { + options.promiseLibrary = PromiseProvider.get(); + } + if (!('useNewUrlParser' in options)) { + if ('useNewUrlParser' in this.base.options) { + options.useNewUrlParser = this.base.options.useNewUrlParser; + } else { + options.useNewUrlParser = false; + } + } + if (!utils.hasUserDefinedProperty(options, 'useUnifiedTopology')) { + if (utils.hasUserDefinedProperty(this.base.options, 'useUnifiedTopology')) { + options.useUnifiedTopology = this.base.options.useUnifiedTopology; + } else { + options.useUnifiedTopology = false; + } + } + if (!utils.hasUserDefinedProperty(options, 'driverInfo')) { + options.driverInfo = { + name: 'Mongoose', + version: pkg.version + }; + } + + const parsePromise = new Promise((resolve, reject) => { + parseConnectionString(uri, options, (err, parsed) => { + if (err) { + return reject(err); + } + if (dbName) { + this.name = dbName; + } else if (parsed.defaultDatabase) { + this.name = parsed.defaultDatabase; + } else { + this.name = get(parsed, 'auth.db', null); + } + this.host = get(parsed, 'hosts.0.host', 'localhost'); + this.port = get(parsed, 'hosts.0.port', 27017); + this.user = this.user || get(parsed, 'auth.username'); + this.pass = this.pass || get(parsed, 'auth.password'); + resolve(); + }); + }); + + const promise = new Promise((resolve, reject) => { + const client = new mongodb.MongoClient(uri, options); + _this.client = client; + client.connect((error) => { + if (error) { + _this.readyState = STATES.disconnected; + return reject(error); + } + + _setClient(_this, client, options, dbName); + + resolve(_this); + }); + }); + + const serverSelectionError = new ServerSelectionError(); + this.$initialConnection = Promise.all([promise, parsePromise]). + then(res => res[0]). + catch(err => { + if (err != null && err.name === 'MongoServerSelectionError') { + err = serverSelectionError.assimilateError(err); + } + + if (this.listeners('error').length > 0) { + process.nextTick(() => this.emit('error', err)); + } + throw err; + }); + this.then = function(resolve, reject) { + return this.$initialConnection.then(() => { + if (typeof resolve === 'function') { + resolve(_this); + } + }, reject); + }; + this.catch = function(reject) { + return this.$initialConnection.catch(reject); + }; + + if (callback != null) { + this.$initialConnection = this.$initialConnection.then( + () => callback(null, this), + err => callback(err) + ); + } + + return this; +}; + +function _setClient(conn, client, options, dbName) { + const db = dbName != null ? client.db(dbName) : client.db(); + conn.db = db; + conn.client = client; + + const _handleReconnect = () => { + // If we aren't disconnected, we assume this reconnect is due to a + // socket timeout. If there's no activity on a socket for + // `socketTimeoutMS`, the driver will attempt to reconnect and emit + // this event. + if (conn.readyState !== STATES.connected) { + conn.readyState = STATES.connected; + conn.emit('reconnect'); + conn.emit('reconnected'); + conn.onOpen(); + } + }; + + // `useUnifiedTopology` events + const type = get(db, 's.topology.s.description.type', ''); + if (options.useUnifiedTopology) { + if (type === 'Single') { + const server = Array.from(db.s.topology.s.servers.values())[0]; + server.s.topology.on('serverHeartbeatSucceeded', () => { + _handleReconnect(); + }); + server.s.pool.on('reconnect', () => { + _handleReconnect(); + }); + client.on('serverDescriptionChanged', ev => { + const newDescription = ev.newDescription; + if (newDescription.type === 'Standalone') { + _handleReconnect(); + } else { + conn.readyState = STATES.disconnected; + } + }); + } else if (type.startsWith('ReplicaSet')) { + client.on('topologyDescriptionChanged', ev => { + // Emit disconnected if we've lost connectivity to _all_ servers + // in the replica set. + const description = ev.newDescription; + const servers = Array.from(ev.newDescription.servers.values()); + const allServersDisconnected = description.type === 'ReplicaSetNoPrimary' && + servers.reduce((cur, d) => cur || d.type === 'Unknown', false); + if (conn.readyState === STATES.connected && allServersDisconnected) { + // Implicitly emits 'disconnected' + conn.readyState = STATES.disconnected; + } else if (conn.readyState === STATES.disconnected && !allServersDisconnected) { + _handleReconnect(); + } + }); + + db.on('close', function() { + const type = get(db, 's.topology.s.description.type', ''); + if (type !== 'ReplicaSetWithPrimary') { + // Implicitly emits 'disconnected' + conn.readyState = STATES.disconnected; + } + }); + } + } + + // Backwards compat for mongoose 4.x + db.on('reconnect', function() { + _handleReconnect(); + }); + db.s.topology.on('reconnectFailed', function() { + conn.emit('reconnectFailed'); + }); + + if (!options.useUnifiedTopology) { + db.s.topology.on('left', function(data) { + conn.emit('left', data); + }); + } + db.s.topology.on('joined', function(data) { + conn.emit('joined', data); + }); + db.s.topology.on('fullsetup', function(data) { + conn.emit('fullsetup', data); + }); + if (get(db, 's.topology.s.coreTopology.s.pool') != null) { + db.s.topology.s.coreTopology.s.pool.on('attemptReconnect', function() { + conn.emit('attemptReconnect'); + }); + } + if (!options.useUnifiedTopology || !type.startsWith('ReplicaSet')) { + db.on('close', function() { + // Implicitly emits 'disconnected' + conn.readyState = STATES.disconnected; + }); + } + + if (!options.useUnifiedTopology) { + client.on('left', function() { + if (conn.readyState === STATES.connected && + get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') { + conn.readyState = STATES.disconnected; + } + }); + } + + db.on('timeout', function() { + conn.emit('timeout'); + }); + + delete conn.then; + delete conn.catch; + conn.readyState = STATES.connected; + + for (const i in conn.collections) { + if (utils.object.hasOwnProperty(conn.collections, i)) { + conn.collections[i].onOpen(); + } + } + + conn.emit('open'); +} + +/*! + * ignore + */ + +const handleUseMongoClient = function handleUseMongoClient(options) { + console.warn('WARNING: The `useMongoClient` option is no longer ' + + 'necessary in mongoose 5.x, please remove it.'); + const stack = new Error().stack; + console.warn(stack.substr(stack.indexOf('\n') + 1)); + delete options.useMongoClient; +}; + +/** + * Closes the connection + * + * @param {Boolean} [force] optional + * @param {Function} [callback] optional + * @return {Promise} + * @api public + */ + +Connection.prototype.close = function(force, callback) { + if (typeof force === 'function') { + callback = force; + force = false; + } + + this.$wasForceClosed = !!force; + + return promiseOrCallback(callback, cb => { + this._close(force, cb); + }); +}; + +/** + * Handles closing the connection + * + * @param {Boolean} force + * @param {Function} callback + * @api private + */ +Connection.prototype._close = function(force, callback) { + const _this = this; + this._closeCalled = true; + + switch (this.readyState) { + case STATES.disconnected: + callback(); + break; + + case STATES.connected: + this.readyState = STATES.disconnecting; + this.doClose(force, function(err) { + if (err) { + return callback(err); + } + _this.onClose(force); + callback(null); + }); + + break; + case STATES.connecting: + this.once('open', function() { + _this.close(callback); + }); + break; + + case STATES.disconnecting: + this.once('close', function() { + callback(); + }); + break; + } + + return this; +}; + +/** + * Called when the connection closes + * + * @api private + */ + +Connection.prototype.onClose = function(force) { + this.readyState = STATES.disconnected; + + // avoid having the collection subscribe to our event emitter + // to prevent 0.3 warning + for (const i in this.collections) { + if (utils.object.hasOwnProperty(this.collections, i)) { + this.collections[i].onClose(force); + } + } + + this.emit('close', force); +}; + +/** + * Retrieves a collection, creating it if not cached. + * + * Not typically needed by applications. Just talk to your collection through your model. + * + * @param {String} name of the collection + * @param {Object} [options] optional collection options + * @return {Collection} collection instance + * @api public + */ + +Connection.prototype.collection = function(name, options) { + options = options ? utils.clone(options) : {}; + options.$wasForceClosed = this.$wasForceClosed; + if (!(name in this.collections)) { + this.collections[name] = new Collection(name, this, options); + } + return this.collections[name]; +}; + +/** + * Declares a plugin executed on all schemas you pass to `conn.model()` + * + * Equivalent to calling `.plugin(fn)` on each schema you create. + * + * ####Example: + * const db = mongoose.createConnection('mongodb://localhost:27017/mydb'); + * db.plugin(() => console.log('Applied')); + * db.plugins.length; // 1 + * + * db.model('Test', new Schema({})); // Prints "Applied" + * + * @param {Function} fn plugin callback + * @param {Object} [opts] optional options + * @return {Connection} this + * @see plugins ./plugins.html + * @api public + */ + +Connection.prototype.plugin = function(fn, opts) { + this.plugins.push([fn, opts]); + return this; +}; + +/** + * Defines or retrieves a model. + * + * const mongoose = require('mongoose'); + * const db = mongoose.createConnection(..); + * db.model('Venue', new Schema(..)); + * const Ticket = db.model('Ticket', new Schema(..)); + * const Venue = db.model('Venue'); + * + * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the [utils.toCollectionName](#utils_exports.toCollectionName) method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._ + * + * ####Example: + * + * const schema = new Schema({ name: String }, { collection: 'actor' }); + * + * // or + * + * schema.set('collection', 'actor'); + * + * // or + * + * const collectionName = 'actor' + * const M = conn.model('Actor', schema, collectionName) + * + * @param {String|Function} name the model name or class extending Model + * @param {Schema} [schema] a schema. necessary when defining a model + * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name + * @see Mongoose#model #index_Mongoose-model + * @return {Model} The compiled model + * @api public + */ + +Connection.prototype.model = function(name, schema, collection) { + if (!(this instanceof Connection)) { + throw new MongooseError('`connection.model()` should not be run with ' + + '`new`. If you are doing `new db.model(foo)(bar)`, use ' + + '`db.model(foo)(bar)` instead'); + } + + let fn; + if (typeof name === 'function') { + fn = name; + name = fn.name; + } + + // collection name discovery + if (typeof schema === 'string') { + collection = schema; + schema = false; + } + + if (utils.isObject(schema) && !schema.instanceOfSchema) { + schema = new Schema(schema); + } + if (schema && !schema.instanceOfSchema) { + throw new Error('The 2nd parameter to `mongoose.model()` should be a ' + + 'schema or a POJO'); + } + + if (this.models[name] && !collection) { + // model exists but we are not subclassing with custom collection + if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) { + throw new MongooseError.OverwriteModelError(name); + } + return this.models[name]; + } + + const opts = { cache: false, connection: this }; + let model; + + if (schema && schema.instanceOfSchema) { + applyPlugins(schema, this.plugins, null, '$connectionPluginsApplied'); + + // compile a model + model = this.base.model(fn || name, schema, collection, opts); + + // only the first model with this name is cached to allow + // for one-offs with custom collection names etc. + if (!this.models[name]) { + this.models[name] = model; + } + + // Errors handled internally, so safe to ignore error + model.init(function $modelInitNoop() {}); + + return model; + } + + if (this.models[name] && collection) { + // subclassing current model with alternate collection + model = this.models[name]; + schema = model.prototype.schema; + const sub = model.__subclass(this, schema, collection); + // do not cache the sub model + return sub; + } + + // lookup model in mongoose module + model = this.base.models[name]; + + if (!model) { + throw new MongooseError.MissingSchemaError(name); + } + + if (this === model.prototype.db + && (!collection || collection === model.collection.name)) { + // model already uses this connection. + + // only the first model with this name is cached to allow + // for one-offs with custom collection names etc. + if (!this.models[name]) { + this.models[name] = model; + } + + return model; + } + this.models[name] = model.__subclass(this, schema, collection); + return this.models[name]; +}; + +/** + * Removes the model named `name` from this connection, if it exists. You can + * use this function to clean up any models you created in your tests to + * prevent OverwriteModelErrors. + * + * ####Example: + * + * conn.model('User', new Schema({ name: String })); + * console.log(conn.model('User')); // Model object + * conn.deleteModel('User'); + * console.log(conn.model('User')); // undefined + * + * // Usually useful in a Mocha `afterEach()` hook + * afterEach(function() { + * conn.deleteModel(/.+/); // Delete every model + * }); + * + * @api public + * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp. + * @return {Connection} this + */ + +Connection.prototype.deleteModel = function(name) { + if (typeof name === 'string') { + const model = this.model(name); + if (model == null) { + return this; + } + const collectionName = model.collection.name; + delete this.models[name]; + delete this.collections[collectionName]; + delete this.base.modelSchemas[name]; + } else if (name instanceof RegExp) { + const pattern = name; + const names = this.modelNames(); + for (const name of names) { + if (pattern.test(name)) { + this.deleteModel(name); + } + } + } else { + throw new Error('First parameter to `deleteModel()` must be a string ' + + 'or regexp, got "' + name + '"'); + } + + return this; +}; + +/** + * Watches the entire underlying database for changes. Similar to + * [`Model.watch()`](/docs/api/model.html#model_Model.watch). + * + * This function does **not** trigger any middleware. In particular, it + * does **not** trigger aggregate middleware. + * + * The ChangeStream object is an event emitter that emits the following events: + * + * - 'change': A change occurred, see below example + * - 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow [this GitHub issue](https://github.com/Automattic/mongoose/issues/6799) for updates. + * - 'end': Emitted if the underlying stream is closed + * - 'close': Emitted if the underlying stream is closed + * + * ####Example: + * + * const User = conn.model('User', new Schema({ name: String })); + * + * const changeStream = conn.watch().on('change', data => console.log(data)); + * + * // Triggers a 'change' event on the change stream. + * await User.create({ name: 'test' }); + * + * @api public + * @param {Array} [pipeline] + * @param {Object} [options] passed without changes to [the MongoDB driver's `Db#watch()` function](https://mongodb.github.io/node-mongodb-native/3.4/api/Db.html#watch) + * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter + */ + +Connection.prototype.watch = function(pipeline, options) { + const disconnectedError = new MongooseError('Connection ' + this.id + + ' was disconnected when calling `watch()`'); + + const changeStreamThunk = cb => { + immediate(() => { + if (this.readyState === STATES.connecting) { + this.once('open', function() { + const driverChangeStream = this.db.watch(pipeline, options); + cb(null, driverChangeStream); + }); + } else if (this.readyState === STATES.disconnected && this.db == null) { + cb(disconnectedError); + } else { + const driverChangeStream = this.db.watch(pipeline, options); + cb(null, driverChangeStream); + } + }); + }; + + const changeStream = new ChangeStream(changeStreamThunk, pipeline, options); + return changeStream; +}; + +/** + * Returns an array of model names created on this connection. + * @api public + * @return {Array} + */ + +Connection.prototype.modelNames = function() { + return Object.keys(this.models); +}; + +/** + * @brief Returns if the connection requires authentication after it is opened. Generally if a + * username and password are both provided than authentication is needed, but in some cases a + * password is not required. + * @api private + * @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false. + */ +Connection.prototype.shouldAuthenticate = function() { + return this.user != null && + (this.pass != null || this.authMechanismDoesNotRequirePassword()); +}; + +/** + * @brief Returns a boolean value that specifies if the current authentication mechanism needs a + * password to authenticate according to the auth objects passed into the openUri methods. + * @api private + * @return {Boolean} true if the authentication mechanism specified in the options object requires + * a password, otherwise false. + */ +Connection.prototype.authMechanismDoesNotRequirePassword = function() { + if (this.options && this.options.auth) { + return noPasswordAuthMechanisms.indexOf(this.options.auth.authMechanism) >= 0; + } + return true; +}; + +/** + * @brief Returns a boolean value that specifies if the provided objects object provides enough + * data to authenticate with. Generally this is true if the username and password are both specified + * but in some authentication methods, a password is not required for authentication so only a username + * is required. + * @param {Object} [options] the options object passed into the openUri methods. + * @api private + * @return {Boolean} true if the provided options object provides enough data to authenticate with, + * otherwise false. + */ +Connection.prototype.optionsProvideAuthenticationData = function(options) { + return (options) && + (options.user) && + ((options.pass) || this.authMechanismDoesNotRequirePassword()); +}; + +/** + * Returns the [MongoDB driver `MongoClient`](http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) instance + * that this connection uses to talk to MongoDB. + * + * ####Example: + * const conn = await mongoose.createConnection('mongodb://localhost:27017/test'); + * + * conn.getClient(); // MongoClient { ... } + * + * @api public + * @return {MongoClient} + */ + +Connection.prototype.getClient = function getClient() { + return this.client; +}; + +/** + * Set the [MongoDB driver `MongoClient`](http://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) instance + * that this connection uses to talk to MongoDB. This is useful if you already have a MongoClient instance, and want to + * reuse it. + * + * ####Example: + * const client = await mongodb.MongoClient.connect('mongodb://localhost:27017/test'); + * + * const conn = mongoose.createConnection().setClient(client); + * + * conn.getClient(); // MongoClient { ... } + * conn.readyState; // 1, means 'CONNECTED' + * + * @api public + * @return {Connection} this + */ + +Connection.prototype.setClient = function setClient(client) { + if (!(client instanceof mongodb.MongoClient)) { + throw new MongooseError('Must call `setClient()` with an instance of MongoClient'); + } + if (this.client != null || this.readyState !== STATES.disconnected) { + throw new MongooseError('Cannot call `setClient()` on a connection that is already connected.'); + } + if (!client.isConnected()) { + throw new MongooseError('Cannot call `setClient()` with a MongoClient that is not connected.'); + } + + this._connectionString = client.s.url; + _setClient(this, client, { useUnifiedTopology: client.s.options.useUnifiedTopology }, client.s.options.dbName); + + return this; +}; + +/** + * Switches to a different database using the same connection pool. + * + * Returns a new connection object, with the new db. + * + * @method useDb + * @memberOf Connection + * @param {String} name The database name + * @param {Object} [options] + * @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object. + * @return {Connection} New Connection Object + * @api public + */ + +/*! + * Module exports. + */ + +Connection.STATES = STATES; +module.exports = Connection; diff --git a/node_modules/mongoose/lib/connectionstate.js b/node_modules/mongoose/lib/connectionstate.js new file mode 100644 index 0000000..920f45b --- /dev/null +++ b/node_modules/mongoose/lib/connectionstate.js @@ -0,0 +1,26 @@ + +/*! + * Connection states + */ + +'use strict'; + +const STATES = module.exports = exports = Object.create(null); + +const disconnected = 'disconnected'; +const connected = 'connected'; +const connecting = 'connecting'; +const disconnecting = 'disconnecting'; +const uninitialized = 'uninitialized'; + +STATES[0] = disconnected; +STATES[1] = connected; +STATES[2] = connecting; +STATES[3] = disconnecting; +STATES[99] = uninitialized; + +STATES[disconnected] = 0; +STATES[connected] = 1; +STATES[connecting] = 2; +STATES[disconnecting] = 3; +STATES[uninitialized] = 99; diff --git a/node_modules/mongoose/lib/cursor/AggregationCursor.js b/node_modules/mongoose/lib/cursor/AggregationCursor.js new file mode 100644 index 0000000..5023b1c --- /dev/null +++ b/node_modules/mongoose/lib/cursor/AggregationCursor.js @@ -0,0 +1,368 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('../error/mongooseError'); +const Readable = require('stream').Readable; +const promiseOrCallback = require('../helpers/promiseOrCallback'); +const eachAsync = require('../helpers/cursor/eachAsync'); +const util = require('util'); + +/** + * An AggregationCursor is a concurrency primitive for processing aggregation + * results one document at a time. It is analogous to QueryCursor. + * + * An AggregationCursor fulfills the Node.js streams3 API, + * in addition to several other mechanisms for loading documents from MongoDB + * one at a time. + * + * Creating an AggregationCursor executes the model's pre aggregate hooks, + * but **not** the model's post aggregate hooks. + * + * Unless you're an advanced user, do **not** instantiate this class directly. + * Use [`Aggregate#cursor()`](/docs/api.html#aggregate_Aggregate-cursor) instead. + * + * @param {Aggregate} agg + * @param {Object} options + * @inherits Readable + * @event `cursor`: Emitted when the cursor is created + * @event `error`: Emitted when an error occurred + * @event `data`: Emitted when the stream is flowing and the next doc is ready + * @event `end`: Emitted when the stream is exhausted + * @api public + */ + +function AggregationCursor(agg) { + Readable.call(this, { objectMode: true }); + + this.cursor = null; + this.agg = agg; + this._transforms = []; + const model = agg._model; + delete agg.options.cursor.useMongooseAggCursor; + this._mongooseOptions = {}; + + _init(model, this, agg); +} + +util.inherits(AggregationCursor, Readable); + +/*! + * ignore + */ + +function _init(model, c, agg) { + if (!model.collection.buffer) { + model.hooks.execPre('aggregate', agg, function() { + c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {}); + c.emit('cursor', c.cursor); + }); + } else { + model.collection.emitter.once('queue', function() { + model.hooks.execPre('aggregate', agg, function() { + c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {}); + c.emit('cursor', c.cursor); + }); + }); + } +} + +/*! + * Necessary to satisfy the Readable API + */ + +AggregationCursor.prototype._read = function() { + const _this = this; + _next(this, function(error, doc) { + if (error) { + return _this.emit('error', error); + } + if (!doc) { + _this.push(null); + _this.cursor.close(function(error) { + if (error) { + return _this.emit('error', error); + } + setTimeout(function() { + // on node >= 14 streams close automatically (gh-8834) + const isNotClosedAutomatically = !_this.destroyed; + if (isNotClosedAutomatically) { + _this.emit('close'); + } + }, 0); + }); + return; + } + _this.push(doc); + }); +}; + +if (Symbol.asyncIterator != null) { + const msg = 'Mongoose does not support using async iterators with an ' + + 'existing aggregation cursor. See http://bit.ly/mongoose-async-iterate-aggregation'; + + AggregationCursor.prototype[Symbol.asyncIterator] = function() { + throw new MongooseError(msg); + }; +} + +/** + * Registers a transform function which subsequently maps documents retrieved + * via the streams interface or `.next()` + * + * ####Example + * + * // Map documents returned by `data` events + * Thing. + * find({ name: /^hello/ }). + * cursor(). + * map(function (doc) { + * doc.foo = "bar"; + * return doc; + * }) + * on('data', function(doc) { console.log(doc.foo); }); + * + * // Or map documents returned by `.next()` + * const cursor = Thing.find({ name: /^hello/ }). + * cursor(). + * map(function (doc) { + * doc.foo = "bar"; + * return doc; + * }); + * cursor.next(function(error, doc) { + * console.log(doc.foo); + * }); + * + * @param {Function} fn + * @return {AggregationCursor} + * @api public + * @method map + */ + +AggregationCursor.prototype.map = function(fn) { + this._transforms.push(fn); + return this; +}; + +/*! + * Marks this cursor as errored + */ + +AggregationCursor.prototype._markError = function(error) { + this._error = error; + return this; +}; + +/** + * Marks this cursor as closed. Will stop streaming and subsequent calls to + * `next()` will error. + * + * @param {Function} callback + * @return {Promise} + * @api public + * @method close + * @emits close + * @see MongoDB driver cursor#close http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html#close + */ + +AggregationCursor.prototype.close = function(callback) { + return promiseOrCallback(callback, cb => { + this.cursor.close(error => { + if (error) { + cb(error); + return this.listeners('error').length > 0 && this.emit('error', error); + } + this.emit('close'); + cb(null); + }); + }); +}; + +/** + * Get the next document from this cursor. Will return `null` when there are + * no documents left. + * + * @param {Function} callback + * @return {Promise} + * @api public + * @method next + */ + +AggregationCursor.prototype.next = function(callback) { + return promiseOrCallback(callback, cb => { + _next(this, cb); + }); +}; + +/** + * Execute `fn` for every document in the cursor. If `fn` returns a promise, + * will wait for the promise to resolve before iterating on to the next one. + * Returns a promise that resolves when done. + * + * @param {Function} fn + * @param {Object} [options] + * @param {Number} [options.parallel] the number of promises to execute in parallel. Defaults to 1. + * @param {Function} [callback] executed when all docs have been processed + * @return {Promise} + * @api public + * @method eachAsync + */ + +AggregationCursor.prototype.eachAsync = function(fn, opts, callback) { + const _this = this; + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + opts = opts || {}; + + return eachAsync(function(cb) { return _next(_this, cb); }, fn, opts, callback); +}; + +/** + * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js + * You do not need to call this function explicitly, the JavaScript runtime + * will call it for you. + * + * ####Example + * + * // Async iterator without explicitly calling `cursor()`. Mongoose still + * // creates an AggregationCursor instance internally. + * const agg = Model.aggregate([{ $match: { age: { $gte: 25 } } }]); + * for await (const doc of agg) { + * console.log(doc.name); + * } + * + * // You can also use an AggregationCursor instance for async iteration + * const cursor = Model.aggregate([{ $match: { age: { $gte: 25 } } }]).cursor(); + * for await (const doc of cursor) { + * console.log(doc.name); + * } + * + * Node.js 10.x supports async iterators natively without any flags. You can + * enable async iterators in Node.js 8.x using the [`--harmony_async_iteration` flag](https://github.com/tc39/proposal-async-iteration/issues/117#issuecomment-346695187). + * + * **Note:** This function is not set if `Symbol.asyncIterator` is undefined. If + * `Symbol.asyncIterator` is undefined, that means your Node.js version does not + * support async iterators. + * + * @method Symbol.asyncIterator + * @memberOf AggregationCursor + * @instance + * @api public + */ + +if (Symbol.asyncIterator != null) { + AggregationCursor.prototype[Symbol.asyncIterator] = function() { + return this.transformNull()._transformForAsyncIterator(); + }; +} + +/*! + * ignore + */ + +AggregationCursor.prototype._transformForAsyncIterator = function() { + if (this._transforms.indexOf(_transformForAsyncIterator) === -1) { + this.map(_transformForAsyncIterator); + } + return this; +}; + +/*! + * ignore + */ + +AggregationCursor.prototype.transformNull = function(val) { + if (arguments.length === 0) { + val = true; + } + this._mongooseOptions.transformNull = val; + return this; +}; + +/*! + * ignore + */ + +function _transformForAsyncIterator(doc) { + return doc == null ? { done: true } : { value: doc, done: false }; +} + +/** + * Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag). + * Useful for setting the `noCursorTimeout` and `tailable` flags. + * + * @param {String} flag + * @param {Boolean} value + * @return {AggregationCursor} this + * @api public + * @method addCursorFlag + */ + +AggregationCursor.prototype.addCursorFlag = function(flag, value) { + const _this = this; + _waitForCursor(this, function() { + _this.cursor.addCursorFlag(flag, value); + }); + return this; +}; + +/*! + * ignore + */ + +function _waitForCursor(ctx, cb) { + if (ctx.cursor) { + return cb(); + } + ctx.once('cursor', function() { + cb(); + }); +} + +/*! + * Get the next doc from the underlying cursor and mongooseify it + * (populate, etc.) + */ + +function _next(ctx, cb) { + let callback = cb; + if (ctx._transforms.length) { + callback = function(err, doc) { + if (err || (doc === null && !ctx._mongooseOptions.transformNull)) { + return cb(err, doc); + } + cb(err, ctx._transforms.reduce(function(doc, fn) { + return fn(doc); + }, doc)); + }; + } + + if (ctx._error) { + return process.nextTick(function() { + callback(ctx._error); + }); + } + + if (ctx.cursor) { + return ctx.cursor.next(function(error, doc) { + if (error) { + return callback(error); + } + if (!doc) { + return callback(null, null); + } + + callback(null, doc); + }); + } else { + ctx.once('cursor', function() { + _next(ctx, cb); + }); + } +} + +module.exports = AggregationCursor; diff --git a/node_modules/mongoose/lib/cursor/ChangeStream.js b/node_modules/mongoose/lib/cursor/ChangeStream.js new file mode 100644 index 0000000..b3445b0 --- /dev/null +++ b/node_modules/mongoose/lib/cursor/ChangeStream.js @@ -0,0 +1,61 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const EventEmitter = require('events').EventEmitter; + +/*! + * ignore + */ + +class ChangeStream extends EventEmitter { + constructor(changeStreamThunk, pipeline, options) { + super(); + + this.driverChangeStream = null; + this.closed = false; + this.pipeline = pipeline; + this.options = options; + + // This wrapper is necessary because of buffering. + changeStreamThunk((err, driverChangeStream) => { + if (err != null) { + this.emit('error', err); + return; + } + + this.driverChangeStream = driverChangeStream; + this._bindEvents(); + this.emit('ready'); + }); + } + + _bindEvents() { + this.driverChangeStream.on('close', () => { + this.closed = true; + }); + + ['close', 'change', 'end', 'error'].forEach(ev => { + this.driverChangeStream.on(ev, data => this.emit(ev, data)); + }); + } + + _queue(cb) { + this.once('ready', () => cb()); + } + + close() { + this.closed = true; + if (this.driverChangeStream) { + this.driverChangeStream.close(); + } + } +} + +/*! + * ignore + */ + +module.exports = ChangeStream; diff --git a/node_modules/mongoose/lib/cursor/QueryCursor.js b/node_modules/mongoose/lib/cursor/QueryCursor.js new file mode 100644 index 0000000..2b5f2c1 --- /dev/null +++ b/node_modules/mongoose/lib/cursor/QueryCursor.js @@ -0,0 +1,418 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const Readable = require('stream').Readable; +const promiseOrCallback = require('../helpers/promiseOrCallback'); +const eachAsync = require('../helpers/cursor/eachAsync'); +const helpers = require('../queryhelpers'); +const util = require('util'); + +/** + * A QueryCursor is a concurrency primitive for processing query results + * one document at a time. A QueryCursor fulfills the Node.js streams3 API, + * in addition to several other mechanisms for loading documents from MongoDB + * one at a time. + * + * QueryCursors execute the model's pre find hooks, but **not** the model's + * post find hooks. + * + * Unless you're an advanced user, do **not** instantiate this class directly. + * Use [`Query#cursor()`](/docs/api.html#query_Query-cursor) instead. + * + * @param {Query} query + * @param {Object} options query options passed to `.find()` + * @inherits Readable + * @event `cursor`: Emitted when the cursor is created + * @event `error`: Emitted when an error occurred + * @event `data`: Emitted when the stream is flowing and the next doc is ready + * @event `end`: Emitted when the stream is exhausted + * @api public + */ + +function QueryCursor(query, options) { + Readable.call(this, { objectMode: true }); + + this.cursor = null; + this.query = query; + const _this = this; + const model = query.model; + this._mongooseOptions = {}; + this._transforms = []; + this.model = model; + this.options = options || {}; + + model.hooks.execPre('find', query, () => { + this._transforms = this._transforms.concat(query._transforms.slice()); + if (this.options.transform) { + this._transforms.push(options.transform); + } + // Re: gh-8039, you need to set the `cursor.batchSize` option, top-level + // `batchSize` option doesn't work. + if (this.options.batchSize) { + this.options.cursor = options.cursor || {}; + this.options.cursor.batchSize = options.batchSize; + } + model.collection.find(query._conditions, this.options, function(err, cursor) { + if (_this._error) { + cursor.close(function() {}); + _this.listeners('error').length > 0 && _this.emit('error', _this._error); + } + if (err) { + return _this.emit('error', err); + } + _this.cursor = cursor; + _this.emit('cursor', cursor); + }); + }); +} + +util.inherits(QueryCursor, Readable); + +/*! + * Necessary to satisfy the Readable API + */ + +QueryCursor.prototype._read = function() { + const _this = this; + _next(this, function(error, doc) { + if (error) { + return _this.emit('error', error); + } + if (!doc) { + _this.push(null); + _this.cursor.close(function(error) { + if (error) { + return _this.emit('error', error); + } + setTimeout(function() { + // on node >= 14 streams close automatically (gh-8834) + const isNotClosedAutomatically = !_this.destroyed; + if (isNotClosedAutomatically) { + _this.emit('close'); + } + }, 0); + }); + return; + } + _this.push(doc); + }); +}; + +/** + * Registers a transform function which subsequently maps documents retrieved + * via the streams interface or `.next()` + * + * ####Example + * + * // Map documents returned by `data` events + * Thing. + * find({ name: /^hello/ }). + * cursor(). + * map(function (doc) { + * doc.foo = "bar"; + * return doc; + * }) + * on('data', function(doc) { console.log(doc.foo); }); + * + * // Or map documents returned by `.next()` + * const cursor = Thing.find({ name: /^hello/ }). + * cursor(). + * map(function (doc) { + * doc.foo = "bar"; + * return doc; + * }); + * cursor.next(function(error, doc) { + * console.log(doc.foo); + * }); + * + * @param {Function} fn + * @return {QueryCursor} + * @api public + * @method map + */ + +QueryCursor.prototype.map = function(fn) { + this._transforms.push(fn); + return this; +}; + +/*! + * Marks this cursor as errored + */ + +QueryCursor.prototype._markError = function(error) { + this._error = error; + return this; +}; + +/** + * Marks this cursor as closed. Will stop streaming and subsequent calls to + * `next()` will error. + * + * @param {Function} callback + * @return {Promise} + * @api public + * @method close + * @emits close + * @see MongoDB driver cursor#close http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html#close + */ + +QueryCursor.prototype.close = function(callback) { + return promiseOrCallback(callback, cb => { + this.cursor.close(error => { + if (error) { + cb(error); + return this.listeners('error').length > 0 && this.emit('error', error); + } + this.emit('close'); + cb(null); + }); + }, this.model.events); +}; + +/** + * Get the next document from this cursor. Will return `null` when there are + * no documents left. + * + * @param {Function} callback + * @return {Promise} + * @api public + * @method next + */ + +QueryCursor.prototype.next = function(callback) { + return promiseOrCallback(callback, cb => { + _next(this, function(error, doc) { + if (error) { + return cb(error); + } + cb(null, doc); + }); + }, this.model.events); +}; + +/** + * Execute `fn` for every document in the cursor. If `fn` returns a promise, + * will wait for the promise to resolve before iterating on to the next one. + * Returns a promise that resolves when done. + * + * ####Example + * + * // Iterate over documents asynchronously + * Thing. + * find({ name: /^hello/ }). + * cursor(). + * eachAsync(async function (doc, i) { + * doc.foo = doc.bar + i; + * await doc.save(); + * }) + * + * @param {Function} fn + * @param {Object} [options] + * @param {Number} [options.parallel] the number of promises to execute in parallel. Defaults to 1. + * @param {Function} [callback] executed when all docs have been processed + * @return {Promise} + * @api public + * @method eachAsync + */ + +QueryCursor.prototype.eachAsync = function(fn, opts, callback) { + const _this = this; + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + opts = opts || {}; + + return eachAsync(function(cb) { return _next(_this, cb); }, fn, opts, callback); +}; + +/** + * The `options` passed in to the `QueryCursor` constructor. + * + * @api public + * @property options + */ + +QueryCursor.prototype.options; + +/** + * Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag). + * Useful for setting the `noCursorTimeout` and `tailable` flags. + * + * @param {String} flag + * @param {Boolean} value + * @return {AggregationCursor} this + * @api public + * @method addCursorFlag + */ + +QueryCursor.prototype.addCursorFlag = function(flag, value) { + const _this = this; + _waitForCursor(this, function() { + _this.cursor.addCursorFlag(flag, value); + }); + return this; +}; + +/*! + * ignore + */ + +QueryCursor.prototype.transformNull = function(val) { + if (arguments.length === 0) { + val = true; + } + this._mongooseOptions.transformNull = val; + return this; +}; + +/*! + * ignore + */ + +QueryCursor.prototype._transformForAsyncIterator = function() { + if (this._transforms.indexOf(_transformForAsyncIterator) === -1) { + this.map(_transformForAsyncIterator); + } + return this; +}; + +/** + * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js). + * You do not need to call this function explicitly, the JavaScript runtime + * will call it for you. + * + * ####Example + * + * // Works without using `cursor()` + * for await (const doc of Model.find([{ $sort: { name: 1 } }])) { + * console.log(doc.name); + * } + * + * // Can also use `cursor()` + * for await (const doc of Model.find([{ $sort: { name: 1 } }]).cursor()) { + * console.log(doc.name); + * } + * + * Node.js 10.x supports async iterators natively without any flags. You can + * enable async iterators in Node.js 8.x using the [`--harmony_async_iteration` flag](https://github.com/tc39/proposal-async-iteration/issues/117#issuecomment-346695187). + * + * **Note:** This function is not if `Symbol.asyncIterator` is undefined. If + * `Symbol.asyncIterator` is undefined, that means your Node.js version does not + * support async iterators. + * + * @method Symbol.asyncIterator + * @memberOf Query + * @instance + * @api public + */ + +if (Symbol.asyncIterator != null) { + QueryCursor.prototype[Symbol.asyncIterator] = function() { + return this.transformNull()._transformForAsyncIterator(); + }; +} + +/*! + * ignore + */ + +function _transformForAsyncIterator(doc) { + return doc == null ? { done: true } : { value: doc, done: false }; +} + +/*! + * Get the next doc from the underlying cursor and mongooseify it + * (populate, etc.) + */ + +function _next(ctx, cb) { + let callback = cb; + if (ctx._transforms.length) { + callback = function(err, doc) { + if (err || (doc === null && !ctx._mongooseOptions.transformNull)) { + return cb(err, doc); + } + cb(err, ctx._transforms.reduce(function(doc, fn) { + return fn.call(ctx, doc); + }, doc)); + }; + } + + if (ctx._error) { + return process.nextTick(function() { + callback(ctx._error); + }); + } + + if (ctx.cursor) { + return ctx.cursor.next(function(error, doc) { + if (error) { + return callback(error); + } + if (!doc) { + return callback(null, null); + } + + const opts = ctx.query._mongooseOptions; + if (!opts.populate) { + return opts.lean ? + callback(null, doc) : + _create(ctx, doc, null, callback); + } + + const pop = helpers.preparePopulationOptionsMQ(ctx.query, + ctx.query._mongooseOptions); + pop.__noPromise = true; + ctx.query.model.populate(doc, pop, function(err, doc) { + if (err) { + return callback(err); + } + return opts.lean ? + callback(null, doc) : + _create(ctx, doc, pop, callback); + }); + }); + } else { + ctx.once('cursor', function() { + _next(ctx, cb); + }); + } +} + +/*! + * ignore + */ + +function _waitForCursor(ctx, cb) { + if (ctx.cursor) { + return cb(); + } + ctx.once('cursor', function() { + cb(); + }); +} + +/*! + * Convert a raw doc into a full mongoose doc. + */ + +function _create(ctx, doc, populatedIds, cb) { + const instance = helpers.createModel(ctx.query.model, doc, ctx.query._fields); + const opts = populatedIds ? + { populated: populatedIds } : + undefined; + + instance.init(doc, opts, function(err) { + if (err) { + return cb(err); + } + cb(null, instance); + }); +} + +module.exports = QueryCursor; diff --git a/node_modules/mongoose/lib/document.js b/node_modules/mongoose/lib/document.js new file mode 100644 index 0000000..9e8cb9a --- /dev/null +++ b/node_modules/mongoose/lib/document.js @@ -0,0 +1,4056 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const EventEmitter = require('events').EventEmitter; +const InternalCache = require('./internal'); +const MongooseError = require('./error/index'); +const MixedSchema = require('./schema/mixed'); +const ObjectExpectedError = require('./error/objectExpected'); +const ObjectParameterError = require('./error/objectParameter'); +const ParallelValidateError = require('./error/parallelValidate'); +const Schema = require('./schema'); +const StrictModeError = require('./error/strict'); +const ValidationError = require('./error/validation'); +const ValidatorError = require('./error/validator'); +const VirtualType = require('./virtualtype'); +const promiseOrCallback = require('./helpers/promiseOrCallback'); +const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths'); +const compile = require('./helpers/document/compile').compile; +const defineKey = require('./helpers/document/compile').defineKey; +const flatten = require('./helpers/common').flatten; +const get = require('./helpers/get'); +const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDiscriminatorPath'); +const handleSpreadDoc = require('./helpers/document/handleSpreadDoc'); +const idGetter = require('./plugins/idGetter'); +const isDefiningProjection = require('./helpers/projection/isDefiningProjection'); +const isExclusive = require('./helpers/projection/isExclusive'); +const inspect = require('util').inspect; +const internalToObjectOptions = require('./options').internalToObjectOptions; +const mpath = require('mpath'); +const queryhelpers = require('./queryhelpers'); +const utils = require('./utils'); +const isPromise = require('./helpers/isPromise'); + +const clone = utils.clone; +const deepEqual = utils.deepEqual; +const isMongooseObject = utils.isMongooseObject; + +const arrayAtomicsBackupSymbol = Symbol('mongoose.Array#atomicsBackup'); +const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol; +const documentArrayParent = require('./helpers/symbols').documentArrayParent; +const documentIsSelected = require('./helpers/symbols').documentIsSelected; +const documentIsModified = require('./helpers/symbols').documentIsModified; +const documentModifiedPaths = require('./helpers/symbols').documentModifiedPaths; +const documentSchemaSymbol = require('./helpers/symbols').documentSchemaSymbol; +const getSymbol = require('./helpers/symbols').getSymbol; +const populateModelSymbol = require('./helpers/symbols').populateModelSymbol; +const scopeSymbol = require('./helpers/symbols').scopeSymbol; + +let DocumentArray; +let MongooseArray; +let Embedded; + +const specialProperties = utils.specialProperties; + +/** + * The core Mongoose document constructor. You should not call this directly, + * the Mongoose [Model constructor](./api.html#Model) calls this for you. + * + * @param {Object} obj the values to set + * @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data + * @param {Object} [options] various configuration options for the document + * @param {Boolean} [options.defaults=true] if `false`, skip applying default values to this document. + * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter + * @event `init`: Emitted on a document after it has been retrieved from the db and fully hydrated by Mongoose. + * @event `save`: Emitted when the document is successfully saved + * @api private + */ + +function Document(obj, fields, skipId, options) { + if (typeof skipId === 'object' && skipId != null) { + options = skipId; + skipId = options.skipId; + } + options = Object.assign({}, options); + const defaults = get(options, 'defaults', true); + options.defaults = defaults; + + // Support `browserDocument.js` syntax + if (this.schema == null) { + const _schema = utils.isObject(fields) && !fields.instanceOfSchema ? + new Schema(fields) : + fields; + this.$__setSchema(_schema); + fields = skipId; + skipId = options; + options = arguments[4] || {}; + } + + this.$__ = new InternalCache; + this.$__.emitter = new EventEmitter(); + this.isNew = 'isNew' in options ? options.isNew : true; + this.errors = undefined; + this.$__.$options = options || {}; + this.$locals = {}; + this.$op = null; + + if (obj != null && typeof obj !== 'object') { + throw new ObjectParameterError(obj, 'obj', 'Document'); + } + + const schema = this.schema; + + if (typeof fields === 'boolean' || fields === 'throw') { + this.$__.strictMode = fields; + fields = undefined; + } else { + this.$__.strictMode = schema.options.strict; + this.$__.selected = fields; + } + + const requiredPaths = schema.requiredPaths(true); + for (const path of requiredPaths) { + this.$__.activePaths.require(path); + } + + this.$__.emitter.setMaxListeners(0); + + let exclude = null; + + // determine if this doc is a result of a query with + // excluded fields + if (utils.isPOJO(fields)) { + exclude = isExclusive(fields); + } + + const hasIncludedChildren = exclude === false && fields ? + $__hasIncludedChildren(fields) : + {}; + + if (this._doc == null) { + this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false); + + // By default, defaults get applied **before** setting initial values + // Re: gh-6155 + if (defaults) { + $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, { + isNew: this.isNew + }); + } + } + + if (obj) { + // Skip set hooks + if (this.$__original_set) { + this.$__original_set(obj, undefined, true); + } else { + this.$set(obj, undefined, true); + } + + if (obj instanceof Document) { + this.isNew = obj.isNew; + } + } + + // Function defaults get applied **after** setting initial values so they + // see the full doc rather than an empty one, unless they opt out. + // Re: gh-3781, gh-6155 + if (options.willInit && defaults) { + EventEmitter.prototype.once.call(this, 'init', () => { + $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, { + isNew: this.isNew + }); + }); + } else if (defaults) { + $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, { + isNew: this.isNew + }); + } + + this.$__._id = this._id; + + if (!this.$__.strictMode && obj) { + const _this = this; + const keys = Object.keys(this._doc); + + keys.forEach(function(key) { + if (!(key in schema.tree)) { + defineKey(key, null, _this); + } + }); + } + + applyQueue(this); +} + +/*! + * Document exposes the NodeJS event emitter API, so you can use + * `on`, `once`, etc. + */ +utils.each( + ['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners', + 'removeAllListeners', 'addListener'], + function(emitterFn) { + Document.prototype[emitterFn] = function() { + return this.$__.emitter[emitterFn].apply(this.$__.emitter, arguments); + }; + }); + +Document.prototype.constructor = Document; + +for (const i in EventEmitter.prototype) { + Document[i] = EventEmitter.prototype[i]; +} + +/** + * The documents schema. + * + * @api public + * @property schema + * @memberOf Document + * @instance + */ + +Document.prototype.schema; + +/** + * Empty object that you can use for storing properties on the document. This + * is handy for passing data to middleware without conflicting with Mongoose + * internals. + * + * ####Example: + * + * schema.pre('save', function() { + * // Mongoose will set `isNew` to `false` if `save()` succeeds + * this.$locals.wasNew = this.isNew; + * }); + * + * schema.post('save', function() { + * // Prints true if `isNew` was set before `save()` + * console.log(this.$locals.wasNew); + * }); + * + * @api public + * @property $locals + * @memberOf Document + * @instance + */ + +Object.defineProperty(Document.prototype, '$locals', { + configurable: false, + enumerable: false, + writable: true +}); + +/** + * Boolean flag specifying if the document is new. + * + * @api public + * @property isNew + * @memberOf Document + * @instance + */ + +Document.prototype.isNew; + +/** + * The string version of this documents _id. + * + * ####Note: + * + * This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](/docs/guide.html#id) of its `Schema` to false at construction time. + * + * new Schema({ name: String }, { id: false }); + * + * @api public + * @see Schema options /docs/guide.html#options + * @property id + * @memberOf Document + * @instance + */ + +Document.prototype.id; + +/** + * Hash containing current validation errors. + * + * @api public + * @property errors + * @memberOf Document + * @instance + */ + +Document.prototype.errors; + +/** + * A string containing the current operation that Mongoose is executing + * on this document. May be `null`, `'save'`, `'validate'`, or `'remove'`. + * + * ####Example: + * + * const doc = new Model({ name: 'test' }); + * doc.$op; // null + * + * const promise = doc.save(); + * doc.$op; // 'save' + * + * await promise; + * doc.$op; // null + * + * @api public + * @property $op + * @memberOf Document + * @instance + */ + +Document.prototype.$op; + +/*! + * ignore + */ + +function $__hasIncludedChildren(fields) { + const hasIncludedChildren = {}; + const keys = Object.keys(fields); + + for (const key of keys) { + const parts = key.split('.'); + const c = []; + + for (const part of parts) { + c.push(part); + hasIncludedChildren[c.join('.')] = 1; + } + } + + return hasIncludedChildren; +} + +/*! + * ignore + */ + +function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters, pathsToSkip) { + const paths = Object.keys(doc.schema.paths); + const plen = paths.length; + + for (let i = 0; i < plen; ++i) { + let def; + let curPath = ''; + const p = paths[i]; + + if (p === '_id' && skipId) { + continue; + } + + const type = doc.schema.paths[p]; + const path = p.indexOf('.') === -1 ? [p] : p.split('.'); + const len = path.length; + let included = false; + let doc_ = doc._doc; + + for (let j = 0; j < len; ++j) { + if (doc_ == null) { + break; + } + + const piece = path[j]; + curPath += (!curPath.length ? '' : '.') + piece; + + if (exclude === true) { + if (curPath in fields) { + break; + } + } else if (exclude === false && fields && !included) { + if (curPath in fields) { + included = true; + } else if (!hasIncludedChildren[curPath]) { + break; + } + } + + if (j === len - 1) { + if (doc_[piece] !== void 0) { + break; + } + + if (typeof type.defaultValue === 'function') { + if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) { + break; + } + if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) { + break; + } + } else if (!isBeforeSetters) { + // Non-function defaults should always run **before** setters + continue; + } + + if (pathsToSkip && pathsToSkip[curPath]) { + break; + } + + if (fields && exclude !== null) { + if (exclude === true) { + // apply defaults to all non-excluded fields + if (p in fields) { + continue; + } + + try { + def = type.getDefault(doc, false); + } catch (err) { + doc.invalidate(p, err); + break; + } + + if (typeof def !== 'undefined') { + doc_[piece] = def; + doc.$__.activePaths.default(p); + } + } else if (included) { + // selected field + try { + def = type.getDefault(doc, false); + } catch (err) { + doc.invalidate(p, err); + break; + } + + if (typeof def !== 'undefined') { + doc_[piece] = def; + doc.$__.activePaths.default(p); + } + } + } else { + try { + def = type.getDefault(doc, false); + } catch (err) { + doc.invalidate(p, err); + break; + } + + if (typeof def !== 'undefined') { + doc_[piece] = def; + doc.$__.activePaths.default(p); + } + } + } else { + doc_ = doc_[piece]; + } + } + } +} + +/** + * Builds the default doc structure + * + * @param {Object} obj + * @param {Object} [fields] + * @param {Boolean} [skipId] + * @api private + * @method $__buildDoc + * @memberOf Document + * @instance + */ + +Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasIncludedChildren) { + const doc = {}; + + const paths = Object.keys(this.schema.paths). + // Don't build up any paths that are underneath a map, we don't know + // what the keys will be + filter(p => !p.includes('$*')); + const plen = paths.length; + let ii = 0; + + for (; ii < plen; ++ii) { + const p = paths[ii]; + + if (p === '_id') { + if (skipId) { + continue; + } + if (obj && '_id' in obj) { + continue; + } + } + + const path = p.split('.'); + const len = path.length; + const last = len - 1; + let curPath = ''; + let doc_ = doc; + let included = false; + + for (let i = 0; i < len; ++i) { + const piece = path[i]; + + curPath += (!curPath.length ? '' : '.') + piece; + + // support excluding intermediary levels + if (exclude === true) { + if (curPath in fields) { + break; + } + } else if (exclude === false && fields && !included) { + if (curPath in fields) { + included = true; + } else if (!hasIncludedChildren[curPath]) { + break; + } + } + + if (i < last) { + doc_ = doc_[piece] || (doc_[piece] = {}); + } + } + } + + this._doc = doc; +}; + +/*! + * Converts to POJO when you use the document for querying + */ + +Document.prototype.toBSON = function() { + return this.toObject(internalToObjectOptions); +}; + +/** + * Initializes the document without setters or marking anything modified. + * + * Called internally after a document is returned from mongodb. Normally, + * you do **not** need to call this function on your own. + * + * This function triggers `init` [middleware](/docs/middleware.html). + * Note that `init` hooks are [synchronous](/docs/middleware.html#synchronous). + * + * @param {Object} doc document returned by mongo + * @api public + * @memberOf Document + * @instance + */ + +Document.prototype.init = function(doc, opts, fn) { + if (typeof opts === 'function') { + fn = opts; + opts = null; + } + + this.$__init(doc, opts); + + if (fn) { + fn(null, this); + } + + return this; +}; + +/*! + * ignore + */ + +Document.prototype.$__init = function(doc, opts) { + this.isNew = false; + this.$init = true; + opts = opts || {}; + + // handle docs with populated paths + // If doc._id is not null or undefined + if (doc._id != null && opts.populated && opts.populated.length) { + const id = String(doc._id); + for (const item of opts.populated) { + if (item.isVirtual) { + this.populated(item.path, utils.getValue(item.path, doc), item); + } else { + this.populated(item.path, item._docs[id], item); + } + + if (item._childDocs == null) { + continue; + } + for (const child of item._childDocs) { + if (child == null || child.$__ == null) { + continue; + } + child.$__.parent = this; + } + } + } + + init(this, doc, this._doc, opts); + + markArraySubdocsPopulated(this, opts.populated); + + this.emit('init', this); + this.constructor.emit('init', this); + + this.$__._id = this._id; + + return this; +}; + +/*! + * If populating a path within a document array, make sure each + * subdoc within the array knows its subpaths are populated. + * + * ####Example: + * const doc = await Article.findOne().populate('comments.author'); + * doc.comments[0].populated('author'); // Should be set + */ + +function markArraySubdocsPopulated(doc, populated) { + if (doc._id == null || populated == null || populated.length === 0) { + return; + } + + const id = String(doc._id); + for (const item of populated) { + if (item.isVirtual) { + continue; + } + const path = item.path; + const pieces = path.split('.'); + for (let i = 0; i < pieces.length - 1; ++i) { + const subpath = pieces.slice(0, i + 1).join('.'); + const rest = pieces.slice(i + 1).join('.'); + const val = doc.get(subpath); + if (val == null) { + continue; + } + + if (val.isMongooseDocumentArray) { + for (let j = 0; j < val.length; ++j) { + val[j].populated(rest, item._docs[id] == null ? [] : item._docs[id][j], item); + } + break; + } + } + } +} + +/*! + * Init helper. + * + * @param {Object} self document instance + * @param {Object} obj raw mongodb doc + * @param {Object} doc object we are initializing + * @api private + */ + +function init(self, obj, doc, opts, prefix) { + prefix = prefix || ''; + + const keys = Object.keys(obj); + const len = keys.length; + let schema; + let path; + let i; + let index = 0; + + while (index < len) { + _init(index++); + } + + function _init(index) { + i = keys[index]; + path = prefix + i; + schema = self.schema.path(path); + + // Should still work if not a model-level discriminator, but should not be + // necessary. This is *only* to catch the case where we queried using the + // base model and the discriminated model has a projection + if (self.schema.$isRootDiscriminator && !self.isSelected(path)) { + return; + } + + if (!schema && utils.isPOJO(obj[i])) { + // assume nested object + if (!doc[i]) { + doc[i] = {}; + } + init(self, obj[i], doc[i], opts, path + '.'); + } else if (!schema) { + doc[i] = obj[i]; + } else { + if (obj[i] === null) { + doc[i] = schema._castNullish(null); + } else if (obj[i] !== undefined) { + const intCache = obj[i].$__ || {}; + const wasPopulated = intCache.wasPopulated || null; + + if (schema && !wasPopulated) { + try { + doc[i] = schema.cast(obj[i], self, true); + } catch (e) { + self.invalidate(e.path, new ValidatorError({ + path: e.path, + message: e.message, + type: 'cast', + value: e.value, + reason: e + })); + } + } else { + doc[i] = obj[i]; + } + } + // mark as hydrated + if (!self.isModified(path)) { + self.$__.activePaths.init(path); + } + } + } +} + +/** + * Sends an update command with this document `_id` as the query selector. + * + * ####Example: + * + * weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback); + * + * ####Valid options: + * + * - same as in [Model.update](#model_Model.update) + * + * @see Model.update #model_Model.update + * @param {Object} doc + * @param {Object} options + * @param {Function} callback + * @return {Query} + * @api public + * @memberOf Document + * @instance + */ + +Document.prototype.update = function update() { + const args = utils.args(arguments); + args.unshift({ _id: this._id }); + const query = this.constructor.update.apply(this.constructor, args); + + if (this.$session() != null) { + if (!('session' in query.options)) { + query.options.session = this.$session(); + } + } + + return query; +}; + +/** + * Sends an updateOne command with this document `_id` as the query selector. + * + * ####Example: + * + * weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 }, callback); + * + * ####Valid options: + * + * - same as in [Model.updateOne](#model_Model.updateOne) + * + * @see Model.updateOne #model_Model.updateOne + * @param {Object} doc + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and the [Mongoose lean tutorial](/docs/tutorials/lean.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Function} callback + * @return {Query} + * @api public + * @memberOf Document + * @instance + */ + +Document.prototype.updateOne = function updateOne(doc, options, callback) { + const query = this.constructor.updateOne({ _id: this._id }, doc, options); + query._pre(cb => { + this.constructor._middleware.execPre('updateOne', this, [this], cb); + }); + query._post(cb => { + this.constructor._middleware.execPost('updateOne', this, [this], {}, cb); + }); + + if (this.$session() != null) { + if (!('session' in query.options)) { + query.options.session = this.$session(); + } + } + + if (callback != null) { + return query.exec(callback); + } + + return query; +}; + +/** + * Sends a replaceOne command with this document `_id` as the query selector. + * + * ####Valid options: + * + * - same as in [Model.replaceOne](#model_Model.replaceOne) + * + * @see Model.replaceOne #model_Model.replaceOne + * @param {Object} doc + * @param {Object} options + * @param {Function} callback + * @return {Query} + * @api public + * @memberOf Document + * @instance + */ + +Document.prototype.replaceOne = function replaceOne() { + const args = utils.args(arguments); + args.unshift({ _id: this._id }); + return this.constructor.replaceOne.apply(this.constructor, args); +}; + +/** + * Getter/setter around the session associated with this document. Used to + * automatically set `session` if you `save()` a doc that you got from a + * query with an associated session. + * + * ####Example: + * + * const session = MyModel.startSession(); + * const doc = await MyModel.findOne().session(session); + * doc.$session() === session; // true + * doc.$session(null); + * doc.$session() === null; // true + * + * If this is a top-level document, setting the session propagates to all child + * docs. + * + * @param {ClientSession} [session] overwrite the current session + * @return {ClientSession} + * @method $session + * @api public + * @memberOf Document + */ + +Document.prototype.$session = function $session(session) { + if (arguments.length === 0) { + return this.$__.session; + } + this.$__.session = session; + + if (!this.ownerDocument) { + const subdocs = this.$__getAllSubdocs(); + for (const child of subdocs) { + child.$session(session); + } + } + + return session; +}; + +/** + * Overwrite all values in this document with the values of `obj`, except + * for immutable properties. Behaves similarly to `set()`, except for it + * unsets all properties that aren't in `obj`. + * + * @param {Object} obj the object to overwrite this document with + * @method overwrite + * @name overwrite + * @memberOf Document + * @instance + * @api public + */ + +Document.prototype.overwrite = function overwrite(obj) { + const keys = Array.from(new Set(Object.keys(this._doc).concat(Object.keys(obj)))); + + for (const key of keys) { + if (key === '_id') { + continue; + } + // Explicitly skip version key + if (this.schema.options.versionKey && key === this.schema.options.versionKey) { + continue; + } + if (this.schema.options.discriminatorKey && key === this.schema.options.discriminatorKey) { + continue; + } + this.$set(key, obj[key]); + } + + return this; +}; + +/** + * Alias for `set()`, used internally to avoid conflicts + * + * @param {String|Object} path path or object of key/vals to set + * @param {Any} val the value to set + * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes + * @param {Object} [options] optionally specify options that modify the behavior of the set + * @method $set + * @name $set + * @memberOf Document + * @instance + * @api public + */ + +Document.prototype.$set = function $set(path, val, type, options) { + if (utils.isPOJO(type)) { + options = type; + type = undefined; + } + + options = options || {}; + const merge = options.merge; + const adhoc = type && type !== true; + const constructing = type === true; + let adhocs; + let keys; + let i = 0; + let pathtype; + let key; + let prefix; + + const strict = 'strict' in options + ? options.strict + : this.$__.strictMode; + + if (adhoc) { + adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); + adhocs[path] = this.schema.interpretAsType(path, type, this.schema.options); + } + + if (typeof path !== 'string') { + // new Document({ key: val }) + if (path instanceof Document) { + if (path.$__isNested) { + path = path.toObject(); + } else { + path = path._doc; + } + } + + if (path == null) { + const _ = path; + path = val; + val = _; + } else { + prefix = val ? val + '.' : ''; + + keys = Object.keys(path); + const len = keys.length; + + // `_skipMinimizeTopLevel` is because we may have deleted the top-level + // nested key to ensure key order. + const _skipMinimizeTopLevel = get(options, '_skipMinimizeTopLevel', false); + if (len === 0 && _skipMinimizeTopLevel) { + delete options._skipMinimizeTopLevel; + if (val) { + this.$set(val, {}); + } + return this; + } + + while (i < len) { + _handleIndex.call(this, i++); + } + + return this; + } + } else { + this.$__.$setCalled.add(path); + } + + function _handleIndex(i) { + key = keys[i]; + const pathName = prefix + key; + pathtype = this.schema.pathType(pathName); + + // On initial set, delete any nested keys if we're going to overwrite + // them to ensure we keep the user's key order. + if (type === true && + !prefix && + path[key] != null && + pathtype === 'nested' && + this._doc[key] != null && + Object.keys(this._doc[key]).length === 0) { + delete this._doc[key]; + // Make sure we set `{}` back even if we minimize re: gh-8565 + options = Object.assign({}, options, { _skipMinimizeTopLevel: true }); + } + + if (typeof path[key] === 'object' && + !utils.isNativeObject(path[key]) && + !utils.isMongooseType(path[key]) && + path[key] != null && + pathtype !== 'virtual' && + pathtype !== 'real' && + pathtype !== 'adhocOrUndefined' && + !(this.$__path(pathName) instanceof MixedSchema) && + !(this.schema.paths[pathName] && + this.schema.paths[pathName].options && + this.schema.paths[pathName].options.ref)) { + this.$__.$setCalled.add(prefix + key); + this.$set(path[key], prefix + key, constructing, options); + } else if (strict) { + // Don't overwrite defaults with undefined keys (gh-3981) (gh-9039) + if (constructing && path[key] === void 0 && + this.get(pathName) !== void 0) { + return; + } + + if (pathtype === 'adhocOrUndefined') { + pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true }); + } + + if (pathtype === 'real' || pathtype === 'virtual') { + // Check for setting single embedded schema to document (gh-3535) + let p = path[key]; + if (this.schema.paths[pathName] && + this.schema.paths[pathName].$isSingleNested && + path[key] instanceof Document) { + p = p.toObject({ virtuals: false, transform: false }); + } + this.$set(prefix + key, p, constructing, options); + } else if (pathtype === 'nested' && path[key] instanceof Document) { + this.$set(prefix + key, + path[key].toObject({ transform: false }), constructing, options); + } else if (strict === 'throw') { + if (pathtype === 'nested') { + throw new ObjectExpectedError(key, path[key]); + } else { + throw new StrictModeError(key); + } + } + } else if (path[key] !== void 0) { + this.$set(prefix + key, path[key], constructing, options); + } + } + + let pathType = this.schema.pathType(path); + if (pathType === 'adhocOrUndefined') { + pathType = getEmbeddedDiscriminatorPath(this, path, { typeOnly: true }); + } + + // Assume this is a Mongoose document that was copied into a POJO using + // `Object.assign()` or `{...doc}` + val = handleSpreadDoc(val); + + if (pathType === 'nested' && val) { + if (typeof val === 'object' && val != null) { + if (!merge) { + this.$__setValue(path, null); + cleanModifiedSubpaths(this, path); + } else { + return this.$set(val, path, constructing); + } + + const keys = Object.keys(val); + this.$__setValue(path, {}); + for (const key of keys) { + this.$set(path + '.' + key, val[key], constructing); + } + this.markModified(path); + cleanModifiedSubpaths(this, path, { skipDocArrays: true }); + return this; + } + this.invalidate(path, new MongooseError.CastError('Object', val, path)); + return this; + } + + let schema; + const parts = path.indexOf('.') === -1 ? [path] : path.split('.'); + + // Might need to change path for top-level alias + if (typeof this.schema.aliases[parts[0]] == 'string') { + parts[0] = this.schema.aliases[parts[0]]; + } + + if (pathType === 'adhocOrUndefined' && strict) { + // check for roots that are Mixed types + let mixed; + + for (i = 0; i < parts.length; ++i) { + const subpath = parts.slice(0, i + 1).join('.'); + + // If path is underneath a virtual, bypass everything and just set it. + if (i + 1 < parts.length && this.schema.pathType(subpath) === 'virtual') { + mpath.set(path, val, this); + return this; + } + + schema = this.schema.path(subpath); + if (schema == null) { + continue; + } + + if (schema instanceof MixedSchema) { + // allow changes to sub paths of mixed types + mixed = true; + break; + } + } + + if (schema == null) { + // Check for embedded discriminators + schema = getEmbeddedDiscriminatorPath(this, path); + } + + if (!mixed && !schema) { + if (strict === 'throw') { + throw new StrictModeError(path); + } + return this; + } + } else if (pathType === 'virtual') { + schema = this.schema.virtualpath(path); + schema.applySetters(val, this); + return this; + } else { + schema = this.$__path(path); + } + + // gh-4578, if setting a deeply nested path that doesn't exist yet, create it + let cur = this._doc; + let curPath = ''; + for (i = 0; i < parts.length - 1; ++i) { + cur = cur[parts[i]]; + curPath += (curPath.length > 0 ? '.' : '') + parts[i]; + if (!cur) { + this.$set(curPath, {}); + // Hack re: gh-5800. If nested field is not selected, it probably exists + // so `MongoError: cannot use the part (nested of nested.num) to + // traverse the element ({nested: null})` is not likely. If user gets + // that error, its their fault for now. We should reconsider disallowing + // modifying not selected paths for 6.x + if (!this.isSelected(curPath)) { + this.unmarkModified(curPath); + } + cur = this.$__getValue(curPath); + } + } + + let pathToMark; + + // When using the $set operator the path to the field must already exist. + // Else mongodb throws: "LEFT_SUBFIELD only supports Object" + + if (parts.length <= 1) { + pathToMark = path; + } else { + for (i = 0; i < parts.length; ++i) { + const subpath = parts.slice(0, i + 1).join('.'); + if (this.get(subpath, null, { getters: false }) === null) { + pathToMark = subpath; + break; + } + } + + if (!pathToMark) { + pathToMark = path; + } + } + + // if this doc is being constructed we should not trigger getters + const priorVal = (() => { + if (this.$__.$options.priorDoc != null) { + return this.$__.$options.priorDoc.$__getValue(path); + } + if (constructing) { + return void 0; + } + return this.$__getValue(path); + })(); + + if (!schema) { + this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal); + return this; + } + + // If overwriting a subdocument path, make sure to clear out + // any errors _before_ setting, so new errors that happen + // get persisted. Re: #9080 + if (schema.$isSingleNested || schema.$isMongooseArray) { + _markValidSubpaths(this, path); + } + + if (schema.$isSingleNested && val != null && merge) { + if (val instanceof Document) { + val = val.toObject({ virtuals: false, transform: false }); + } + const keys = Object.keys(val); + for (const key of keys) { + this.$set(path + '.' + key, val[key], constructing, options); + } + + return this; + } + + let shouldSet = true; + try { + // If the user is trying to set a ref path to a document with + // the correct model name, treat it as populated + const refMatches = (() => { + if (schema.options == null) { + return false; + } + if (!(val instanceof Document)) { + return false; + } + const model = val.constructor; + + // Check ref + const ref = schema.options.ref; + if (ref != null && (ref === model.modelName || ref === model.baseModelName)) { + return true; + } + + // Check refPath + const refPath = schema.options.refPath; + if (refPath == null) { + return false; + } + const modelName = val.get(refPath); + return modelName === model.modelName || modelName === model.baseModelName; + })(); + + let didPopulate = false; + if (refMatches && val instanceof Document) { + this.populated(path, val._id, { [populateModelSymbol]: val.constructor }); + didPopulate = true; + } + + let popOpts; + if (schema.options && + Array.isArray(schema.options[this.schema.options.typeKey]) && + schema.options[this.schema.options.typeKey].length && + schema.options[this.schema.options.typeKey][0].ref && + _isManuallyPopulatedArray(val, schema.options[this.schema.options.typeKey][0].ref)) { + if (this.ownerDocument) { + popOpts = { [populateModelSymbol]: val[0].constructor }; + this.ownerDocument().populated(this.$__fullPath(path), + val.map(function(v) { return v._id; }), popOpts); + } else { + popOpts = { [populateModelSymbol]: val[0].constructor }; + this.populated(path, val.map(function(v) { return v._id; }), popOpts); + } + didPopulate = true; + } + + if (this.schema.singleNestedPaths[path] == null) { + // If this path is underneath a single nested schema, we'll call the setter + // later in `$__set()` because we don't take `_doc` when we iterate through + // a single nested doc. That's to make sure we get the correct context. + // Otherwise we would double-call the setter, see gh-7196. + val = schema.applySetters(val, this, false, priorVal); + } + + if (schema.$isMongooseDocumentArray && + Array.isArray(val) && + val.length > 0 && + val[0] != null && + val[0].$__ != null && + val[0].$__.populated != null) { + const populatedPaths = Object.keys(val[0].$__.populated); + for (const populatedPath of populatedPaths) { + this.populated(path + '.' + populatedPath, + val.map(v => v.populated(populatedPath)), + val[0].$__.populated[populatedPath].options); + } + didPopulate = true; + } + + if (!didPopulate && this.$__.populated) { + // If this array partially contains populated documents, convert them + // all to ObjectIds re: #8443 + if (Array.isArray(val) && this.$__.populated[path]) { + for (let i = 0; i < val.length; ++i) { + if (val[i] instanceof Document) { + val[i] = val[i]._id; + } + } + } + delete this.$__.populated[path]; + } + + if (schema.$isSingleNested && val != null) { + _checkImmutableSubpaths(val, schema, priorVal); + } + + this.$markValid(path); + } catch (e) { + if (e instanceof MongooseError.StrictModeError && e.isImmutableError) { + this.invalidate(path, e); + } else if (e instanceof MongooseError.CastError) { + this.invalidate(e.path, e); + if (e.$originalErrorPath) { + this.invalidate(path, + new MongooseError.CastError(schema.instance, val, path, e.$originalErrorPath)); + } + } else { + this.invalidate(path, + new MongooseError.CastError(schema.instance, val, path, e)); + } + shouldSet = false; + } + + if (shouldSet) { + this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal); + } + + if (schema.$isSingleNested && (this.isDirectModified(path) || val == null)) { + cleanModifiedSubpaths(this, path); + } + + return this; +}; + +/*! + * ignore + */ + +function _isManuallyPopulatedArray(val, ref) { + if (!Array.isArray(val)) { + return false; + } + if (val.length === 0) { + return false; + } + + for (const el of val) { + if (!(el instanceof Document)) { + return false; + } + const modelName = el.constructor.modelName; + if (modelName == null) { + return false; + } + if (el.constructor.modelName != ref && el.constructor.baseModelName != ref) { + return false; + } + } + + return true; +} + +/** + * Sets the value of a path, or many paths. + * + * ####Example: + * + * // path, value + * doc.set(path, value) + * + * // object + * doc.set({ + * path : value + * , path2 : { + * path : value + * } + * }) + * + * // on-the-fly cast to number + * doc.set(path, value, Number) + * + * // on-the-fly cast to string + * doc.set(path, value, String) + * + * // changing strict mode behavior + * doc.set(path, value, { strict: false }); + * + * @param {String|Object} path path or object of key/vals to set + * @param {Any} val the value to set + * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for "on-the-fly" attributes + * @param {Object} [options] optionally specify options that modify the behavior of the set + * @api public + * @method set + * @memberOf Document + * @instance + */ + +Document.prototype.set = Document.prototype.$set; + +/** + * Determine if we should mark this change as modified. + * + * @return {Boolean} + * @api private + * @method $__shouldModify + * @memberOf Document + * @instance + */ + +Document.prototype.$__shouldModify = function(pathToMark, path, constructing, parts, schema, val, priorVal) { + if (this.isNew) { + return true; + } + + // Re: the note about gh-7196, `val` is the raw value without casting or + // setters if the full path is under a single nested subdoc because we don't + // want to double run setters. So don't set it as modified. See gh-7264. + if (this.schema.singleNestedPaths[path] != null) { + return false; + } + + if (val === void 0 && !this.isSelected(path)) { + // when a path is not selected in a query, its initial + // value will be undefined. + return true; + } + + if (val === void 0 && path in this.$__.activePaths.states.default) { + // we're just unsetting the default value which was never saved + return false; + } + + // gh-3992: if setting a populated field to a doc, don't mark modified + // if they have the same _id + if (this.populated(path) && + val instanceof Document && + deepEqual(val._id, priorVal)) { + return false; + } + + if (!deepEqual(val, priorVal || utils.getValue(path, this))) { + return true; + } + + if (!constructing && + val !== null && + val !== undefined && + path in this.$__.activePaths.states.default && + deepEqual(val, schema.getDefault(this, constructing))) { + // a path with a default was $unset on the server + // and the user is setting it to the same value again + return true; + } + return false; +}; + +/** + * Handles the actual setting of the value and marking the path modified if appropriate. + * + * @api private + * @method $__set + * @memberOf Document + * @instance + */ + +Document.prototype.$__set = function(pathToMark, path, constructing, parts, schema, val, priorVal) { + Embedded = Embedded || require('./types/embedded'); + + const shouldModify = this.$__shouldModify(pathToMark, path, constructing, parts, + schema, val, priorVal); + const _this = this; + + if (shouldModify) { + this.markModified(pathToMark); + + // handle directly setting arrays (gh-1126) + MongooseArray || (MongooseArray = require('./types/array')); + if (val && val.isMongooseArray) { + val._registerAtomic('$set', val); + + // Update embedded document parent references (gh-5189) + if (val.isMongooseDocumentArray) { + val.forEach(function(item) { + item && item.__parentArray && (item.__parentArray = val); + }); + } + + // Small hack for gh-1638: if we're overwriting the entire array, ignore + // paths that were modified before the array overwrite + this.$__.activePaths.forEach(function(modifiedPath) { + if (modifiedPath.startsWith(path + '.')) { + _this.$__.activePaths.ignore(modifiedPath); + } + }); + } + } + + let obj = this._doc; + let i = 0; + const l = parts.length; + let cur = ''; + + for (; i < l; i++) { + const next = i + 1; + const last = next === l; + cur += (cur ? '.' + parts[i] : parts[i]); + if (specialProperties.has(parts[i])) { + return; + } + + if (last) { + if (obj instanceof Map) { + obj.set(parts[i], val); + } else { + obj[parts[i]] = val; + } + } else { + if (utils.isPOJO(obj[parts[i]])) { + obj = obj[parts[i]]; + } else if (obj[parts[i]] && obj[parts[i]] instanceof Embedded) { + obj = obj[parts[i]]; + } else if (obj[parts[i]] && obj[parts[i]].$isSingleNested) { + obj = obj[parts[i]]; + } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) { + obj = obj[parts[i]]; + } else { + obj[parts[i]] = obj[parts[i]] || {}; + obj = obj[parts[i]]; + } + } + } +}; + +/** + * Gets a raw value from a path (no getters) + * + * @param {String} path + * @api private + */ + +Document.prototype.$__getValue = function(path) { + return utils.getValue(path, this._doc); +}; + +/** + * Sets a raw value for a path (no casting, setters, transformations) + * + * @param {String} path + * @param {Object} value + * @api private + */ + +Document.prototype.$__setValue = function(path, val) { + utils.setValue(path, val, this._doc); + return this; +}; + +/** + * Returns the value of a path. + * + * ####Example + * + * // path + * doc.get('age') // 47 + * + * // dynamic casting to a string + * doc.get('age', String) // "47" + * + * @param {String} path + * @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for on-the-fly attributes + * @param {Object} [options] + * @param {Boolean} [options.virtuals=false] Apply virtuals before getting this path + * @param {Boolean} [options.getters=true] If false, skip applying getters and just get the raw value + * @api public + */ + +Document.prototype.get = function(path, type, options) { + let adhoc; + options = options || {}; + if (type) { + adhoc = this.schema.interpretAsType(path, type, this.schema.options); + } + + let schema = this.$__path(path); + if (schema == null) { + schema = this.schema.virtualpath(path); + } + if (schema instanceof MixedSchema) { + const virtual = this.schema.virtualpath(path); + if (virtual != null) { + schema = virtual; + } + } + const pieces = path.split('.'); + let obj = this._doc; + + if (schema instanceof VirtualType) { + if (schema.getters.length === 0) { + return void 0; + } + return schema.applyGetters(null, this); + } + + // Might need to change path for top-level alias + if (typeof this.schema.aliases[pieces[0]] == 'string') { + pieces[0] = this.schema.aliases[pieces[0]]; + } + + for (let i = 0, l = pieces.length; i < l; i++) { + if (obj && obj._doc) { + obj = obj._doc; + } + + if (obj == null) { + obj = void 0; + } else if (obj instanceof Map) { + obj = obj.get(pieces[i], { getters: false }); + } else if (i === l - 1) { + obj = utils.getValue(pieces[i], obj); + } else { + obj = obj[pieces[i]]; + } + } + + if (adhoc) { + obj = adhoc.cast(obj); + } + + if (schema != null && options.getters !== false) { + obj = schema.applyGetters(obj, this); + } else if (this.schema.nested[path] && options.virtuals) { + // Might need to apply virtuals if this is a nested path + return applyVirtuals(this, utils.clone(obj) || {}, { path: path }); + } + + return obj; +}; + +/*! + * ignore + */ + +Document.prototype[getSymbol] = Document.prototype.get; + +/** + * Returns the schematype for the given `path`. + * + * @param {String} path + * @api private + * @method $__path + * @memberOf Document + * @instance + */ + +Document.prototype.$__path = function(path) { + const adhocs = this.$__.adhocPaths; + const adhocType = adhocs && adhocs.hasOwnProperty(path) ? adhocs[path] : null; + + if (adhocType) { + return adhocType; + } + return this.schema.path(path); +}; + +/** + * Marks the path as having pending changes to write to the db. + * + * _Very helpful when using [Mixed](./schematypes.html#mixed) types._ + * + * ####Example: + * + * doc.mixed.type = 'changed'; + * doc.markModified('mixed.type'); + * doc.save() // changes to mixed.type are now persisted + * + * @param {String} path the path to mark modified + * @param {Document} [scope] the scope to run validators with + * @api public + */ + +Document.prototype.markModified = function(path, scope) { + this.$__.activePaths.modify(path); + if (scope != null && !this.ownerDocument) { + this.$__.pathsToScopes[path] = scope; + } +}; + +/** + * Clears the modified state on the specified path. + * + * ####Example: + * + * doc.foo = 'bar'; + * doc.unmarkModified('foo'); + * doc.save(); // changes to foo will not be persisted + * + * @param {String} path the path to unmark modified + * @api public + */ + +Document.prototype.unmarkModified = function(path) { + this.$__.activePaths.init(path); + delete this.$__.pathsToScopes[path]; +}; + +/** + * Don't run validation on this path or persist changes to this path. + * + * ####Example: + * + * doc.foo = null; + * doc.$ignore('foo'); + * doc.save(); // changes to foo will not be persisted and validators won't be run + * + * @memberOf Document + * @instance + * @method $ignore + * @param {String} path the path to ignore + * @api public + */ + +Document.prototype.$ignore = function(path) { + this.$__.activePaths.ignore(path); +}; + +/** + * Returns the list of paths that have been directly modified. A direct + * modified path is a path that you explicitly set, whether via `doc.foo = 'bar'`, + * `Object.assign(doc, { foo: 'bar' })`, or `doc.set('foo', 'bar')`. + * + * A path `a` may be in `modifiedPaths()` but not in `directModifiedPaths()` + * because a child of `a` was directly modified. + * + * ####Example + * const schema = new Schema({ foo: String, nested: { bar: String } }); + * const Model = mongoose.model('Test', schema); + * await Model.create({ foo: 'original', nested: { bar: 'original' } }); + * + * const doc = await Model.findOne(); + * doc.nested.bar = 'modified'; + * doc.directModifiedPaths(); // ['nested.bar'] + * doc.modifiedPaths(); // ['nested', 'nested.bar'] + * + * @return {Array} + * @api public + */ + +Document.prototype.directModifiedPaths = function() { + return Object.keys(this.$__.activePaths.states.modify); +}; + +/** + * Returns true if the given path is nullish or only contains empty objects. + * Useful for determining whether this subdoc will get stripped out by the + * [minimize option](/docs/guide.html#minimize). + * + * ####Example: + * const schema = new Schema({ nested: { foo: String } }); + * const Model = mongoose.model('Test', schema); + * const doc = new Model({}); + * doc.$isEmpty('nested'); // true + * doc.nested.$isEmpty(); // true + * + * doc.nested.foo = 'bar'; + * doc.$isEmpty('nested'); // false + * doc.nested.$isEmpty(); // false + * + * @memberOf Document + * @instance + * @api public + * @method $isEmpty + * @return {Boolean} + */ + +Document.prototype.$isEmpty = function(path) { + const isEmptyOptions = { + minimize: true, + virtuals: false, + getters: false, + transform: false + }; + + if (arguments.length > 0) { + const v = this.get(path); + if (v == null) { + return true; + } + if (typeof v !== 'object') { + return false; + } + if (utils.isPOJO(v)) { + return _isEmpty(v); + } + return Object.keys(v.toObject(isEmptyOptions)).length === 0; + } + + return Object.keys(this.toObject(isEmptyOptions)).length === 0; +}; + +function _isEmpty(v) { + if (v == null) { + return true; + } + if (typeof v !== 'object' || Array.isArray(v)) { + return false; + } + for (const key of Object.keys(v)) { + if (!_isEmpty(v[key])) { + return false; + } + } + return true; +} + +/** + * Returns the list of paths that have been modified. + * + * @param {Object} [options] + * @param {Boolean} [options.includeChildren=false] if true, returns children of modified paths as well. For example, if false, the list of modified paths for `doc.colors = { primary: 'blue' };` will **not** contain `colors.primary`. If true, `modifiedPaths()` will return an array that contains `colors.primary`. + * @return {Array} + * @api public + */ + +Document.prototype.modifiedPaths = function(options) { + options = options || {}; + const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify); + const _this = this; + return directModifiedPaths.reduce(function(list, path) { + const parts = path.split('.'); + list = list.concat(parts.reduce(function(chains, part, i) { + return chains.concat(parts.slice(0, i).concat(part).join('.')); + }, []).filter(function(chain) { + return (list.indexOf(chain) === -1); + })); + + if (!options.includeChildren) { + return list; + } + + let cur = _this.get(path); + if (cur != null && typeof cur === 'object') { + if (cur._doc) { + cur = cur._doc; + } + if (Array.isArray(cur)) { + const len = cur.length; + for (let i = 0; i < len; ++i) { + if (list.indexOf(path + '.' + i) === -1) { + list.push(path + '.' + i); + if (cur[i] != null && cur[i].$__) { + const modified = cur[i].modifiedPaths(); + for (const childPath of modified) { + list.push(path + '.' + i + '.' + childPath); + } + } + } + } + } else { + Object.keys(cur). + filter(function(key) { + return list.indexOf(path + '.' + key) === -1; + }). + forEach(function(key) { + list.push(path + '.' + key); + }); + } + } + + return list; + }, []); +}; + +Document.prototype[documentModifiedPaths] = Document.prototype.modifiedPaths; + +/** + * Returns true if this document was modified, else false. + * + * If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified. + * + * ####Example + * + * doc.set('documents.0.title', 'changed'); + * doc.isModified() // true + * doc.isModified('documents') // true + * doc.isModified('documents.0.title') // true + * doc.isModified('documents otherProp') // true + * doc.isDirectModified('documents') // false + * + * @param {String} [path] optional + * @return {Boolean} + * @api public + */ + +Document.prototype.isModified = function(paths, modifiedPaths) { + if (paths) { + if (!Array.isArray(paths)) { + paths = paths.split(' '); + } + const modified = modifiedPaths || this[documentModifiedPaths](); + const directModifiedPaths = Object.keys(this.$__.activePaths.states.modify); + const isModifiedChild = paths.some(function(path) { + return !!~modified.indexOf(path); + }); + + return isModifiedChild || paths.some(function(path) { + return directModifiedPaths.some(function(mod) { + return mod === path || path.startsWith(mod + '.'); + }); + }); + } + + return this.$__.activePaths.some('modify'); +}; + +Document.prototype[documentIsModified] = Document.prototype.isModified; + +/** + * Checks if a path is set to its default. + * + * ####Example + * + * MyModel = mongoose.model('test', { name: { type: String, default: 'Val '} }); + * const m = new MyModel(); + * m.$isDefault('name'); // true + * + * @memberOf Document + * @instance + * @method $isDefault + * @param {String} [path] + * @return {Boolean} + * @api public + */ + +Document.prototype.$isDefault = function(path) { + return (path in this.$__.activePaths.states.default); +}; + +/** + * Getter/setter, determines whether the document was removed or not. + * + * ####Example: + * product.remove(function (err, product) { + * product.$isDeleted(); // true + * product.remove(); // no-op, doesn't send anything to the db + * + * product.$isDeleted(false); + * product.$isDeleted(); // false + * product.remove(); // will execute a remove against the db + * }) + * + * @param {Boolean} [val] optional, overrides whether mongoose thinks the doc is deleted + * @return {Boolean} whether mongoose thinks this doc is deleted. + * @method $isDeleted + * @memberOf Document + * @instance + * @api public + */ + +Document.prototype.$isDeleted = function(val) { + if (arguments.length === 0) { + return !!this.$__.isDeleted; + } + + this.$__.isDeleted = !!val; + return this; +}; + +/** + * Returns true if `path` was directly set and modified, else false. + * + * ####Example + * + * doc.set('documents.0.title', 'changed'); + * doc.isDirectModified('documents.0.title') // true + * doc.isDirectModified('documents') // false + * + * @param {String} path + * @return {Boolean} + * @api public + */ + +Document.prototype.isDirectModified = function(path) { + return (path in this.$__.activePaths.states.modify); +}; + +/** + * Checks if `path` was initialized. + * + * @param {String} path + * @return {Boolean} + * @api public + */ + +Document.prototype.isInit = function(path) { + return (path in this.$__.activePaths.states.init); +}; + +/** + * Checks if `path` was selected in the source query which initialized this document. + * + * ####Example + * + * Thing.findOne().select('name').exec(function (err, doc) { + * doc.isSelected('name') // true + * doc.isSelected('age') // false + * }) + * + * @param {String} path + * @return {Boolean} + * @api public + */ + +Document.prototype.isSelected = function isSelected(path) { + if (this.$__.selected) { + if (path === '_id') { + return this.$__.selected._id !== 0; + } + + const paths = Object.keys(this.$__.selected); + let i = paths.length; + let inclusive = null; + let cur; + + if (i === 1 && paths[0] === '_id') { + // only _id was selected. + return this.$__.selected._id === 0; + } + + while (i--) { + cur = paths[i]; + if (cur === '_id') { + continue; + } + if (!isDefiningProjection(this.$__.selected[cur])) { + continue; + } + inclusive = !!this.$__.selected[cur]; + break; + } + + if (inclusive === null) { + return true; + } + + if (path in this.$__.selected) { + return inclusive; + } + + i = paths.length; + const pathDot = path + '.'; + + while (i--) { + cur = paths[i]; + if (cur === '_id') { + continue; + } + + if (cur.startsWith(pathDot)) { + return inclusive || cur !== pathDot; + } + + if (pathDot.startsWith(cur + '.')) { + return inclusive; + } + } + + return !inclusive; + } + + return true; +}; + +Document.prototype[documentIsSelected] = Document.prototype.isSelected; + +/** + * Checks if `path` was explicitly selected. If no projection, always returns + * true. + * + * ####Example + * + * Thing.findOne().select('nested.name').exec(function (err, doc) { + * doc.isDirectSelected('nested.name') // true + * doc.isDirectSelected('nested.otherName') // false + * doc.isDirectSelected('nested') // false + * }) + * + * @param {String} path + * @return {Boolean} + * @api public + */ + +Document.prototype.isDirectSelected = function isDirectSelected(path) { + if (this.$__.selected) { + if (path === '_id') { + return this.$__.selected._id !== 0; + } + + const paths = Object.keys(this.$__.selected); + let i = paths.length; + let inclusive = null; + let cur; + + if (i === 1 && paths[0] === '_id') { + // only _id was selected. + return this.$__.selected._id === 0; + } + + while (i--) { + cur = paths[i]; + if (cur === '_id') { + continue; + } + if (!isDefiningProjection(this.$__.selected[cur])) { + continue; + } + inclusive = !!this.$__.selected[cur]; + break; + } + + if (inclusive === null) { + return true; + } + + if (path in this.$__.selected) { + return inclusive; + } + + return !inclusive; + } + + return true; +}; + +/** + * Executes registered validation rules for this document. + * + * ####Note: + * + * This method is called `pre` save and if a validation rule is violated, [save](#model_Model-save) is aborted and the error is returned to your `callback`. + * + * ####Example: + * + * doc.validate(function (err) { + * if (err) handleError(err); + * else // validation passed + * }); + * + * @param {Array|String} [pathsToValidate] list of paths to validate. If set, Mongoose will validate only the modified paths that are in the given list. + * @param {Object} [options] internal options + * @param {Boolean} [options.validateModifiedOnly=false] if `true` mongoose validates only modified paths. + * @param {Function} [callback] optional callback called after validation completes, passing an error if one occurred + * @return {Promise} Promise + * @api public + */ + +Document.prototype.validate = function(pathsToValidate, options, callback) { + let parallelValidate; + this.$op = 'validate'; + + if (this.ownerDocument != null) { + // Skip parallel validate check for subdocuments + } else if (this.$__.validating) { + parallelValidate = new ParallelValidateError(this, { + parentStack: options && options.parentStack, + conflictStack: this.$__.validating.stack + }); + } else { + this.$__.validating = new ParallelValidateError(this, { parentStack: options && options.parentStack }); + } + + if (typeof pathsToValidate === 'function') { + callback = pathsToValidate; + options = null; + pathsToValidate = null; + } else if (typeof options === 'function') { + callback = options; + options = pathsToValidate; + pathsToValidate = null; + } + + return promiseOrCallback(callback, cb => { + if (parallelValidate != null) { + return cb(parallelValidate); + } + + this.$__validate(pathsToValidate, options, (error) => { + this.$op = null; + cb(error); + }); + }, this.constructor.events); +}; + +/*! + * ignore + */ + +function _evaluateRequiredFunctions(doc) { + Object.keys(doc.$__.activePaths.states.require).forEach(path => { + const p = doc.schema.path(path); + + if (p != null && typeof p.originalRequiredValue === 'function') { + doc.$__.cachedRequired[path] = p.originalRequiredValue.call(doc, doc); + } + }); +} + +/*! + * ignore + */ + +function _getPathsToValidate(doc) { + const skipSchemaValidators = {}; + + _evaluateRequiredFunctions(doc); + + // only validate required fields when necessary + let paths = new Set(Object.keys(doc.$__.activePaths.states.require).filter(function(path) { + if (!doc.isSelected(path) && !doc.isModified(path)) { + return false; + } + if (path in doc.$__.cachedRequired) { + return doc.$__.cachedRequired[path]; + } + return true; + })); + + + Object.keys(doc.$__.activePaths.states.init).forEach(addToPaths); + Object.keys(doc.$__.activePaths.states.modify).forEach(addToPaths); + Object.keys(doc.$__.activePaths.states.default).forEach(addToPaths); + function addToPaths(p) { paths.add(p); } + + const subdocs = doc.$__getAllSubdocs(); + const modifiedPaths = doc.modifiedPaths(); + for (const subdoc of subdocs) { + if (subdoc.$basePath) { + // Remove child paths for now, because we'll be validating the whole + // subdoc + for (const p of paths) { + if (p === null || p.startsWith(subdoc.$basePath + '.')) { + paths.delete(p); + } + } + + if (doc.isModified(subdoc.$basePath, modifiedPaths) && + !doc.isDirectModified(subdoc.$basePath) && + !doc.$isDefault(subdoc.$basePath)) { + paths.add(subdoc.$basePath); + + skipSchemaValidators[subdoc.$basePath] = true; + } + } + } + + // from here on we're not removing items from paths + + // gh-661: if a whole array is modified, make sure to run validation on all + // the children as well + for (const path of paths) { + const _pathType = doc.schema.path(path); + if (!_pathType || + !_pathType.$isMongooseArray || + // To avoid potential performance issues, skip doc arrays whose children + // are not required. `getPositionalPathType()` may be slow, so avoid + // it unless we have a case of #6364 + (_pathType.$isMongooseDocumentArray && !get(_pathType, 'schemaOptions.required'))) { + continue; + } + + const val = doc.$__getValue(path); + _pushNestedArrayPaths(val, paths, path); + } + + function _pushNestedArrayPaths(val, paths, path) { + if (val != null) { + const numElements = val.length; + for (let j = 0; j < numElements; ++j) { + if (Array.isArray(val[j])) { + _pushNestedArrayPaths(val[j], paths, path + '.' + j); + } else { + paths.add(path + '.' + j); + } + } + } + } + + const flattenOptions = { skipArrays: true }; + for (const pathToCheck of paths) { + if (doc.schema.nested[pathToCheck]) { + let _v = doc.$__getValue(pathToCheck); + if (isMongooseObject(_v)) { + _v = _v.toObject({ transform: false }); + } + const flat = flatten(_v, pathToCheck, flattenOptions, doc.schema); + Object.keys(flat).forEach(addToPaths); + } + } + + + for (const path of paths) { + // Single nested paths (paths embedded under single nested subdocs) will + // be validated on their own when we call `validate()` on the subdoc itself. + // Re: gh-8468 + if (doc.schema.singleNestedPaths.hasOwnProperty(path)) { + paths.delete(path); + continue; + } + const _pathType = doc.schema.path(path); + if (!_pathType || !_pathType.$isSchemaMap) { + continue; + } + + const val = doc.$__getValue(path); + if (val == null) { + continue; + } + for (const key of val.keys()) { + paths.add(path + '.' + key); + } + } + + paths = Array.from(paths); + return [paths, skipSchemaValidators]; +} + +/*! + * ignore + */ + +Document.prototype.$__validate = function(pathsToValidate, options, callback) { + if (typeof pathsToValidate === 'function') { + callback = pathsToValidate; + options = null; + pathsToValidate = null; + } else if (typeof options === 'function') { + callback = options; + options = null; + } + + const hasValidateModifiedOnlyOption = options && + (typeof options === 'object') && + ('validateModifiedOnly' in options); + + let shouldValidateModifiedOnly; + if (hasValidateModifiedOnlyOption) { + shouldValidateModifiedOnly = !!options.validateModifiedOnly; + } else { + shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly; + } + + const _this = this; + const _complete = () => { + let validationError = this.$__.validationError; + this.$__.validationError = undefined; + + if (shouldValidateModifiedOnly && validationError != null) { + // Remove any validation errors that aren't from modified paths + const errors = Object.keys(validationError.errors); + for (const errPath of errors) { + if (!this.isModified(errPath)) { + delete validationError.errors[errPath]; + } + } + if (Object.keys(validationError.errors).length === 0) { + validationError = void 0; + } + } + + this.$__.cachedRequired = {}; + this.emit('validate', _this); + this.constructor.emit('validate', _this); + + this.$__.validating = null; + if (validationError) { + for (const key in validationError.errors) { + // Make sure cast errors persist + if (!this[documentArrayParent] && + validationError.errors[key] instanceof MongooseError.CastError) { + this.invalidate(key, validationError.errors[key]); + } + } + + return validationError; + } + }; + + // only validate required fields when necessary + const pathDetails = _getPathsToValidate(this); + let paths = shouldValidateModifiedOnly ? + pathDetails[0].filter((path) => this.isModified(path)) : + pathDetails[0]; + const skipSchemaValidators = pathDetails[1]; + + if (Array.isArray(pathsToValidate)) { + paths = _handlePathsToValidate(paths, pathsToValidate); + } + + if (paths.length === 0) { + return process.nextTick(function() { + const error = _complete(); + if (error) { + return _this.schema.s.hooks.execPost('validate:error', _this, [_this], { error: error }, function(error) { + callback(error); + }); + } + callback(null, _this); + }); + } + + const validated = {}; + let total = 0; + + const complete = function() { + const error = _complete(); + if (error) { + return _this.schema.s.hooks.execPost('validate:error', _this, [_this], { error: error }, function(error) { + callback(error); + }); + } + callback(null, _this); + }; + + const validatePath = function(path) { + if (path == null || validated[path]) { + return; + } + + validated[path] = true; + total++; + + process.nextTick(function() { + const schemaType = _this.schema.path(path); + + if (!schemaType) { + return --total || complete(); + } + + // If user marked as invalid or there was a cast error, don't validate + if (!_this.$isValid(path)) { + --total || complete(); + return; + } + + let val = _this.$__getValue(path); + + // If you `populate()` and get back a null value, required validators + // shouldn't fail (gh-8018). We should always fall back to the populated + // value. + let pop; + if (val == null && (pop = _this.populated(path))) { + val = pop; + } + const scope = path in _this.$__.pathsToScopes ? + _this.$__.pathsToScopes[path] : + _this; + + const doValidateOptions = { + skipSchemaValidators: skipSchemaValidators[path], + path: path + }; + schemaType.doValidate(val, function(err) { + if (err && (!schemaType.$isMongooseDocumentArray || err.$isArrayValidatorError)) { + if (schemaType.$isSingleNested && + err instanceof ValidationError && + schemaType.schema.options.storeSubdocValidationError === false) { + return --total || complete(); + } + _this.invalidate(path, err, undefined, true); + } + --total || complete(); + }, scope, doValidateOptions); + }); + }; + + const numPaths = paths.length; + for (let i = 0; i < numPaths; ++i) { + validatePath(paths[i]); + } +}; + +/*! + * ignore + */ + +function _handlePathsToValidate(paths, pathsToValidate) { + const _pathsToValidate = new Set(pathsToValidate); + const parentPaths = new Map([]); + for (const path of pathsToValidate) { + if (path.indexOf('.') === -1) { + continue; + } + const pieces = path.split('.'); + let cur = pieces[0]; + for (let i = 1; i < pieces.length; ++i) { + // Since we skip subpaths under single nested subdocs to + // avoid double validation, we need to add back the + // single nested subpath if the user asked for it (gh-8626) + parentPaths.set(cur, path); + cur = cur + '.' + pieces[i]; + } + } + + const ret = []; + for (const path of paths) { + if (_pathsToValidate.has(path)) { + ret.push(path); + } else if (parentPaths.has(path)) { + ret.push(parentPaths.get(path)); + } + } + return ret; +} + +/** + * Executes registered validation rules (skipping asynchronous validators) for this document. + * + * ####Note: + * + * This method is useful if you need synchronous validation. + * + * ####Example: + * + * const err = doc.validateSync(); + * if (err) { + * handleError(err); + * } else { + * // validation passed + * } + * + * @param {Array|string} pathsToValidate only validate the given paths + * @param {Object} [options] options for validation + * @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths. + * @return {ValidationError|undefined} ValidationError if there are errors during validation, or undefined if there is no error. + * @api public + */ + +Document.prototype.validateSync = function(pathsToValidate, options) { + const _this = this; + + const hasValidateModifiedOnlyOption = options && + (typeof options === 'object') && + ('validateModifiedOnly' in options); + + let shouldValidateModifiedOnly; + if (hasValidateModifiedOnlyOption) { + shouldValidateModifiedOnly = !!options.validateModifiedOnly; + } else { + shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly; + } + + if (typeof pathsToValidate === 'string') { + pathsToValidate = pathsToValidate.split(' '); + } + + // only validate required fields when necessary + const pathDetails = _getPathsToValidate(this); + let paths = shouldValidateModifiedOnly ? + pathDetails[0].filter((path) => this.isModified(path)) : + pathDetails[0]; + const skipSchemaValidators = pathDetails[1]; + + if (Array.isArray(pathsToValidate)) { + paths = _handlePathsToValidate(paths, pathsToValidate); + } + + const validating = {}; + + paths.forEach(function(path) { + if (validating[path]) { + return; + } + + validating[path] = true; + + const p = _this.schema.path(path); + if (!p) { + return; + } + if (!_this.$isValid(path)) { + return; + } + + const val = _this.$__getValue(path); + const err = p.doValidateSync(val, _this, { + skipSchemaValidators: skipSchemaValidators[path], + path: path + }); + if (err && (!p.$isMongooseDocumentArray || err.$isArrayValidatorError)) { + if (p.$isSingleNested && + err instanceof ValidationError && + p.schema.options.storeSubdocValidationError === false) { + return; + } + _this.invalidate(path, err, undefined, true); + } + }); + + const err = _this.$__.validationError; + _this.$__.validationError = undefined; + _this.emit('validate', _this); + _this.constructor.emit('validate', _this); + + if (err) { + for (const key in err.errors) { + // Make sure cast errors persist + if (err.errors[key] instanceof MongooseError.CastError) { + _this.invalidate(key, err.errors[key]); + } + } + } + + return err; +}; + +/** + * Marks a path as invalid, causing validation to fail. + * + * The `errorMsg` argument will become the message of the `ValidationError`. + * + * The `value` argument (if passed) will be available through the `ValidationError.value` property. + * + * doc.invalidate('size', 'must be less than 20', 14); + + * doc.validate(function (err) { + * console.log(err) + * // prints + * { message: 'Validation failed', + * name: 'ValidationError', + * errors: + * { size: + * { message: 'must be less than 20', + * name: 'ValidatorError', + * path: 'size', + * type: 'user defined', + * value: 14 } } } + * }) + * + * @param {String} path the field to invalidate. For array elements, use the `array.i.field` syntax, where `i` is the 0-based index in the array. + * @param {String|Error} errorMsg the error which states the reason `path` was invalid + * @param {Object|String|Number|any} value optional invalid value + * @param {String} [kind] optional `kind` property for the error + * @return {ValidationError} the current ValidationError, with all currently invalidated paths + * @api public + */ + +Document.prototype.invalidate = function(path, err, val, kind) { + if (!this.$__.validationError) { + this.$__.validationError = new ValidationError(this); + } + + if (this.$__.validationError.errors[path]) { + return; + } + + if (!err || typeof err === 'string') { + err = new ValidatorError({ + path: path, + message: err, + type: kind || 'user defined', + value: val + }); + } + + if (this.$__.validationError === err) { + return this.$__.validationError; + } + + this.$__.validationError.addError(path, err); + return this.$__.validationError; +}; + +/** + * Marks a path as valid, removing existing validation errors. + * + * @param {String} path the field to mark as valid + * @api public + * @memberOf Document + * @instance + * @method $markValid + */ + +Document.prototype.$markValid = function(path) { + if (!this.$__.validationError || !this.$__.validationError.errors[path]) { + return; + } + + delete this.$__.validationError.errors[path]; + if (Object.keys(this.$__.validationError.errors).length === 0) { + this.$__.validationError = null; + } +}; + +/*! + * ignore + */ + +function _markValidSubpaths(doc, path) { + if (!doc.$__.validationError) { + return; + } + + const keys = Object.keys(doc.$__.validationError.errors); + for (const key of keys) { + if (key.startsWith(path + '.')) { + delete doc.$__.validationError.errors[key]; + } + } + if (Object.keys(doc.$__.validationError.errors).length === 0) { + doc.$__.validationError = null; + } +} + +/*! + * ignore + */ + +function _checkImmutableSubpaths(subdoc, schematype, priorVal) { + const schema = schematype.schema; + if (schema == null) { + return; + } + + for (const key of Object.keys(schema.paths)) { + const path = schema.paths[key]; + if (path.$immutableSetter == null) { + continue; + } + const oldVal = priorVal == null ? void 0 : priorVal.$__getValue(key); + // Calling immutableSetter with `oldVal` even though it expects `newVal` + // is intentional. That's because `$immutableSetter` compares its param + // to the current value. + path.$immutableSetter.call(subdoc, oldVal); + } +} + +/** + * Saves this document by inserting a new document into the database if [document.isNew](/docs/api.html#document_Document-isNew) is `true`, + * or sends an [updateOne](/docs/api.html#document_Document-updateOne) operation **only** with the modifications to the database, it does not replace the whole document in the latter case. + * + * ####Example: + * + * product.sold = Date.now(); + * product = await product.save(); + * + * If save is successful, the returned promise will fulfill with the document + * saved. + * + * ####Example: + * + * const newProduct = await product.save(); + * newProduct === product; // true + * + * @param {Object} [options] options optional options + * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](api.html#document_Document-$session). + * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead. + * @param {Boolean} [options.validateBeforeSave] set to false to save without validating. + * @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths. + * @param {Number|String} [options.w] set the [write concern](https://docs.mongodb.com/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern) + * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://docs.mongodb.com/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern) + * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern). + * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names) + * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](./guide.html#timestamps) are enabled, skip timestamps for this `save()`. + * @param {Function} [fn] optional callback + * @method save + * @memberOf Document + * @instance + * @throws {DocumentNotFoundError} if this [save updates an existing document](api.html#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating). + * @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise. + * @api public + * @see middleware http://mongoosejs.com/docs/middleware.html + */ + +/** + * Checks if a path is invalid + * + * @param {String} path the field to check + * @method $isValid + * @memberOf Document + * @instance + * @api private + */ + +Document.prototype.$isValid = function(path) { + return !this.$__.validationError || !this.$__.validationError.errors[path]; +}; + +/** + * Resets the internal modified state of this document. + * + * @api private + * @return {Document} + * @method $__reset + * @memberOf Document + * @instance + */ + +Document.prototype.$__reset = function reset() { + let _this = this; + DocumentArray || (DocumentArray = require('./types/documentarray')); + + this.$__.activePaths + .map('init', 'modify', function(i) { + return _this.$__getValue(i); + }) + .filter(function(val) { + return val && val instanceof Array && val.isMongooseDocumentArray && val.length; + }) + .forEach(function(array) { + let i = array.length; + while (i--) { + const doc = array[i]; + if (!doc) { + continue; + } + doc.$__reset(); + } + + _this.$__.activePaths.init(array.$path()); + + array[arrayAtomicsBackupSymbol] = array[arrayAtomicsSymbol]; + array[arrayAtomicsSymbol] = {}; + }); + + this.$__.activePaths. + map('init', 'modify', function(i) { + return _this.$__getValue(i); + }). + filter(function(val) { + return val && val.$isSingleNested; + }). + forEach(function(doc) { + doc.$__reset(); + _this.$__.activePaths.init(doc.$basePath); + }); + + // clear atomics + this.$__dirty().forEach(function(dirt) { + const type = dirt.value; + + if (type && type[arrayAtomicsSymbol]) { + type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol]; + type[arrayAtomicsSymbol] = {}; + } + }); + + this.$__.backup = {}; + this.$__.backup.activePaths = { + modify: Object.assign({}, this.$__.activePaths.states.modify), + default: Object.assign({}, this.$__.activePaths.states.default) + }; + this.$__.backup.validationError = this.$__.validationError; + this.$__.backup.errors = this.errors; + + // Clear 'dirty' cache + this.$__.activePaths.clear('modify'); + this.$__.activePaths.clear('default'); + this.$__.validationError = undefined; + this.errors = undefined; + _this = this; + this.schema.requiredPaths().forEach(function(path) { + _this.$__.activePaths.require(path); + }); + + return this; +}; + +/*! + * ignore + */ + +Document.prototype.$__undoReset = function $__undoReset() { + if (this.$__.backup == null || this.$__.backup.activePaths == null) { + return; + } + + this.$__.activePaths.states.modify = this.$__.backup.activePaths.modify; + this.$__.activePaths.states.default = this.$__.backup.activePaths.default; + + this.$__.validationError = this.$__.backup.validationError; + this.errors = this.$__.backup.errors; + + for (const dirt of this.$__dirty()) { + const type = dirt.value; + + if (type && type[arrayAtomicsSymbol] && type[arrayAtomicsBackupSymbol]) { + type[arrayAtomicsSymbol] = type[arrayAtomicsBackupSymbol]; + } + } + + for (const subdoc of this.$__getAllSubdocs()) { + subdoc.$__undoReset(); + } +}; + +/** + * Returns this documents dirty paths / vals. + * + * @api private + * @method $__dirty + * @memberOf Document + * @instance + */ + +Document.prototype.$__dirty = function() { + const _this = this; + + let all = this.$__.activePaths.map('modify', function(path) { + return { + path: path, + value: _this.$__getValue(path), + schema: _this.$__path(path) + }; + }); + + // gh-2558: if we had to set a default and the value is not undefined, + // we have to save as well + all = all.concat(this.$__.activePaths.map('default', function(path) { + if (path === '_id' || _this.$__getValue(path) == null) { + return; + } + return { + path: path, + value: _this.$__getValue(path), + schema: _this.$__path(path) + }; + })); + + // Sort dirty paths in a flat hierarchy. + all.sort(function(a, b) { + return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0)); + }); + + // Ignore "foo.a" if "foo" is dirty already. + const minimal = []; + let lastPath; + let top; + + all.forEach(function(item) { + if (!item) { + return; + } + if (lastPath == null || item.path.indexOf(lastPath) !== 0) { + lastPath = item.path + '.'; + minimal.push(item); + top = item; + } else if (top != null && + top.value != null && + top.value[arrayAtomicsSymbol] != null && + top.value.hasAtomics()) { + // special case for top level MongooseArrays + // the `top` array itself and a sub path of `top` are being modified. + // the only way to honor all of both modifications is through a $set + // of entire array. + top.value[arrayAtomicsSymbol] = {}; + top.value[arrayAtomicsSymbol].$set = top.value; + } + }); + + top = lastPath = null; + return minimal; +}; + +/** + * Assigns/compiles `schema` into this documents prototype. + * + * @param {Schema} schema + * @api private + * @method $__setSchema + * @memberOf Document + * @instance + */ + +Document.prototype.$__setSchema = function(schema) { + schema.plugin(idGetter, { deduplicate: true }); + compile(schema.tree, this, undefined, schema.options); + + // Apply default getters if virtual doesn't have any (gh-6262) + for (const key of Object.keys(schema.virtuals)) { + schema.virtuals[key]._applyDefaultGetters(); + } + + this.schema = schema; + this[documentSchemaSymbol] = schema; +}; + + +/** + * Get active path that were changed and are arrays + * + * @api private + * @method $__getArrayPathsToValidate + * @memberOf Document + * @instance + */ + +Document.prototype.$__getArrayPathsToValidate = function() { + DocumentArray || (DocumentArray = require('./types/documentarray')); + + // validate all document arrays. + return this.$__.activePaths + .map('init', 'modify', function(i) { + return this.$__getValue(i); + }.bind(this)) + .filter(function(val) { + return val && val instanceof Array && val.isMongooseDocumentArray && val.length; + }).reduce(function(seed, array) { + return seed.concat(array); + }, []) + .filter(function(doc) { + return doc; + }); +}; + + +/** + * Get all subdocs (by bfs) + * + * @api private + * @method $__getAllSubdocs + * @memberOf Document + * @instance + */ + +Document.prototype.$__getAllSubdocs = function() { + DocumentArray || (DocumentArray = require('./types/documentarray')); + Embedded = Embedded || require('./types/embedded'); + + function docReducer(doc, seed, path) { + let val = doc; + if (path) { + if (doc instanceof Document && doc[documentSchemaSymbol].paths[path]) { + val = doc._doc[path]; + } else { + val = doc[path]; + } + } + if (val instanceof Embedded) { + seed.push(val); + } else if (val instanceof Map) { + seed = Array.from(val.keys()).reduce(function(seed, path) { + return docReducer(val.get(path), seed, null); + }, seed); + } else if (val && val.$isSingleNested) { + seed = Object.keys(val._doc).reduce(function(seed, path) { + return docReducer(val._doc, seed, path); + }, seed); + seed.push(val); + } else if (val && val.isMongooseDocumentArray) { + val.forEach(function _docReduce(doc) { + if (!doc || !doc._doc) { + return; + } + seed = Object.keys(doc._doc).reduce(function(seed, path) { + return docReducer(doc._doc, seed, path); + }, seed); + if (doc instanceof Embedded) { + seed.push(doc); + } + }); + } else if (val instanceof Document && val.$__isNested) { + seed = Object.keys(val).reduce(function(seed, path) { + return docReducer(val, seed, path); + }, seed); + } + return seed; + } + + const _this = this; + const subDocs = Object.keys(this._doc).reduce(function(seed, path) { + return docReducer(_this, seed, path); + }, []); + + return subDocs; +}; + +/*! + * Runs queued functions + */ + +function applyQueue(doc) { + const q = doc.schema && doc.schema.callQueue; + if (!q.length) { + return; + } + + for (const pair of q) { + if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') { + doc[pair[0]].apply(doc, pair[1]); + } + } +} + +/*! + * ignore + */ + +Document.prototype.$__handleReject = function handleReject(err) { + // emit on the Model if listening + if (this.listeners('error').length) { + this.emit('error', err); + } else if (this.constructor.listeners && this.constructor.listeners('error').length) { + this.constructor.emit('error', err); + } +}; + +/** + * Internal helper for toObject() and toJSON() that doesn't manipulate options + * + * @api private + * @method $toObject + * @memberOf Document + * @instance + */ + +Document.prototype.$toObject = function(options, json) { + let defaultOptions = { + transform: true, + flattenDecimals: true + }; + + const path = json ? 'toJSON' : 'toObject'; + const baseOptions = get(this, 'constructor.base.options.' + path, {}); + const schemaOptions = get(this, 'schema.options', {}); + // merge base default options with Schema's set default options if available. + // `clone` is necessary here because `utils.options` directly modifies the second input. + defaultOptions = utils.options(defaultOptions, clone(baseOptions)); + defaultOptions = utils.options(defaultOptions, clone(schemaOptions[path] || {})); + + // If options do not exist or is not an object, set it to empty object + options = utils.isPOJO(options) ? clone(options) : {}; + options._calledWithOptions = options._calledWithOptions || clone(options); + + if (!('flattenMaps' in options)) { + options.flattenMaps = defaultOptions.flattenMaps; + } + + let _minimize; + if (options._calledWithOptions.minimize != null) { + _minimize = options.minimize; + } else if (defaultOptions.minimize != null) { + _minimize = defaultOptions.minimize; + } else { + _minimize = schemaOptions.minimize; + } + + // The original options that will be passed to `clone()`. Important because + // `clone()` will recursively call `$toObject()` on embedded docs, so we + // need the original options the user passed in, plus `_isNested` and + // `_parentOptions` for checking whether we need to depopulate. + const cloneOptions = Object.assign(utils.clone(options), { + _isNested: true, + json: json, + minimize: _minimize + }); + + if (utils.hasUserDefinedProperty(options, 'getters')) { + cloneOptions.getters = options.getters; + } + if (utils.hasUserDefinedProperty(options, 'virtuals')) { + cloneOptions.virtuals = options.virtuals; + } + + const depopulate = options.depopulate || + get(options, '_parentOptions.depopulate', false); + // _isNested will only be true if this is not the top level document, we + // should never depopulate + if (depopulate && options._isNested && this.$__.wasPopulated) { + // populated paths that we set to a document + return clone(this._id, cloneOptions); + } + + // merge default options with input options. + options = utils.options(defaultOptions, options); + options._isNested = true; + options.json = json; + options.minimize = _minimize; + + cloneOptions._parentOptions = options; + cloneOptions._skipSingleNestedGetters = true; + + const gettersOptions = Object.assign({}, cloneOptions); + gettersOptions._skipSingleNestedGetters = false; + + // remember the root transform function + // to save it from being overwritten by sub-transform functions + const originalTransform = options.transform; + + let ret = clone(this._doc, cloneOptions) || {}; + + if (options.getters) { + applyGetters(this, ret, gettersOptions); + + if (options.minimize) { + ret = minimize(ret) || {}; + } + } + + if (options.virtuals || (options.getters && options.virtuals !== false)) { + applyVirtuals(this, ret, gettersOptions, options); + } + + if (options.versionKey === false && this.schema.options.versionKey) { + delete ret[this.schema.options.versionKey]; + } + + let transform = options.transform; + + // In the case where a subdocument has its own transform function, we need to + // check and see if the parent has a transform (options.transform) and if the + // child schema has a transform (this.schema.options.toObject) In this case, + // we need to adjust options.transform to be the child schema's transform and + // not the parent schema's + if (transform) { + applySchemaTypeTransforms(this, ret); + } + + if (options.useProjection) { + omitDeselectedFields(this, ret); + } + + if (transform === true || (schemaOptions.toObject && transform)) { + const opts = options.json ? schemaOptions.toJSON : schemaOptions.toObject; + + if (opts) { + transform = (typeof options.transform === 'function' ? options.transform : opts.transform); + } + } else { + options.transform = originalTransform; + } + + if (typeof transform === 'function') { + const xformed = transform(this, ret, options); + if (typeof xformed !== 'undefined') { + ret = xformed; + } + } + + return ret; +}; + +/** + * Converts this document into a plain javascript object, ready for storage in MongoDB. + * + * Buffers are converted to instances of [mongodb.Binary](http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html) for proper storage. + * + * ####Options: + * + * - `getters` apply all getters (path and virtual getters), defaults to false + * - `aliases` apply all aliases if `virtuals=true`, defaults to true + * - `virtuals` apply virtual getters (can override `getters` option), defaults to false + * - `minimize` remove empty objects, defaults to true + * - `transform` a transform function to apply to the resulting document before returning + * - `depopulate` depopulate any populated paths, replacing them with their original refs, defaults to false + * - `versionKey` whether to include the version key, defaults to true + * - `flattenMaps` convert Maps to POJOs. Useful if you want to JSON.stringify() the result of toObject(), defaults to false + * - `useProjection` set to `true` to omit fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema. + * + * ####Getters/Virtuals + * + * Example of only applying path getters + * + * doc.toObject({ getters: true, virtuals: false }) + * + * Example of only applying virtual getters + * + * doc.toObject({ virtuals: true }) + * + * Example of applying both path and virtual getters + * + * doc.toObject({ getters: true }) + * + * To apply these options to every document of your schema by default, set your [schemas](#schema_Schema) `toObject` option to the same argument. + * + * schema.set('toObject', { virtuals: true }) + * + * ####Transform + * + * We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional `transform` function. + * + * Transform functions receive three arguments + * + * function (doc, ret, options) {} + * + * - `doc` The mongoose document which is being converted + * - `ret` The plain object representation which has been converted + * - `options` The options in use (either schema options or the options passed inline) + * + * ####Example + * + * // specify the transform schema option + * if (!schema.options.toObject) schema.options.toObject = {}; + * schema.options.toObject.transform = function (doc, ret, options) { + * // remove the _id of every document before returning the result + * delete ret._id; + * return ret; + * } + * + * // without the transformation in the schema + * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' } + * + * // with the transformation + * doc.toObject(); // { name: 'Wreck-it Ralph' } + * + * With transformations we can do a lot more than remove properties. We can even return completely new customized objects: + * + * if (!schema.options.toObject) schema.options.toObject = {}; + * schema.options.toObject.transform = function (doc, ret, options) { + * return { movie: ret.name } + * } + * + * // without the transformation in the schema + * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' } + * + * // with the transformation + * doc.toObject(); // { movie: 'Wreck-it Ralph' } + * + * _Note: if a transform function returns `undefined`, the return value will be ignored._ + * + * Transformations may also be applied inline, overridding any transform set in the options: + * + * function xform (doc, ret, options) { + * return { inline: ret.name, custom: true } + * } + * + * // pass the transform as an inline option + * doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true } + * + * If you want to skip transformations, use `transform: false`: + * + * schema.options.toObject.hide = '_id'; + * schema.options.toObject.transform = function (doc, ret, options) { + * if (options.hide) { + * options.hide.split(' ').forEach(function (prop) { + * delete ret[prop]; + * }); + * } + * return ret; + * } + * + * const doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }); + * doc.toObject(); // { secret: 47, name: 'Wreck-it Ralph' } + * doc.toObject({ hide: 'secret _id', transform: false });// { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' } + * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' } + * + * If you pass a transform in `toObject()` options, Mongoose will apply the transform + * to [subdocuments](/docs/subdocs.html) in addition to the top-level document. + * Similarly, `transform: false` skips transforms for all subdocuments. + * Note that this is behavior is different for transforms defined in the schema: + * if you define a transform in `schema.options.toObject.transform`, that transform + * will **not** apply to subdocuments. + * + * const memberSchema = new Schema({ name: String, email: String }); + * const groupSchema = new Schema({ members: [memberSchema], name: String, email }); + * const Group = mongoose.model('Group', groupSchema); + * + * const doc = new Group({ + * name: 'Engineering', + * email: 'dev@mongoosejs.io', + * members: [{ name: 'Val', email: 'val@mongoosejs.io' }] + * }); + * + * // Removes `email` from both top-level document **and** array elements + * // { name: 'Engineering', members: [{ name: 'Val' }] } + * doc.toObject({ transform: (doc, ret) => { delete ret.email; return ret; } }); + * + * Transforms, like all of these options, are also available for `toJSON`. See [this guide to `JSON.stringify()`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html) to learn why `toJSON()` and `toObject()` are separate functions. + * + * See [schema options](/docs/guide.html#toObject) for some more details. + * + * _During save, no custom options are applied to the document before being sent to the database._ + * + * @param {Object} [options] + * @param {Boolean} [options.getters=false] if true, apply all getters, including virtuals + * @param {Boolean} [options.virtuals=false] if true, apply virtuals, including aliases. Use `{ getters: true, virtuals: false }` to just apply getters, not virtuals + * @param {Boolean} [options.aliases=true] if `options.virtuals = true`, you can set `options.aliases = false` to skip applying aliases. This option is a no-op if `options.virtuals = false`. + * @param {Boolean} [options.minimize=true] if true, omit any empty objects from the output + * @param {Function|null} [options.transform=null] if set, mongoose will call this function to allow you to transform the returned object + * @param {Boolean} [options.depopulate=false] if true, replace any conventionally populated paths with the original id in the output. Has no affect on virtual populated paths. + * @param {Boolean} [options.versionKey=true] if false, exclude the version key (`__v` by default) from the output + * @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`. + * @param {Boolean} [options.useProjection=false] - If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema. + * @return {Object} js object + * @see mongodb.Binary http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html + * @api public + * @memberOf Document + * @instance + */ + +Document.prototype.toObject = function(options) { + return this.$toObject(options); +}; + +/*! + * Minimizes an object, removing undefined values and empty objects + * + * @param {Object} object to minimize + * @return {Object} + */ + +function minimize(obj) { + const keys = Object.keys(obj); + let i = keys.length; + let hasKeys; + let key; + let val; + + while (i--) { + key = keys[i]; + val = obj[key]; + + if (utils.isObject(val) && !Buffer.isBuffer(val)) { + obj[key] = minimize(val); + } + + if (undefined === obj[key]) { + delete obj[key]; + continue; + } + + hasKeys = true; + } + + return hasKeys + ? obj + : undefined; +} + +/*! + * Applies virtuals properties to `json`. + */ + +function applyVirtuals(self, json, options, toObjectOptions) { + const schema = self.schema; + const paths = Object.keys(schema.virtuals); + let i = paths.length; + const numPaths = i; + let path; + let assignPath; + let cur = self._doc; + let v; + const aliases = get(toObjectOptions, 'aliases', true); + + if (!cur) { + return json; + } + + options = options || {}; + for (i = 0; i < numPaths; ++i) { + path = paths[i]; + + // Allow skipping aliases with `toObject({ virtuals: true, aliases: false })` + if (!aliases && schema.aliases.hasOwnProperty(path)) { + continue; + } + + // We may be applying virtuals to a nested object, for example if calling + // `doc.nestedProp.toJSON()`. If so, the path we assign to, `assignPath`, + // will be a trailing substring of the `path`. + assignPath = path; + if (options.path != null) { + if (!path.startsWith(options.path + '.')) { + continue; + } + assignPath = path.substr(options.path.length + 1); + } + const parts = assignPath.split('.'); + v = clone(self.get(path), options); + if (v === void 0) { + continue; + } + const plen = parts.length; + cur = json; + for (let j = 0; j < plen - 1; ++j) { + cur[parts[j]] = cur[parts[j]] || {}; + cur = cur[parts[j]]; + } + cur[parts[plen - 1]] = v; + } + + return json; +} + +/*! + * Applies virtuals properties to `json`. + * + * @param {Document} self + * @param {Object} json + * @return {Object} `json` + */ + +function applyGetters(self, json, options) { + const schema = self.schema; + const paths = Object.keys(schema.paths); + let i = paths.length; + let path; + let cur = self._doc; + let v; + + if (!cur) { + return json; + } + + while (i--) { + path = paths[i]; + + const parts = path.split('.'); + const plen = parts.length; + const last = plen - 1; + let branch = json; + let part; + cur = self._doc; + + if (!self.isSelected(path)) { + continue; + } + + for (let ii = 0; ii < plen; ++ii) { + part = parts[ii]; + v = cur[part]; + if (ii === last) { + const val = self.get(path); + branch[part] = clone(val, options); + } else if (v == null) { + if (part in cur) { + branch[part] = v; + } + break; + } else { + branch = branch[part] || (branch[part] = {}); + } + cur = v; + } + } + + return json; +} + +/*! + * Applies schema type transforms to `json`. + * + * @param {Document} self + * @param {Object} json + * @return {Object} `json` + */ + +function applySchemaTypeTransforms(self, json) { + const schema = self.schema; + const paths = Object.keys(schema.paths || {}); + const cur = self._doc; + + if (!cur) { + return json; + } + + for (const path of paths) { + const schematype = schema.paths[path]; + if (typeof schematype.options.transform === 'function') { + const val = self.get(path); + const transformedValue = schematype.options.transform.call(self, val); + throwErrorIfPromise(path, transformedValue); + json[path] = transformedValue; + } else if (schematype.$embeddedSchemaType != null && + typeof schematype.$embeddedSchemaType.options.transform === 'function') { + const vals = [].concat(self.get(path)); + const transform = schematype.$embeddedSchemaType.options.transform; + for (let i = 0; i < vals.length; ++i) { + const transformedValue = transform.call(self, vals[i]); + vals[i] = transformedValue; + throwErrorIfPromise(path, transformedValue); + } + + json[path] = vals; + } + } + + return json; +} + +function throwErrorIfPromise(path, transformedValue) { + if (isPromise(transformedValue)) { + throw new Error('`transform` function must be synchronous, but the transform on path `' + path + '` returned a promise.'); + } +} + +/*! + * ignore + */ + +function omitDeselectedFields(self, json) { + const schema = self.schema; + const paths = Object.keys(schema.paths || {}); + const cur = self._doc; + + if (!cur) { + return json; + } + + let selected = self.$__.selected; + if (selected === void 0) { + selected = {}; + queryhelpers.applyPaths(selected, schema); + } + if (selected == null || Object.keys(selected).length === 0) { + return json; + } + + for (const path of paths) { + if (selected[path] != null && !selected[path]) { + delete json[path]; + } + } + + return json; +} + +/** + * The return value of this method is used in calls to JSON.stringify(doc). + * + * This method accepts the same options as [Document#toObject](#document_Document-toObject). To apply the options to every document of your schema by default, set your [schemas](#schema_Schema) `toJSON` option to the same argument. + * + * schema.set('toJSON', { virtuals: true }) + * + * See [schema options](/docs/guide.html#toJSON) for details. + * + * @param {Object} options + * @return {Object} + * @see Document#toObject #document_Document-toObject + * @see JSON.stringify() in JavaScript https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html + * @api public + * @memberOf Document + * @instance + */ + +Document.prototype.toJSON = function(options) { + return this.$toObject(options, true); +}; + +/** + * If this document is a subdocument or populated document, returns the document's + * parent. Returns `undefined` otherwise. + * + * @api public + * @method parent + * @memberOf Document + * @instance + */ + +Document.prototype.parent = function() { + return this.$__.parent; +}; + +/** + * Helper for console.log + * + * @api public + * @method inspect + * @memberOf Document + * @instance + */ + +Document.prototype.inspect = function(options) { + const isPOJO = utils.isPOJO(options); + let opts; + if (isPOJO) { + opts = options; + opts.minimize = false; + } + const ret = this.toObject(opts); + + if (ret == null) { + // If `toObject()` returns null, `this` is still an object, so if `inspect()` + // prints out null this can cause some serious confusion. See gh-7942. + return 'MongooseDocument { ' + ret + ' }'; + } + + return ret; +}; + +if (inspect.custom) { + /*! + * Avoid Node deprecation warning DEP0079 + */ + + Document.prototype[inspect.custom] = Document.prototype.inspect; +} + +/** + * Helper for console.log + * + * @api public + * @method toString + * @memberOf Document + * @instance + */ + +Document.prototype.toString = function() { + const ret = this.inspect(); + if (typeof ret === 'string') { + return ret; + } + return inspect(ret); +}; + +/** + * Returns true if the Document stores the same data as doc. + * + * Documents are considered equal when they have matching `_id`s, unless neither + * document has an `_id`, in which case this function falls back to using + * `deepEqual()`. + * + * @param {Document} doc a document to compare + * @return {Boolean} + * @api public + * @memberOf Document + * @instance + */ + +Document.prototype.equals = function(doc) { + if (!doc) { + return false; + } + + const tid = this.$__getValue('_id'); + const docid = doc.$__ != null ? doc.$__getValue('_id') : doc; + if (!tid && !docid) { + return deepEqual(this, doc); + } + return tid && tid.equals + ? tid.equals(docid) + : tid === docid; +}; + +/** + * Populates document references, executing the `callback` when complete. + * If you want to use promises instead, use this function with + * [`execPopulate()`](#document_Document-execPopulate) + * + * ####Example: + * + * doc + * .populate('company') + * .populate({ + * path: 'notes', + * match: /airline/, + * select: 'text', + * model: 'modelName' + * options: opts + * }, function (err, user) { + * assert(doc._id === user._id) // the document itself is passed + * }) + * + * // summary + * doc.populate(path) // not executed + * doc.populate(options); // not executed + * doc.populate(path, callback) // executed + * doc.populate(options, callback); // executed + * doc.populate(callback); // executed + * doc.populate(options).execPopulate() // executed, returns promise + * + * + * ####NOTE: + * + * Population does not occur unless a `callback` is passed *or* you explicitly + * call `execPopulate()`. + * Passing the same path a second time will overwrite the previous path options. + * See [Model.populate()](#model_Model.populate) for explaination of options. + * + * @see Model.populate #model_Model.populate + * @see Document.execPopulate #document_Document-execPopulate + * @param {String|Object} [path] The path to populate or an options object + * @param {Function} [callback] When passed, population is invoked + * @api public + * @return {Document} this + * @memberOf Document + * @instance + */ + +Document.prototype.populate = function populate() { + if (arguments.length === 0) { + return this; + } + + const pop = this.$__.populate || (this.$__.populate = {}); + const args = utils.args(arguments); + let fn; + + if (typeof args[args.length - 1] === 'function') { + fn = args.pop(); + } + + // allow `doc.populate(callback)` + if (args.length) { + // use hash to remove duplicate paths + const res = utils.populate.apply(null, args); + for (const populateOptions of res) { + pop[populateOptions.path] = populateOptions; + } + } + + if (fn) { + const paths = utils.object.vals(pop); + this.$__.populate = undefined; + let topLevelModel = this.constructor; + if (this.$__isNested) { + topLevelModel = this.$__[scopeSymbol].constructor; + const nestedPath = this.$__.nestedPath; + paths.forEach(function(populateOptions) { + populateOptions.path = nestedPath + '.' + populateOptions.path; + }); + } + + // Use `$session()` by default if the document has an associated session + // See gh-6754 + if (this.$session() != null) { + const session = this.$session(); + paths.forEach(path => { + if (path.options == null) { + path.options = { session: session }; + return; + } + if (!('session' in path.options)) { + path.options.session = session; + } + }); + } + + topLevelModel.populate(this, paths, fn); + } + + return this; +}; + +/** + * Explicitly executes population and returns a promise. Useful for ES2015 + * integration. + * + * ####Example: + * + * const promise = doc. + * populate('company'). + * populate({ + * path: 'notes', + * match: /airline/, + * select: 'text', + * model: 'modelName' + * options: opts + * }). + * execPopulate(); + * + * // summary + * doc.execPopulate().then(resolve, reject); + * + * // you can also use doc.execPopulate(options) as a shorthand for + * // doc.populate(options).execPopulate() + * + * + * ####Example: + * const promise = doc.execPopulate({ path: 'company', select: 'employees' }); + * + * // summary + * promise.then(resolve,reject); + * + * @see Document.populate #document_Document-populate + * @api public + * @param {Function} [callback] optional callback. If specified, a promise will **not** be returned + * @return {Promise} promise that resolves to the document when population is done + * @memberOf Document + * @instance + */ + +Document.prototype.execPopulate = function(callback) { + const isUsingShorthand = callback != null && typeof callback !== 'function'; + if (isUsingShorthand) { + return this.populate.apply(this, arguments).execPopulate(); + } + + return promiseOrCallback(callback, cb => { + this.populate(cb); + }, this.constructor.events); +}; + +/** + * Gets _id(s) used during population of the given `path`. + * + * ####Example: + * + * Model.findOne().populate('author').exec(function (err, doc) { + * console.log(doc.author.name) // Dr.Seuss + * console.log(doc.populated('author')) // '5144cf8050f071d979c118a7' + * }) + * + * If the path was not populated, undefined is returned. + * + * @param {String} path + * @return {Array|ObjectId|Number|Buffer|String|undefined} + * @memberOf Document + * @instance + * @api public + */ + +Document.prototype.populated = function(path, val, options) { + // val and options are internal + if (val === null || val === void 0) { + if (!this.$__.populated) { + return undefined; + } + const v = this.$__.populated[path]; + if (v) { + return v.value; + } + return undefined; + } + + // internal + if (val === true) { + if (!this.$__.populated) { + return undefined; + } + return this.$__.populated[path]; + } + + this.$__.populated || (this.$__.populated = {}); + this.$__.populated[path] = { value: val, options: options }; + + // If this was a nested populate, make sure each populated doc knows + // about its populated children (gh-7685) + const pieces = path.split('.'); + for (let i = 0; i < pieces.length - 1; ++i) { + const subpath = pieces.slice(0, i + 1).join('.'); + const subdoc = this.get(subpath); + if (subdoc != null && subdoc.$__ != null && this.populated(subpath)) { + const rest = pieces.slice(i + 1).join('.'); + subdoc.populated(rest, val, options); + // No need to continue because the above recursion should take care of + // marking the rest of the docs as populated + break; + } + } + + return val; +}; + +/** + * Takes a populated field and returns it to its unpopulated state. + * + * ####Example: + * + * Model.findOne().populate('author').exec(function (err, doc) { + * console.log(doc.author.name); // Dr.Seuss + * console.log(doc.depopulate('author')); + * console.log(doc.author); // '5144cf8050f071d979c118a7' + * }) + * + * If the path was not populated, this is a no-op. + * + * @param {String} path + * @return {Document} this + * @see Document.populate #document_Document-populate + * @api public + * @memberOf Document + * @instance + */ + +Document.prototype.depopulate = function(path) { + if (typeof path === 'string') { + path = path.split(' '); + } + + let populatedIds; + const virtualKeys = this.$$populatedVirtuals ? Object.keys(this.$$populatedVirtuals) : []; + const populated = get(this, '$__.populated', {}); + + if (arguments.length === 0) { + // Depopulate all + for (const virtualKey of virtualKeys) { + delete this.$$populatedVirtuals[virtualKey]; + delete this._doc[virtualKey]; + delete populated[virtualKey]; + } + + const keys = Object.keys(populated); + + for (const key of keys) { + populatedIds = this.populated(key); + if (!populatedIds) { + continue; + } + delete populated[key]; + this.$set(key, populatedIds); + } + return this; + } + + for (const singlePath of path) { + populatedIds = this.populated(singlePath); + delete populated[singlePath]; + + if (virtualKeys.indexOf(singlePath) !== -1) { + delete this.$$populatedVirtuals[singlePath]; + delete this._doc[singlePath]; + } else if (populatedIds) { + this.$set(singlePath, populatedIds); + } + } + return this; +}; + + +/** + * Returns the full path to this document. + * + * @param {String} [path] + * @return {String} + * @api private + * @method $__fullPath + * @memberOf Document + * @instance + */ + +Document.prototype.$__fullPath = function(path) { + // overridden in SubDocuments + return path || ''; +}; + +/** + * Returns the changes that happened to the document + * in the format that will be sent to MongoDB. + * + * #### Example: + * + * const userSchema = new Schema({ + * name: String, + * age: Number, + * country: String + * }); + * const User = mongoose.model('User', userSchema); + * const user = await User.create({ + * name: 'Hafez', + * age: 25, + * country: 'Egypt' + * }); + * + * // returns an empty object, no changes happened yet + * user.getChanges(); // { } + * + * user.country = undefined; + * user.age = 26; + * + * user.getChanges(); // { $set: { age: 26 }, { $unset: { country: 1 } } } + * + * await user.save(); + * + * user.getChanges(); // { } + * + * Modifying the object that `getChanges()` returns does not affect the document's + * change tracking state. Even if you `delete user.getChanges().$set`, Mongoose + * will still send a `$set` to the server. + * + * @return {Object} + * @api public + * @method getChanges + * @memberOf Document + * @instance + */ + +Document.prototype.getChanges = function() { + const delta = this.$__delta(); + + const changes = delta ? delta[1] : {}; + return changes; +}; + +/*! + * Module exports. + */ + +Document.ValidationError = ValidationError; +module.exports = exports = Document; diff --git a/node_modules/mongoose/lib/document_provider.js b/node_modules/mongoose/lib/document_provider.js new file mode 100644 index 0000000..1ace61f --- /dev/null +++ b/node_modules/mongoose/lib/document_provider.js @@ -0,0 +1,30 @@ +'use strict'; + +/* eslint-env browser */ + +/*! + * Module dependencies. + */ +const Document = require('./document.js'); +const BrowserDocument = require('./browserDocument.js'); + +let isBrowser = false; + +/** + * Returns the Document constructor for the current context + * + * @api private + */ +module.exports = function() { + if (isBrowser) { + return BrowserDocument; + } + return Document; +}; + +/*! + * ignore + */ +module.exports.setBrowser = function(flag) { + isBrowser = flag; +}; diff --git a/node_modules/mongoose/lib/driver.js b/node_modules/mongoose/lib/driver.js new file mode 100644 index 0000000..cf7ca3d --- /dev/null +++ b/node_modules/mongoose/lib/driver.js @@ -0,0 +1,15 @@ +'use strict'; + +/*! + * ignore + */ + +let driver = null; + +module.exports.get = function() { + return driver; +}; + +module.exports.set = function(v) { + driver = v; +}; diff --git a/node_modules/mongoose/lib/drivers/SPEC.md b/node_modules/mongoose/lib/drivers/SPEC.md new file mode 100644 index 0000000..6464693 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/SPEC.md @@ -0,0 +1,4 @@ + +# Driver Spec + +TODO diff --git a/node_modules/mongoose/lib/drivers/browser/ReadPreference.js b/node_modules/mongoose/lib/drivers/browser/ReadPreference.js new file mode 100644 index 0000000..1363570 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/browser/ReadPreference.js @@ -0,0 +1,7 @@ +/*! + * ignore + */ + +'use strict'; + +module.exports = function() {}; diff --git a/node_modules/mongoose/lib/drivers/browser/binary.js b/node_modules/mongoose/lib/drivers/browser/binary.js new file mode 100644 index 0000000..4658f7b --- /dev/null +++ b/node_modules/mongoose/lib/drivers/browser/binary.js @@ -0,0 +1,14 @@ + +/*! + * Module dependencies. + */ + +'use strict'; + +const Binary = require('bson').Binary; + +/*! + * Module exports. + */ + +module.exports = exports = Binary; diff --git a/node_modules/mongoose/lib/drivers/browser/decimal128.js b/node_modules/mongoose/lib/drivers/browser/decimal128.js new file mode 100644 index 0000000..5668182 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/browser/decimal128.js @@ -0,0 +1,7 @@ +/*! + * ignore + */ + +'use strict'; + +module.exports = require('bson').Decimal128; diff --git a/node_modules/mongoose/lib/drivers/browser/index.js b/node_modules/mongoose/lib/drivers/browser/index.js new file mode 100644 index 0000000..56d0b8a --- /dev/null +++ b/node_modules/mongoose/lib/drivers/browser/index.js @@ -0,0 +1,13 @@ +/*! + * Module exports. + */ + +'use strict'; + +exports.Binary = require('./binary'); +exports.Collection = function() { + throw new Error('Cannot create a collection from browser library'); +}; +exports.Decimal128 = require('./decimal128'); +exports.ObjectId = require('./objectid'); +exports.ReadPreference = require('./ReadPreference'); diff --git a/node_modules/mongoose/lib/drivers/browser/objectid.js b/node_modules/mongoose/lib/drivers/browser/objectid.js new file mode 100644 index 0000000..b1e603d --- /dev/null +++ b/node_modules/mongoose/lib/drivers/browser/objectid.js @@ -0,0 +1,28 @@ + +/*! + * [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) ObjectId + * @constructor NodeMongoDbObjectId + * @see ObjectId + */ + +'use strict'; + +const ObjectId = require('bson').ObjectID; + +/*! + * Getter for convenience with populate, see gh-6115 + */ + +Object.defineProperty(ObjectId.prototype, '_id', { + enumerable: false, + configurable: true, + get: function() { + return this; + } +}); + +/*! + * ignore + */ + +module.exports = exports = ObjectId; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/ReadPreference.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/ReadPreference.js new file mode 100644 index 0000000..024ee18 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/ReadPreference.js @@ -0,0 +1,47 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const mongodb = require('mongodb'); +const ReadPref = mongodb.ReadPreference; + +/*! + * Converts arguments to ReadPrefs the driver + * can understand. + * + * @param {String|Array} pref + * @param {Array} [tags] + */ + +module.exports = function readPref(pref, tags) { + if (Array.isArray(pref)) { + tags = pref[1]; + pref = pref[0]; + } + + if (pref instanceof ReadPref) { + return pref; + } + + switch (pref) { + case 'p': + pref = 'primary'; + break; + case 'pp': + pref = 'primaryPreferred'; + break; + case 's': + pref = 'secondary'; + break; + case 'sp': + pref = 'secondaryPreferred'; + break; + case 'n': + pref = 'nearest'; + break; + } + + return new ReadPref(pref, tags); +}; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js new file mode 100644 index 0000000..4e3c86f --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js @@ -0,0 +1,10 @@ + +/*! + * Module dependencies. + */ + +'use strict'; + +const Binary = require('mongodb').Binary; + +module.exports = exports = Binary; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js new file mode 100644 index 0000000..d660c7d --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js @@ -0,0 +1,368 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const MongooseCollection = require('../../collection'); +const MongooseError = require('../../error/mongooseError'); +const Collection = require('mongodb').Collection; +const get = require('../../helpers/get'); +const sliced = require('sliced'); +const stream = require('stream'); +const util = require('util'); + +/** + * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) collection implementation. + * + * All methods methods from the [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver are copied and wrapped in queue management. + * + * @inherits Collection + * @api private + */ + +function NativeCollection(name, options) { + this.collection = null; + this.Promise = options.Promise || Promise; + this._closed = false; + MongooseCollection.apply(this, arguments); +} + +/*! + * Inherit from abstract Collection. + */ + +NativeCollection.prototype.__proto__ = MongooseCollection.prototype; + +/** + * Called when the connection opens. + * + * @api private + */ + +NativeCollection.prototype.onOpen = function() { + const _this = this; + + // always get a new collection in case the user changed host:port + // of parent db instance when re-opening the connection. + + if (!_this.opts.capped.size) { + // non-capped + callback(null, _this.conn.db.collection(_this.name)); + return _this.collection; + } + + if (_this.opts.autoCreate === false) { + _this.collection = _this.conn.db.collection(_this.name); + return _this.collection; + } + + // capped + return _this.conn.db.collection(_this.name, function(err, c) { + if (err) return callback(err); + + // discover if this collection exists and if it is capped + _this.conn.db.listCollections({ name: _this.name }).toArray(function(err, docs) { + if (err) { + return callback(err); + } + const doc = docs[0]; + const exists = !!doc; + + if (exists) { + if (doc.options && doc.options.capped) { + callback(null, c); + } else { + const msg = 'A non-capped collection exists with the name: ' + _this.name + '\n\n' + + ' To use this collection as a capped collection, please ' + + 'first convert it.\n' + + ' http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-Convertingacollectiontocapped'; + err = new Error(msg); + callback(err); + } + } else { + // create + const opts = Object.assign({}, _this.opts.capped); + opts.capped = true; + _this.conn.db.createCollection(_this.name, opts, callback); + } + }); + }); + + function callback(err, collection) { + if (err) { + // likely a strict mode error + _this.conn.emit('error', err); + } else { + _this.collection = collection; + MongooseCollection.prototype.onOpen.call(_this); + } + } +}; + +/** + * Called when the connection closes + * + * @api private + */ + +NativeCollection.prototype.onClose = function(force) { + MongooseCollection.prototype.onClose.call(this, force); +}; + +/*! + * ignore + */ + +const syncCollectionMethods = { watch: true }; + +/*! + * Copy the collection methods and make them subject to queues + */ + +function iter(i) { + NativeCollection.prototype[i] = function() { + const collection = this.collection; + const args = Array.from(arguments); + const _this = this; + const debug = get(_this, 'conn.base.options.debug'); + const lastArg = arguments[arguments.length - 1]; + + // If user force closed, queueing will hang forever. See #5664 + if (this.conn.$wasForceClosed) { + const error = new MongooseError('Connection was force closed'); + if (args.length > 0 && + typeof args[args.length - 1] === 'function') { + args[args.length - 1](error); + return; + } else { + throw error; + } + } + + if (this._shouldBufferCommands() && this.buffer) { + if (syncCollectionMethods[i]) { + throw new Error('Collection method ' + i + ' is synchronous'); + } + if (typeof lastArg === 'function') { + this.addQueue(i, args); + return; + } + + this.conn.emit('buffer', { method: i, args: args }); + + return new this.Promise((resolve, reject) => { + this.addQueue(i, [].concat(args).concat([(err, res) => { + if (err != null) { + return reject(err); + } + resolve(res); + }])); + }); + } + + if (debug) { + if (typeof debug === 'function') { + debug.apply(_this, + [_this.name, i].concat(sliced(args, 0, args.length - 1))); + } else if (debug instanceof stream.Writable) { + this.$printToStream(_this.name, i, args, debug); + } else { + this.$print(_this.name, i, args, typeof debug.color === 'undefined' ? true : debug.color); + } + } + + try { + if (collection == null) { + throw new MongooseError('Cannot call `' + this.name + '.' + i + '()` before initial connection is complete if `bufferCommands = false`. Make sure you `await mongoose.connect()` if you have `bufferCommands = false`.'); + } + + return collection[i].apply(collection, args); + } catch (error) { + // Collection operation may throw because of max bson size, catch it here + // See gh-3906 + if (args.length > 0 && + typeof args[args.length - 1] === 'function') { + args[args.length - 1](error); + } else { + throw error; + } + } + }; +} + +for (const key of Object.keys(Collection.prototype)) { + // Janky hack to work around gh-3005 until we can get rid of the mongoose + // collection abstraction + const descriptor = Object.getOwnPropertyDescriptor(Collection.prototype, key); + // Skip properties with getters because they may throw errors (gh-8528) + if (descriptor.get !== undefined) { + continue; + } + if (typeof Collection.prototype[key] !== 'function') { + continue; + } + + iter(key); +} + +/** + * Debug print helper + * + * @api public + * @method $print + */ + +NativeCollection.prototype.$print = function(name, i, args, color) { + const moduleName = color ? '\x1B[0;36mMongoose:\x1B[0m ' : 'Mongoose: '; + const functionCall = [name, i].join('.'); + const _args = []; + for (let j = args.length - 1; j >= 0; --j) { + if (this.$format(args[j]) || _args.length) { + _args.unshift(this.$format(args[j], color)); + } + } + const params = '(' + _args.join(', ') + ')'; + + console.info(moduleName + functionCall + params); +}; + +/** + * Debug print helper + * + * @api public + * @method $print + */ + +NativeCollection.prototype.$printToStream = function(name, i, args, stream) { + const functionCall = [name, i].join('.'); + const _args = []; + for (let j = args.length - 1; j >= 0; --j) { + if (this.$format(args[j]) || _args.length) { + _args.unshift(this.$format(args[j])); + } + } + const params = '(' + _args.join(', ') + ')'; + + stream.write(functionCall + params, 'utf8'); +}; + +/** + * Formatter for debug print args + * + * @api public + * @method $format + */ + +NativeCollection.prototype.$format = function(arg, color) { + const type = typeof arg; + if (type === 'function' || type === 'undefined') return ''; + return format(arg, false, color); +}; + +/*! + * Debug print helper + */ + +function inspectable(representation) { + const ret = { + inspect: function() { return representation; } + }; + if (util.inspect.custom) { + ret[util.inspect.custom] = ret.inspect; + } + return ret; +} +function map(o) { + return format(o, true); +} +function formatObjectId(x, key) { + x[key] = inspectable('ObjectId("' + x[key].toHexString() + '")'); +} +function formatDate(x, key) { + x[key] = inspectable('new Date("' + x[key].toUTCString() + '")'); +} +function format(obj, sub, color) { + if (obj && typeof obj.toBSON === 'function') { + obj = obj.toBSON(); + } + if (obj == null) { + return obj; + } + + const clone = require('../../helpers/clone'); + let x = clone(obj, { transform: false }); + + if (x.constructor.name === 'Binary') { + x = 'BinData(' + x.sub_type + ', "' + x.toString('base64') + '")'; + } else if (x.constructor.name === 'ObjectID') { + x = inspectable('ObjectId("' + x.toHexString() + '")'); + } else if (x.constructor.name === 'Date') { + x = inspectable('new Date("' + x.toUTCString() + '")'); + } else if (x.constructor.name === 'Object') { + const keys = Object.keys(x); + const numKeys = keys.length; + let key; + for (let i = 0; i < numKeys; ++i) { + key = keys[i]; + if (x[key]) { + let error; + if (typeof x[key].toBSON === 'function') { + try { + // `session.toBSON()` throws an error. This means we throw errors + // in debug mode when using transactions, see gh-6712. As a + // workaround, catch `toBSON()` errors, try to serialize without + // `toBSON()`, and rethrow if serialization still fails. + x[key] = x[key].toBSON(); + } catch (_error) { + error = _error; + } + } + if (x[key].constructor.name === 'Binary') { + x[key] = 'BinData(' + x[key].sub_type + ', "' + + x[key].buffer.toString('base64') + '")'; + } else if (x[key].constructor.name === 'Object') { + x[key] = format(x[key], true); + } else if (x[key].constructor.name === 'ObjectID') { + formatObjectId(x, key); + } else if (x[key].constructor.name === 'Date') { + formatDate(x, key); + } else if (x[key].constructor.name === 'ClientSession') { + x[key] = inspectable('ClientSession("' + + get(x[key], 'id.id.buffer', '').toString('hex') + '")'); + } else if (Array.isArray(x[key])) { + x[key] = x[key].map(map); + } else if (error != null) { + // If there was an error with `toBSON()` and the object wasn't + // already converted to a string representation, rethrow it. + // Open to better ideas on how to handle this. + throw error; + } + } + } + } + if (sub) { + return x; + } + + return util. + inspect(x, false, 10, color). + replace(/\n/g, ''). + replace(/\s{2,}/g, ' '); +} + +/** + * Retrieves information about this collections indexes. + * + * @param {Function} callback + * @method getIndexes + * @api public + */ + +NativeCollection.prototype.getIndexes = NativeCollection.prototype.indexInformation; + +/*! + * Module exports. + */ + +module.exports = NativeCollection; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js new file mode 100644 index 0000000..8041a88 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js @@ -0,0 +1,185 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseConnection = require('../../connection'); +const STATES = require('../../connectionstate'); + +/** + * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation. + * + * @inherits Connection + * @api private + */ + +function NativeConnection() { + MongooseConnection.apply(this, arguments); + this._listening = false; +} + +/** + * Expose the possible connection states. + * @api public + */ + +NativeConnection.STATES = STATES; + +/*! + * Inherits from Connection. + */ + +NativeConnection.prototype.__proto__ = MongooseConnection.prototype; + +/** + * Switches to a different database using the same connection pool. + * + * Returns a new connection object, with the new db. If you set the `useCache` + * option, `useDb()` will cache connections by `name`. + * + * @param {String} name The database name + * @param {Object} [options] + * @param {Boolean} [options.useCache=false] If true, cache results so calling `useDb()` multiple times with the same name only creates 1 connection object. + * @return {Connection} New Connection Object + * @api public + */ + +NativeConnection.prototype.useDb = function(name, options) { + // Return immediately if cached + if (options && options.useCache && this.relatedDbs[name]) { + return this.relatedDbs[name]; + } + + // we have to manually copy all of the attributes... + const newConn = new this.constructor(); + newConn.name = name; + newConn.base = this.base; + newConn.collections = {}; + newConn.models = {}; + newConn.replica = this.replica; + newConn.config = Object.assign({}, this.base.connection.config, newConn.config); + newConn.name = this.name; + newConn.options = this.options; + newConn._readyState = this._readyState; + newConn._closeCalled = this._closeCalled; + newConn._hasOpened = this._hasOpened; + newConn._listening = false; + + newConn.host = this.host; + newConn.port = this.port; + newConn.user = this.user; + newConn.pass = this.pass; + + // First, when we create another db object, we are not guaranteed to have a + // db object to work with. So, in the case where we have a db object and it + // is connected, we can just proceed with setting everything up. However, if + // we do not have a db or the state is not connected, then we need to wait on + // the 'open' event of the connection before doing the rest of the setup + // the 'connected' event is the first time we'll have access to the db object + + const _this = this; + + newConn.client = _this.client; + + if (this.db && this._readyState === STATES.connected) { + wireup(); + } else { + this.once('connected', wireup); + } + + function wireup() { + newConn.client = _this.client; + newConn.db = _this.client.db(name); + newConn.onOpen(); + // setup the events appropriately + listen(newConn); + } + + newConn.name = name; + + // push onto the otherDbs stack, this is used when state changes + this.otherDbs.push(newConn); + newConn.otherDbs.push(this); + + // push onto the relatedDbs cache, this is used when state changes + if (options && options.useCache) { + this.relatedDbs[newConn.name] = newConn; + newConn.relatedDbs = this.relatedDbs; + } + + return newConn; +}; + +/*! + * Register listeners for important events and bubble appropriately. + */ + +function listen(conn) { + if (conn.db._listening) { + return; + } + conn.db._listening = true; + + conn.db.on('close', function(force) { + if (conn._closeCalled) return; + + // the driver never emits an `open` event. auto_reconnect still + // emits a `close` event but since we never get another + // `open` we can't emit close + if (conn.db.serverConfig.autoReconnect) { + conn.readyState = STATES.disconnected; + conn.emit('close'); + return; + } + conn.onClose(force); + }); + conn.db.on('error', function(err) { + conn.emit('error', err); + }); + conn.db.on('reconnect', function() { + conn.readyState = STATES.connected; + conn.emit('reconnect'); + conn.emit('reconnected'); + conn.onOpen(); + }); + conn.db.on('timeout', function(err) { + conn.emit('timeout', err); + }); + conn.db.on('open', function(err, db) { + if (STATES.disconnected === conn.readyState && db && db.databaseName) { + conn.readyState = STATES.connected; + conn.emit('reconnect'); + conn.emit('reconnected'); + } + }); + conn.db.on('parseError', function(err) { + conn.emit('parseError', err); + }); +} + +/** + * Closes the connection + * + * @param {Boolean} [force] + * @param {Function} [fn] + * @return {Connection} this + * @api private + */ + +NativeConnection.prototype.doClose = function(force, fn) { + this.client.close(force, (err, res) => { + // Defer because the driver will wait at least 1ms before finishing closing + // the pool, see https://github.com/mongodb-js/mongodb-core/blob/a8f8e4ce41936babc3b9112bf42d609779f03b39/lib/connection/pool.js#L1026-L1030. + // If there's queued operations, you may still get some background work + // after the callback is called. + setTimeout(() => fn(err, res), 1); + }); + return this; +}; + +/*! + * Module exports. + */ + +module.exports = NativeConnection; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/decimal128.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/decimal128.js new file mode 100644 index 0000000..c895f17 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/decimal128.js @@ -0,0 +1,7 @@ +/*! + * ignore + */ + +'use strict'; + +module.exports = require('mongodb').Decimal128; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/index.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/index.js new file mode 100644 index 0000000..2cd749e --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/index.js @@ -0,0 +1,11 @@ +/*! + * Module exports. + */ + +'use strict'; + +exports.Binary = require('./binary'); +exports.Collection = require('./collection'); +exports.Decimal128 = require('./decimal128'); +exports.ObjectId = require('./objectid'); +exports.ReadPreference = require('./ReadPreference');
\ No newline at end of file diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js new file mode 100644 index 0000000..6f432b7 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js @@ -0,0 +1,16 @@ + +/*! + * [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) ObjectId + * @constructor NodeMongoDbObjectId + * @see ObjectId + */ + +'use strict'; + +const ObjectId = require('mongodb').ObjectId; + +/*! + * ignore + */ + +module.exports = exports = ObjectId; diff --git a/node_modules/mongoose/lib/error/browserMissingSchema.js b/node_modules/mongoose/lib/error/browserMissingSchema.js new file mode 100644 index 0000000..fe492d5 --- /dev/null +++ b/node_modules/mongoose/lib/error/browserMissingSchema.js @@ -0,0 +1,28 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + + +class MissingSchemaError extends MongooseError { + /*! + * MissingSchema Error constructor. + */ + constructor() { + super('Schema hasn\'t been registered for document.\n' + + 'Use mongoose.Document(name, schema)'); + } +} + +Object.defineProperty(MissingSchemaError.prototype, 'name', { + value: 'MongooseError' +}); + +/*! + * exports + */ + +module.exports = MissingSchemaError; diff --git a/node_modules/mongoose/lib/error/cast.js b/node_modules/mongoose/lib/error/cast.js new file mode 100644 index 0000000..c6ff277 --- /dev/null +++ b/node_modules/mongoose/lib/error/cast.js @@ -0,0 +1,120 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const MongooseError = require('./mongooseError'); +const get = require('../helpers/get'); +const util = require('util'); + +/** + * Casting Error constructor. + * + * @param {String} type + * @param {String} value + * @inherits MongooseError + * @api private + */ + +class CastError extends MongooseError { + constructor(type, value, path, reason, schemaType) { + // If no args, assume we'll `init()` later. + if (arguments.length > 0) { + const stringValue = getStringValue(value); + const messageFormat = getMessageFormat(schemaType); + const msg = formatMessage(null, type, stringValue, path, messageFormat); + super(msg); + this.init(type, value, path, reason, schemaType); + } else { + super(formatMessage()); + } + } + + /*! + * ignore + */ + init(type, value, path, reason, schemaType) { + this.stringValue = getStringValue(value); + this.messageFormat = getMessageFormat(schemaType); + this.kind = type; + this.value = value; + this.path = path; + this.reason = reason; + } + + /*! + * ignore + * @param {Readonly<CastError>} other + */ + copy(other) { + this.messageFormat = other.messageFormat; + this.stringValue = other.stringValue; + this.kind = other.kind; + this.value = other.value; + this.path = other.path; + this.reason = other.reason; + this.message = other.message; + } + + /*! + * ignore + */ + setModel(model) { + this.model = model; + this.message = formatMessage(model, this.kind, this.stringValue, this.path, + this.messageFormat); + } +} + +Object.defineProperty(CastError.prototype, 'name', { + value: 'CastError' +}); + +function getStringValue(value) { + let stringValue = util.inspect(value); + stringValue = stringValue.replace(/^'|'$/g, '"'); + if (!stringValue.startsWith('"')) { + stringValue = '"' + stringValue + '"'; + } + return stringValue; +} + +function getMessageFormat(schemaType) { + const messageFormat = get(schemaType, 'options.cast', null); + if (typeof messageFormat === 'string') { + return messageFormat; + } +} + +/*! + * ignore + */ + +function formatMessage(model, kind, stringValue, path, messageFormat) { + if (messageFormat != null) { + let ret = messageFormat. + replace('{KIND}', kind). + replace('{VALUE}', stringValue). + replace('{PATH}', path); + if (model != null) { + ret = ret.replace('{MODEL}', model.modelName); + } + + return ret; + } else { + let ret = 'Cast to ' + kind + ' failed for value ' + + stringValue + ' at path "' + path + '"'; + if (model != null) { + ret += ' for model "' + model.modelName + '"'; + } + + return ret; + } +} + +/*! + * exports + */ + +module.exports = CastError; diff --git a/node_modules/mongoose/lib/error/disconnected.js b/node_modules/mongoose/lib/error/disconnected.js new file mode 100644 index 0000000..777a1de --- /dev/null +++ b/node_modules/mongoose/lib/error/disconnected.js @@ -0,0 +1,34 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + + +/** + * The connection failed to reconnect and will never successfully reconnect to + * MongoDB without manual intervention. + * @api private + */ +class DisconnectedError extends MongooseError { + /** + * @param {String} connectionString + */ + constructor(connectionString) { + super('Ran out of retries trying to reconnect to "' + + connectionString + '". Try setting `server.reconnectTries` and ' + + '`server.reconnectInterval` to something higher.'); + } +} + +Object.defineProperty(DisconnectedError.prototype, 'name', { + value: 'DisconnectedError' +}); + +/*! + * exports + */ + +module.exports = DisconnectedError; diff --git a/node_modules/mongoose/lib/error/divergentArray.js b/node_modules/mongoose/lib/error/divergentArray.js new file mode 100644 index 0000000..ed86caf --- /dev/null +++ b/node_modules/mongoose/lib/error/divergentArray.js @@ -0,0 +1,37 @@ + +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + +class DivergentArrayError extends MongooseError { + /*! + * DivergentArrayError constructor. + * @param {Array<String>} paths + */ + constructor(paths) { + const msg = 'For your own good, using `document.save()` to update an array ' + + 'which was selected using an $elemMatch projection OR ' + + 'populated using skip, limit, query conditions, or exclusion of ' + + 'the _id field when the operation results in a $pop or $set of ' + + 'the entire array is not supported. The following ' + + 'path(s) would have been modified unsafely:\n' + + ' ' + paths.join('\n ') + '\n' + + 'Use Model.update() to update these arrays instead.'; + // TODO write up a docs page (FAQ) and link to it + super(msg); + } +} + +Object.defineProperty(DivergentArrayError.prototype, 'name', { + value: 'DivergentArrayError' +}); + +/*! + * exports + */ + +module.exports = DivergentArrayError; diff --git a/node_modules/mongoose/lib/error/index.js b/node_modules/mongoose/lib/error/index.js new file mode 100644 index 0000000..ec4188d --- /dev/null +++ b/node_modules/mongoose/lib/error/index.js @@ -0,0 +1,205 @@ +'use strict'; + +/** + * MongooseError constructor. MongooseError is the base class for all + * Mongoose-specific errors. + * + * ####Example: + * const Model = mongoose.model('Test', new Schema({ answer: Number })); + * const doc = new Model({ answer: 'not a number' }); + * const err = doc.validateSync(); + * + * err instanceof mongoose.Error; // true + * + * @constructor Error + * @param {String} msg Error message + * @inherits Error https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error + */ + +const MongooseError = require('./mongooseError'); + +/** + * The name of the error. The name uniquely identifies this Mongoose error. The + * possible values are: + * + * - `MongooseError`: general Mongoose error + * - `CastError`: Mongoose could not convert a value to the type defined in the schema path. May be in a `ValidationError` class' `errors` property. + * - `DisconnectedError`: This [connection](connections.html) timed out in trying to reconnect to MongoDB and will not successfully reconnect to MongoDB unless you explicitly reconnect. + * - `DivergentArrayError`: You attempted to `save()` an array that was modified after you loaded it with a `$elemMatch` or similar projection + * - `MissingSchemaError`: You tried to access a model with [`mongoose.model()`](api.html#mongoose_Mongoose-model) that was not defined + * - `DocumentNotFoundError`: The document you tried to [`save()`](api.html#document_Document-save) was not found + * - `ValidatorError`: error from an individual schema path's validator + * - `ValidationError`: error returned from [`validate()`](api.html#document_Document-validate) or [`validateSync()`](api.html#document_Document-validateSync). Contains zero or more `ValidatorError` instances in `.errors` property. + * - `MissingSchemaError`: You called `mongoose.Document()` without a schema + * - `ObjectExpectedError`: Thrown when you set a nested path to a non-object value with [strict mode set](guide.html#strict). + * - `ObjectParameterError`: Thrown when you pass a non-object value to a function which expects an object as a paramter + * - `OverwriteModelError`: Thrown when you call [`mongoose.model()`](api.html#mongoose_Mongoose-model) to re-define a model that was already defined. + * - `ParallelSaveError`: Thrown when you call [`save()`](api.html#model_Model-save) on a document when the same document instance is already saving. + * - `StrictModeError`: Thrown when you set a path that isn't the schema and [strict mode](guide.html#strict) is set to `throw`. + * - `VersionError`: Thrown when the [document is out of sync](guide.html#versionKey) + * + * @api public + * @property {String} name + * @memberOf Error + * @instance + */ + +/*! + * Module exports. + */ + +module.exports = exports = MongooseError; + +/** + * The default built-in validator error messages. + * + * @see Error.messages #error_messages_MongooseError-messages + * @api public + * @memberOf Error + * @static messages + */ + +MongooseError.messages = require('./messages'); + +// backward compat +MongooseError.Messages = MongooseError.messages; + +/** + * An instance of this error class will be returned when `save()` fails + * because the underlying + * document was not found. The constructor takes one parameter, the + * conditions that mongoose passed to `update()` when trying to update + * the document. + * + * @api public + * @memberOf Error + * @static DocumentNotFoundError + */ + +MongooseError.DocumentNotFoundError = require('./notFound'); + +/** + * An instance of this error class will be returned when mongoose failed to + * cast a value. + * + * @api public + * @memberOf Error + * @static CastError + */ + +MongooseError.CastError = require('./cast'); + +/** + * An instance of this error class will be returned when [validation](/docs/validation.html) failed. + * The `errors` property contains an object whose keys are the paths that failed and whose values are + * instances of CastError or ValidationError. + * + * @api public + * @memberOf Error + * @static ValidationError + */ + +MongooseError.ValidationError = require('./validation'); + +/** + * A `ValidationError` has a hash of `errors` that contain individual + * `ValidatorError` instances. + * + * ####Example: + * + * const schema = Schema({ name: { type: String, required: true } }); + * const Model = mongoose.model('Test', schema); + * const doc = new Model({}); + * + * // Top-level error is a ValidationError, **not** a ValidatorError + * const err = doc.validateSync(); + * err instanceof mongoose.Error.ValidationError; // true + * + * // A ValidationError `err` has 0 or more ValidatorErrors keyed by the + * // path in the `err.errors` property. + * err.errors['name'] instanceof mongoose.Error.ValidatorError; + * + * err.errors['name'].kind; // 'required' + * err.errors['name'].path; // 'name' + * err.errors['name'].value; // undefined + * + * Instances of `ValidatorError` have the following properties: + * + * - `kind`: The validator's `type`, like `'required'` or `'regexp'` + * - `path`: The path that failed validation + * - `value`: The value that failed validation + * + * @api public + * @memberOf Error + * @static ValidatorError + */ + +MongooseError.ValidatorError = require('./validator'); + +/** + * An instance of this error class will be returned when you call `save()` after + * the document in the database was changed in a potentially unsafe way. See + * the [`versionKey` option](/docs/guide.html#versionKey) for more information. + * + * @api public + * @memberOf Error + * @static VersionError + */ + +MongooseError.VersionError = require('./version'); + +/** + * An instance of this error class will be returned when you call `save()` multiple + * times on the same document in parallel. See the [FAQ](/docs/faq.html) for more + * information. + * + * @api public + * @memberOf Error + * @static ParallelSaveError + */ + +MongooseError.ParallelSaveError = require('./parallelSave'); + +/** + * Thrown when a model with the given name was already registered on the connection. + * See [the FAQ about `OverwriteModelError`](/docs/faq.html#overwrite-model-error). + * + * @api public + * @memberOf Error + * @static OverwriteModelError + */ + +MongooseError.OverwriteModelError = require('./overwriteModel'); + +/** + * Thrown when you try to access a model that has not been registered yet + * + * @api public + * @memberOf Error + * @static MissingSchemaError + */ + +MongooseError.MissingSchemaError = require('./missingSchema'); + +/** + * An instance of this error will be returned if you used an array projection + * and then modified the array in an unsafe way. + * + * @api public + * @memberOf Error + * @static DivergentArrayError + */ + +MongooseError.DivergentArrayError = require('./divergentArray'); + +/** + * Thrown when your try to pass values to model contrtuctor that + * were not specified in schema or change immutable properties when + * `strict` mode is `"throw"` + * + * @api public + * @memberOf Error + * @static StrictModeError + */ + +MongooseError.StrictModeError = require('./strict'); diff --git a/node_modules/mongoose/lib/error/messages.js b/node_modules/mongoose/lib/error/messages.js new file mode 100644 index 0000000..ac0294a --- /dev/null +++ b/node_modules/mongoose/lib/error/messages.js @@ -0,0 +1,47 @@ + +/** + * The default built-in validator error messages. These may be customized. + * + * // customize within each schema or globally like so + * const mongoose = require('mongoose'); + * mongoose.Error.messages.String.enum = "Your custom message for {PATH}."; + * + * As you might have noticed, error messages support basic templating + * + * - `{PATH}` is replaced with the invalid document path + * - `{VALUE}` is replaced with the invalid value + * - `{TYPE}` is replaced with the validator type such as "regexp", "min", or "user defined" + * - `{MIN}` is replaced with the declared min value for the Number.min validator + * - `{MAX}` is replaced with the declared max value for the Number.max validator + * + * Click the "show code" link below to see all defaults. + * + * @static messages + * @receiver MongooseError + * @api public + */ + +'use strict'; + +const msg = module.exports = exports = {}; + +msg.DocumentNotFoundError = null; + +msg.general = {}; +msg.general.default = 'Validator failed for path `{PATH}` with value `{VALUE}`'; +msg.general.required = 'Path `{PATH}` is required.'; + +msg.Number = {}; +msg.Number.min = 'Path `{PATH}` ({VALUE}) is less than minimum allowed value ({MIN}).'; +msg.Number.max = 'Path `{PATH}` ({VALUE}) is more than maximum allowed value ({MAX}).'; +msg.Number.enum = '`{VALUE}` is not a valid enum value for path `{PATH}`.'; + +msg.Date = {}; +msg.Date.min = 'Path `{PATH}` ({VALUE}) is before minimum allowed value ({MIN}).'; +msg.Date.max = 'Path `{PATH}` ({VALUE}) is after maximum allowed value ({MAX}).'; + +msg.String = {}; +msg.String.enum = '`{VALUE}` is not a valid enum value for path `{PATH}`.'; +msg.String.match = 'Path `{PATH}` is invalid ({VALUE}).'; +msg.String.minlength = 'Path `{PATH}` (`{VALUE}`) is shorter than the minimum allowed length ({MINLENGTH}).'; +msg.String.maxlength = 'Path `{PATH}` (`{VALUE}`) is longer than the maximum allowed length ({MAXLENGTH}).'; diff --git a/node_modules/mongoose/lib/error/missingSchema.js b/node_modules/mongoose/lib/error/missingSchema.js new file mode 100644 index 0000000..ca306b7 --- /dev/null +++ b/node_modules/mongoose/lib/error/missingSchema.js @@ -0,0 +1,30 @@ + +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + +class MissingSchemaError extends MongooseError { + /*! + * MissingSchema Error constructor. + * @param {String} name + */ + constructor(name) { + const msg = 'Schema hasn\'t been registered for model "' + name + '".\n' + + 'Use mongoose.model(name, schema)'; + super(msg); + } +} + +Object.defineProperty(MissingSchemaError.prototype, 'name', { + value: 'MissingSchemaError' +}); + +/*! + * exports + */ + +module.exports = MissingSchemaError; diff --git a/node_modules/mongoose/lib/error/mongooseError.js b/node_modules/mongoose/lib/error/mongooseError.js new file mode 100644 index 0000000..5505105 --- /dev/null +++ b/node_modules/mongoose/lib/error/mongooseError.js @@ -0,0 +1,13 @@ +'use strict'; + +/*! + * ignore + */ + +class MongooseError extends Error { } + +Object.defineProperty(MongooseError.prototype, 'name', { + value: 'MongooseError' +}); + +module.exports = MongooseError; diff --git a/node_modules/mongoose/lib/error/notFound.js b/node_modules/mongoose/lib/error/notFound.js new file mode 100644 index 0000000..0e8386b --- /dev/null +++ b/node_modules/mongoose/lib/error/notFound.js @@ -0,0 +1,44 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const MongooseError = require('./'); +const util = require('util'); + +class DocumentNotFoundError extends MongooseError { + /*! + * OverwriteModel Error constructor. + */ + constructor(filter, model, numAffected, result) { + let msg; + const messages = MongooseError.messages; + if (messages.DocumentNotFoundError != null) { + msg = typeof messages.DocumentNotFoundError === 'function' ? + messages.DocumentNotFoundError(filter, model) : + messages.DocumentNotFoundError; + } else { + msg = 'No document found for query "' + util.inspect(filter) + + '" on model "' + model + '"'; + } + + super(msg); + + this.result = result; + this.numAffected = numAffected; + this.filter = filter; + // Backwards compat + this.query = filter; + } +} + +Object.defineProperty(DocumentNotFoundError.prototype, 'name', { + value: 'DocumentNotFoundError' +}); + +/*! + * exports + */ + +module.exports = DocumentNotFoundError; diff --git a/node_modules/mongoose/lib/error/objectExpected.js b/node_modules/mongoose/lib/error/objectExpected.js new file mode 100644 index 0000000..3b02bb4 --- /dev/null +++ b/node_modules/mongoose/lib/error/objectExpected.js @@ -0,0 +1,30 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + + +class ObjectExpectedError extends MongooseError { + /** + * Strict mode error constructor + * + * @param {string} type + * @param {string} value + * @api private + */ + constructor(path, val) { + const typeDescription = Array.isArray(val) ? 'array' : 'primitive value'; + super('Tried to set nested object field `' + path + + `\` to ${typeDescription} \`` + val + '` and strict mode is set to throw.'); + this.path = path; + } +} + +Object.defineProperty(ObjectExpectedError.prototype, 'name', { + value: 'ObjectExpectedError' +}); + +module.exports = ObjectExpectedError; diff --git a/node_modules/mongoose/lib/error/objectParameter.js b/node_modules/mongoose/lib/error/objectParameter.js new file mode 100644 index 0000000..5881a48 --- /dev/null +++ b/node_modules/mongoose/lib/error/objectParameter.js @@ -0,0 +1,30 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + +class ObjectParameterError extends MongooseError { + /** + * Constructor for errors that happen when a parameter that's expected to be + * an object isn't an object + * + * @param {Any} value + * @param {String} paramName + * @param {String} fnName + * @api private + */ + constructor(value, paramName, fnName) { + super('Parameter "' + paramName + '" to ' + fnName + + '() must be an object, got ' + value.toString()); + } +} + + +Object.defineProperty(ObjectParameterError.prototype, 'name', { + value: 'ObjectParameterError' +}); + +module.exports = ObjectParameterError; diff --git a/node_modules/mongoose/lib/error/overwriteModel.js b/node_modules/mongoose/lib/error/overwriteModel.js new file mode 100644 index 0000000..d509e19 --- /dev/null +++ b/node_modules/mongoose/lib/error/overwriteModel.js @@ -0,0 +1,29 @@ + +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + + +class OverwriteModelError extends MongooseError { + /*! + * OverwriteModel Error constructor. + * @param {String} name + */ + constructor(name) { + super('Cannot overwrite `' + name + '` model once compiled.'); + } +} + +Object.defineProperty(OverwriteModelError.prototype, 'name', { + value: 'OverwriteModelError' +}); + +/*! + * exports + */ + +module.exports = OverwriteModelError; diff --git a/node_modules/mongoose/lib/error/parallelSave.js b/node_modules/mongoose/lib/error/parallelSave.js new file mode 100644 index 0000000..e062857 --- /dev/null +++ b/node_modules/mongoose/lib/error/parallelSave.js @@ -0,0 +1,30 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const MongooseError = require('./'); + +class ParallelSaveError extends MongooseError { + /** + * ParallelSave Error constructor. + * + * @param {Document} doc + * @api private + */ + constructor(doc) { + const msg = 'Can\'t save() the same doc multiple times in parallel. Document: '; + super(msg + doc._id); + } +} + +Object.defineProperty(ParallelSaveError.prototype, 'name', { + value: 'ParallelSaveError' +}); + +/*! + * exports + */ + +module.exports = ParallelSaveError; diff --git a/node_modules/mongoose/lib/error/parallelValidate.js b/node_modules/mongoose/lib/error/parallelValidate.js new file mode 100644 index 0000000..18697b6 --- /dev/null +++ b/node_modules/mongoose/lib/error/parallelValidate.js @@ -0,0 +1,31 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const MongooseError = require('./mongooseError'); + + +class ParallelValidateError extends MongooseError { + /** + * ParallelValidate Error constructor. + * + * @param {Document} doc + * @api private + */ + constructor(doc) { + const msg = 'Can\'t validate() the same doc multiple times in parallel. Document: '; + super(msg + doc._id); + } +} + +Object.defineProperty(ParallelValidateError.prototype, 'name', { + value: 'ParallelValidateError' +}); + +/*! + * exports + */ + +module.exports = ParallelValidateError;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/error/serverSelection.js b/node_modules/mongoose/lib/error/serverSelection.js new file mode 100644 index 0000000..162e2fc --- /dev/null +++ b/node_modules/mongoose/lib/error/serverSelection.js @@ -0,0 +1,61 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./mongooseError'); +const allServersUnknown = require('../helpers/topology/allServersUnknown'); +const isAtlas = require('../helpers/topology/isAtlas'); +const isSSLError = require('../helpers/topology/isSSLError'); + +/*! + * ignore + */ + +const atlasMessage = 'Could not connect to any servers in your MongoDB Atlas cluster. ' + + 'One common reason is that you\'re trying to access the database from ' + + 'an IP that isn\'t whitelisted. Make sure your current IP address is on your Atlas ' + + 'cluster\'s IP whitelist: https://docs.atlas.mongodb.com/security-whitelist/'; + +const sslMessage = 'Mongoose is connecting with SSL enabled, but the server is ' + + 'not accepting SSL connections. Please ensure that the MongoDB server you are ' + + 'connecting to is configured to accept SSL connections. Learn more: ' + + 'https://mongoosejs.com/docs/tutorials/ssl.html'; + +class MongooseServerSelectionError extends MongooseError { + /** + * MongooseServerSelectionError constructor + * + * @api private + */ + assimilateError(err) { + const reason = err.reason; + // Special message for a case that is likely due to IP whitelisting issues. + const isAtlasWhitelistError = isAtlas(reason) && + allServersUnknown(reason) && + err.message.indexOf('bad auth') === -1 && + err.message.indexOf('Authentication failed') === -1; + + if (isAtlasWhitelistError) { + this.message = atlasMessage; + } else if (isSSLError(reason)) { + this.message = sslMessage; + } else { + this.message = err.message; + } + for (const key in err) { + if (key !== 'name') { + this[key] = err[key]; + } + } + + return this; + } +} + +Object.defineProperty(MongooseServerSelectionError.prototype, 'name', { + value: 'MongooseServerSelectionError' +}); + +module.exports = MongooseServerSelectionError; diff --git a/node_modules/mongoose/lib/error/strict.js b/node_modules/mongoose/lib/error/strict.js new file mode 100644 index 0000000..393ca6e --- /dev/null +++ b/node_modules/mongoose/lib/error/strict.js @@ -0,0 +1,33 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + + +class StrictModeError extends MongooseError { + /** + * Strict mode error constructor + * + * @param {String} path + * @param {String} [msg] + * @param {Boolean} [immutable] + * @inherits MongooseError + * @api private + */ + constructor(path, msg, immutable) { + msg = msg || 'Field `' + path + '` is not in schema and strict ' + + 'mode is set to throw.'; + super(msg); + this.isImmutableError = !!immutable; + this.path = path; + } +} + +Object.defineProperty(StrictModeError.prototype, 'name', { + value: 'StrictModeError' +}); + +module.exports = StrictModeError; diff --git a/node_modules/mongoose/lib/error/validation.js b/node_modules/mongoose/lib/error/validation.js new file mode 100644 index 0000000..ccae07a --- /dev/null +++ b/node_modules/mongoose/lib/error/validation.js @@ -0,0 +1,110 @@ +/*! + * Module requirements + */ + +'use strict'; + +const MongooseError = require('./mongooseError'); +const util = require('util'); + +class ValidationError extends MongooseError { + /** + * Document Validation Error + * + * @api private + * @param {Document} [instance] + * @inherits MongooseError + */ + constructor(instance) { + let _message; + if (instance && instance.constructor.name === 'model') { + _message = instance.constructor.modelName + ' validation failed'; + } else { + _message = 'Validation failed'; + } + + super(_message); + + this.errors = {}; + this._message = _message; + + if (instance) { + instance.errors = this.errors; + } + } + + /** + * Console.log helper + */ + toString() { + return this.name + ': ' + _generateMessage(this); + } + + /*! + * inspect helper + */ + inspect() { + return Object.assign(new Error(this.message), this); + } + + /*! + * add message + */ + addError(path, error) { + this.errors[path] = error; + this.message = this._message + ': ' + _generateMessage(this); + } +} + + +if (util.inspect.custom) { + /*! + * Avoid Node deprecation warning DEP0079 + */ + + ValidationError.prototype[util.inspect.custom] = ValidationError.prototype.inspect; +} + +/*! + * Helper for JSON.stringify + */ +Object.defineProperty(ValidationError.prototype, 'toJSON', { + enumerable: false, + writable: false, + configurable: true, + value: function() { + return Object.assign({}, this, { message: this.message }); + } +}); + + +Object.defineProperty(ValidationError.prototype, 'name', { + value: 'ValidationError' +}); + +/*! + * ignore + */ + +function _generateMessage(err) { + const keys = Object.keys(err.errors || {}); + const len = keys.length; + const msgs = []; + let key; + + for (let i = 0; i < len; ++i) { + key = keys[i]; + if (err === err.errors[key]) { + continue; + } + msgs.push(key + ': ' + err.errors[key].message); + } + + return msgs.join(', '); +} + +/*! + * Module exports + */ + +module.exports = ValidationError; diff --git a/node_modules/mongoose/lib/error/validator.js b/node_modules/mongoose/lib/error/validator.js new file mode 100644 index 0000000..f880e2b --- /dev/null +++ b/node_modules/mongoose/lib/error/validator.js @@ -0,0 +1,94 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseError = require('./'); + + +class ValidatorError extends MongooseError { + /** + * Schema validator error + * + * @param {Object} properties + * @api private + */ + constructor(properties) { + let msg = properties.message; + if (!msg) { + msg = MongooseError.messages.general.default; + } + + const message = formatMessage(msg, properties); + super(message); + + properties = Object.assign({}, properties, { message: message }); + this.properties = properties; + this.kind = properties.type; + this.path = properties.path; + this.value = properties.value; + this.reason = properties.reason; + } + + /*! + * toString helper + * TODO remove? This defaults to `${this.name}: ${this.message}` + */ + toString() { + return this.message; + } + + /*! + * Ensure `name` and `message` show up in toJSON output re: gh-9296 + */ + + toJSON() { + return Object.assign({ name: this.name, message: this.message }, this); + } +} + + +Object.defineProperty(ValidatorError.prototype, 'name', { + value: 'ValidatorError' +}); + +/*! + * The object used to define this validator. Not enumerable to hide + * it from `require('util').inspect()` output re: gh-3925 + */ + +Object.defineProperty(ValidatorError.prototype, 'properties', { + enumerable: false, + writable: true, + value: null +}); + +// Exposed for testing +ValidatorError.prototype.formatMessage = formatMessage; + +/*! + * Formats error messages + */ + +function formatMessage(msg, properties) { + if (typeof msg === 'function') { + return msg(properties); + } + + const propertyNames = Object.keys(properties); + for (const propertyName of propertyNames) { + if (propertyName === 'message') { + continue; + } + msg = msg.replace('{' + propertyName.toUpperCase() + '}', properties[propertyName]); + } + + return msg; +} + +/*! + * exports + */ + +module.exports = ValidatorError; diff --git a/node_modules/mongoose/lib/error/version.js b/node_modules/mongoose/lib/error/version.js new file mode 100644 index 0000000..b357fb1 --- /dev/null +++ b/node_modules/mongoose/lib/error/version.js @@ -0,0 +1,36 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const MongooseError = require('./'); + +class VersionError extends MongooseError { + /** + * Version Error constructor. + * + * @param {Document} doc + * @param {Number} currentVersion + * @param {Array<String>} modifiedPaths + * @api private + */ + constructor(doc, currentVersion, modifiedPaths) { + const modifiedPathsStr = modifiedPaths.join(', '); + super('No matching document found for id "' + doc._id + + '" version ' + currentVersion + ' modifiedPaths "' + modifiedPathsStr + '"'); + this.version = currentVersion; + this.modifiedPaths = modifiedPaths; + } +} + + +Object.defineProperty(VersionError.prototype, 'name', { + value: 'VersionError' +}); + +/*! + * exports + */ + +module.exports = VersionError; diff --git a/node_modules/mongoose/lib/helpers/aggregate/stringifyAccumulatorOptions.js b/node_modules/mongoose/lib/helpers/aggregate/stringifyAccumulatorOptions.js new file mode 100644 index 0000000..7362514 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/aggregate/stringifyAccumulatorOptions.js @@ -0,0 +1,38 @@ +'use strict'; + +module.exports = function stringifyAccumulatorOptions(pipeline) { + if (!Array.isArray(pipeline)) { + return; + } + + for (const stage of pipeline) { + if (stage == null) { + continue; + } + + const canHaveAccumulator = stage.$group || stage.$bucket || stage.$bucketAuto; + if (canHaveAccumulator != null) { + for (const key of Object.keys(canHaveAccumulator)) { + handleAccumulator(canHaveAccumulator[key]); + } + } + + if (stage.$facet != null) { + for (const key of Object.keys(stage.$facet)) { + stringifyAccumulatorOptions(stage.$facet[key]); + } + } + } +}; + +function handleAccumulator(operator) { + if (operator == null || operator.$accumulator == null) { + return; + } + + for (const key of ['init', 'accumulate', 'merge', 'finalize']) { + if (typeof operator.$accumulator[key] === 'function') { + operator.$accumulator[key] = String(operator.$accumulator[key]); + } + } +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/arrayDepth.js b/node_modules/mongoose/lib/helpers/arrayDepth.js new file mode 100644 index 0000000..2c6f2e5 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/arrayDepth.js @@ -0,0 +1,30 @@ +'use strict'; + +module.exports = arrayDepth; + +function arrayDepth(arr) { + if (!Array.isArray(arr)) { + return { min: 0, max: 0, containsNonArrayItem: true }; + } + if (arr.length === 0) { + return { min: 1, max: 1, containsNonArrayItem: false }; + } + + const res = arrayDepth(arr[0]); + + for (let i = 1; i < arr.length; ++i) { + const _res = arrayDepth(arr[i]); + if (_res.min < res.min) { + res.min = _res.min; + } + if (_res.max > res.max) { + res.max = _res.max; + } + res.containsNonArrayItem = res.containsNonArrayItem || _res.containsNonArrayItem; + } + + res.min = res.min + 1; + res.max = res.max + 1; + + return res; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/clone.js b/node_modules/mongoose/lib/helpers/clone.js new file mode 100644 index 0000000..5f0b2c9 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/clone.js @@ -0,0 +1,143 @@ +'use strict'; + + +const cloneRegExp = require('regexp-clone'); +const Decimal = require('../types/decimal128'); +const ObjectId = require('../types/objectid'); +const specialProperties = require('./specialProperties'); +const isMongooseObject = require('./isMongooseObject'); +const getFunctionName = require('./getFunctionName'); +const isBsonType = require('./isBsonType'); +const isObject = require('./isObject'); +const symbols = require('./symbols'); +const utils = require('../utils'); + + +/*! + * Object clone with Mongoose natives support. + * + * If options.minimize is true, creates a minimal data object. Empty objects and undefined values will not be cloned. This makes the data payload sent to MongoDB as small as possible. + * + * Functions are never cloned. + * + * @param {Object} obj the object to clone + * @param {Object} options + * @param {Boolean} isArrayChild true if cloning immediately underneath an array. Special case for minimize. + * @return {Object} the cloned object + * @api private + */ + +function clone(obj, options, isArrayChild) { + if (obj == null) { + return obj; + } + + if (Array.isArray(obj)) { + return cloneArray(obj, options); + } + + if (isMongooseObject(obj)) { + // Single nested subdocs should apply getters later in `applyGetters()` + // when calling `toObject()`. See gh-7442, gh-8295 + if (options && options._skipSingleNestedGetters && obj.$isSingleNested) { + options = Object.assign({}, options, { getters: false }); + } + + if (utils.isPOJO(obj) && obj.$__ != null && obj._doc != null) { + return obj._doc; + } + + if (options && options.json && typeof obj.toJSON === 'function') { + return obj.toJSON(options); + } + return obj.toObject(options); + } + + if (obj.constructor) { + switch (getFunctionName(obj.constructor)) { + case 'Object': + return cloneObject(obj, options, isArrayChild); + case 'Date': + return new obj.constructor(+obj); + case 'RegExp': + return cloneRegExp(obj); + default: + // ignore + break; + } + } + + if (obj instanceof ObjectId) { + return new ObjectId(obj.id); + } + + if (isBsonType(obj, 'Decimal128')) { + if (options && options.flattenDecimals) { + return obj.toJSON(); + } + return Decimal.fromString(obj.toString()); + } + + if (!obj.constructor && isObject(obj)) { + // object created with Object.create(null) + return cloneObject(obj, options, isArrayChild); + } + + if (obj[symbols.schemaTypeSymbol]) { + return obj.clone(); + } + + // If we're cloning this object to go into a MongoDB command, + // and there's a `toBSON()` function, assume this object will be + // stored as a primitive in MongoDB and doesn't need to be cloned. + if (options && options.bson && typeof obj.toBSON === 'function') { + return obj; + } + + if (obj.valueOf != null) { + return obj.valueOf(); + } + + return cloneObject(obj, options, isArrayChild); +} +module.exports = clone; + +/*! + * ignore + */ + +function cloneObject(obj, options, isArrayChild) { + const minimize = options && options.minimize; + const ret = {}; + let hasKeys; + + for (const k in obj) { + if (specialProperties.has(k)) { + continue; + } + + // Don't pass `isArrayChild` down + const val = clone(obj[k], options); + + if (!minimize || (typeof val !== 'undefined')) { + if (minimize === false && typeof val === 'undefined') { + delete ret[k]; + } else { + hasKeys || (hasKeys = true); + ret[k] = val; + } + } + } + + return minimize && !isArrayChild ? hasKeys && ret : ret; +} + +function cloneArray(arr, options) { + const ret = []; + + for (const item of arr) { + ret.push(clone(item, options, true)); + } + + return ret; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/common.js b/node_modules/mongoose/lib/helpers/common.js new file mode 100644 index 0000000..ed7dc42 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/common.js @@ -0,0 +1,106 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const Binary = require('../driver').get().Binary; +const Decimal128 = require('../types/decimal128'); +const ObjectId = require('../types/objectid'); +const isMongooseObject = require('./isMongooseObject'); + +exports.flatten = flatten; +exports.modifiedPaths = modifiedPaths; + +/*! + * ignore + */ + +function flatten(update, path, options, schema) { + let keys; + if (update && isMongooseObject(update) && !Buffer.isBuffer(update)) { + keys = Object.keys(update.toObject({ transform: false, virtuals: false })); + } else { + keys = Object.keys(update || {}); + } + + const numKeys = keys.length; + const result = {}; + path = path ? path + '.' : ''; + + for (let i = 0; i < numKeys; ++i) { + const key = keys[i]; + const val = update[key]; + result[path + key] = val; + + // Avoid going into mixed paths if schema is specified + const keySchema = schema && schema.path && schema.path(path + key); + const isNested = schema && schema.nested && schema.nested[path + key]; + if (keySchema && keySchema.instance === 'Mixed') continue; + + if (shouldFlatten(val)) { + if (options && options.skipArrays && Array.isArray(val)) { + continue; + } + const flat = flatten(val, path + key, options, schema); + for (const k in flat) { + result[k] = flat[k]; + } + if (Array.isArray(val)) { + result[path + key] = val; + } + } + + if (isNested) { + const paths = Object.keys(schema.paths); + for (const p of paths) { + if (p.startsWith(path + key + '.') && !result.hasOwnProperty(p)) { + result[p] = void 0; + } + } + } + } + + return result; +} + +/*! + * ignore + */ + +function modifiedPaths(update, path, result) { + const keys = Object.keys(update || {}); + const numKeys = keys.length; + result = result || {}; + path = path ? path + '.' : ''; + + for (let i = 0; i < numKeys; ++i) { + const key = keys[i]; + let val = update[key]; + + result[path + key] = true; + if (isMongooseObject(val) && !Buffer.isBuffer(val)) { + val = val.toObject({ transform: false, virtuals: false }); + } + if (shouldFlatten(val)) { + modifiedPaths(val, path + key, result); + } + } + + return result; +} + +/*! + * ignore + */ + +function shouldFlatten(val) { + return val && + typeof val === 'object' && + !(val instanceof Date) && + !(val instanceof ObjectId) && + (!Array.isArray(val) || val.length > 0) && + !(val instanceof Buffer) && + !(val instanceof Decimal128) && + !(val instanceof Binary); +} diff --git a/node_modules/mongoose/lib/helpers/cursor/eachAsync.js b/node_modules/mongoose/lib/helpers/cursor/eachAsync.js new file mode 100644 index 0000000..4afff03 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/cursor/eachAsync.js @@ -0,0 +1,121 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const promiseOrCallback = require('../promiseOrCallback'); + +/** + * Execute `fn` for every document in the cursor. If `fn` returns a promise, + * will wait for the promise to resolve before iterating on to the next one. + * Returns a promise that resolves when done. + * + * @param {Function} next the thunk to call to get the next document + * @param {Function} fn + * @param {Object} options + * @param {Function} [callback] executed when all docs have been processed + * @return {Promise} + * @api public + * @method eachAsync + */ + +module.exports = function eachAsync(next, fn, options, callback) { + const parallel = options.parallel || 1; + const enqueue = asyncQueue(); + + return promiseOrCallback(callback, cb => { + iterate(cb); + }); + + function iterate(finalCallback) { + let drained = false; + let handleResultsInProgress = 0; + let currentDocumentIndex = 0; + + let error = null; + for (let i = 0; i < parallel; ++i) { + enqueue(fetch); + } + + function fetch(done) { + if (drained || error) { + return done(); + } + + next(function(err, doc) { + if (drained || error != null) { + return done(); + } + if (err != null) { + error = err; + finalCallback(err); + return done(); + } + if (doc == null) { + drained = true; + if (handleResultsInProgress <= 0) { + finalCallback(null); + } + return done(); + } + + ++handleResultsInProgress; + + // Kick off the subsequent `next()` before handling the result, but + // make sure we know that we still have a result to handle re: #8422 + process.nextTick(() => done()); + + handleNextResult(doc, currentDocumentIndex++, function(err) { + --handleResultsInProgress; + if (err != null) { + error = err; + return finalCallback(err); + } + if (drained && handleResultsInProgress <= 0) { + return finalCallback(null); + } + + setTimeout(() => enqueue(fetch), 0); + }); + }); + } + } + + function handleNextResult(doc, i, callback) { + const promise = fn(doc, i); + if (promise && typeof promise.then === 'function') { + promise.then( + function() { callback(null); }, + function(error) { callback(error || new Error('`eachAsync()` promise rejected without error')); }); + } else { + callback(null); + } + } +}; + +// `next()` can only execute one at a time, so make sure we always execute +// `next()` in series, while still allowing multiple `fn()` instances to run +// in parallel. +function asyncQueue() { + const _queue = []; + let inProgress = null; + let id = 0; + + return function enqueue(fn) { + if (_queue.length === 0 && inProgress == null) { + inProgress = id++; + return fn(_step); + } + _queue.push(fn); + }; + + function _step() { + inProgress = null; + if (_queue.length > 0) { + inProgress = id++; + const fn = _queue.shift(); + fn(_step); + } + } +} diff --git a/node_modules/mongoose/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js b/node_modules/mongoose/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js new file mode 100644 index 0000000..755de88 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = function checkEmbeddedDiscriminatorKeyProjection(userProjection, path, schema, selected, addedPaths) { + const userProjectedInPath = Object.keys(userProjection). + reduce((cur, key) => cur || key.startsWith(path + '.'), false); + const _discriminatorKey = path + '.' + schema.options.discriminatorKey; + if (!userProjectedInPath && + addedPaths.length === 1 && + addedPaths[0] === _discriminatorKey) { + selected.splice(selected.indexOf(_discriminatorKey), 1); + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/discriminator/getConstructor.js b/node_modules/mongoose/lib/helpers/discriminator/getConstructor.js new file mode 100644 index 0000000..04a3ded --- /dev/null +++ b/node_modules/mongoose/lib/helpers/discriminator/getConstructor.js @@ -0,0 +1,25 @@ +'use strict'; + +const getDiscriminatorByValue = require('./getDiscriminatorByValue'); + +/*! + * Find the correct constructor, taking into account discriminators + */ + +module.exports = function getConstructor(Constructor, value) { + const discriminatorKey = Constructor.schema.options.discriminatorKey; + if (value != null && + Constructor.discriminators && + value[discriminatorKey] != null) { + if (Constructor.discriminators[value[discriminatorKey]]) { + Constructor = Constructor.discriminators[value[discriminatorKey]]; + } else { + const constructorByValue = getDiscriminatorByValue(Constructor, value[discriminatorKey]); + if (constructorByValue) { + Constructor = constructorByValue; + } + } + } + + return Constructor; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/discriminator/getDiscriminatorByValue.js b/node_modules/mongoose/lib/helpers/discriminator/getDiscriminatorByValue.js new file mode 100644 index 0000000..a107a91 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/discriminator/getDiscriminatorByValue.js @@ -0,0 +1,27 @@ +'use strict'; + +/*! +* returns discriminator by discriminatorMapping.value +* +* @param {Model} model +* @param {string} value +*/ + +module.exports = function getDiscriminatorByValue(model, value) { + let discriminator = null; + if (!model.discriminators) { + return discriminator; + } + for (const name in model.discriminators) { + const it = model.discriminators[name]; + if ( + it.schema && + it.schema.discriminatorMapping && + it.schema.discriminatorMapping.value == value + ) { + discriminator = it; + break; + } + } + return discriminator; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js b/node_modules/mongoose/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js new file mode 100644 index 0000000..f3e71a0 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/discriminator/getSchemaDiscriminatorByValue.js @@ -0,0 +1,24 @@ +'use strict'; + +/*! +* returns discriminator by discriminatorMapping.value +* +* @param {Schema} schema +* @param {string} value +*/ + +module.exports = function getSchemaDiscriminatorByValue(schema, value) { + if (schema == null || schema.discriminators == null) { + return null; + } + for (const key of Object.keys(schema.discriminators)) { + const discriminatorSchema = schema.discriminators[key]; + if (discriminatorSchema.discriminatorMapping == null) { + continue; + } + if (discriminatorSchema.discriminatorMapping.value === value) { + return discriminatorSchema; + } + } + return null; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/document/cleanModifiedSubpaths.js b/node_modules/mongoose/lib/helpers/document/cleanModifiedSubpaths.js new file mode 100644 index 0000000..252d348 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/document/cleanModifiedSubpaths.js @@ -0,0 +1,28 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = function cleanModifiedSubpaths(doc, path, options) { + options = options || {}; + const skipDocArrays = options.skipDocArrays; + + let deleted = 0; + if (!doc) { + return deleted; + } + for (const modifiedPath of Object.keys(doc.$__.activePaths.states.modify)) { + if (skipDocArrays) { + const schemaType = doc.schema.path(modifiedPath); + if (schemaType && schemaType.$isMongooseDocumentArray) { + continue; + } + } + if (modifiedPath.startsWith(path + '.')) { + delete doc.$__.activePaths.states.modify[modifiedPath]; + ++deleted; + } + } + return deleted; +}; diff --git a/node_modules/mongoose/lib/helpers/document/compile.js b/node_modules/mongoose/lib/helpers/document/compile.js new file mode 100644 index 0000000..def45e6 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/document/compile.js @@ -0,0 +1,204 @@ +'use strict'; + +const documentSchemaSymbol = require('../../helpers/symbols').documentSchemaSymbol; +const get = require('../../helpers/get'); +const internalToObjectOptions = require('../../options').internalToObjectOptions; +const utils = require('../../utils'); + +let Document; +const getSymbol = require('../../helpers/symbols').getSymbol; +const scopeSymbol = require('../../helpers/symbols').scopeSymbol; + +/*! + * exports + */ + +exports.compile = compile; +exports.defineKey = defineKey; + +/*! + * Compiles schemas. + */ + +function compile(tree, proto, prefix, options) { + Document = Document || require('../../document'); + const keys = Object.keys(tree); + const len = keys.length; + let limb; + let key; + + for (let i = 0; i < len; ++i) { + key = keys[i]; + limb = tree[key]; + + const hasSubprops = utils.isPOJO(limb) && Object.keys(limb).length && + (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type)); + const subprops = hasSubprops ? limb : null; + + defineKey(key, subprops, proto, prefix, keys, options); + } +} + +/*! + * Defines the accessor named prop on the incoming prototype. + */ + +function defineKey(prop, subprops, prototype, prefix, keys, options) { + Document = Document || require('../../document'); + const path = (prefix ? prefix + '.' : '') + prop; + prefix = prefix || ''; + + if (subprops) { + Object.defineProperty(prototype, prop, { + enumerable: true, + configurable: true, + get: function() { + const _this = this; + if (!this.$__.getters) { + this.$__.getters = {}; + } + + if (!this.$__.getters[path]) { + const nested = Object.create(Document.prototype, getOwnPropertyDescriptors(this)); + + // save scope for nested getters/setters + if (!prefix) { + nested.$__[scopeSymbol] = this; + } + nested.$__.nestedPath = path; + + Object.defineProperty(nested, 'schema', { + enumerable: false, + configurable: true, + writable: false, + value: prototype.schema + }); + + Object.defineProperty(nested, documentSchemaSymbol, { + enumerable: false, + configurable: true, + writable: false, + value: prototype.schema + }); + + Object.defineProperty(nested, 'toObject', { + enumerable: false, + configurable: true, + writable: false, + value: function() { + return utils.clone(_this.get(path, null, { + virtuals: get(this, 'schema.options.toObject.virtuals', null) + })); + } + }); + + Object.defineProperty(nested, '$__get', { + enumerable: false, + configurable: true, + writable: false, + value: function() { + return _this.get(path, null, { + virtuals: get(this, 'schema.options.toObject.virtuals', null) + }); + } + }); + + Object.defineProperty(nested, 'toJSON', { + enumerable: false, + configurable: true, + writable: false, + value: function() { + return _this.get(path, null, { + virtuals: get(_this, 'schema.options.toJSON.virtuals', null) + }); + } + }); + + Object.defineProperty(nested, '$__isNested', { + enumerable: false, + configurable: true, + writable: false, + value: true + }); + + const _isEmptyOptions = Object.freeze({ + minimize: true, + virtuals: false, + getters: false, + transform: false + }); + Object.defineProperty(nested, '$isEmpty', { + enumerable: false, + configurable: true, + writable: false, + value: function() { + return Object.keys(this.get(path, null, _isEmptyOptions) || {}).length === 0; + } + }); + + Object.defineProperty(nested, '$__parent', { + enumerable: false, + configurable: true, + writable: false, + value: this + }); + + compile(subprops, nested, path, options); + this.$__.getters[path] = nested; + } + + return this.$__.getters[path]; + }, + set: function(v) { + if (v != null && v.$__isNested) { + // Convert top-level to POJO, but leave subdocs hydrated so `$set` + // can handle them. See gh-9293. + v = v.$__get(); + } else if (v instanceof Document && !v.$__isNested) { + v = v.toObject(internalToObjectOptions); + } + const doc = this.$__[scopeSymbol] || this; + doc.$set(path, v); + } + }); + } else { + Object.defineProperty(prototype, prop, { + enumerable: true, + configurable: true, + get: function() { + return this[getSymbol].call(this.$__[scopeSymbol] || this, path); + }, + set: function(v) { + this.$set.call(this.$__[scopeSymbol] || this, path, v); + } + }); + } +} + +// gets descriptors for all properties of `object` +// makes all properties non-enumerable to match previous behavior to #2211 +function getOwnPropertyDescriptors(object) { + const result = {}; + + Object.getOwnPropertyNames(object).forEach(function(key) { + result[key] = Object.getOwnPropertyDescriptor(object, key); + // Assume these are schema paths, ignore them re: #5470 + if (result[key].get) { + delete result[key]; + return; + } + result[key].enumerable = [ + 'isNew', + '$__', + 'errors', + '_doc', + '$locals', + '$op', + '__parentArray', + '__index', + '$isDocumentArrayElement' + ].indexOf(key) === -1; + }); + + return result; +} diff --git a/node_modules/mongoose/lib/helpers/document/getEmbeddedDiscriminatorPath.js b/node_modules/mongoose/lib/helpers/document/getEmbeddedDiscriminatorPath.js new file mode 100644 index 0000000..a2784e3 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/document/getEmbeddedDiscriminatorPath.js @@ -0,0 +1,43 @@ +'use strict'; + +const get = require('../get'); + +/*! + * Like `schema.path()`, except with a document, because impossible to + * determine path type without knowing the embedded discriminator key. + */ + +module.exports = function getEmbeddedDiscriminatorPath(doc, path, options) { + options = options || {}; + const typeOnly = options.typeOnly; + const parts = path.split('.'); + let schema = null; + let type = 'adhocOrUndefined'; + + for (let i = 0; i < parts.length; ++i) { + const subpath = parts.slice(0, i + 1).join('.'); + schema = doc.schema.path(subpath); + if (schema == null) { + type = 'adhocOrUndefined'; + continue; + } + if (schema.instance === 'Mixed') { + return typeOnly ? 'real' : schema; + } + type = doc.schema.pathType(subpath); + if ((schema.$isSingleNested || schema.$isMongooseDocumentArrayElement) && + schema.schema.discriminators != null) { + const discriminators = schema.schema.discriminators; + const discriminatorKey = doc.get(subpath + '.' + + get(schema, 'schema.options.discriminatorKey')); + if (discriminatorKey == null || discriminators[discriminatorKey] == null) { + continue; + } + const rest = parts.slice(i + 1).join('.'); + return getEmbeddedDiscriminatorPath(doc.get(subpath), rest, options); + } + } + + // Are we getting the whole schema or just the type, 'real', 'nested', etc. + return typeOnly ? type : schema; +}; diff --git a/node_modules/mongoose/lib/helpers/document/handleSpreadDoc.js b/node_modules/mongoose/lib/helpers/document/handleSpreadDoc.js new file mode 100644 index 0000000..d3075e4 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/document/handleSpreadDoc.js @@ -0,0 +1,17 @@ +'use strict'; + +const utils = require('../../utils'); + +/** + * Using spread operator on a Mongoose document gives you a + * POJO that has a tendency to cause infinite recursion. So + * we use this function on `set()` to prevent that. + */ + +module.exports = function handleSpreadDoc(v) { + if (utils.isPOJO(v) && v.$__ != null && v._doc != null) { + return v._doc; + } + + return v; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/each.js b/node_modules/mongoose/lib/helpers/each.js new file mode 100644 index 0000000..fe70069 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/each.js @@ -0,0 +1,25 @@ +'use strict'; + +module.exports = function each(arr, cb, done) { + if (arr.length === 0) { + return done(); + } + + let remaining = arr.length; + let err = null; + for (const v of arr) { + cb(v, function(_err) { + if (err != null) { + return; + } + if (_err != null) { + err = _err; + return done(err); + } + + if (--remaining <= 0) { + return done(); + } + }); + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/get.js b/node_modules/mongoose/lib/helpers/get.js new file mode 100644 index 0000000..dcb3881 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/get.js @@ -0,0 +1,39 @@ +'use strict'; + +/*! + * Simplified lodash.get to work around the annoying null quirk. See: + * https://github.com/lodash/lodash/issues/3659 + */ + +module.exports = function get(obj, path, def) { + const parts = path.split('.'); + let rest = path; + let cur = obj; + for (const part of parts) { + if (cur == null) { + return def; + } + + // `lib/cast.js` depends on being able to get dotted paths in updates, + // like `{ $set: { 'a.b': 42 } }` + if (cur[rest] != null) { + return cur[rest]; + } + + cur = getProperty(cur, part); + + rest = rest.substr(part.length + 1); + } + + return cur == null ? def : cur; +}; + +function getProperty(obj, prop) { + if (obj == null) { + return obj; + } + if (obj instanceof Map) { + return obj.get(prop); + } + return obj[prop]; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/getDefaultBulkwriteResult.js b/node_modules/mongoose/lib/helpers/getDefaultBulkwriteResult.js new file mode 100644 index 0000000..7d10f17 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/getDefaultBulkwriteResult.js @@ -0,0 +1,27 @@ +'use strict'; +function getDefaultBulkwriteResult() { + return { + result: { + ok: 1, + writeErrors: [], + writeConcernErrors: [], + insertedIds: [], + nInserted: 0, + nUpserted: 0, + nMatched: 0, + nModified: 0, + nRemoved: 0, + upserted: [] + }, + insertedCount: 0, + matchedCount: 0, + modifiedCount: 0, + deletedCount: 0, + upsertedCount: 0, + upsertedIds: {}, + insertedIds: {}, + n: 0 + }; +} + +module.exports = getDefaultBulkwriteResult;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/getFunctionName.js b/node_modules/mongoose/lib/helpers/getFunctionName.js new file mode 100644 index 0000000..87a2c69 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/getFunctionName.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(fn) { + if (fn.name) { + return fn.name; + } + return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1]; +}; diff --git a/node_modules/mongoose/lib/helpers/immediate.js b/node_modules/mongoose/lib/helpers/immediate.js new file mode 100644 index 0000000..ddb7060 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/immediate.js @@ -0,0 +1,12 @@ +/*! + * Centralize this so we can more easily work around issues with people + * stubbing out `process.nextTick()` in tests using sinon: + * https://github.com/sinonjs/lolex#automatically-incrementing-mocked-time + * See gh-6074 + */ + +'use strict'; + +module.exports = function immediate(cb) { + return process.nextTick(cb); +}; diff --git a/node_modules/mongoose/lib/helpers/indexes/isDefaultIdIndex.js b/node_modules/mongoose/lib/helpers/indexes/isDefaultIdIndex.js new file mode 100644 index 0000000..c975dcf --- /dev/null +++ b/node_modules/mongoose/lib/helpers/indexes/isDefaultIdIndex.js @@ -0,0 +1,18 @@ +'use strict'; + +const get = require('../get'); + +module.exports = function isDefaultIdIndex(index) { + if (Array.isArray(index)) { + // Mongoose syntax + const keys = Object.keys(index[0]); + return keys.length === 1 && keys[0] === '_id' && index[0]._id !== 'hashed'; + } + + if (typeof index !== 'object') { + return false; + } + + const key = get(index, 'key', {}); + return Object.keys(key).length === 1 && key.hasOwnProperty('_id'); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/indexes/isIndexEqual.js b/node_modules/mongoose/lib/helpers/indexes/isIndexEqual.js new file mode 100644 index 0000000..d59d734 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/indexes/isIndexEqual.js @@ -0,0 +1,95 @@ +'use strict'; + +const get = require('../get'); +const utils = require('../../utils'); + +/** + * Given a Mongoose index definition (key + options objects) and a MongoDB server + * index definition, determine if the two indexes are equal. + * + * @param {Object} key the Mongoose index spec + * @param {Object} options the Mongoose index definition's options + * @param {Object} dbIndex the index in MongoDB as returned by `listIndexes()` + * @api private + */ + +module.exports = function isIndexEqual(key, options, dbIndex) { + // Special case: text indexes have a special format in the db. For example, + // `{ name: 'text' }` becomes: + // { + // v: 2, + // key: { _fts: 'text', _ftsx: 1 }, + // name: 'name_text', + // ns: 'test.tests', + // background: true, + // weights: { name: 1 }, + // default_language: 'english', + // language_override: 'language', + // textIndexVersion: 3 + // } + if (dbIndex.textIndexVersion != null) { + const weights = dbIndex.weights; + if (Object.keys(weights).length !== Object.keys(key).length) { + return false; + } + for (const prop of Object.keys(weights)) { + if (!(prop in key)) { + return false; + } + const weight = weights[prop]; + if (weight !== get(options, 'weights.' + prop) && !(weight === 1 && get(options, 'weights.' + prop) == null)) { + return false; + } + } + + if (options['default_language'] !== dbIndex['default_language']) { + return dbIndex['default_language'] === 'english' && options['default_language'] == null; + } + + return true; + } + + const optionKeys = [ + 'unique', + 'partialFilterExpression', + 'sparse', + 'expireAfterSeconds', + 'collation' + ]; + for (const key of optionKeys) { + if (!(key in options) && !(key in dbIndex)) { + continue; + } + if (key === 'collation') { + if (options[key] == null || dbIndex[key] == null) { + return options[key] == null && dbIndex[key] == null; + } + const definedKeys = Object.keys(options.collation); + const schemaCollation = options.collation; + const dbCollation = dbIndex.collation; + for (const opt of definedKeys) { + if (get(schemaCollation, opt) !== get(dbCollation, opt)) { + return false; + } + } + } else if (!utils.deepEqual(options[key], dbIndex[key])) { + return false; + } + } + + const schemaIndexKeys = Object.keys(key); + const dbIndexKeys = Object.keys(dbIndex.key); + if (schemaIndexKeys.length !== dbIndexKeys.length) { + return false; + } + for (let i = 0; i < schemaIndexKeys.length; ++i) { + if (schemaIndexKeys[i] !== dbIndexKeys[i]) { + return false; + } + if (!utils.deepEqual(key[schemaIndexKeys[i]], dbIndex.key[dbIndexKeys[i]])) { + return false; + } + } + + return true; +}; diff --git a/node_modules/mongoose/lib/helpers/isBsonType.js b/node_modules/mongoose/lib/helpers/isBsonType.js new file mode 100644 index 0000000..01435d3 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/isBsonType.js @@ -0,0 +1,13 @@ +'use strict'; + +const get = require('./get'); + +/*! + * Get the bson type, if it exists + */ + +function isBsonType(obj, typename) { + return get(obj, '_bsontype', void 0) === typename; +} + +module.exports = isBsonType; diff --git a/node_modules/mongoose/lib/helpers/isMongooseObject.js b/node_modules/mongoose/lib/helpers/isMongooseObject.js new file mode 100644 index 0000000..016f9e6 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/isMongooseObject.js @@ -0,0 +1,21 @@ +'use strict'; + +/*! + * Returns if `v` is a mongoose object that has a `toObject()` method we can use. + * + * This is for compatibility with libs like Date.js which do foolish things to Natives. + * + * @param {any} v + * @api private + */ + +module.exports = function(v) { + if (v == null) { + return false; + } + + return v.$__ != null || // Document + v.isMongooseArray || // Array or Document Array + v.isMongooseBuffer || // Buffer + v.$isMongooseMap; // Map +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/isObject.js b/node_modules/mongoose/lib/helpers/isObject.js new file mode 100644 index 0000000..f8ac313 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/isObject.js @@ -0,0 +1,16 @@ +'use strict'; + +/*! + * Determines if `arg` is an object. + * + * @param {Object|Array|String|Function|RegExp|any} arg + * @api private + * @return {Boolean} + */ + +module.exports = function(arg) { + if (Buffer.isBuffer(arg)) { + return true; + } + return Object.prototype.toString.call(arg) === '[object Object]'; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/isPromise.js b/node_modules/mongoose/lib/helpers/isPromise.js new file mode 100644 index 0000000..d6db260 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/isPromise.js @@ -0,0 +1,6 @@ +'use strict'; +function isPromise(val) { + return !!val && (typeof val === 'object' || typeof val === 'function') && typeof val.then === 'function'; +} + +module.exports = isPromise;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/model/applyHooks.js b/node_modules/mongoose/lib/helpers/model/applyHooks.js new file mode 100644 index 0000000..9570a36 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/model/applyHooks.js @@ -0,0 +1,135 @@ +'use strict'; + +const symbols = require('../../schema/symbols'); +const promiseOrCallback = require('../promiseOrCallback'); + +/*! + * ignore + */ + +module.exports = applyHooks; + +/*! + * ignore + */ + +applyHooks.middlewareFunctions = [ + 'deleteOne', + 'save', + 'validate', + 'remove', + 'updateOne', + 'init' +]; + +/*! + * Register hooks for this model + * + * @param {Model} model + * @param {Schema} schema + */ + +function applyHooks(model, schema, options) { + options = options || {}; + + const kareemOptions = { + useErrorHandlers: true, + numCallbackParams: 1, + nullResultByDefault: true, + contextParameter: true + }; + const objToDecorate = options.decorateDoc ? model : model.prototype; + + model.$appliedHooks = true; + for (const key of Object.keys(schema.paths)) { + const type = schema.paths[key]; + let childModel = null; + if (type.$isSingleNested) { + childModel = type.caster; + } else if (type.$isMongooseDocumentArray) { + childModel = type.Constructor; + } else { + continue; + } + + if (childModel.$appliedHooks) { + continue; + } + + applyHooks(childModel, type.schema, options); + if (childModel.discriminators != null) { + const keys = Object.keys(childModel.discriminators); + for (const key of keys) { + applyHooks(childModel.discriminators[key], + childModel.discriminators[key].schema, options); + } + } + } + + // Built-in hooks rely on hooking internal functions in order to support + // promises and make it so that `doc.save.toString()` provides meaningful + // information. + + const middleware = schema.s.hooks. + filter(hook => { + if (hook.name === 'updateOne' || hook.name === 'deleteOne') { + return !!hook['document']; + } + if (hook.name === 'remove' || hook.name === 'init') { + return hook['document'] == null || !!hook['document']; + } + return true; + }). + filter(hook => { + // If user has overwritten the method, don't apply built-in middleware + if (schema.methods[hook.name]) { + return !hook.fn[symbols.builtInMiddleware]; + } + + return true; + }); + + model._middleware = middleware; + + objToDecorate.$__originalValidate = objToDecorate.$__originalValidate || objToDecorate.$__validate; + + for (const method of ['save', 'validate', 'remove', 'deleteOne']) { + const toWrap = method === 'validate' ? '$__originalValidate' : `$__${method}`; + const wrapped = middleware. + createWrapper(method, objToDecorate[toWrap], null, kareemOptions); + objToDecorate[`$__${method}`] = wrapped; + } + objToDecorate.$__init = middleware. + createWrapperSync('init', objToDecorate.$__init, null, kareemOptions); + + // Support hooks for custom methods + const customMethods = Object.keys(schema.methods); + const customMethodOptions = Object.assign({}, kareemOptions, { + // Only use `checkForPromise` for custom methods, because mongoose + // query thunks are not as consistent as I would like about returning + // a nullish value rather than the query. If a query thunk returns + // a query, `checkForPromise` causes infinite recursion + checkForPromise: true + }); + for (const method of customMethods) { + if (!middleware.hasHooks(method)) { + // Don't wrap if there are no hooks for the custom method to avoid + // surprises. Also, `createWrapper()` enforces consistent async, + // so wrapping a sync method would break it. + continue; + } + const originalMethod = objToDecorate[method]; + objToDecorate[method] = function() { + const args = Array.prototype.slice.call(arguments); + const cb = args.slice(-1).pop(); + const argsWithoutCallback = typeof cb === 'function' ? + args.slice(0, args.length - 1) : args; + return promiseOrCallback(cb, callback => { + return this[`$__${method}`].apply(this, + argsWithoutCallback.concat([callback])); + }, model.events); + }; + objToDecorate[`$__${method}`] = middleware. + createWrapper(method, originalMethod, null, customMethodOptions); + } +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/model/applyMethods.js b/node_modules/mongoose/lib/helpers/model/applyMethods.js new file mode 100644 index 0000000..912f3aa --- /dev/null +++ b/node_modules/mongoose/lib/helpers/model/applyMethods.js @@ -0,0 +1,56 @@ +'use strict'; + +const get = require('../get'); + +/*! + * Register methods for this model + * + * @param {Model} model + * @param {Schema} schema + */ + +module.exports = function applyMethods(model, schema) { + function apply(method, schema) { + Object.defineProperty(model.prototype, method, { + get: function() { + const h = {}; + for (const k in schema.methods[method]) { + h[k] = schema.methods[method][k].bind(this); + } + return h; + }, + configurable: true + }); + } + for (const method of Object.keys(schema.methods)) { + const fn = schema.methods[method]; + if (schema.tree.hasOwnProperty(method)) { + throw new Error('You have a method and a property in your schema both ' + + 'named "' + method + '"'); + } + if (schema.reserved[method] && + !get(schema, `methodOptions.${method}.suppressWarning`, false)) { + console.warn(`mongoose: the method name "${method}" is used by mongoose ` + + 'internally, overwriting it may cause bugs. If you\'re sure you know ' + + 'what you\'re doing, you can suppress this error by using ' + + `\`schema.method('${method}', fn, { suppressWarning: true })\`.`); + } + if (typeof fn === 'function') { + model.prototype[method] = fn; + } else { + apply(method, schema); + } + } + + // Recursively call `applyMethods()` on child schemas + model.$appliedMethods = true; + for (const key of Object.keys(schema.paths)) { + const type = schema.paths[key]; + if (type.$isSingleNested && !type.caster.$appliedMethods) { + applyMethods(type.caster, type.schema); + } + if (type.$isMongooseDocumentArray && !type.Constructor.$appliedMethods) { + applyMethods(type.Constructor, type.schema); + } + } +}; diff --git a/node_modules/mongoose/lib/helpers/model/applyStaticHooks.js b/node_modules/mongoose/lib/helpers/model/applyStaticHooks.js new file mode 100644 index 0000000..219e289 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/model/applyStaticHooks.js @@ -0,0 +1,71 @@ +'use strict'; + +const middlewareFunctions = require('../query/applyQueryMiddleware').middlewareFunctions; +const promiseOrCallback = require('../promiseOrCallback'); + +module.exports = function applyStaticHooks(model, hooks, statics) { + const kareemOptions = { + useErrorHandlers: true, + numCallbackParams: 1 + }; + + hooks = hooks.filter(hook => { + // If the custom static overwrites an existing query middleware, don't apply + // middleware to it by default. This avoids a potential backwards breaking + // change with plugins like `mongoose-delete` that use statics to overwrite + // built-in Mongoose functions. + if (middlewareFunctions.indexOf(hook.name) !== -1) { + return !!hook.model; + } + return hook.model !== false; + }); + + model.$__insertMany = hooks.createWrapper('insertMany', + model.$__insertMany, model, kareemOptions); + + for (const key of Object.keys(statics)) { + if (hooks.hasHooks(key)) { + const original = model[key]; + + model[key] = function() { + const numArgs = arguments.length; + const lastArg = numArgs > 0 ? arguments[numArgs - 1] : null; + const cb = typeof lastArg === 'function' ? lastArg : null; + const args = Array.prototype.slice. + call(arguments, 0, cb == null ? numArgs : numArgs - 1); + // Special case: can't use `Kareem#wrap()` because it doesn't currently + // support wrapped functions that return a promise. + return promiseOrCallback(cb, callback => { + hooks.execPre(key, model, args, function(err) { + if (err != null) { + return callback(err); + } + + let postCalled = 0; + const ret = original.apply(model, args.concat(post)); + if (ret != null && typeof ret.then === 'function') { + ret.then(res => post(null, res), err => post(err)); + } + + function post(error, res) { + if (postCalled++ > 0) { + return; + } + + if (error != null) { + return callback(error); + } + + hooks.execPost(key, model, [res], function(error) { + if (error != null) { + return callback(error); + } + callback(null, res); + }); + } + }); + }, model.events); + }; + } + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/model/applyStatics.js b/node_modules/mongoose/lib/helpers/model/applyStatics.js new file mode 100644 index 0000000..3b9501e --- /dev/null +++ b/node_modules/mongoose/lib/helpers/model/applyStatics.js @@ -0,0 +1,12 @@ +'use strict'; + +/*! + * Register statics for this model + * @param {Model} model + * @param {Schema} schema + */ +module.exports = function applyStatics(model, schema) { + for (const i in schema.statics) { + model[i] = schema.statics[i]; + } +}; diff --git a/node_modules/mongoose/lib/helpers/model/castBulkWrite.js b/node_modules/mongoose/lib/helpers/model/castBulkWrite.js new file mode 100644 index 0000000..6e7a830 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/model/castBulkWrite.js @@ -0,0 +1,224 @@ +'use strict'; + +const getDiscriminatorByValue = require('../../helpers/discriminator/getDiscriminatorByValue'); +const applyTimestampsToChildren = require('../update/applyTimestampsToChildren'); +const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate'); +const cast = require('../../cast'); +const castUpdate = require('../query/castUpdate'); +const setDefaultsOnInsert = require('../setDefaultsOnInsert'); + +/*! + * Given a model and a bulkWrite op, return a thunk that handles casting and + * validating the individual op. + */ + +module.exports = function castBulkWrite(originalModel, op, options) { + const now = originalModel.base.now(); + + if (op['insertOne']) { + return (callback) => { + const model = decideModelByObject(originalModel, op['insertOne']['document']); + + const doc = new model(op['insertOne']['document']); + if (model.schema.options.timestamps != null) { + doc.initializeTimestamps(); + } + if (options.session != null) { + doc.$session(options.session); + } + op['insertOne']['document'] = doc; + op['insertOne']['document'].validate({ __noPromise: true }, function(error) { + if (error) { + return callback(error, null); + } + callback(null); + }); + }; + } else if (op['updateOne']) { + return (callback) => { + try { + if (!op['updateOne']['filter']) { + throw new Error('Must provide a filter object.'); + } + if (!op['updateOne']['update']) { + throw new Error('Must provide an update object.'); + } + + const model = decideModelByObject(originalModel, op['updateOne']['filter']); + const schema = model.schema; + const strict = options.strict != null ? options.strict : model.schema.options.strict; + + _addDiscriminatorToObject(schema, op['updateOne']['filter']); + + if (model.schema.$timestamps != null && op['updateOne'].timestamps !== false) { + const createdAt = model.schema.$timestamps.createdAt; + const updatedAt = model.schema.$timestamps.updatedAt; + applyTimestampsToUpdate(now, createdAt, updatedAt, op['updateOne']['update'], {}); + } + + applyTimestampsToChildren(now, op['updateOne']['update'], model.schema); + + if (op['updateOne'].setDefaultsOnInsert) { + setDefaultsOnInsert(op['updateOne']['filter'], model.schema, op['updateOne']['update'], { + setDefaultsOnInsert: true, + upsert: op['updateOne'].upsert + }); + } + + op['updateOne']['filter'] = cast(model.schema, op['updateOne']['filter'], { + strict: strict, + upsert: op['updateOne'].upsert + }); + + op['updateOne']['update'] = castUpdate(model.schema, op['updateOne']['update'], { + strict: strict, + overwrite: false, + upsert: op['updateOne'].upsert + }, model, op['updateOne']['filter']); + } catch (error) { + return callback(error, null); + } + + callback(null); + }; + } else if (op['updateMany']) { + return (callback) => { + try { + if (!op['updateMany']['filter']) { + throw new Error('Must provide a filter object.'); + } + if (!op['updateMany']['update']) { + throw new Error('Must provide an update object.'); + } + + const model = decideModelByObject(originalModel, op['updateMany']['filter']); + const schema = model.schema; + const strict = options.strict != null ? options.strict : model.schema.options.strict; + + if (op['updateMany'].setDefaultsOnInsert) { + setDefaultsOnInsert(op['updateMany']['filter'], model.schema, op['updateMany']['update'], { + setDefaultsOnInsert: true, + upsert: op['updateMany'].upsert + }); + } + + if (model.schema.$timestamps != null && op['updateMany'].timestamps !== false) { + const createdAt = model.schema.$timestamps.createdAt; + const updatedAt = model.schema.$timestamps.updatedAt; + applyTimestampsToUpdate(now, createdAt, updatedAt, op['updateMany']['update'], {}); + } + + applyTimestampsToChildren(now, op['updateMany']['update'], model.schema); + + _addDiscriminatorToObject(schema, op['updateMany']['filter']); + + op['updateMany']['filter'] = cast(model.schema, op['updateMany']['filter'], { + strict: strict, + upsert: op['updateMany'].upsert + }); + + op['updateMany']['update'] = castUpdate(model.schema, op['updateMany']['update'], { + strict: strict, + overwrite: false, + upsert: op['updateMany'].upsert + }, model, op['updateMany']['filter']); + + } catch (error) { + return callback(error, null); + } + + callback(null); + }; + } else if (op['replaceOne']) { + return (callback) => { + const model = decideModelByObject(originalModel, op['replaceOne']['filter']); + const schema = model.schema; + const strict = options.strict != null ? options.strict : model.schema.options.strict; + + _addDiscriminatorToObject(schema, op['replaceOne']['filter']); + try { + op['replaceOne']['filter'] = cast(model.schema, op['replaceOne']['filter'], { + strict: strict, + upsert: op['replaceOne'].upsert + }); + } catch (error) { + return callback(error, null); + } + + // set `skipId`, otherwise we get "_id field cannot be changed" + const doc = new model(op['replaceOne']['replacement'], strict, true); + if (model.schema.options.timestamps != null) { + doc.initializeTimestamps(); + } + if (options.session != null) { + doc.$session(options.session); + } + op['replaceOne']['replacement'] = doc; + + op['replaceOne']['replacement'].validate({ __noPromise: true }, function(error) { + if (error) { + return callback(error, null); + } + op['replaceOne']['replacement'] = op['replaceOne']['replacement'].toBSON(); + callback(null); + }); + }; + } else if (op['deleteOne']) { + return (callback) => { + const model = decideModelByObject(originalModel, op['deleteOne']['filter']); + const schema = model.schema; + + _addDiscriminatorToObject(schema, op['deleteOne']['filter']); + + try { + op['deleteOne']['filter'] = cast(model.schema, + op['deleteOne']['filter']); + } catch (error) { + return callback(error, null); + } + + callback(null); + }; + } else if (op['deleteMany']) { + return (callback) => { + const model = decideModelByObject(originalModel, op['deleteMany']['filter']); + const schema = model.schema; + + _addDiscriminatorToObject(schema, op['deleteMany']['filter']); + + try { + op['deleteMany']['filter'] = cast(model.schema, + op['deleteMany']['filter']); + } catch (error) { + return callback(error, null); + } + + callback(null); + }; + } else { + return (callback) => { + callback(new Error('Invalid op passed to `bulkWrite()`'), null); + }; + } +}; + +function _addDiscriminatorToObject(schema, obj) { + if (schema == null) { + return; + } + if (schema.discriminatorMapping && !schema.discriminatorMapping.isRoot) { + obj[schema.discriminatorMapping.key] = schema.discriminatorMapping.value; + } +} + +/*! + * gets discriminator model if discriminator key is present in object + */ + +function decideModelByObject(model, object) { + const discriminatorKey = model.schema.options.discriminatorKey; + if (object != null && object.hasOwnProperty(discriminatorKey)) { + model = getDiscriminatorByValue(model, object[discriminatorKey]) || model; + } + return model; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/model/discriminator.js b/node_modules/mongoose/lib/helpers/model/discriminator.js new file mode 100644 index 0000000..2ad4c5e --- /dev/null +++ b/node_modules/mongoose/lib/helpers/model/discriminator.js @@ -0,0 +1,205 @@ +'use strict'; + +const Mixed = require('../../schema/mixed'); +const defineKey = require('../document/compile').defineKey; +const get = require('../get'); +const utils = require('../../utils'); + +const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = { + toJSON: true, + toObject: true, + _id: true, + id: true +}; + +/*! + * ignore + */ + +module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) { + if (!(schema && schema.instanceOfSchema)) { + throw new Error('You must pass a valid discriminator Schema'); + } + + if (model.schema.discriminatorMapping && + !model.schema.discriminatorMapping.isRoot) { + throw new Error('Discriminator "' + name + + '" can only be a discriminator of the root model'); + } + + if (applyPlugins) { + const applyPluginsToDiscriminators = get(model.base, + 'options.applyPluginsToDiscriminators', false); + // Even if `applyPluginsToDiscriminators` isn't set, we should still apply + // global plugins to schemas embedded in the discriminator schema (gh-7370) + model.base._applyPlugins(schema, { + skipTopLevel: !applyPluginsToDiscriminators + }); + } + + const key = model.schema.options.discriminatorKey; + + const existingPath = model.schema.path(key); + if (existingPath != null) { + if (!utils.hasUserDefinedProperty(existingPath.options, 'select')) { + existingPath.options.select = true; + } + existingPath.options.$skipDiscriminatorCheck = true; + } else { + const baseSchemaAddition = {}; + baseSchemaAddition[key] = { + default: void 0, + select: true, + $skipDiscriminatorCheck: true + }; + baseSchemaAddition[key][model.schema.options.typeKey] = String; + model.schema.add(baseSchemaAddition); + defineKey(key, null, model.prototype, null, [key], model.schema.options); + } + + if (schema.path(key) && schema.path(key).options.$skipDiscriminatorCheck !== true) { + throw new Error('Discriminator "' + name + + '" cannot have field with name "' + key + '"'); + } + + let value = name; + if (typeof tiedValue == 'string' && tiedValue.length) { + value = tiedValue; + } + + function merge(schema, baseSchema) { + // Retain original schema before merging base schema + schema._baseSchema = baseSchema; + if (baseSchema.paths._id && + baseSchema.paths._id.options && + !baseSchema.paths._id.options.auto) { + schema.remove('_id'); + } + + // Find conflicting paths: if something is a path in the base schema + // and a nested path in the child schema, overwrite the base schema path. + // See gh-6076 + const baseSchemaPaths = Object.keys(baseSchema.paths); + const conflictingPaths = []; + + for (const path of baseSchemaPaths) { + if (schema.nested[path]) { + conflictingPaths.push(path); + continue; + } + + if (path.indexOf('.') === -1) { + continue; + } + const sp = path.split('.').slice(0, -1); + let cur = ''; + for (const piece of sp) { + cur += (cur.length ? '.' : '') + piece; + if (schema.paths[cur] instanceof Mixed || + schema.singleNestedPaths[cur] instanceof Mixed) { + conflictingPaths.push(path); + } + } + } + + utils.merge(schema, baseSchema, { + isDiscriminatorSchemaMerge: true, + omit: { discriminators: true, base: true }, + omitNested: conflictingPaths.reduce((cur, path) => { + cur['tree.' + path] = true; + return cur; + }, {}) + }); + + // Clean up conflicting paths _after_ merging re: gh-6076 + for (const conflictingPath of conflictingPaths) { + delete schema.paths[conflictingPath]; + } + + // Rebuild schema models because schemas may have been merged re: #7884 + schema.childSchemas.forEach(obj => { + obj.model.prototype.$__setSchema(obj.schema); + }); + + const obj = {}; + obj[key] = { + default: value, + select: true, + set: function(newName) { + if (newName === value) { + return value; + } + throw new Error('Can\'t set discriminator key "' + key + '"'); + }, + $skipDiscriminatorCheck: true + }; + obj[key][schema.options.typeKey] = existingPath ? + existingPath.instance : + String; + schema.add(obj); + schema.discriminatorMapping = { key: key, value: value, isRoot: false }; + + if (baseSchema.options.collection) { + schema.options.collection = baseSchema.options.collection; + } + + const toJSON = schema.options.toJSON; + const toObject = schema.options.toObject; + const _id = schema.options._id; + const id = schema.options.id; + + const keys = Object.keys(schema.options); + schema.options.discriminatorKey = baseSchema.options.discriminatorKey; + + for (const _key of keys) { + if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) { + // Special case: compiling a model sets `pluralization = true` by default. Avoid throwing an error + // for that case. See gh-9238 + if (_key === 'pluralization' && schema.options[_key] == true && baseSchema.options[_key] == null) { + continue; + } + + if (!utils.deepEqual(schema.options[_key], baseSchema.options[_key])) { + throw new Error('Can\'t customize discriminator option ' + _key + + ' (can only modify ' + + Object.keys(CUSTOMIZABLE_DISCRIMINATOR_OPTIONS).join(', ') + + ')'); + } + } + } + schema.options = utils.clone(baseSchema.options); + if (toJSON) schema.options.toJSON = toJSON; + if (toObject) schema.options.toObject = toObject; + if (typeof _id !== 'undefined') { + schema.options._id = _id; + } + schema.options.id = id; + schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks); + + schema.plugins = Array.prototype.slice.call(baseSchema.plugins); + schema.callQueue = baseSchema.callQueue.concat(schema.callQueue); + delete schema._requiredpaths; // reset just in case Schema#requiredPaths() was called on either schema + } + + // merges base schema into new discriminator schema and sets new type field. + merge(schema, model.schema); + + if (!model.discriminators) { + model.discriminators = {}; + } + + if (!model.schema.discriminatorMapping) { + model.schema.discriminatorMapping = { key: key, value: null, isRoot: true }; + } + if (!model.schema.discriminators) { + model.schema.discriminators = {}; + } + + model.schema.discriminators[name] = schema; + + if (model.discriminators[name]) { + throw new Error('Discriminator with name "' + name + '" already exists'); + } + + return schema; +}; diff --git a/node_modules/mongoose/lib/helpers/once.js b/node_modules/mongoose/lib/helpers/once.js new file mode 100644 index 0000000..0267579 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/once.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = function once(fn) { + let called = false; + return function() { + if (called) { + return; + } + called = true; + return fn.apply(null, arguments); + }; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/parallelLimit.js b/node_modules/mongoose/lib/helpers/parallelLimit.js new file mode 100644 index 0000000..9b07c02 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/parallelLimit.js @@ -0,0 +1,55 @@ +'use strict'; + +module.exports = parallelLimit; + +/*! + * ignore + */ + +function parallelLimit(fns, limit, callback) { + let numInProgress = 0; + let numFinished = 0; + let error = null; + + if (limit <= 0) { + throw new Error('Limit must be positive'); + } + + if (fns.length === 0) { + return callback(null, []); + } + + for (let i = 0; i < fns.length && i < limit; ++i) { + _start(); + } + + function _start() { + fns[numFinished + numInProgress](_done(numFinished + numInProgress)); + ++numInProgress; + } + + const results = []; + + function _done(index) { + return (err, res) => { + --numInProgress; + ++numFinished; + + if (error != null) { + return; + } + if (err != null) { + error = err; + return callback(error); + } + + results[index] = res; + + if (numFinished === fns.length) { + return callback(null, results); + } else if (numFinished + numInProgress < fns.length) { + _start(); + } + }; + } +} diff --git a/node_modules/mongoose/lib/helpers/populate/SkipPopulateValue.js b/node_modules/mongoose/lib/helpers/populate/SkipPopulateValue.js new file mode 100644 index 0000000..5d46cfd --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/SkipPopulateValue.js @@ -0,0 +1,10 @@ +'use strict'; + +module.exports = function SkipPopulateValue(val) { + if (!(this instanceof SkipPopulateValue)) { + return new SkipPopulateValue(val); + } + + this.val = val; + return this; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/populate/assignRawDocsToIdStructure.js b/node_modules/mongoose/lib/helpers/populate/assignRawDocsToIdStructure.js new file mode 100644 index 0000000..843c148 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/assignRawDocsToIdStructure.js @@ -0,0 +1,98 @@ +'use strict'; + +const leanPopulateMap = require('./leanPopulateMap'); +const modelSymbol = require('../symbols').modelSymbol; +const utils = require('../../utils'); + +module.exports = assignRawDocsToIdStructure; + +/*! + * Assign `vals` returned by mongo query to the `rawIds` + * structure returned from utils.getVals() honoring + * query sort order if specified by user. + * + * This can be optimized. + * + * Rules: + * + * if the value of the path is not an array, use findOne rules, else find. + * for findOne the results are assigned directly to doc path (including null results). + * for find, if user specified sort order, results are assigned directly + * else documents are put back in original order of array if found in results + * + * @param {Array} rawIds + * @param {Array} vals + * @param {Boolean} sort + * @api private + */ + +function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, recursed) { + // honor user specified sort order + const newOrder = []; + const sorting = options.sort && rawIds.length > 1; + const nullIfNotFound = options.$nullIfNotFound; + let doc; + let sid; + let id; + + for (let i = 0; i < rawIds.length; ++i) { + id = rawIds[i]; + + if (Array.isArray(id)) { + // handle [ [id0, id2], [id3] ] + assignRawDocsToIdStructure(id, resultDocs, resultOrder, options, true); + newOrder.push(id); + continue; + } + + if (id === null && !sorting) { + // keep nulls for findOne unless sorting, which always + // removes them (backward compat) + newOrder.push(id); + continue; + } + + sid = String(id); + + doc = resultDocs[sid]; + // If user wants separate copies of same doc, use this option + if (options.clone && doc != null) { + if (options.lean) { + const _model = leanPopulateMap.get(doc); + doc = utils.clone(doc); + leanPopulateMap.set(doc, _model); + } else { + doc = doc.constructor.hydrate(doc._doc); + } + } + + if (recursed) { + if (doc) { + if (sorting) { + newOrder[resultOrder[sid]] = doc; + } else { + newOrder.push(doc); + } + } else if (id != null && id[modelSymbol] != null) { + newOrder.push(id); + } else { + newOrder.push(options.retainNullValues || nullIfNotFound ? null : id); + } + } else { + // apply findOne behavior - if document in results, assign, else assign null + newOrder[i] = doc || null; + } + } + + rawIds.length = 0; + if (newOrder.length) { + // reassign the documents based on corrected order + + // forEach skips over sparse entries in arrays so we + // can safely use this to our advantage dealing with sorted + // result sets too. + newOrder.forEach(function(doc, i) { + rawIds[i] = doc; + }); + } +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/populate/assignVals.js b/node_modules/mongoose/lib/helpers/populate/assignVals.js new file mode 100644 index 0000000..9fd51d8 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/assignVals.js @@ -0,0 +1,274 @@ +'use strict'; + +const SkipPopulateValue = require('./SkipPopulateValue'); +const assignRawDocsToIdStructure = require('./assignRawDocsToIdStructure'); +const get = require('../get'); +const getVirtual = require('./getVirtual'); +const leanPopulateMap = require('./leanPopulateMap'); +const lookupLocalFields = require('./lookupLocalFields'); +const mpath = require('mpath'); +const sift = require('sift').default; +const utils = require('../../utils'); + +module.exports = function assignVals(o) { + // Options that aren't explicitly listed in `populateOptions` + const userOptions = get(o, 'allOptions.options.options'); + // `o.options` contains options explicitly listed in `populateOptions`, like + // `match` and `limit`. + const populateOptions = Object.assign({}, o.options, userOptions, { + justOne: o.justOne + }); + populateOptions.$nullIfNotFound = o.isVirtual; + const populatedModel = o.populatedModel; + + const originalIds = [].concat(o.rawIds); + + // replace the original ids in our intermediate _ids structure + // with the documents found by query + assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, populateOptions); + + // now update the original documents being populated using the + // result structure that contains real documents. + const docs = o.docs; + const rawIds = o.rawIds; + const options = o.options; + const count = o.count && o.isVirtual; + + function setValue(val) { + if (count) { + return val; + } + if (val instanceof SkipPopulateValue) { + return val.val; + } + + if (o.justOne === true && Array.isArray(val)) { + // Might be an embedded discriminator (re: gh-9244) with multiple models, so make sure to pick the right + // model before assigning. + const ret = []; + for (const doc of val) { + const _docPopulatedModel = leanPopulateMap.get(doc); + if (_docPopulatedModel == null || _docPopulatedModel === populatedModel) { + ret.push(doc); + } + } + // Since we don't want to have to create a new mongoosearray, make sure to + // modify the array in place + while (val.length > ret.length) { + Array.prototype.pop.apply(val, []); + } + for (let i = 0; i < ret.length; ++i) { + val[i] = ret[i]; + } + + return valueFilter(val[0], options, populateOptions, populatedModel); + } else if (o.justOne === false && !Array.isArray(val)) { + return valueFilter([val], options, populateOptions, populatedModel); + } + return valueFilter(val, options, populateOptions, populatedModel); + } + + for (let i = 0; i < docs.length; ++i) { + const existingVal = mpath.get(o.path, docs[i], lookupLocalFields); + if (existingVal == null && !getVirtual(o.originalModel.schema, o.path)) { + continue; + } + + let valueToSet; + if (count) { + valueToSet = numDocs(rawIds[i]); + } else if (Array.isArray(o.match)) { + valueToSet = Array.isArray(rawIds[i]) ? + sift(o.match[i], rawIds[i]) : + sift(o.match[i], [rawIds[i]])[0]; + } else { + valueToSet = rawIds[i]; + } + + // If we're populating a map, the existing value will be an object, so + // we need to transform again + const originalSchema = o.originalModel.schema; + const isDoc = get(docs[i], '$__', null) != null; + let isMap = isDoc ? + existingVal instanceof Map : + utils.isPOJO(existingVal); + // If we pass the first check, also make sure the local field's schematype + // is map (re: gh-6460) + isMap = isMap && get(originalSchema._getSchema(o.path), '$isSchemaMap'); + if (!o.isVirtual && isMap) { + const _keys = existingVal instanceof Map ? + Array.from(existingVal.keys()) : + Object.keys(existingVal); + valueToSet = valueToSet.reduce((cur, v, i) => { + cur.set(_keys[i], v); + return cur; + }, new Map()); + } + + if (isDoc && Array.isArray(valueToSet)) { + for (const val of valueToSet) { + if (val != null && val.$__ != null) { + val.$__.parent = docs[i]; + } + } + } else if (isDoc && valueToSet != null && valueToSet.$__ != null) { + valueToSet.$__.parent = docs[i]; + } + + if (o.isVirtual && isDoc) { + docs[i].populated(o.path, o.justOne ? originalIds[0] : originalIds, o.allOptions); + // If virtual populate and doc is already init-ed, need to walk through + // the actual doc to set rather than setting `_doc` directly + mpath.set(o.path, valueToSet, docs[i], setValue); + continue; + } + + const parts = o.path.split('.'); + let cur = docs[i]; + const curPath = parts[0]; + for (let j = 0; j < parts.length - 1; ++j) { + // If we get to an array with a dotted path, like `arr.foo`, don't set + // `foo` on the array. + if (Array.isArray(cur) && !utils.isArrayIndex(parts[j])) { + break; + } + + if (parts[j] === '$*') { + break; + } + + if (cur[parts[j]] == null) { + // If nothing to set, avoid creating an unnecessary array. Otherwise + // we'll end up with a single doc in the array with only defaults. + // See gh-8342, gh-8455 + const schematype = originalSchema._getSchema(curPath); + if (valueToSet == null && schematype != null && schematype.$isMongooseArray) { + return; + } + cur[parts[j]] = {}; + } + cur = cur[parts[j]]; + // If the property in MongoDB is a primitive, we won't be able to populate + // the nested path, so skip it. See gh-7545 + if (typeof cur !== 'object') { + return; + } + } + if (docs[i].$__) { + docs[i].populated(o.path, o.allIds[i], o.allOptions); + } + + // If lean, need to check that each individual virtual respects + // `justOne`, because you may have a populated virtual with `justOne` + // underneath an array. See gh-6867 + mpath.set(o.path, valueToSet, docs[i], lookupLocalFields, setValue, false); + } +}; + +function numDocs(v) { + if (Array.isArray(v)) { + // If setting underneath an array of populated subdocs, we may have an + // array of arrays. See gh-7573 + if (v.some(el => Array.isArray(el))) { + return v.map(el => numDocs(el)); + } + return v.length; + } + return v == null ? 0 : 1; +} + +/*! + * 1) Apply backwards compatible find/findOne behavior to sub documents + * + * find logic: + * a) filter out non-documents + * b) remove _id from sub docs when user specified + * + * findOne + * a) if no doc found, set to null + * b) remove _id from sub docs when user specified + * + * 2) Remove _ids when specified by users query. + * + * background: + * _ids are left in the query even when user excludes them so + * that population mapping can occur. + */ + +function valueFilter(val, assignmentOpts, populateOptions) { + if (Array.isArray(val)) { + // find logic + const ret = []; + const numValues = val.length; + for (let i = 0; i < numValues; ++i) { + const subdoc = val[i]; + if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null)) { + continue; + } + maybeRemoveId(subdoc, assignmentOpts); + ret.push(subdoc); + if (assignmentOpts.originalLimit && + ret.length >= assignmentOpts.originalLimit) { + break; + } + } + + // Since we don't want to have to create a new mongoosearray, make sure to + // modify the array in place + while (val.length > ret.length) { + Array.prototype.pop.apply(val, []); + } + for (let i = 0; i < ret.length; ++i) { + val[i] = ret[i]; + } + return val; + } + + // findOne + if (isPopulatedObject(val)) { + maybeRemoveId(val, assignmentOpts); + return val; + } + + if (val instanceof Map) { + return val; + } + + if (populateOptions.justOne === true) { + return (val == null ? val : null); + } + if (populateOptions.justOne === false) { + return []; + } + return val; +} + +/*! + * Remove _id from `subdoc` if user specified "lean" query option + */ + +function maybeRemoveId(subdoc, assignmentOpts) { + if (subdoc != null && assignmentOpts.excludeId) { + if (typeof subdoc.$__setValue === 'function') { + delete subdoc._doc._id; + } else { + delete subdoc._id; + } + } +} + +/*! + * Determine if `obj` is something we can set a populated path to. Can be a + * document, a lean document, or an array/map that contains docs. + */ + +function isPopulatedObject(obj) { + if (obj == null) { + return false; + } + + return Array.isArray(obj) || + obj.$isMongooseMap || + obj.$__ != null || + leanPopulateMap.has(obj); +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/populate/getModelsMapForPopulate.js b/node_modules/mongoose/lib/helpers/populate/getModelsMapForPopulate.js new file mode 100644 index 0000000..66baf19 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/getModelsMapForPopulate.js @@ -0,0 +1,522 @@ +'use strict'; + +const MongooseError = require('../../error/index'); +const SkipPopulateValue = require('./SkipPopulateValue'); +const get = require('../get'); +const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue'); +const isPathExcluded = require('../projection/isPathExcluded'); +const getSchemaTypes = require('./getSchemaTypes'); +const getVirtual = require('./getVirtual'); +const lookupLocalFields = require('./lookupLocalFields'); +const mpath = require('mpath'); +const normalizeRefPath = require('./normalizeRefPath'); +const util = require('util'); +const utils = require('../../utils'); + +const modelSymbol = require('../symbols').modelSymbol; +const populateModelSymbol = require('../symbols').populateModelSymbol; +const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol; + +module.exports = function getModelsMapForPopulate(model, docs, options) { + let i; + let doc; + const len = docs.length; + const available = {}; + const map = []; + const modelNameFromQuery = options.model && options.model.modelName || options.model; + let schema; + let refPath; + let Model; + let currentOptions; + let modelNames; + let modelName; + let modelForFindSchema; + + const originalModel = options.model; + let isVirtual = false; + const modelSchema = model.schema; + + let allSchemaTypes = getSchemaTypes(modelSchema, null, options.path); + allSchemaTypes = Array.isArray(allSchemaTypes) ? allSchemaTypes : [allSchemaTypes].filter(v => v != null); + const _firstWithRefPath = allSchemaTypes.find(schematype => get(schematype, 'options.refPath', null) != null); + + for (i = 0; i < len; i++) { + doc = docs[i]; + let justOne = null; + + schema = getSchemaTypes(modelSchema, doc, options.path); + // Special case: populating a path that's a DocumentArray unless + // there's an explicit `ref` or `refPath` re: gh-8946 + if (schema != null && + schema.$isMongooseDocumentArray && + schema.options.ref == null && + schema.options.refPath == null) { + continue; + } + // Populating a nested path should always be a no-op re: #9073. + // People shouldn't do this, but apparently they do. + if (modelSchema.nested[options.path]) { + continue; + } + const isUnderneathDocArray = schema && schema.$isUnderneathDocArray; + if (isUnderneathDocArray && get(options, 'options.sort') != null) { + return new MongooseError('Cannot populate with `sort` on path ' + options.path + + ' because it is a subproperty of a document array'); + } + + modelNames = null; + let isRefPath = !!_firstWithRefPath; + let normalizedRefPath = _firstWithRefPath ? get(_firstWithRefPath, 'options.refPath', null) : null; + + if (Array.isArray(schema)) { + const schemasArray = schema; + for (const _schema of schemasArray) { + let _modelNames; + let res; + try { + res = _getModelNames(doc, _schema); + _modelNames = res.modelNames; + isRefPath = isRefPath || res.isRefPath; + normalizedRefPath = normalizeRefPath(normalizedRefPath, doc, options.path) || + res.refPath; + justOne = res.justOne; + } catch (error) { + return error; + } + + if (isRefPath && !res.isRefPath) { + continue; + } + if (!_modelNames) { + continue; + } + modelNames = modelNames || []; + for (const modelName of _modelNames) { + if (modelNames.indexOf(modelName) === -1) { + modelNames.push(modelName); + } + } + } + } else { + try { + const res = _getModelNames(doc, schema); + modelNames = res.modelNames; + isRefPath = res.isRefPath; + normalizedRefPath = res.refPath; + justOne = res.justOne; + } catch (error) { + return error; + } + + if (!modelNames) { + continue; + } + } + + const _virtualRes = getVirtual(model.schema, options.path); + const virtual = _virtualRes == null ? null : _virtualRes.virtual; + + let localField; + let count = false; + if (virtual && virtual.options) { + const virtualPrefix = _virtualRes.nestedSchemaPath ? + _virtualRes.nestedSchemaPath + '.' : ''; + if (typeof virtual.options.localField === 'function') { + localField = virtualPrefix + virtual.options.localField.call(doc, doc); + } else { + localField = virtualPrefix + virtual.options.localField; + } + count = virtual.options.count; + + if (virtual.options.skip != null && !options.hasOwnProperty('skip')) { + options.skip = virtual.options.skip; + } + if (virtual.options.limit != null && !options.hasOwnProperty('limit')) { + options.limit = virtual.options.limit; + } + if (virtual.options.perDocumentLimit != null && !options.hasOwnProperty('perDocumentLimit')) { + options.perDocumentLimit = virtual.options.perDocumentLimit; + } + } else { + localField = options.path; + } + let foreignField = virtual && virtual.options ? + virtual.options.foreignField : + '_id'; + + // `justOne = null` means we don't know from the schema whether the end + // result should be an array or a single doc. This can result from + // populating a POJO using `Model.populate()` + if ('justOne' in options && options.justOne !== void 0) { + justOne = options.justOne; + } else if (virtual && virtual.options && virtual.options.refPath) { + const normalizedRefPath = + normalizeRefPath(virtual.options.refPath, doc, options.path); + justOne = !!virtual.options.justOne; + isVirtual = true; + const refValue = utils.getValue(normalizedRefPath, doc); + modelNames = Array.isArray(refValue) ? refValue : [refValue]; + } else if (virtual && virtual.options && virtual.options.ref) { + let normalizedRef; + if (typeof virtual.options.ref === 'function') { + normalizedRef = virtual.options.ref.call(doc, doc); + } else { + normalizedRef = virtual.options.ref; + } + justOne = !!virtual.options.justOne; + isVirtual = true; + if (!modelNames) { + modelNames = [].concat(normalizedRef); + } + } else if (schema && !schema[schemaMixedSymbol]) { + // Skip Mixed types because we explicitly don't do casting on those. + justOne = Array.isArray(schema) ? + schema.every(schema => !schema.$isMongooseArray) : + !schema.$isMongooseArray; + } + + if (!modelNames) { + continue; + } + + if (virtual && (!localField || !foreignField)) { + return new MongooseError('If you are populating a virtual, you must set the ' + + 'localField and foreignField options'); + } + + options.isVirtual = isVirtual; + options.virtual = virtual; + if (typeof localField === 'function') { + localField = localField.call(doc, doc); + } + if (typeof foreignField === 'function') { + foreignField = foreignField.call(doc); + } + + const localFieldPathType = modelSchema._getPathType(localField); + const localFieldPath = localFieldPathType === 'real' ? modelSchema.path(localField) : localFieldPathType.schema; + const localFieldGetters = localFieldPath && localFieldPath.getters ? localFieldPath.getters : []; + let ret; + + const _populateOptions = get(options, 'options', {}); + + const getters = 'getters' in _populateOptions ? + _populateOptions.getters : + options.isVirtual && get(virtual, 'options.getters', false); + if (localFieldGetters.length > 0 && getters) { + const hydratedDoc = (doc.$__ != null) ? doc : model.hydrate(doc); + const localFieldValue = mpath.get(localField, doc, lookupLocalFields); + if (Array.isArray(localFieldValue)) { + const localFieldHydratedValue = mpath.get(localField.split('.').slice(0, -1), hydratedDoc, lookupLocalFields); + ret = localFieldValue.map((localFieldArrVal, localFieldArrIndex) => + localFieldPath.applyGetters(localFieldArrVal, localFieldHydratedValue[localFieldArrIndex])); + } else { + ret = localFieldPath.applyGetters(localFieldValue, hydratedDoc); + } + } else { + ret = convertTo_id(mpath.get(localField, doc, lookupLocalFields), schema); + } + + const id = String(utils.getValue(foreignField, doc)); + options._docs[id] = Array.isArray(ret) ? ret.slice() : ret; + + let match = get(options, 'match', null) || + get(currentOptions, 'match', null) || + get(options, 'virtual.options.match', null) || + get(options, 'virtual.options.options.match', null); + + const hasMatchFunction = typeof match === 'function'; + if (hasMatchFunction) { + match = match.call(doc, doc); + } + + // Re: gh-8452. Embedded discriminators may not have `refPath`, so clear + // out embedded discriminator docs that don't have a `refPath` on the + // populated path. + if (isRefPath && normalizedRefPath != null) { + const pieces = normalizedRefPath.split('.'); + let cur = ''; + for (let j = 0; j < pieces.length; ++j) { + const piece = pieces[j]; + cur = cur + (cur.length === 0 ? '' : '.') + piece; + const schematype = modelSchema.path(cur); + if (schematype != null && + schematype.$isMongooseArray && + schematype.caster.discriminators != null && + Object.keys(schematype.caster.discriminators).length > 0) { + const subdocs = utils.getValue(cur, doc); + const remnant = options.path.substr(cur.length + 1); + const discriminatorKey = schematype.caster.schema.options.discriminatorKey; + modelNames = []; + for (const subdoc of subdocs) { + const discriminatorName = utils.getValue(discriminatorKey, subdoc); + const discriminator = schematype.caster.discriminators[discriminatorName]; + const discriminatorSchema = discriminator && discriminator.schema; + if (discriminatorSchema == null) { + continue; + } + const _path = discriminatorSchema.path(remnant); + if (_path == null || _path.options.refPath == null) { + const docValue = utils.getValue(localField.substr(cur.length + 1), subdoc); + ret = ret.map(v => v === docValue ? SkipPopulateValue(v) : v); + continue; + } + const modelName = utils.getValue(pieces.slice(j + 1).join('.'), subdoc); + modelNames.push(modelName); + } + } + } + } + + let k = modelNames.length; + while (k--) { + modelName = modelNames[k]; + if (modelName == null) { + continue; + } + + // `PopulateOptions#connection`: if the model is passed as a string, the + // connection matters because different connections have different models. + const connection = options.connection != null ? options.connection : model.db; + + try { + Model = originalModel && originalModel[modelSymbol] ? + originalModel : + modelName[modelSymbol] ? modelName : connection.model(modelName); + } catch (error) { + // If `ret` is undefined, we'll add an empty entry to modelsMap. We shouldn't + // execute a query, but it is necessary to make sure `justOne` gets handled + // correctly for setting an empty array (see gh-8455) + if (ret !== undefined) { + return error; + } + } + + let ids = ret; + const flat = Array.isArray(ret) ? utils.array.flatten(ret) : []; + + if (isRefPath && Array.isArray(ret) && flat.length === modelNames.length) { + ids = flat.filter((val, i) => modelNames[i] === modelName); + } + + if (!available[modelName] || currentOptions.perDocumentLimit != null || get(currentOptions, 'options.perDocumentLimit') != null) { + currentOptions = { + model: Model + }; + + if (isVirtual && get(virtual, 'options.options')) { + currentOptions.options = utils.clone(virtual.options.options); + } + utils.merge(currentOptions, options); + + // Used internally for checking what model was used to populate this + // path. + options[populateModelSymbol] = Model; + + available[modelName] = { + model: Model, + options: currentOptions, + match: hasMatchFunction ? [match] : match, + docs: [doc], + ids: [ids], + allIds: [ret], + localField: new Set([localField]), + foreignField: new Set([foreignField]), + justOne: justOne, + isVirtual: isVirtual, + virtual: virtual, + count: count, + [populateModelSymbol]: Model + }; + map.push(available[modelName]); + } else { + available[modelName].localField.add(localField); + available[modelName].foreignField.add(foreignField); + available[modelName].docs.push(doc); + available[modelName].ids.push(ids); + available[modelName].allIds.push(ret); + if (hasMatchFunction) { + available[modelName].match.push(match); + } + } + } + } + + return map; + + function _getModelNames(doc, schema) { + let modelNames; + let discriminatorKey; + let isRefPath = false; + let justOne = null; + + if (schema && schema.caster) { + schema = schema.caster; + } + if (schema && schema.$isSchemaMap) { + schema = schema.$__schemaType; + } + + if (!schema && model.discriminators) { + discriminatorKey = model.schema.discriminatorMapping.key; + } + + refPath = schema && schema.options && schema.options.refPath; + + const normalizedRefPath = normalizeRefPath(refPath, doc, options.path); + + if (modelNameFromQuery) { + modelNames = [modelNameFromQuery]; // query options + } else if (normalizedRefPath) { + if (options._queryProjection != null && isPathExcluded(options._queryProjection, normalizedRefPath)) { + throw new MongooseError('refPath `' + normalizedRefPath + + '` must not be excluded in projection, got ' + + util.inspect(options._queryProjection)); + } + + if (modelSchema.virtuals.hasOwnProperty(normalizedRefPath) && doc.$__ == null) { + modelNames = [modelSchema.virtuals[normalizedRefPath].applyGetters(void 0, doc)]; + } else { + modelNames = utils.getValue(normalizedRefPath, doc); + } + + if (Array.isArray(modelNames)) { + modelNames = utils.array.flatten(modelNames); + } + + isRefPath = true; + } else { + let modelForCurrentDoc = model; + let schemaForCurrentDoc; + + if (!schema && discriminatorKey) { + modelForFindSchema = utils.getValue(discriminatorKey, doc); + if (modelForFindSchema) { + // `modelForFindSchema` is the discriminator value, so we might need + // find the discriminated model name + const discriminatorModel = getDiscriminatorByValue(model, modelForFindSchema); + if (discriminatorModel != null) { + modelForCurrentDoc = discriminatorModel; + } else { + try { + modelForCurrentDoc = model.db.model(modelForFindSchema); + } catch (error) { + return error; + } + } + + schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path); + + if (schemaForCurrentDoc && schemaForCurrentDoc.caster) { + schemaForCurrentDoc = schemaForCurrentDoc.caster; + } + } + } else { + schemaForCurrentDoc = schema; + } + const _virtualRes = getVirtual(modelForCurrentDoc.schema, options.path); + const virtual = _virtualRes == null ? null : _virtualRes.virtual; + + if (schemaForCurrentDoc != null) { + justOne = !schemaForCurrentDoc.$isMongooseArray && !schemaForCurrentDoc._arrayPath; + } + + let ref; + let refPath; + + if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) { + ref = handleRefFunction(ref, doc); + modelNames = [ref]; + } else if ((ref = get(virtual, 'options.ref')) != null) { + ref = handleRefFunction(ref, doc); + + // When referencing nested arrays, the ref should be an Array + // of modelNames. + if (Array.isArray(ref)) { + modelNames = ref; + } else { + modelNames = [ref]; + } + + isVirtual = true; + } else if ((refPath = get(schemaForCurrentDoc, 'options.refPath')) != null) { + isRefPath = true; + refPath = normalizeRefPath(refPath, doc, options.path); + modelNames = utils.getValue(refPath, doc); + if (Array.isArray(modelNames)) { + modelNames = utils.array.flatten(modelNames); + } + } else { + // We may have a discriminator, in which case we don't want to + // populate using the base model by default + modelNames = discriminatorKey ? null : [model.modelName]; + } + } + + if (!modelNames) { + return { modelNames: modelNames, isRefPath: isRefPath, refPath: normalizedRefPath, justOne: justOne }; + } + + if (!Array.isArray(modelNames)) { + modelNames = [modelNames]; + } + + return { modelNames: modelNames, isRefPath: isRefPath, refPath: normalizedRefPath, justOne: justOne }; + } +}; + +/*! + * ignore + */ + +function handleRefFunction(ref, doc) { + if (typeof ref === 'function' && !ref[modelSymbol]) { + return ref.call(doc, doc); + } + return ref; +} + +/*! + * Retrieve the _id of `val` if a Document or Array of Documents. + * + * @param {Array|Document|Any} val + * @return {Array|Document|Any} + */ + +function convertTo_id(val, schema) { + if (val != null && val.$__ != null) return val._id; + + if (Array.isArray(val)) { + for (let i = 0; i < val.length; ++i) { + if (val[i] != null && val[i].$__ != null) { + val[i] = val[i]._id; + } + } + if (val.isMongooseArray && val.$schema()) { + return val.$schema().cast(val, val.$parent()); + } + + return [].concat(val); + } + + // `populate('map')` may be an object if populating on a doc that hasn't + // been hydrated yet + if (val != null && + val.constructor.name === 'Object' && + // The intent here is we should only flatten the object if we expect + // to get a Map in the end. Avoid doing this for mixed types. + (schema == null || schema[schemaMixedSymbol] == null)) { + const ret = []; + for (const key of Object.keys(val)) { + ret.push(val[key]); + } + return ret; + } + // If doc has already been hydrated, e.g. `doc.populate('map').execPopulate()` + // then `val` will already be a map + if (val instanceof Map) { + return Array.from(val.values()); + } + + return val; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/populate/getSchemaTypes.js b/node_modules/mongoose/lib/helpers/populate/getSchemaTypes.js new file mode 100644 index 0000000..15660df --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/getSchemaTypes.js @@ -0,0 +1,198 @@ +'use strict'; + +/*! + * ignore + */ + +const Mixed = require('../../schema/mixed'); +const get = require('../get'); +const leanPopulateMap = require('./leanPopulateMap'); +const mpath = require('mpath'); + +const populateModelSymbol = require('../symbols').populateModelSymbol; + +/*! + * @param {Schema} schema + * @param {Object} doc POJO + * @param {string} path + */ + +module.exports = function getSchemaTypes(schema, doc, path) { + const pathschema = schema.path(path); + const topLevelDoc = doc; + + if (pathschema) { + return pathschema; + } + + function search(parts, schema, subdoc, nestedPath) { + let p = parts.length + 1; + let foundschema; + let trypath; + + while (p--) { + trypath = parts.slice(0, p).join('.'); + foundschema = schema.path(trypath); + + if (foundschema == null) { + continue; + } + + if (foundschema.caster) { + // array of Mixed? + if (foundschema.caster instanceof Mixed) { + return foundschema.caster; + } + + let schemas = null; + if (foundschema.schema != null && foundschema.schema.discriminators != null) { + const discriminators = foundschema.schema.discriminators; + const discriminatorKeyPath = trypath + '.' + + foundschema.schema.options.discriminatorKey; + const keys = subdoc ? mpath.get(discriminatorKeyPath, subdoc) || [] : []; + schemas = Object.keys(discriminators). + reduce(function(cur, discriminator) { + if (doc == null || keys.indexOf(discriminator) !== -1) { + cur.push(discriminators[discriminator]); + } + return cur; + }, []); + } + + // Now that we found the array, we need to check if there + // are remaining document paths to look up for casting. + // Also we need to handle array.$.path since schema.path + // doesn't work for that. + // If there is no foundschema.schema we are dealing with + // a path like array.$ + if (p !== parts.length && foundschema.schema) { + let ret; + if (parts[p] === '$') { + if (p + 1 === parts.length) { + // comments.$ + return foundschema; + } + // comments.$.comments.$.title + ret = search( + parts.slice(p + 1), + schema, + subdoc ? mpath.get(trypath, subdoc) : null, + nestedPath.concat(parts.slice(0, p)) + ); + if (ret) { + ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || + !foundschema.schema.$isSingleNested; + } + return ret; + } + + if (schemas != null && schemas.length > 0) { + ret = []; + for (const schema of schemas) { + const _ret = search( + parts.slice(p), + schema, + subdoc ? mpath.get(trypath, subdoc) : null, + nestedPath.concat(parts.slice(0, p)) + ); + if (_ret != null) { + _ret.$isUnderneathDocArray = _ret.$isUnderneathDocArray || + !foundschema.schema.$isSingleNested; + if (_ret.$isUnderneathDocArray) { + ret.$isUnderneathDocArray = true; + } + ret.push(_ret); + } + } + return ret; + } else { + ret = search( + parts.slice(p), + foundschema.schema, + subdoc ? mpath.get(trypath, subdoc) : null, + nestedPath.concat(parts.slice(0, p)) + ); + + if (ret) { + ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || + !foundschema.schema.$isSingleNested; + } + + return ret; + } + } else if (p !== parts.length && + foundschema.$isMongooseArray && + foundschema.casterConstructor.$isMongooseArray) { + // Nested arrays. Drill down to the bottom of the nested array. + // Ignore discriminators. + let type = foundschema; + while (type.$isMongooseArray && !type.$isMongooseDocumentArray) { + type = type.casterConstructor; + } + return search( + parts.slice(p), + type.schema, + null, + nestedPath.concat(parts.slice(0, p)) + ); + } + } + + const fullPath = nestedPath.concat([trypath]).join('.'); + if (topLevelDoc != null && topLevelDoc.$__ && topLevelDoc.populated(fullPath) && p < parts.length) { + const model = doc.$__.populated[fullPath].options[populateModelSymbol]; + if (model != null) { + const ret = search( + parts.slice(p), + model.schema, + subdoc ? mpath.get(trypath, subdoc) : null, + nestedPath.concat(parts.slice(0, p)) + ); + + if (ret) { + ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || + !model.schema.$isSingleNested; + } + + return ret; + } + } + + const _val = get(topLevelDoc, trypath); + if (_val != null) { + const model = Array.isArray(_val) && _val.length > 0 ? + leanPopulateMap.get(_val[0]) : + leanPopulateMap.get(_val); + // Populated using lean, `leanPopulateMap` value is the foreign model + const schema = model != null ? model.schema : null; + if (schema != null) { + const ret = search( + parts.slice(p), + schema, + subdoc ? mpath.get(trypath, subdoc) : null, + nestedPath.concat(parts.slice(0, p)) + ); + + if (ret) { + ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || + !schema.$isSingleNested; + } + + return ret; + } + } + + return foundschema; + } + } + + // look for arrays + const parts = path.split('.'); + for (let i = 0; i < parts.length; ++i) { + if (parts[i] === '$') { + // Re: gh-5628, because `schema.path()` doesn't take $ into account. + parts[i] = '0'; + } + } + return search(parts, schema, doc, []); +}; diff --git a/node_modules/mongoose/lib/helpers/populate/getVirtual.js b/node_modules/mongoose/lib/helpers/populate/getVirtual.js new file mode 100644 index 0000000..fc1641d --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/getVirtual.js @@ -0,0 +1,72 @@ +'use strict'; + +module.exports = getVirtual; + +/*! + * ignore + */ + +function getVirtual(schema, name) { + if (schema.virtuals[name]) { + return { virtual: schema.virtuals[name], path: void 0 }; + } + + const parts = name.split('.'); + let cur = ''; + let nestedSchemaPath = ''; + for (let i = 0; i < parts.length; ++i) { + cur += (cur.length > 0 ? '.' : '') + parts[i]; + if (schema.virtuals[cur]) { + if (i === parts.length - 1) { + return { virtual: schema.virtuals[cur], path: nestedSchemaPath }; + } + continue; + } + + if (schema.nested[cur]) { + continue; + } + + if (schema.paths[cur] && schema.paths[cur].schema) { + schema = schema.paths[cur].schema; + const rest = parts.slice(i + 1).join('.'); + + if (schema.virtuals[rest]) { + if (i === parts.length - 2) { + return { + virtual: schema.virtuals[rest], + nestedSchemaPath: [nestedSchemaPath, cur].filter(v => !!v).join('.') + }; + } + continue; + } + + if (i + 1 < parts.length && schema.discriminators) { + for (const key of Object.keys(schema.discriminators)) { + const res = getVirtual(schema.discriminators[key], rest); + if (res != null) { + const _path = [nestedSchemaPath, cur, res.nestedSchemaPath]. + filter(v => !!v).join('.'); + return { + virtual: res.virtual, + nestedSchemaPath: _path + }; + } + } + } + + nestedSchemaPath += (nestedSchemaPath.length > 0 ? '.' : '') + cur; + cur = ''; + continue; + } + + if (schema.discriminators) { + for (const discriminatorKey of Object.keys(schema.discriminators)) { + const virtualFromDiscriminator = getVirtual(schema.discriminators[discriminatorKey], name); + if (virtualFromDiscriminator) return virtualFromDiscriminator; + } + } + + return null; + } +} diff --git a/node_modules/mongoose/lib/helpers/populate/leanPopulateMap.js b/node_modules/mongoose/lib/helpers/populate/leanPopulateMap.js new file mode 100644 index 0000000..a333124 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/leanPopulateMap.js @@ -0,0 +1,7 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = new WeakMap();
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/populate/lookupLocalFields.js b/node_modules/mongoose/lib/helpers/populate/lookupLocalFields.js new file mode 100644 index 0000000..08ed763 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/lookupLocalFields.js @@ -0,0 +1,26 @@ +'use strict'; + +module.exports = function lookupLocalFields(cur, path, val) { + if (cur == null) { + return cur; + } + + if (cur._doc != null) { + cur = cur._doc; + } + + if (arguments.length >= 3) { + cur[path] = val; + return val; + } + + + // Support populating paths under maps using `map.$*.subpath` + if (path === '$*') { + return cur instanceof Map ? + Array.from(cur.values()) : + Object.keys(cur).map(key => cur[key]); + } + + return cur[path]; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/populate/normalizeRefPath.js b/node_modules/mongoose/lib/helpers/populate/normalizeRefPath.js new file mode 100644 index 0000000..233b741 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/normalizeRefPath.js @@ -0,0 +1,45 @@ +'use strict'; + +module.exports = function normalizeRefPath(refPath, doc, populatedPath) { + if (refPath == null) { + return refPath; + } + + if (typeof refPath === 'function') { + refPath = refPath.call(doc, doc, populatedPath); + } + + // If populated path has numerics, the end `refPath` should too. For example, + // if populating `a.0.b` instead of `a.b` and `b` has `refPath = a.c`, we + // should return `a.0.c` for the refPath. + const hasNumericProp = /(\.\d+$|\.\d+\.)/g; + + if (hasNumericProp.test(populatedPath)) { + const chunks = populatedPath.split(hasNumericProp); + + if (chunks[chunks.length - 1] === '') { + throw new Error('Can\'t populate individual element in an array'); + } + + let _refPath = ''; + let _remaining = refPath; + // 2nd, 4th, etc. will be numeric props. For example: `[ 'a', '.0.', 'b' ]` + for (let i = 0; i < chunks.length; i += 2) { + const chunk = chunks[i]; + if (_remaining.startsWith(chunk + '.')) { + _refPath += _remaining.substr(0, chunk.length) + chunks[i + 1]; + _remaining = _remaining.substr(chunk.length + 1); + } else if (i === chunks.length - 1) { + _refPath += _remaining; + _remaining = ''; + break; + } else { + throw new Error('Could not normalize ref path, chunk ' + chunk + ' not in populated path'); + } + } + + return _refPath; + } + + return refPath; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/populate/removeDeselectedForeignField.js b/node_modules/mongoose/lib/helpers/populate/removeDeselectedForeignField.js new file mode 100644 index 0000000..39b893a --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/removeDeselectedForeignField.js @@ -0,0 +1,31 @@ +'use strict'; + +const get = require('../get'); +const mpath = require('mpath'); +const parseProjection = require('../projection/parseProjection'); + +/*! + * ignore + */ + +module.exports = function removeDeselectedForeignField(foreignFields, options, docs) { + const projection = parseProjection(get(options, 'select', null), true) || + parseProjection(get(options, 'options.select', null), true); + + if (projection == null) { + return; + } + for (const foreignField of foreignFields) { + if (!projection.hasOwnProperty('-' + foreignField)) { + continue; + } + + for (const val of docs) { + if (val.$__ != null) { + mpath.unset(foreignField, val._doc); + } else { + mpath.unset(foreignField, val); + } + } + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/populate/validateRef.js b/node_modules/mongoose/lib/helpers/populate/validateRef.js new file mode 100644 index 0000000..9dc2b6f --- /dev/null +++ b/node_modules/mongoose/lib/helpers/populate/validateRef.js @@ -0,0 +1,19 @@ +'use strict'; + +const MongooseError = require('../../error/mongooseError'); +const util = require('util'); + +module.exports = validateRef; + +function validateRef(ref, path) { + if (typeof ref === 'string') { + return; + } + + if (typeof ref === 'function') { + return; + } + + throw new MongooseError('Invalid ref at path "' + path + '". Got ' + + util.inspect(ref, { depth: 0 })); +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/printJestWarning.js b/node_modules/mongoose/lib/helpers/printJestWarning.js new file mode 100644 index 0000000..eb3a8eb --- /dev/null +++ b/node_modules/mongoose/lib/helpers/printJestWarning.js @@ -0,0 +1,8 @@ +'use strict'; + +if (typeof jest !== 'undefined' && typeof window !== 'undefined') { + console.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' + + 'with Jest\'s default jsdom test environment. Please make sure you read ' + + 'Mongoose\'s docs on configuring Jest to test Node.js apps: ' + + 'http://mongoosejs.com/docs/jest.html'); +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/projection/isDefiningProjection.js b/node_modules/mongoose/lib/helpers/projection/isDefiningProjection.js new file mode 100644 index 0000000..67dfb39 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/projection/isDefiningProjection.js @@ -0,0 +1,18 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = function isDefiningProjection(val) { + if (val == null) { + // `undefined` or `null` become exclusive projections + return true; + } + if (typeof val === 'object') { + // Only cases where a value does **not** define whether the whole projection + // is inclusive or exclusive are `$meta` and `$slice`. + return !('$meta' in val) && !('$slice' in val); + } + return true; +}; diff --git a/node_modules/mongoose/lib/helpers/projection/isExclusive.js b/node_modules/mongoose/lib/helpers/projection/isExclusive.js new file mode 100644 index 0000000..8c64bc5 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/projection/isExclusive.js @@ -0,0 +1,28 @@ +'use strict'; + +const isDefiningProjection = require('./isDefiningProjection'); + +/*! + * ignore + */ + +module.exports = function isExclusive(projection) { + const keys = Object.keys(projection); + let ki = keys.length; + let exclude = null; + + if (ki === 1 && keys[0] === '_id') { + exclude = !!projection[keys[ki]]; + } else { + while (ki--) { + // Does this projection explicitly define inclusion/exclusion? + // Explicitly avoid `$meta` and `$slice` + if (keys[ki] !== '_id' && isDefiningProjection(projection[keys[ki]])) { + exclude = !projection[keys[ki]]; + break; + } + } + } + + return exclude; +}; diff --git a/node_modules/mongoose/lib/helpers/projection/isInclusive.js b/node_modules/mongoose/lib/helpers/projection/isInclusive.js new file mode 100644 index 0000000..098309f --- /dev/null +++ b/node_modules/mongoose/lib/helpers/projection/isInclusive.js @@ -0,0 +1,34 @@ +'use strict'; + +const isDefiningProjection = require('./isDefiningProjection'); + +/*! + * ignore + */ + +module.exports = function isInclusive(projection) { + if (projection == null) { + return false; + } + + const props = Object.keys(projection); + const numProps = props.length; + if (numProps === 0) { + return false; + } + + for (let i = 0; i < numProps; ++i) { + const prop = props[i]; + // Plus paths can't define the projection (see gh-7050) + if (prop.startsWith('+')) { + continue; + } + // If field is truthy (1, true, etc.) and not an object, then this + // projection must be inclusive. If object, assume its $meta, $slice, etc. + if (isDefiningProjection(projection[prop]) && !!projection[prop]) { + return true; + } + } + + return false; +}; diff --git a/node_modules/mongoose/lib/helpers/projection/isPathExcluded.js b/node_modules/mongoose/lib/helpers/projection/isPathExcluded.js new file mode 100644 index 0000000..fc2592c --- /dev/null +++ b/node_modules/mongoose/lib/helpers/projection/isPathExcluded.js @@ -0,0 +1,35 @@ +'use strict'; + +const isDefiningProjection = require('./isDefiningProjection'); + +/*! + * Determines if `path` is excluded by `projection` + * + * @param {Object} projection + * @param {string} path + * @return {Boolean} + */ + +module.exports = function isPathExcluded(projection, path) { + if (path === '_id') { + return projection._id === 0; + } + + const paths = Object.keys(projection); + let type = null; + + for (const _path of paths) { + if (isDefiningProjection(projection[_path])) { + type = projection[path] === 1 ? 'inclusive' : 'exclusive'; + break; + } + } + + if (type === 'inclusive') { + return projection[path] !== 1; + } + if (type === 'exclusive') { + return projection[path] === 0; + } + return false; +}; diff --git a/node_modules/mongoose/lib/helpers/projection/isPathSelectedInclusive.js b/node_modules/mongoose/lib/helpers/projection/isPathSelectedInclusive.js new file mode 100644 index 0000000..8a05fc9 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/projection/isPathSelectedInclusive.js @@ -0,0 +1,28 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = function isPathSelectedInclusive(fields, path) { + const chunks = path.split('.'); + let cur = ''; + let j; + let keys; + let numKeys; + for (let i = 0; i < chunks.length; ++i) { + cur += cur.length ? '.' : '' + chunks[i]; + if (fields[cur]) { + keys = Object.keys(fields); + numKeys = keys.length; + for (j = 0; j < numKeys; ++j) { + if (keys[i].indexOf(cur + '.') === 0 && keys[i].indexOf(path) !== 0) { + continue; + } + } + return true; + } + } + + return false; +}; diff --git a/node_modules/mongoose/lib/helpers/projection/parseProjection.js b/node_modules/mongoose/lib/helpers/projection/parseProjection.js new file mode 100644 index 0000000..d2a44b1 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/projection/parseProjection.js @@ -0,0 +1,33 @@ +'use strict'; + +/** + * Convert a string or array into a projection object, retaining all + * `-` and `+` paths. + */ + +module.exports = function parseProjection(v, retainMinusPaths) { + const type = typeof v; + + if (type === 'string') { + v = v.split(/\s+/); + } + if (!Array.isArray(v) && Object.prototype.toString.call(v) !== '[object Arguments]') { + return v; + } + + const len = v.length; + const ret = {}; + for (let i = 0; i < len; ++i) { + let field = v[i]; + if (!field) { + continue; + } + const include = '-' == field[0] ? 0 : 1; + if (!retainMinusPaths && include === 0) { + field = field.substring(1); + } + ret[field] = include; + } + + return ret; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/promiseOrCallback.js b/node_modules/mongoose/lib/helpers/promiseOrCallback.js new file mode 100644 index 0000000..a1aff55 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/promiseOrCallback.js @@ -0,0 +1,45 @@ +'use strict'; + +const PromiseProvider = require('../promise_provider'); + +const emittedSymbol = Symbol.for('mongoose:emitted'); + +module.exports = function promiseOrCallback(callback, fn, ee) { + if (typeof callback === 'function') { + return fn(function(error) { + if (error != null) { + if (ee != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) { + error[emittedSymbol] = true; + ee.emit('error', error); + } + try { + callback(error); + } catch (error) { + return process.nextTick(() => { + throw error; + }); + } + return; + } + callback.apply(this, arguments); + }); + } + + const Promise = PromiseProvider.get(); + + return new Promise((resolve, reject) => { + fn(function(error, res) { + if (error != null) { + if (ee != null && ee.listeners('error').length > 0 && !error[emittedSymbol]) { + error[emittedSymbol] = true; + ee.emit('error', error); + } + return reject(error); + } + if (arguments.length > 2) { + return resolve(Array.prototype.slice.call(arguments, 1)); + } + resolve(res); + }); + }); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/query/applyGlobalMaxTimeMS.js b/node_modules/mongoose/lib/helpers/query/applyGlobalMaxTimeMS.js new file mode 100644 index 0000000..cb49260 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/applyGlobalMaxTimeMS.js @@ -0,0 +1,15 @@ +'use strict'; + +const utils = require('../../utils'); + +module.exports = function applyGlobalMaxTimeMS(options, model) { + if (utils.hasUserDefinedProperty(options, 'maxTimeMS')) { + return; + } + + if (utils.hasUserDefinedProperty(model.db.options, 'maxTimeMS')) { + options.maxTimeMS = model.db.options.maxTimeMS; + } else if (utils.hasUserDefinedProperty(model.base.options, 'maxTimeMS')) { + options.maxTimeMS = model.base.options.maxTimeMS; + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/query/applyQueryMiddleware.js b/node_modules/mongoose/lib/helpers/query/applyQueryMiddleware.js new file mode 100644 index 0000000..a08baf8 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/applyQueryMiddleware.js @@ -0,0 +1,90 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = applyQueryMiddleware; + +/*! + * ignore + */ + +applyQueryMiddleware.middlewareFunctions = [ + 'count', + 'countDocuments', + 'deleteMany', + 'deleteOne', + 'distinct', + 'estimatedDocumentCount', + 'find', + 'findOne', + 'findOneAndDelete', + 'findOneAndRemove', + 'findOneAndReplace', + 'findOneAndUpdate', + 'remove', + 'replaceOne', + 'update', + 'updateMany', + 'updateOne', + 'validate' +]; + +/*! + * Apply query middleware + * + * @param {Query} query constructor + * @param {Model} model + */ + +function applyQueryMiddleware(Query, model) { + const kareemOptions = { + useErrorHandlers: true, + numCallbackParams: 1, + nullResultByDefault: true + }; + + const middleware = model.hooks.filter(hook => { + const contexts = _getContexts(hook); + if (hook.name === 'updateOne') { + return contexts.query == null || !!contexts.query; + } + if (hook.name === 'deleteOne') { + return !!contexts.query || Object.keys(contexts).length === 0; + } + if (hook.name === 'validate' || hook.name === 'remove') { + return !!contexts.query; + } + return true; + }); + + // `update()` thunk has a different name because `_update` was already taken + Query.prototype._execUpdate = middleware.createWrapper('update', + Query.prototype._execUpdate, null, kareemOptions); + // `distinct()` thunk has a different name because `_distinct` was already taken + Query.prototype.__distinct = middleware.createWrapper('distinct', + Query.prototype.__distinct, null, kareemOptions); + + // `validate()` doesn't have a thunk because it doesn't execute a query. + Query.prototype.validate = middleware.createWrapper('validate', + Query.prototype.validate, null, kareemOptions); + + applyQueryMiddleware.middlewareFunctions. + filter(v => v !== 'update' && v !== 'distinct' && v !== 'validate'). + forEach(fn => { + Query.prototype[`_${fn}`] = middleware.createWrapper(fn, + Query.prototype[`_${fn}`], null, kareemOptions); + }); +} + +function _getContexts(hook) { + const ret = {}; + if (hook.hasOwnProperty('query')) { + ret.query = hook.query; + } + if (hook.hasOwnProperty('document')) { + ret.document = hook.document; + } + return ret; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/query/castFilterPath.js b/node_modules/mongoose/lib/helpers/query/castFilterPath.js new file mode 100644 index 0000000..74ff1c2 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/castFilterPath.js @@ -0,0 +1,55 @@ +'use strict'; + +const isOperator = require('./isOperator'); + +module.exports = function castFilterPath(query, schematype, val) { + const ctx = query; + const any$conditionals = Object.keys(val).some(isOperator); + + if (!any$conditionals) { + return schematype.castForQueryWrapper({ + val: val, + context: ctx + }); + } + + const ks = Object.keys(val); + + let k = ks.length; + + while (k--) { + const $cond = ks[k]; + const nested = val[$cond]; + + if ($cond === '$not') { + if (nested && schematype && !schematype.caster) { + const _keys = Object.keys(nested); + if (_keys.length && isOperator(_keys[0])) { + for (const key in nested) { + nested[key] = schematype.castForQueryWrapper({ + $conditional: key, + val: nested[key], + context: ctx + }); + } + } else { + val[$cond] = schematype.castForQueryWrapper({ + $conditional: $cond, + val: nested, + context: ctx + }); + } + continue; + } + // cast(schematype.caster ? schematype.caster.schema : schema, nested, options, context); + } else { + val[$cond] = schematype.castForQueryWrapper({ + $conditional: $cond, + val: nested, + context: ctx + }); + } + } + + return val; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/query/castUpdate.js b/node_modules/mongoose/lib/helpers/query/castUpdate.js new file mode 100644 index 0000000..bf60e5d --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/castUpdate.js @@ -0,0 +1,538 @@ +'use strict'; + +const CastError = require('../../error/cast'); +const MongooseError = require('../../error/mongooseError'); +const StrictModeError = require('../../error/strict'); +const ValidationError = require('../../error/validation'); +const castNumber = require('../../cast/number'); +const cast = require('../../cast'); +const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath'); +const handleImmutable = require('./handleImmutable'); +const moveImmutableProperties = require('../update/moveImmutableProperties'); +const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol; +const utils = require('../../utils'); + +/*! + * Casts an update op based on the given schema + * + * @param {Schema} schema + * @param {Object} obj + * @param {Object} options + * @param {Boolean} [options.overwrite] defaults to false + * @param {Boolean|String} [options.strict] defaults to true + * @param {Query} context passed to setters + * @return {Boolean} true iff the update is non-empty + */ + +module.exports = function castUpdate(schema, obj, options, context, filter) { + if (obj == null) { + return undefined; + } + options = options || {}; + + // Update pipeline + if (Array.isArray(obj)) { + const len = obj.length; + for (let i = 0; i < len; ++i) { + const ops = Object.keys(obj[i]); + for (const op of ops) { + obj[i][op] = castPipelineOperator(op, obj[i][op]); + } + } + return obj; + } + + if (options.upsert) { + moveImmutableProperties(schema, obj, context); + } + + const ops = Object.keys(obj); + let i = ops.length; + const ret = {}; + let val; + let hasDollarKey = false; + const overwrite = options.overwrite; + + filter = filter || {}; + + while (i--) { + const op = ops[i]; + // if overwrite is set, don't do any of the special $set stuff + if (op[0] !== '$' && !overwrite) { + // fix up $set sugar + if (!ret.$set) { + if (obj.$set) { + ret.$set = obj.$set; + } else { + ret.$set = {}; + } + } + ret.$set[op] = obj[op]; + ops.splice(i, 1); + if (!~ops.indexOf('$set')) ops.push('$set'); + } else if (op === '$set') { + if (!ret.$set) { + ret[op] = obj[op]; + } + } else { + ret[op] = obj[op]; + } + } + + // cast each value + i = ops.length; + + while (i--) { + const op = ops[i]; + val = ret[op]; + hasDollarKey = hasDollarKey || op.startsWith('$'); + + if (val && + typeof val === 'object' && + !Buffer.isBuffer(val) && + (!overwrite || hasDollarKey)) { + walkUpdatePath(schema, val, op, options, context, filter); + } else if (overwrite && ret && typeof ret === 'object') { + walkUpdatePath(schema, ret, '$set', options, context, filter); + } else { + const msg = 'Invalid atomic update value for ' + op + '. ' + + 'Expected an object, received ' + typeof val; + throw new Error(msg); + } + + if (op.startsWith('$') && utils.isEmptyObject(val)) { + delete ret[op]; + } + } + + if (Object.keys(ret).length === 0 && + options.upsert && + Object.keys(filter).length > 0) { + // Trick the driver into allowing empty upserts to work around + // https://github.com/mongodb/node-mongodb-native/pull/2490 + return { $setOnInsert: filter }; + } + + return ret; +}; + +/*! + * ignore + */ + +function castPipelineOperator(op, val) { + if (op === '$unset') { + if (!Array.isArray(val) || val.find(v => typeof v !== 'string')) { + throw new MongooseError('Invalid $unset in pipeline, must be ' + + 'an array of strings'); + } + return val; + } + if (op === '$project') { + if (val == null || typeof val !== 'object') { + throw new MongooseError('Invalid $project in pipeline, must be an object'); + } + return val; + } + if (op === '$addFields' || op === '$set') { + if (val == null || typeof val !== 'object') { + throw new MongooseError('Invalid ' + op + ' in pipeline, must be an object'); + } + return val; + } else if (op === '$replaceRoot' || op === '$replaceWith') { + if (val == null || typeof val !== 'object') { + throw new MongooseError('Invalid ' + op + ' in pipeline, must be an object'); + } + return val; + } + + throw new MongooseError('Invalid update pipeline operator: "' + op + '"'); +} + +/*! + * Walk each path of obj and cast its values + * according to its schema. + * + * @param {Schema} schema + * @param {Object} obj - part of a query + * @param {String} op - the atomic operator ($pull, $set, etc) + * @param {Object} options + * @param {Boolean|String} [options.strict] + * @param {Boolean} [options.omitUndefined] + * @param {Query} context + * @param {String} pref - path prefix (internal only) + * @return {Bool} true if this path has keys to update + * @api private + */ + +function walkUpdatePath(schema, obj, op, options, context, filter, pref) { + const strict = options.strict; + const prefix = pref ? pref + '.' : ''; + const keys = Object.keys(obj); + let i = keys.length; + let hasKeys = false; + let schematype; + let key; + let val; + + let aggregatedError = null; + + let useNestedStrict; + if (options.useNestedStrict === undefined) { + useNestedStrict = schema.options.useNestedStrict; + } else { + useNestedStrict = options.useNestedStrict; + } + + while (i--) { + key = keys[i]; + val = obj[key]; + + // `$pull` is special because we need to cast the RHS as a query, not as + // an update. + if (op === '$pull') { + schematype = schema._getSchema(prefix + key); + if (schematype != null && schematype.schema != null) { + obj[key] = cast(schematype.schema, obj[key], options, context); + hasKeys = true; + continue; + } + } + + if (val && val.constructor.name === 'Object') { + // watch for embedded doc schemas + schematype = schema._getSchema(prefix + key); + + if (op !== '$setOnInsert' && + handleImmutable(schematype, strict, obj, key, prefix + key, context)) { + continue; + } + + if (schematype && schematype.caster && op in castOps) { + // embedded doc schema + if ('$each' in val) { + hasKeys = true; + try { + obj[key] = { + $each: castUpdateVal(schematype, val.$each, op, key, context, prefix + key) + }; + } catch (error) { + aggregatedError = _handleCastError(error, context, key, aggregatedError); + } + + if (val.$slice != null) { + obj[key].$slice = val.$slice | 0; + } + + if (val.$sort) { + obj[key].$sort = val.$sort; + } + + if (val.$position != null) { + obj[key].$position = castNumber(val.$position); + } + } else { + if (schematype != null && schematype.$isSingleNested) { + // Special case to make sure `strict` bubbles down correctly to + // single nested re: gh-8735 + let _strict = strict; + if (useNestedStrict !== false && schematype.schema.options.hasOwnProperty('strict')) { + _strict = schematype.schema.options.strict; + } else if (useNestedStrict === false) { + _strict = schema.options.strict; + } + try { + obj[key] = schematype.castForQuery(val, context, { strict: _strict }); + } catch (error) { + aggregatedError = _handleCastError(error, context, key, aggregatedError); + } + } else { + try { + obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key); + } catch (error) { + aggregatedError = _handleCastError(error, context, key, aggregatedError); + } + } + + if (options.omitUndefined && obj[key] === void 0) { + delete obj[key]; + continue; + } + + hasKeys = true; + } + } else if ((op === '$currentDate') || (op in castOps && schematype)) { + // $currentDate can take an object + try { + obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key); + } catch (error) { + aggregatedError = _handleCastError(error, context, key, aggregatedError); + } + + if (options.omitUndefined && obj[key] === void 0) { + delete obj[key]; + continue; + } + + hasKeys = true; + } else { + const pathToCheck = (prefix + key); + const v = schema._getPathType(pathToCheck); + let _strict = strict; + if (useNestedStrict && + v && + v.schema && + 'strict' in v.schema.options) { + _strict = v.schema.options.strict; + } + + if (v.pathType === 'undefined') { + if (_strict === 'throw') { + throw new StrictModeError(pathToCheck); + } else if (_strict) { + delete obj[key]; + continue; + } + } + + // gh-2314 + // we should be able to set a schema-less field + // to an empty object literal + hasKeys |= walkUpdatePath(schema, val, op, options, context, filter, prefix + key) || + (utils.isObject(val) && Object.keys(val).length === 0); + } + } else { + const checkPath = (key === '$each' || key === '$or' || key === '$and' || key === '$in') ? + pref : prefix + key; + schematype = schema._getSchema(checkPath); + + // You can use `$setOnInsert` with immutable keys + if (op !== '$setOnInsert' && + handleImmutable(schematype, strict, obj, key, prefix + key, context)) { + continue; + } + + let pathDetails = schema._getPathType(checkPath); + + // If no schema type, check for embedded discriminators because the + // filter or update may imply an embedded discriminator type. See #8378 + if (schematype == null) { + const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, checkPath); + if (_res.schematype != null) { + schematype = _res.schematype; + pathDetails = _res.type; + } + } + + let isStrict = strict; + if (useNestedStrict && + pathDetails && + pathDetails.schema && + 'strict' in pathDetails.schema.options) { + isStrict = pathDetails.schema.options.strict; + } + + const skip = isStrict && + !schematype && + !/real|nested/.test(pathDetails.pathType); + + if (skip) { + // Even if strict is `throw`, avoid throwing an error because of + // virtuals because of #6731 + if (isStrict === 'throw' && schema.virtuals[checkPath] == null) { + throw new StrictModeError(prefix + key); + } else { + delete obj[key]; + } + } else { + // gh-1845 temporary fix: ignore $rename. See gh-3027 for tracking + // improving this. + if (op === '$rename') { + hasKeys = true; + continue; + } + + try { + obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key); + } catch (error) { + aggregatedError = _handleCastError(error, context, key, aggregatedError); + } + + if (Array.isArray(obj[key]) && (op === '$addToSet' || op === '$push') && key !== '$each') { + if (schematype && schematype.caster && !schematype.caster.$isMongooseArray) { + obj[key] = { $each: obj[key] }; + } + } + + if (options.omitUndefined && obj[key] === void 0) { + delete obj[key]; + continue; + } + + hasKeys = true; + } + } + } + + if (aggregatedError != null) { + throw aggregatedError; + } + + return hasKeys; +} + +/*! + * ignore + */ + +function _handleCastError(error, query, key, aggregatedError) { + if (typeof query !== 'object' || !query.options.multipleCastError) { + throw error; + } + aggregatedError = aggregatedError || new ValidationError(); + aggregatedError.addError(key, error); + return aggregatedError; +} + +/*! + * These operators should be cast to numbers instead + * of their path schema type. + */ + +const numberOps = { + $pop: 1, + $inc: 1 +}; + +/*! + * These ops require no casting because the RHS doesn't do anything. + */ + +const noCastOps = { + $unset: 1 +}; + +/*! + * These operators require casting docs + * to real Documents for Update operations. + */ + +const castOps = { + $push: 1, + $addToSet: 1, + $set: 1, + $setOnInsert: 1 +}; + +/*! + * ignore + */ + +const overwriteOps = { + $set: 1, + $setOnInsert: 1 +}; + +/*! + * Casts `val` according to `schema` and atomic `op`. + * + * @param {SchemaType} schema + * @param {Object} val + * @param {String} op - the atomic operator ($pull, $set, etc) + * @param {String} $conditional + * @param {Query} context + * @api private + */ + +function castUpdateVal(schema, val, op, $conditional, context, path) { + if (!schema) { + // non-existing schema path + if (op in numberOps) { + try { + return castNumber(val); + } catch (err) { + throw new CastError('number', val, path); + } + } + return val; + } + + const cond = schema.caster && op in castOps && + (utils.isObject(val) || Array.isArray(val)); + if (cond && !overwriteOps[op]) { + // Cast values for ops that add data to MongoDB. + // Ensures embedded documents get ObjectIds etc. + let schemaArrayDepth = 0; + let cur = schema; + while (cur.$isMongooseArray) { + ++schemaArrayDepth; + cur = cur.caster; + } + let arrayDepth = 0; + let _val = val; + while (Array.isArray(_val)) { + ++arrayDepth; + _val = _val[0]; + } + + const additionalNesting = schemaArrayDepth - arrayDepth; + while (arrayDepth < schemaArrayDepth) { + val = [val]; + ++arrayDepth; + } + + let tmp = schema.applySetters(Array.isArray(val) ? val : [val], context); + + for (let i = 0; i < additionalNesting; ++i) { + tmp = tmp[0]; + } + return tmp; + } + + if (op in noCastOps) { + return val; + } + if (op in numberOps) { + // Null and undefined not allowed for $pop, $inc + if (val == null) { + throw new CastError('number', val, schema.path); + } + if (op === '$inc') { + // Support `$inc` with long, int32, etc. (gh-4283) + return schema.castForQueryWrapper({ + val: val, + context: context + }); + } + try { + return castNumber(val); + } catch (error) { + throw new CastError('number', val, schema.path); + } + } + if (op === '$currentDate') { + if (typeof val === 'object') { + return { $type: val.$type }; + } + return Boolean(val); + } + + if (/^\$/.test($conditional)) { + return schema.castForQueryWrapper({ + $conditional: $conditional, + val: val, + context: context + }); + } + + if (overwriteOps[op]) { + return schema.castForQueryWrapper({ + val: val, + context: context, + $skipQueryCastForUpdate: val != null && schema.$isMongooseArray && schema.$fullPath != null && !schema.$fullPath.match(/\d+$/), + $applySetters: schema[schemaMixedSymbol] != null + }); + } + + return schema.castForQueryWrapper({ val: val, context: context }); +} diff --git a/node_modules/mongoose/lib/helpers/query/completeMany.js b/node_modules/mongoose/lib/helpers/query/completeMany.js new file mode 100644 index 0000000..aabe596 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/completeMany.js @@ -0,0 +1,47 @@ +'use strict'; + +const helpers = require('../../queryhelpers'); + +module.exports = completeMany; + +/*! + * Given a model and an array of docs, hydrates all the docs to be instances + * of the model. Used to initialize docs returned from the db from `find()` + * + * @param {Model} model + * @param {Array} docs + * @param {Object} fields the projection used, including `select` from schemas + * @param {Object} userProvidedFields the user-specified projection + * @param {Object} opts + * @param {Array} [opts.populated] + * @param {ClientSession} [opts.session] + * @param {Function} callback + */ + +function completeMany(model, docs, fields, userProvidedFields, opts, callback) { + const arr = []; + let count = docs.length; + const len = count; + let error = null; + + function init(_error) { + if (_error != null) { + error = error || _error; + } + if (error != null) { + --count || process.nextTick(() => callback(error)); + return; + } + --count || process.nextTick(() => callback(error, arr)); + } + + for (let i = 0; i < len; ++i) { + arr[i] = helpers.createModel(model, docs[i], fields, userProvidedFields); + try { + arr[i].init(docs[i], opts, init); + } catch (error) { + init(error); + } + arr[i].$session(opts.session); + } +} diff --git a/node_modules/mongoose/lib/helpers/query/getEmbeddedDiscriminatorPath.js b/node_modules/mongoose/lib/helpers/query/getEmbeddedDiscriminatorPath.js new file mode 100644 index 0000000..395ea75 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/getEmbeddedDiscriminatorPath.js @@ -0,0 +1,68 @@ +'use strict'; + +const cleanPositionalOperators = require('../schema/cleanPositionalOperators'); +const get = require('../get'); +const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue'); + +/*! + * Like `schema.path()`, except with a document, because impossible to + * determine path type without knowing the embedded discriminator key. + */ + +module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, path) { + const parts = path.split('.'); + let schematype = null; + let type = 'adhocOrUndefined'; + + filter = filter || {}; + update = update || {}; + + for (let i = 0; i < parts.length; ++i) { + const subpath = cleanPositionalOperators(parts.slice(0, i + 1).join('.')); + schematype = schema.path(subpath); + if (schematype == null) { + continue; + } + + type = schema.pathType(subpath); + if ((schematype.$isSingleNested || schematype.$isMongooseDocumentArrayElement) && + schematype.schema.discriminators != null) { + const key = get(schematype, 'schema.options.discriminatorKey'); + const discriminatorValuePath = subpath + '.' + key; + const discriminatorFilterPath = + discriminatorValuePath.replace(/\.\d+\./, '.'); + let discriminatorKey = null; + + if (discriminatorValuePath in filter) { + discriminatorKey = filter[discriminatorValuePath]; + } + if (discriminatorFilterPath in filter) { + discriminatorKey = filter[discriminatorFilterPath]; + } + const wrapperPath = subpath.replace(/\.\d+$/, ''); + if (schematype.$isMongooseDocumentArrayElement && + get(filter[wrapperPath], '$elemMatch.' + key) != null) { + discriminatorKey = filter[wrapperPath].$elemMatch[key]; + } + + if (discriminatorValuePath in update) { + discriminatorKey = update[discriminatorValuePath]; + } + + if (discriminatorKey == null) { + continue; + } + + const discriminatorSchema = getDiscriminatorByValue(schematype.caster, discriminatorKey).schema; + + const rest = parts.slice(i + 1).join('.'); + schematype = discriminatorSchema.path(rest); + if (schematype != null) { + type = discriminatorSchema._getPathType(rest); + break; + } + } + } + + return { type: type, schematype: schematype }; +}; diff --git a/node_modules/mongoose/lib/helpers/query/handleImmutable.js b/node_modules/mongoose/lib/helpers/query/handleImmutable.js new file mode 100644 index 0000000..22adb3c --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/handleImmutable.js @@ -0,0 +1,28 @@ +'use strict'; + +const StrictModeError = require('../../error/strict'); + +module.exports = function handleImmutable(schematype, strict, obj, key, fullPath, ctx) { + if (schematype == null || !schematype.options || !schematype.options.immutable) { + return false; + } + let immutable = schematype.options.immutable; + + if (typeof immutable === 'function') { + immutable = immutable.call(ctx, ctx); + } + if (!immutable) { + return false; + } + + if (strict === false) { + return false; + } + if (strict === 'throw') { + throw new StrictModeError(null, + `Field ${fullPath} is immutable and strict = 'throw'`); + } + + delete obj[key]; + return true; +}; diff --git a/node_modules/mongoose/lib/helpers/query/hasDollarKeys.js b/node_modules/mongoose/lib/helpers/query/hasDollarKeys.js new file mode 100644 index 0000000..958e1c9 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/hasDollarKeys.js @@ -0,0 +1,19 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = function(obj) { + if (obj == null) { + return false; + } + const keys = Object.keys(obj); + const len = keys.length; + for (let i = 0; i < len; ++i) { + if (keys[i].startsWith('$')) { + return true; + } + } + return false; +}; diff --git a/node_modules/mongoose/lib/helpers/query/isOperator.js b/node_modules/mongoose/lib/helpers/query/isOperator.js new file mode 100644 index 0000000..3b98139 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/isOperator.js @@ -0,0 +1,11 @@ +'use strict'; + +const specialKeys = new Set([ + '$ref', + '$id', + '$db' +]); + +module.exports = function isOperator(path) { + return path.startsWith('$') && !specialKeys.has(path); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/query/selectPopulatedFields.js b/node_modules/mongoose/lib/helpers/query/selectPopulatedFields.js new file mode 100644 index 0000000..0653f18 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/selectPopulatedFields.js @@ -0,0 +1,46 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = function selectPopulatedFields(query) { + const opts = query._mongooseOptions; + + if (opts.populate != null) { + const paths = Object.keys(opts.populate); + const userProvidedFields = query._userProvidedFields || {}; + if (query.selectedInclusively()) { + for (const path of paths) { + if (!isPathInFields(userProvidedFields, path)) { + query.select(path); + } else if (userProvidedFields[path] === 0) { + delete query._fields[path]; + } + } + } else if (query.selectedExclusively()) { + for (const path of paths) { + if (userProvidedFields[path] == null) { + delete query._fields[path]; + } + } + } + } +}; + +/*! + * ignore + */ + +function isPathInFields(userProvidedFields, path) { + const pieces = path.split('.'); + const len = pieces.length; + let cur = pieces[0]; + for (let i = 1; i < len; ++i) { + if (userProvidedFields[cur] != null) { + return true; + } + cur += '.' + pieces[i]; + } + return userProvidedFields[cur] != null; +} diff --git a/node_modules/mongoose/lib/helpers/query/wrapThunk.js b/node_modules/mongoose/lib/helpers/query/wrapThunk.js new file mode 100644 index 0000000..0005c33 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/query/wrapThunk.js @@ -0,0 +1,18 @@ +'use strict'; + +/*! + * A query thunk is the function responsible for sending the query to MongoDB, + * like `Query#_findOne()` or `Query#_execUpdate()`. The `Query#exec()` function + * calls a thunk. The term "thunk" here is the traditional Node.js definition: + * a function that takes exactly 1 parameter, a callback. + * + * This function defines common behavior for all query thunks. + */ + +module.exports = function wrapThunk(fn) { + return function _wrappedThunk(cb) { + ++this._executionCount; + + fn.call(this, cb); + }; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/schema/addAutoId.js b/node_modules/mongoose/lib/helpers/schema/addAutoId.js new file mode 100644 index 0000000..11a1f23 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/addAutoId.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = function addAutoId(schema) { + const _obj = { _id: { auto: true } }; + _obj._id[schema.options.typeKey] = 'ObjectId'; + schema.add(_obj); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/schema/applyPlugins.js b/node_modules/mongoose/lib/helpers/schema/applyPlugins.js new file mode 100644 index 0000000..f1daf40 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/applyPlugins.js @@ -0,0 +1,44 @@ +'use strict'; + +module.exports = function applyPlugins(schema, plugins, options, cacheKey) { + if (schema[cacheKey]) { + return; + } + schema[cacheKey] = true; + + if (!options || !options.skipTopLevel) { + for (const plugin of plugins) { + schema.plugin(plugin[0], plugin[1]); + } + } + + options = Object.assign({}, options); + delete options.skipTopLevel; + + if (options.applyPluginsToChildSchemas !== false) { + for (const path of Object.keys(schema.paths)) { + const type = schema.paths[path]; + if (type.schema != null) { + applyPlugins(type.schema, plugins, options, cacheKey); + + // Recompile schema because plugins may have changed it, see gh-7572 + type.caster.prototype.$__setSchema(type.schema); + } + } + } + + const discriminators = schema.discriminators; + if (discriminators == null) { + return; + } + + const applyPluginsToDiscriminators = options.applyPluginsToDiscriminators; + + const keys = Object.keys(discriminators); + for (const discriminatorKey of keys) { + const discriminatorSchema = discriminators[discriminatorKey]; + + applyPlugins(discriminatorSchema, plugins, + { skipTopLevel: !applyPluginsToDiscriminators }, cacheKey); + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/schema/applyWriteConcern.js b/node_modules/mongoose/lib/helpers/schema/applyWriteConcern.js new file mode 100644 index 0000000..168156d --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/applyWriteConcern.js @@ -0,0 +1,16 @@ +'use strict'; + +const get = require('../get'); + +module.exports = function applyWriteConcern(schema, options) { + const writeConcern = get(schema, 'options.writeConcern', {}); + if (!('w' in options) && writeConcern.w != null) { + options.w = writeConcern.w; + } + if (!('j' in options) && writeConcern.j != null) { + options.j = writeConcern.j; + } + if (!('wtimeout' in options) && writeConcern.wtimeout != null) { + options.wtimeout = writeConcern.wtimeout; + } +}; diff --git a/node_modules/mongoose/lib/helpers/schema/cleanPositionalOperators.js b/node_modules/mongoose/lib/helpers/schema/cleanPositionalOperators.js new file mode 100644 index 0000000..b467be4 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/cleanPositionalOperators.js @@ -0,0 +1,12 @@ +'use strict'; + +/** + * For consistency's sake, we replace positional operator `$` and array filters + * `$[]` and `$[foo]` with `0` when looking up schema paths. + */ + +module.exports = function cleanPositionalOperators(path) { + return path. + replace(/\.\$(\[[^\]]*\])?\./g, '.0.'). + replace(/\.(\[[^\]]*\])?\$$/g, '.0'); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/schema/getIndexes.js b/node_modules/mongoose/lib/helpers/schema/getIndexes.js new file mode 100644 index 0000000..be907db --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/getIndexes.js @@ -0,0 +1,155 @@ +'use strict'; + +const get = require('../get'); +const helperIsObject = require('../isObject'); + +/*! + * Gather all indexes defined in the schema, including single nested, + * document arrays, and embedded discriminators. + */ + +module.exports = function getIndexes(schema) { + let indexes = []; + const schemaStack = new WeakMap(); + const indexTypes = schema.constructor.indexTypes; + const indexByName = new Map(); + + collectIndexes(schema); + return indexes; + + function collectIndexes(schema, prefix, baseSchema) { + // Ignore infinitely nested schemas, if we've already seen this schema + // along this path there must be a cycle + if (schemaStack.has(schema)) { + return; + } + schemaStack.set(schema, true); + + prefix = prefix || ''; + const keys = Object.keys(schema.paths); + + for (const key of keys) { + const path = schema.paths[key]; + if (baseSchema != null && baseSchema.paths[key]) { + // If looking at an embedded discriminator schema, don't look at paths + // that the + continue; + } + + if (path.$isMongooseDocumentArray || path.$isSingleNested) { + if (get(path, 'options.excludeIndexes') !== true && + get(path, 'schemaOptions.excludeIndexes') !== true && + get(path, 'schema.options.excludeIndexes') !== true) { + collectIndexes(path.schema, prefix + key + '.'); + } + + if (path.schema.discriminators != null) { + const discriminators = path.schema.discriminators; + const discriminatorKeys = Object.keys(discriminators); + for (const discriminatorKey of discriminatorKeys) { + collectIndexes(discriminators[discriminatorKey], + prefix + key + '.', path.schema); + } + } + + // Retained to minimize risk of backwards breaking changes due to + // gh-6113 + if (path.$isMongooseDocumentArray) { + continue; + } + } + + const index = path._index || (path.caster && path.caster._index); + + if (index !== false && index !== null && index !== undefined) { + const field = {}; + const isObject = helperIsObject(index); + const options = isObject ? index : {}; + const type = typeof index === 'string' ? index : + isObject ? index.type : + false; + + if (type && indexTypes.indexOf(type) !== -1) { + field[prefix + key] = type; + } else if (options.text) { + field[prefix + key] = 'text'; + delete options.text; + } else { + const isDescendingIndex = Number(index) === -1; + field[prefix + key] = isDescendingIndex ? -1 : 1; + } + + delete options.type; + if (!('background' in options)) { + options.background = true; + } + if (schema.options.autoIndex != null) { + options._autoIndex = schema.options.autoIndex; + } + + const indexName = options && options.name; + if (typeof indexName === 'string') { + if (indexByName.has(indexName)) { + Object.assign(indexByName.get(indexName), field); + } else { + indexes.push([field, options]); + indexByName.set(indexName, field); + } + } else { + indexes.push([field, options]); + indexByName.set(indexName, field); + } + } + } + + schemaStack.delete(schema); + + if (prefix) { + fixSubIndexPaths(schema, prefix); + } else { + schema._indexes.forEach(function(index) { + if (!('background' in index[1])) { + index[1].background = true; + } + }); + indexes = indexes.concat(schema._indexes); + } + } + + /*! + * Checks for indexes added to subdocs using Schema.index(). + * These indexes need their paths prefixed properly. + * + * schema._indexes = [ [indexObj, options], [indexObj, options] ..] + */ + + function fixSubIndexPaths(schema, prefix) { + const subindexes = schema._indexes; + const len = subindexes.length; + for (let i = 0; i < len; ++i) { + const indexObj = subindexes[i][0]; + const indexOptions = subindexes[i][1]; + const keys = Object.keys(indexObj); + const klen = keys.length; + const newindex = {}; + + // use forward iteration, order matters + for (let j = 0; j < klen; ++j) { + const key = keys[j]; + newindex[prefix + key] = indexObj[key]; + } + + const newIndexOptions = Object.assign({}, indexOptions); + if (indexOptions != null && indexOptions.partialFilterExpression != null) { + newIndexOptions.partialFilterExpression = {}; + const partialFilterExpression = indexOptions.partialFilterExpression; + for (const key of Object.keys(partialFilterExpression)) { + newIndexOptions.partialFilterExpression[prefix + key] = + partialFilterExpression[key]; + } + } + + indexes.push([newindex, newIndexOptions]); + } + } +}; diff --git a/node_modules/mongoose/lib/helpers/schema/getPath.js b/node_modules/mongoose/lib/helpers/schema/getPath.js new file mode 100644 index 0000000..ccbc67c --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/getPath.js @@ -0,0 +1,35 @@ +'use strict'; + +/*! + * Behaves like `Schema#path()`, except for it also digs into arrays without + * needing to put `.0.`, so `getPath(schema, 'docArr.elProp')` works. + */ + +module.exports = function getPath(schema, path) { + let schematype = schema.path(path); + if (schematype != null) { + return schematype; + } + + const pieces = path.split('.'); + let cur = ''; + let isArray = false; + + for (const piece of pieces) { + if (/^\d+$/.test(piece) && isArray) { + continue; + } + cur = cur.length === 0 ? piece : cur + '.' + piece; + + schematype = schema.path(cur); + if (schematype != null && schematype.schema) { + schema = schematype.schema; + cur = ''; + if (schematype.$isMongooseDocumentArray) { + isArray = true; + } + } + } + + return schematype; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/schema/handleIdOption.js b/node_modules/mongoose/lib/helpers/schema/handleIdOption.js new file mode 100644 index 0000000..569bf9f --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/handleIdOption.js @@ -0,0 +1,20 @@ +'use strict'; + +const addAutoId = require('./addAutoId'); + +module.exports = function handleIdOption(schema, options) { + if (options == null || options._id == null) { + return schema; + } + + schema = schema.clone(); + if (!options._id) { + schema.remove('_id'); + schema.options._id = false; + } else if (!schema.paths['_id']) { + addAutoId(schema); + schema.options._id = true; + } + + return schema; +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/schema/handleTimestampOption.js b/node_modules/mongoose/lib/helpers/schema/handleTimestampOption.js new file mode 100644 index 0000000..1551b7c --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/handleTimestampOption.js @@ -0,0 +1,24 @@ +'use strict'; + +module.exports = handleTimestampOption; + +/*! + * ignore + */ + +function handleTimestampOption(arg, prop) { + if (arg == null) { + return null; + } + + if (typeof arg === 'boolean') { + return prop; + } + if (typeof arg[prop] === 'boolean') { + return arg[prop] ? prop : null; + } + if (!(prop in arg)) { + return prop; + } + return arg[prop]; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/schema/merge.js b/node_modules/mongoose/lib/helpers/schema/merge.js new file mode 100644 index 0000000..d206500 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schema/merge.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = function merge(s1, s2) { + s1.add(s2.tree || {}); + + s1.callQueue = s1.callQueue.concat(s2.callQueue); + s1.method(s2.methods); + s1.static(s2.statics); + + for (const query in s2.query) { + s1.query[query] = s2.query[query]; + } + + for (const virtual in s2.virtuals) { + s1.virtuals[virtual] = s2.virtuals[virtual].clone(); + } + + s1.s.hooks.merge(s2.s.hooks, false); +}; diff --git a/node_modules/mongoose/lib/helpers/schematype/handleImmutable.js b/node_modules/mongoose/lib/helpers/schematype/handleImmutable.js new file mode 100644 index 0000000..e31c4af --- /dev/null +++ b/node_modules/mongoose/lib/helpers/schematype/handleImmutable.js @@ -0,0 +1,45 @@ +'use strict'; + +const StrictModeError = require('../../error/strict'); + +/*! + * ignore + */ + +module.exports = function(schematype) { + if (schematype.$immutable) { + schematype.$immutableSetter = createImmutableSetter(schematype.path, + schematype.options.immutable); + schematype.set(schematype.$immutableSetter); + } else if (schematype.$immutableSetter) { + schematype.setters = schematype.setters. + filter(fn => fn !== schematype.$immutableSetter); + delete schematype.$immutableSetter; + } +}; + +function createImmutableSetter(path, immutable) { + return function immutableSetter(v) { + if (this == null || this.$__ == null) { + return v; + } + if (this.isNew) { + return v; + } + + const _immutable = typeof immutable === 'function' ? + immutable.call(this, this) : + immutable; + if (!_immutable) { + return v; + } + + const _value = this.$__getValue(path); + if (this.$__.strictMode === 'throw' && v !== _value) { + throw new StrictModeError(path, 'Path `' + path + '` is immutable ' + + 'and strict mode is set to throw.', true); + } + + return _value; + }; +} diff --git a/node_modules/mongoose/lib/helpers/setDefaultsOnInsert.js b/node_modules/mongoose/lib/helpers/setDefaultsOnInsert.js new file mode 100644 index 0000000..14e3f1b --- /dev/null +++ b/node_modules/mongoose/lib/helpers/setDefaultsOnInsert.js @@ -0,0 +1,127 @@ +'use strict'; +const modifiedPaths = require('./common').modifiedPaths; +const get = require('./get'); + +/** + * Applies defaults to update and findOneAndUpdate operations. + * + * @param {Object} filter + * @param {Schema} schema + * @param {Object} castedDoc + * @param {Object} options + * @method setDefaultsOnInsert + * @api private + */ + +module.exports = function(filter, schema, castedDoc, options) { + options = options || {}; + + const shouldSetDefaultsOnInsert = + options.setDefaultsOnInsert != null ? + options.setDefaultsOnInsert : + schema.base.options.setDefaultsOnInsert; + + if (!options.upsert || !shouldSetDefaultsOnInsert) { + return castedDoc; + } + + const keys = Object.keys(castedDoc || {}); + const updatedKeys = {}; + const updatedValues = {}; + const numKeys = keys.length; + const modified = {}; + + let hasDollarUpdate = false; + + for (let i = 0; i < numKeys; ++i) { + if (keys[i].startsWith('$')) { + modifiedPaths(castedDoc[keys[i]], '', modified); + hasDollarUpdate = true; + } + } + + if (!hasDollarUpdate) { + modifiedPaths(castedDoc, '', modified); + } + + const paths = Object.keys(filter); + const numPaths = paths.length; + for (let i = 0; i < numPaths; ++i) { + const path = paths[i]; + const condition = filter[path]; + if (condition && typeof condition === 'object') { + const conditionKeys = Object.keys(condition); + const numConditionKeys = conditionKeys.length; + let hasDollarKey = false; + for (let j = 0; j < numConditionKeys; ++j) { + if (conditionKeys[j].startsWith('$')) { + hasDollarKey = true; + break; + } + } + if (hasDollarKey) { + continue; + } + } + updatedKeys[path] = true; + modified[path] = true; + } + + if (options && options.overwrite && !hasDollarUpdate) { + // Defaults will be set later, since we're overwriting we'll cast + // the whole update to a document + return castedDoc; + } + + schema.eachPath(function(path, schemaType) { + // Skip single nested paths if underneath a map + const isUnderneathMap = schemaType.path.endsWith('.$*') || + schemaType.path.indexOf('.$*.') !== -1; + if (schemaType.$isSingleNested && !isUnderneathMap) { + // Only handle nested schemas 1-level deep to avoid infinite + // recursion re: https://github.com/mongodb-js/mongoose-autopopulate/issues/11 + schemaType.schema.eachPath(function(_path, _schemaType) { + if (_path === '_id' && _schemaType.auto) { + // Ignore _id if auto id so we don't create subdocs + return; + } + + const def = _schemaType.getDefault(null, true); + if (!isModified(modified, path + '.' + _path) && + typeof def !== 'undefined') { + castedDoc = castedDoc || {}; + castedDoc.$setOnInsert = castedDoc.$setOnInsert || {}; + castedDoc.$setOnInsert[path + '.' + _path] = def; + updatedValues[path + '.' + _path] = def; + } + }); + } else { + const def = schemaType.getDefault(null, true); + if (!isModified(modified, path) && typeof def !== 'undefined') { + castedDoc = castedDoc || {}; + castedDoc.$setOnInsert = castedDoc.$setOnInsert || {}; + if (get(castedDoc, path) == null) { + castedDoc.$setOnInsert[path] = def; + } + updatedValues[path] = def; + } + } + }); + + return castedDoc; +}; + +function isModified(modified, path) { + if (modified[path]) { + return true; + } + const sp = path.split('.'); + let cur = sp[0]; + for (let i = 1; i < sp.length; ++i) { + if (modified[cur]) { + return true; + } + cur += '.' + sp[i]; + } + return false; +} diff --git a/node_modules/mongoose/lib/helpers/specialProperties.js b/node_modules/mongoose/lib/helpers/specialProperties.js new file mode 100644 index 0000000..1e1aca5 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/specialProperties.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = new Set(['__proto__', 'constructor', 'prototype']);
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/symbols.js b/node_modules/mongoose/lib/helpers/symbols.js new file mode 100644 index 0000000..9d619f7 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/symbols.js @@ -0,0 +1,19 @@ +'use strict'; + +exports.arrayAtomicsSymbol = Symbol('mongoose#Array#_atomics'); +exports.arrayParentSymbol = Symbol('mongoose#Array#_parent'); +exports.arrayPathSymbol = Symbol('mongoose#Array#_path'); +exports.arraySchemaSymbol = Symbol('mongoose#Array#_schema'); +exports.documentArrayParent = Symbol('mongoose:documentArrayParent'); +exports.documentIsSelected = Symbol('mongoose#Document#isSelected'); +exports.documentIsModified = Symbol('mongoose#Document#isModified'); +exports.documentModifiedPaths = Symbol('mongoose#Document#modifiedPaths'); +exports.documentSchemaSymbol = Symbol('mongoose#Document#schema'); +exports.getSymbol = Symbol('mongoose#Document#get'); +exports.modelSymbol = Symbol('mongoose#Model'); +exports.objectIdSymbol = Symbol('mongoose#ObjectId'); +exports.populateModelSymbol = Symbol('mongoose.PopulateOptions#Model'); +exports.schemaTypeSymbol = Symbol('mongoose#schemaType'); +exports.sessionNewDocuments = Symbol('mongoose:ClientSession#newDocuments'); +exports.scopeSymbol = Symbol('mongoose#Document#scope'); +exports.validatorErrorSymbol = Symbol('mongoose:validatorError');
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/timestamps/setupTimestamps.js b/node_modules/mongoose/lib/helpers/timestamps/setupTimestamps.js new file mode 100644 index 0000000..752a565 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/timestamps/setupTimestamps.js @@ -0,0 +1,104 @@ +'use strict'; + +const applyTimestampsToChildren = require('../update/applyTimestampsToChildren'); +const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate'); +const get = require('../get'); +const handleTimestampOption = require('../schema/handleTimestampOption'); +const symbols = require('../../schema/symbols'); + +module.exports = function setupTimestamps(schema, timestamps) { + const childHasTimestamp = schema.childSchemas.find(withTimestamp); + + function withTimestamp(s) { + const ts = s.schema.options.timestamps; + return !!ts; + } + + if (!timestamps && !childHasTimestamp) { + return; + } + + const createdAt = handleTimestampOption(timestamps, 'createdAt'); + const updatedAt = handleTimestampOption(timestamps, 'updatedAt'); + const currentTime = timestamps != null && timestamps.hasOwnProperty('currentTime') ? + timestamps.currentTime : + null; + const schemaAdditions = {}; + + schema.$timestamps = { createdAt: createdAt, updatedAt: updatedAt }; + + if (updatedAt && !schema.paths[updatedAt]) { + schemaAdditions[updatedAt] = Date; + } + + if (createdAt && !schema.paths[createdAt]) { + schemaAdditions[createdAt] = Date; + } + + schema.add(schemaAdditions); + + schema.pre('save', function(next) { + const timestampOption = get(this, '$__.saveOptions.timestamps'); + if (timestampOption === false) { + return next(); + } + + const skipUpdatedAt = timestampOption != null && timestampOption.updatedAt === false; + const skipCreatedAt = timestampOption != null && timestampOption.createdAt === false; + + const defaultTimestamp = currentTime != null ? + currentTime() : + (this.ownerDocument ? this.ownerDocument() : this).constructor.base.now(); + const auto_id = this._id && this._id.auto; + + if (!skipCreatedAt && createdAt && !this.get(createdAt) && this.isSelected(createdAt)) { + this.$set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp); + } + + if (!skipUpdatedAt && updatedAt && (this.isNew || this.isModified())) { + let ts = defaultTimestamp; + if (this.isNew) { + if (createdAt != null) { + ts = this.$__getValue(createdAt); + } else if (auto_id) { + ts = this._id.getTimestamp(); + } + } + this.$set(updatedAt, ts); + } + + next(); + }); + + schema.methods.initializeTimestamps = function() { + const ts = currentTime != null ? + currentTime() : + this.constructor.base.now(); + if (createdAt && !this.get(createdAt)) { + this.$set(createdAt, ts); + } + if (updatedAt && !this.get(updatedAt)) { + this.$set(updatedAt, ts); + } + return this; + }; + + _setTimestampsOnUpdate[symbols.builtInMiddleware] = true; + + const opts = { query: true, model: false }; + schema.pre('findOneAndUpdate', opts, _setTimestampsOnUpdate); + schema.pre('replaceOne', opts, _setTimestampsOnUpdate); + schema.pre('update', opts, _setTimestampsOnUpdate); + schema.pre('updateOne', opts, _setTimestampsOnUpdate); + schema.pre('updateMany', opts, _setTimestampsOnUpdate); + + function _setTimestampsOnUpdate(next) { + const now = currentTime != null ? + currentTime() : + this.model.base.now(); + applyTimestampsToUpdate(now, createdAt, updatedAt, this.getUpdate(), + this.options, this.schema); + applyTimestampsToChildren(now, this.getUpdate(), this.model.schema); + next(); + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/topology/allServersUnknown.js b/node_modules/mongoose/lib/helpers/topology/allServersUnknown.js new file mode 100644 index 0000000..d3c7b4b --- /dev/null +++ b/node_modules/mongoose/lib/helpers/topology/allServersUnknown.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = function allServersUnknown(topologyDescription) { + if (topologyDescription == null || + topologyDescription.constructor.name !== 'TopologyDescription') { + return false; + } + + const servers = Array.from(topologyDescription.servers.values()); + return servers.length > 0 && servers.every(server => server.type === 'Unknown'); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/topology/isAtlas.js b/node_modules/mongoose/lib/helpers/topology/isAtlas.js new file mode 100644 index 0000000..3bb8129 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/topology/isAtlas.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = function isAtlas(topologyDescription) { + if (topologyDescription == null || + topologyDescription.constructor.name !== 'TopologyDescription') { + return false; + } + + const hostnames = Array.from(topologyDescription.servers.keys()); + return hostnames.length > 0 && + hostnames.every(host => host.endsWith('.mongodb.net:27017')); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/topology/isSSLError.js b/node_modules/mongoose/lib/helpers/topology/isSSLError.js new file mode 100644 index 0000000..61e57d1 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/topology/isSSLError.js @@ -0,0 +1,15 @@ +'use strict'; + +const nonSSLMessage = 'Client network socket disconnected before secure TLS ' + + 'connection was established'; + +module.exports = function isSSLError(topologyDescription) { + if (topologyDescription == null || + topologyDescription.constructor.name !== 'TopologyDescription') { + return false; + } + + const descriptions = Array.from(topologyDescription.servers.values()); + return descriptions.length > 0 && + descriptions.every(descr => descr.error && descr.error.message.indexOf(nonSSLMessage) !== -1); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/update/applyTimestampsToChildren.js b/node_modules/mongoose/lib/helpers/update/applyTimestampsToChildren.js new file mode 100644 index 0000000..680d692 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/update/applyTimestampsToChildren.js @@ -0,0 +1,172 @@ +'use strict'; + +const cleanPositionalOperators = require('../schema/cleanPositionalOperators'); +const handleTimestampOption = require('../schema/handleTimestampOption'); + +module.exports = applyTimestampsToChildren; + +/*! + * ignore + */ + +function applyTimestampsToChildren(now, update, schema) { + if (update == null) { + return; + } + + const keys = Object.keys(update); + const hasDollarKey = keys.some(key => key.startsWith('$')); + + if (hasDollarKey) { + if (update.$push) { + for (const key of Object.keys(update.$push)) { + const $path = schema.path(key); + if (update.$push[key] && + $path && + $path.$isMongooseDocumentArray && + $path.schema.options.timestamps) { + const timestamps = $path.schema.options.timestamps; + const createdAt = handleTimestampOption(timestamps, 'createdAt'); + const updatedAt = handleTimestampOption(timestamps, 'updatedAt'); + if (update.$push[key].$each) { + update.$push[key].$each.forEach(function(subdoc) { + if (updatedAt != null) { + subdoc[updatedAt] = now; + } + if (createdAt != null) { + subdoc[createdAt] = now; + } + }); + } else { + if (updatedAt != null) { + update.$push[key][updatedAt] = now; + } + if (createdAt != null) { + update.$push[key][createdAt] = now; + } + } + } + } + } + if (update.$set != null) { + const keys = Object.keys(update.$set); + for (const key of keys) { + applyTimestampsToUpdateKey(schema, key, update.$set, now); + } + } + } + + const updateKeys = Object.keys(update).filter(key => !key.startsWith('$')); + for (const key of updateKeys) { + applyTimestampsToUpdateKey(schema, key, update, now); + } +} + +function applyTimestampsToDocumentArray(arr, schematype, now) { + const timestamps = schematype.schema.options.timestamps; + + if (!timestamps) { + return; + } + + const len = arr.length; + + const createdAt = handleTimestampOption(timestamps, 'createdAt'); + const updatedAt = handleTimestampOption(timestamps, 'updatedAt'); + for (let i = 0; i < len; ++i) { + if (updatedAt != null) { + arr[i][updatedAt] = now; + } + if (createdAt != null) { + arr[i][createdAt] = now; + } + + applyTimestampsToChildren(now, arr[i], schematype.schema); + } +} + +function applyTimestampsToSingleNested(subdoc, schematype, now) { + const timestamps = schematype.schema.options.timestamps; + if (!timestamps) { + return; + } + + const createdAt = handleTimestampOption(timestamps, 'createdAt'); + const updatedAt = handleTimestampOption(timestamps, 'updatedAt'); + if (updatedAt != null) { + subdoc[updatedAt] = now; + } + if (createdAt != null) { + subdoc[createdAt] = now; + } + + applyTimestampsToChildren(now, subdoc, schematype.schema); +} + +function applyTimestampsToUpdateKey(schema, key, update, now) { + // Replace positional operator `$` and array filters `$[]` and `$[.*]` + const keyToSearch = cleanPositionalOperators(key); + const path = schema.path(keyToSearch); + if (!path) { + return; + } + + const parentSchemaTypes = []; + const pieces = keyToSearch.split('.'); + for (let i = pieces.length - 1; i > 0; --i) { + const s = schema.path(pieces.slice(0, i).join('.')); + if (s != null && + (s.$isMongooseDocumentArray || s.$isSingleNested)) { + parentSchemaTypes.push({ parentPath: key.split('.').slice(0, i).join('.'), parentSchemaType: s }); + } + } + + if (Array.isArray(update[key]) && path.$isMongooseDocumentArray) { + applyTimestampsToDocumentArray(update[key], path, now); + } else if (update[key] && path.$isSingleNested) { + applyTimestampsToSingleNested(update[key], path, now); + } else if (parentSchemaTypes.length > 0) { + for (const item of parentSchemaTypes) { + const parentPath = item.parentPath; + const parentSchemaType = item.parentSchemaType; + const timestamps = parentSchemaType.schema.options.timestamps; + const updatedAt = handleTimestampOption(timestamps, 'updatedAt'); + + if (!timestamps || updatedAt == null) { + continue; + } + + if (parentSchemaType.$isSingleNested) { + // Single nested is easy + update[parentPath + '.' + updatedAt] = now; + } else if (parentSchemaType.$isMongooseDocumentArray) { + let childPath = key.substr(parentPath.length + 1); + + if (/^\d+$/.test(childPath)) { + update[parentPath + '.' + childPath][updatedAt] = now; + continue; + } + + const firstDot = childPath.indexOf('.'); + childPath = firstDot !== -1 ? childPath.substr(0, firstDot) : childPath; + + update[parentPath + '.' + childPath + '.' + updatedAt] = now; + } + } + } else if (path.schema != null && path.schema != schema && update[key]) { + const timestamps = path.schema.options.timestamps; + const createdAt = handleTimestampOption(timestamps, 'createdAt'); + const updatedAt = handleTimestampOption(timestamps, 'updatedAt'); + + if (!timestamps) { + return; + } + + if (updatedAt != null) { + update[key][updatedAt] = now; + } + if (createdAt != null) { + update[key][createdAt] = now; + } + } +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/update/applyTimestampsToUpdate.js b/node_modules/mongoose/lib/helpers/update/applyTimestampsToUpdate.js new file mode 100644 index 0000000..27d6e32 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/update/applyTimestampsToUpdate.js @@ -0,0 +1,119 @@ +'use strict'; + +/*! + * ignore + */ + +const get = require('../get'); + +module.exports = applyTimestampsToUpdate; + +/*! + * ignore + */ + +function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options) { + const updates = currentUpdate; + let _updates = updates; + const overwrite = get(options, 'overwrite', false); + const timestamps = get(options, 'timestamps', true); + + // Support skipping timestamps at the query level, see gh-6980 + if (!timestamps || updates == null) { + return currentUpdate; + } + + const skipCreatedAt = timestamps != null && timestamps.createdAt === false; + const skipUpdatedAt = timestamps != null && timestamps.updatedAt === false; + + if (overwrite) { + if (currentUpdate && currentUpdate.$set) { + currentUpdate = currentUpdate.$set; + updates.$set = {}; + _updates = updates.$set; + } + if (!skipUpdatedAt && updatedAt && !currentUpdate[updatedAt]) { + _updates[updatedAt] = now; + } + if (!skipCreatedAt && createdAt && !currentUpdate[createdAt]) { + _updates[createdAt] = now; + } + return updates; + } + currentUpdate = currentUpdate || {}; + + if (Array.isArray(updates)) { + // Update with aggregation pipeline + updates.push({ $set: { updatedAt: now } }); + + return updates; + } + + updates.$set = updates.$set || {}; + if (!skipUpdatedAt && updatedAt && + (!currentUpdate.$currentDate || !currentUpdate.$currentDate[updatedAt])) { + let timestampSet = false; + if (updatedAt.indexOf('.') !== -1) { + const pieces = updatedAt.split('.'); + for (let i = 1; i < pieces.length; ++i) { + const remnant = pieces.slice(-i).join('.'); + const start = pieces.slice(0, -i).join('.'); + if (currentUpdate[start] != null) { + currentUpdate[start][remnant] = now; + timestampSet = true; + break; + } else if (currentUpdate.$set && currentUpdate.$set[start]) { + currentUpdate.$set[start][remnant] = now; + timestampSet = true; + break; + } + } + } + + if (!timestampSet) { + updates.$set[updatedAt] = now; + } + + if (updates.hasOwnProperty(updatedAt)) { + delete updates[updatedAt]; + } + } + + if (!skipCreatedAt && createdAt) { + if (currentUpdate[createdAt]) { + delete currentUpdate[createdAt]; + } + if (currentUpdate.$set && currentUpdate.$set[createdAt]) { + delete currentUpdate.$set[createdAt]; + } + + let timestampSet = false; + if (createdAt.indexOf('.') !== -1) { + const pieces = createdAt.split('.'); + for (let i = 1; i < pieces.length; ++i) { + const remnant = pieces.slice(-i).join('.'); + const start = pieces.slice(0, -i).join('.'); + if (currentUpdate[start] != null) { + currentUpdate[start][remnant] = now; + timestampSet = true; + break; + } else if (currentUpdate.$set && currentUpdate.$set[start]) { + currentUpdate.$set[start][remnant] = now; + timestampSet = true; + break; + } + } + } + + if (!timestampSet) { + updates.$setOnInsert = updates.$setOnInsert || {}; + updates.$setOnInsert[createdAt] = now; + } + } + + if (Object.keys(updates.$set).length === 0) { + delete updates.$set; + } + + return updates; +} diff --git a/node_modules/mongoose/lib/helpers/update/castArrayFilters.js b/node_modules/mongoose/lib/helpers/update/castArrayFilters.js new file mode 100644 index 0000000..57018d9 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/update/castArrayFilters.js @@ -0,0 +1,78 @@ +'use strict'; + +const castFilterPath = require('../query/castFilterPath'); +const cleanPositionalOperators = require('../schema/cleanPositionalOperators'); +const getPath = require('../schema/getPath'); +const modifiedPaths = require('./modifiedPaths'); + +module.exports = function castArrayFilters(query) { + const arrayFilters = query.options.arrayFilters; + if (!Array.isArray(arrayFilters)) { + return; + } + + const update = query.getUpdate(); + const schema = query.schema; + const strictQuery = schema.options.strictQuery; + + const updatedPaths = modifiedPaths(update); + + const updatedPathsByFilter = Object.keys(updatedPaths).reduce((cur, path) => { + const matches = path.match(/\$\[[^\]]+\]/g); + if (matches == null) { + return cur; + } + for (const match of matches) { + const firstMatch = path.indexOf(match); + if (firstMatch !== path.lastIndexOf(match)) { + throw new Error(`Path '${path}' contains the same array filter multiple times`); + } + cur[match.substring(2, match.length - 1)] = path. + substr(0, firstMatch - 1). + replace(/\$\[[^\]]+\]/g, '0'); + } + return cur; + }, {}); + + for (const filter of arrayFilters) { + if (filter == null) { + throw new Error(`Got null array filter in ${arrayFilters}`); + } + for (const key in filter) { + + if (filter[key] == null) { + continue; + } + + const dot = key.indexOf('.'); + let filterPath = dot === -1 ? + updatedPathsByFilter[key] + '.0' : + updatedPathsByFilter[key.substr(0, dot)] + '.0' + key.substr(dot); + + if (filterPath == null) { + throw new Error(`Filter path not found for ${key}`); + } + + // If there are multiple array filters in the path being updated, make sure + // to replace them so we can get the schema path. + filterPath = cleanPositionalOperators(filterPath); + + const schematype = getPath(schema, filterPath); + if (schematype == null) { + if (!strictQuery) { + return; + } + // For now, treat `strictQuery = true` and `strictQuery = 'throw'` as + // equivalent for casting array filters. `strictQuery = true` doesn't + // quite work in this context because we never want to silently strip out + // array filters, even if the path isn't in the schema. + throw new Error(`Could not find path "${filterPath}" in schema`); + } + if (typeof filter[key] === 'object') { + filter[key] = castFilterPath(query, schematype, filter[key]); + } else { + filter[key] = schematype.castForQuery(filter[key]); + } + } + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/update/modifiedPaths.js b/node_modules/mongoose/lib/helpers/update/modifiedPaths.js new file mode 100644 index 0000000..9cb567d --- /dev/null +++ b/node_modules/mongoose/lib/helpers/update/modifiedPaths.js @@ -0,0 +1,33 @@ +'use strict'; + +const _modifiedPaths = require('../common').modifiedPaths; + +/** + * Given an update document with potential update operators (`$set`, etc.) + * returns an object whose keys are the directly modified paths. + * + * If there are any top-level keys that don't start with `$`, we assume those + * will get wrapped in a `$set`. The Mongoose Query is responsible for wrapping + * top-level keys in `$set`. + * + * @param {Object} update + * @return {Object} modified + */ + +module.exports = function modifiedPaths(update) { + const keys = Object.keys(update); + const res = {}; + + const withoutDollarKeys = {}; + for (const key of keys) { + if (key.startsWith('$')) { + _modifiedPaths(update[key], '', res); + continue; + } + withoutDollarKeys[key] = update[key]; + } + + _modifiedPaths(withoutDollarKeys, '', res); + + return res; +}; diff --git a/node_modules/mongoose/lib/helpers/update/moveImmutableProperties.js b/node_modules/mongoose/lib/helpers/update/moveImmutableProperties.js new file mode 100644 index 0000000..8541c5b --- /dev/null +++ b/node_modules/mongoose/lib/helpers/update/moveImmutableProperties.js @@ -0,0 +1,53 @@ +'use strict'; + +const get = require('../get'); + +/** + * Given an update, move all $set on immutable properties to $setOnInsert. + * This should only be called for upserts, because $setOnInsert bypasses the + * strictness check for immutable properties. + */ + +module.exports = function moveImmutableProperties(schema, update, ctx) { + if (update == null) { + return; + } + + const keys = Object.keys(update); + for (const key of keys) { + const isDollarKey = key.startsWith('$'); + + if (key === '$set') { + const updatedPaths = Object.keys(update[key]); + for (const path of updatedPaths) { + _walkUpdatePath(schema, update[key], path, update, ctx); + } + } else if (!isDollarKey) { + _walkUpdatePath(schema, update, key, update, ctx); + } + + } +}; + +function _walkUpdatePath(schema, op, path, update, ctx) { + const schematype = schema.path(path); + if (schematype == null) { + return; + } + + let immutable = get(schematype, 'options.immutable', null); + if (immutable == null) { + return; + } + if (typeof immutable === 'function') { + immutable = immutable.call(ctx, ctx); + } + + if (!immutable) { + return; + } + + update.$setOnInsert = update.$setOnInsert || {}; + update.$setOnInsert[path] = op[path]; + delete op[path]; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/update/removeUnusedArrayFilters.js b/node_modules/mongoose/lib/helpers/update/removeUnusedArrayFilters.js new file mode 100644 index 0000000..d460628 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/update/removeUnusedArrayFilters.js @@ -0,0 +1,18 @@ +'use strict'; + +/** + * MongoDB throws an error if there's unused array filters. That is, if `options.arrayFilters` defines + * a filter, but none of the `update` keys use it. This should be enough to filter out all unused array + * filters. + */ + +module.exports = function removeUnusedArrayFilters(update, arrayFilters) { + const updateKeys = Object.keys(update).map(key => Object.keys(update[key])).reduce((cur, arr) => cur.concat(arr), []); + return arrayFilters.filter(obj => { + const firstKey = Object.keys(obj)[0]; + const firstDot = firstKey.indexOf('.'); + const arrayFilterKey = firstDot === -1 ? firstKey : firstKey.slice(0, firstDot); + + return updateKeys.find(key => key.includes('$[' + arrayFilterKey + ']')) != null; + }); +};
\ No newline at end of file diff --git a/node_modules/mongoose/lib/helpers/updateValidators.js b/node_modules/mongoose/lib/helpers/updateValidators.js new file mode 100644 index 0000000..aeea634 --- /dev/null +++ b/node_modules/mongoose/lib/helpers/updateValidators.js @@ -0,0 +1,257 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const ValidationError = require('../error/validation'); +const cleanPositionalOperators = require('./schema/cleanPositionalOperators'); +const flatten = require('./common').flatten; +const modifiedPaths = require('./common').modifiedPaths; + +/** + * Applies validators and defaults to update and findOneAndUpdate operations, + * specifically passing a null doc as `this` to validators and defaults + * + * @param {Query} query + * @param {Schema} schema + * @param {Object} castedDoc + * @param {Object} options + * @method runValidatorsOnUpdate + * @api private + */ + +module.exports = function(query, schema, castedDoc, options, callback) { + let _keys; + const keys = Object.keys(castedDoc || {}); + let updatedKeys = {}; + let updatedValues = {}; + const isPull = {}; + const arrayAtomicUpdates = {}; + const numKeys = keys.length; + let hasDollarUpdate = false; + const modified = {}; + let currentUpdate; + let key; + let i; + + for (i = 0; i < numKeys; ++i) { + if (keys[i].startsWith('$')) { + hasDollarUpdate = true; + if (keys[i] === '$push' || keys[i] === '$addToSet') { + _keys = Object.keys(castedDoc[keys[i]]); + for (let ii = 0; ii < _keys.length; ++ii) { + currentUpdate = castedDoc[keys[i]][_keys[ii]]; + if (currentUpdate && currentUpdate.$each) { + arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []). + concat(currentUpdate.$each); + } else { + arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []). + concat([currentUpdate]); + } + } + continue; + } + modifiedPaths(castedDoc[keys[i]], '', modified); + const flat = flatten(castedDoc[keys[i]], null, null, schema); + const paths = Object.keys(flat); + const numPaths = paths.length; + for (let j = 0; j < numPaths; ++j) { + const updatedPath = cleanPositionalOperators(paths[j]); + key = keys[i]; + // With `$pull` we might flatten `$in`. Skip stuff nested under `$in` + // for the rest of the logic, it will get handled later. + if (updatedPath.includes('$')) { + continue; + } + if (key === '$set' || key === '$setOnInsert' || + key === '$pull' || key === '$pullAll') { + updatedValues[updatedPath] = flat[paths[j]]; + isPull[updatedPath] = key === '$pull' || key === '$pullAll'; + } else if (key === '$unset') { + updatedValues[updatedPath] = undefined; + } + updatedKeys[updatedPath] = true; + } + } + } + + if (!hasDollarUpdate) { + modifiedPaths(castedDoc, '', modified); + updatedValues = flatten(castedDoc, null, null, schema); + updatedKeys = Object.keys(updatedValues); + } + + const updates = Object.keys(updatedValues); + const numUpdates = updates.length; + const validatorsToExecute = []; + const validationErrors = []; + + const alreadyValidated = []; + + const context = options && options.context === 'query' ? query : null; + function iter(i, v) { + const schemaPath = schema._getSchema(updates[i]); + if (schemaPath == null) { + return; + } + if (schemaPath.instance === 'Mixed' && schemaPath.path !== updates[i]) { + return; + } + + if (v && Array.isArray(v.$in)) { + v.$in.forEach((v, i) => { + validatorsToExecute.push(function(callback) { + schemaPath.doValidate( + v, + function(err) { + if (err) { + err.path = updates[i] + '.$in.' + i; + validationErrors.push(err); + } + callback(null); + }, + context, + { updateValidator: true }); + }); + }); + } else { + if (isPull[updates[i]] && + schemaPath.$isMongooseArray) { + return; + } + + if (schemaPath.$isMongooseDocumentArrayElement && v != null && v.$__ != null) { + alreadyValidated.push(updates[i]); + validatorsToExecute.push(function(callback) { + schemaPath.doValidate(v, function(err) { + if (err) { + err.path = updates[i]; + validationErrors.push(err); + return callback(null); + } + + v.validate(function(err) { + if (err) { + if (err.errors) { + for (const key of Object.keys(err.errors)) { + const _err = err.errors[key]; + _err.path = updates[i] + '.' + key; + validationErrors.push(_err); + } + } else { + err.path = updates[i]; + validationErrors.push(err); + } + } + callback(null); + }); + }, context, { updateValidator: true }); + }); + } else { + validatorsToExecute.push(function(callback) { + for (const path of alreadyValidated) { + if (updates[i].startsWith(path + '.')) { + return callback(null); + } + } + + schemaPath.doValidate(v, function(err) { + if (schemaPath.schema != null && + schemaPath.schema.options.storeSubdocValidationError === false && + err instanceof ValidationError) { + return callback(null); + } + + if (err) { + err.path = updates[i]; + validationErrors.push(err); + } + callback(null); + }, context, { updateValidator: true }); + }); + } + } + } + for (i = 0; i < numUpdates; ++i) { + iter(i, updatedValues[updates[i]]); + } + + const arrayUpdates = Object.keys(arrayAtomicUpdates); + for (const arrayUpdate of arrayUpdates) { + let schemaPath = schema._getSchema(arrayUpdate); + if (schemaPath && schemaPath.$isMongooseDocumentArray) { + validatorsToExecute.push(function(callback) { + schemaPath.doValidate( + arrayAtomicUpdates[arrayUpdate], + getValidationCallback(arrayUpdate, validationErrors, callback), + options && options.context === 'query' ? query : null); + }); + } else { + schemaPath = schema._getSchema(arrayUpdate + '.0'); + for (const atomicUpdate of arrayAtomicUpdates[arrayUpdate]) { + validatorsToExecute.push(function(callback) { + schemaPath.doValidate( + atomicUpdate, + getValidationCallback(arrayUpdate, validationErrors, callback), + options && options.context === 'query' ? query : null, + { updateValidator: true }); + }); + } + } + } + + if (callback != null) { + let numValidators = validatorsToExecute.length; + if (numValidators === 0) { + return _done(callback); + } + for (const validator of validatorsToExecute) { + validator(function() { + if (--numValidators <= 0) { + _done(callback); + } + }); + } + + return; + } + + return function(callback) { + let numValidators = validatorsToExecute.length; + if (numValidators === 0) { + return _done(callback); + } + for (const validator of validatorsToExecute) { + validator(function() { + if (--numValidators <= 0) { + _done(callback); + } + }); + } + }; + + function _done(callback) { + if (validationErrors.length) { + const err = new ValidationError(null); + + for (const validationError of validationErrors) { + err.addError(validationError.path, validationError); + } + + return callback(err); + } + callback(null); + } + + function getValidationCallback(arrayUpdate, validationErrors, callback) { + return function(err) { + if (err) { + err.path = arrayUpdate; + validationErrors.push(err); + } + callback(null); + }; + } +}; + diff --git a/node_modules/mongoose/lib/index.js b/node_modules/mongoose/lib/index.js new file mode 100644 index 0000000..fccb625 --- /dev/null +++ b/node_modules/mongoose/lib/index.js @@ -0,0 +1,1132 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +if (global.MONGOOSE_DRIVER_PATH) { + const deprecationWarning = 'The `MONGOOSE_DRIVER_PATH` global property is ' + + 'deprecated. Use `mongoose.driver.set()` instead.'; + const setDriver = require('util').deprecate(function() { + require('./driver').set(require(global.MONGOOSE_DRIVER_PATH)); + }, deprecationWarning); + setDriver(); +} else { + require('./driver').set(require('./drivers/node-mongodb-native')); +} + +const Document = require('./document'); +const Schema = require('./schema'); +const SchemaType = require('./schematype'); +const SchemaTypes = require('./schema/index'); +const VirtualType = require('./virtualtype'); +const STATES = require('./connectionstate'); +const VALID_OPTIONS = require('./validoptions'); +const Types = require('./types'); +const Query = require('./query'); +const Model = require('./model'); +const applyPlugins = require('./helpers/schema/applyPlugins'); +const get = require('./helpers/get'); +const promiseOrCallback = require('./helpers/promiseOrCallback'); +const legacyPluralize = require('mongoose-legacy-pluralize'); +const utils = require('./utils'); +const pkg = require('../package.json'); +const cast = require('./cast'); +const removeSubdocs = require('./plugins/removeSubdocs'); +const saveSubdocs = require('./plugins/saveSubdocs'); +const trackTransaction = require('./plugins/trackTransaction'); +const validateBeforeSave = require('./plugins/validateBeforeSave'); + +const Aggregate = require('./aggregate'); +const PromiseProvider = require('./promise_provider'); +const shardingPlugin = require('./plugins/sharding'); + +const defaultMongooseSymbol = Symbol.for('mongoose:default'); + +require('./helpers/printJestWarning'); + +/** + * Mongoose constructor. + * + * The exports object of the `mongoose` module is an instance of this class. + * Most apps will only use this one instance. + * + * ####Example: + * const mongoose = require('mongoose'); + * mongoose instanceof mongoose.Mongoose; // true + * + * // Create a new Mongoose instance with its own `connect()`, `set()`, `model()`, etc. + * const m = new mongoose.Mongoose(); + * + * @api public + * @param {Object} options see [`Mongoose#set()` docs](/docs/api/mongoose.html#mongoose_Mongoose-set) + */ +function Mongoose(options) { + this.connections = []; + this.models = {}; + this.modelSchemas = {}; + // default global options + this.options = Object.assign({ + pluralization: true + }, options); + const conn = this.createConnection(); // default connection + conn.models = this.models; + + if (this.options.pluralization) { + this._pluralize = legacyPluralize; + } + + // If a user creates their own Mongoose instance, give them a separate copy + // of the `Schema` constructor so they get separate custom types. (gh-6933) + if (!options || !options[defaultMongooseSymbol]) { + const _this = this; + this.Schema = function() { + this.base = _this; + return Schema.apply(this, arguments); + }; + this.Schema.prototype = Object.create(Schema.prototype); + + Object.assign(this.Schema, Schema); + this.Schema.base = this; + this.Schema.Types = Object.assign({}, Schema.Types); + } else { + // Hack to work around babel's strange behavior with + // `import mongoose, { Schema } from 'mongoose'`. Because `Schema` is not + // an own property of a Mongoose global, Schema will be undefined. See gh-5648 + for (const key of ['Schema', 'model']) { + this[key] = Mongoose.prototype[key]; + } + } + this.Schema.prototype.base = this; + + Object.defineProperty(this, 'plugins', { + configurable: false, + enumerable: true, + writable: false, + value: [ + [saveSubdocs, { deduplicate: true }], + [validateBeforeSave, { deduplicate: true }], + [shardingPlugin, { deduplicate: true }], + [removeSubdocs, { deduplicate: true }], + [trackTransaction, { deduplicate: true }] + ] + }); +} +Mongoose.prototype.cast = cast; +/** + * Expose connection states for user-land + * + * @memberOf Mongoose + * @property STATES + * @api public + */ +Mongoose.prototype.STATES = STATES; + +/** + * The underlying driver this Mongoose instance uses to communicate with + * the database. A driver is a Mongoose-specific interface that defines functions + * like `find()`. + * + * @memberOf Mongoose + * @property driver + * @api public + */ + +Mongoose.prototype.driver = require('./driver'); + +/** + * Sets mongoose options + * + * ####Example: + * + * mongoose.set('test', value) // sets the 'test' option to `value` + * + * mongoose.set('debug', true) // enable logging collection methods + arguments to the console/file + * + * mongoose.set('debug', function(collectionName, methodName, ...methodArgs) {}); // use custom function to log collection methods + arguments + * + * Currently supported options are: + * - 'debug': If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arugments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`. + * - 'returnOriginal': If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](/docs/tutorials/findoneandupdate.html) for more information. + * - 'bufferCommands': enable/disable mongoose's buffering mechanism for all connections and models + * - 'useCreateIndex': false by default. Set to `true` to make Mongoose's default index build use `createIndex()` instead of `ensureIndex()` to avoid deprecation warnings from the MongoDB driver. + * - 'useFindAndModify': true by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. + * - 'useNewUrlParser': false by default. Set to `true` to make all connections set the `useNewUrlParser` option by default + * - 'useUnifiedTopology': false by default. Set to `true` to make all connections set the `useUnifiedTopology` option by default + * - 'cloneSchemas': false by default. Set to `true` to `clone()` all schemas before compiling into a model. + * - 'applyPluginsToDiscriminators': false by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema. + * - 'applyPluginsToChildSchemas': true by default. Set to false to skip applying global plugins to child schemas + * - 'objectIdGetter': true by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter. + * - 'runValidators': false by default. Set to true to enable [update validators](/docs/validation.html#update-validators) for all validators by default. + * - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject) + * - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()` + * - 'strict': true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas. + * - 'strictQuery': false by default, may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas. + * - 'selectPopulatedPaths': true by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one. + * - 'typePojoToMixed': true by default, may be `false` or `true`. Sets the default typePojoToMixed for schemas. + * - 'maxTimeMS': If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query + * - 'autoIndex': true by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance. + * - 'autoCreate': Set to `true` to make Mongoose call [`Model.createCollection()`](/docs/api/model.html#model_Model.createCollection) automatically when you create a model with `mongoose.model()` or `conn.model()`. This is useful for testing transactions, change streams, and other features that require the collection to exist. + * + * @param {String} key + * @param {String|Function|Boolean} value + * @api public + */ + +Mongoose.prototype.set = function(key, value) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + if (VALID_OPTIONS.indexOf(key) === -1) throw new Error(`\`${key}\` is an invalid option.`); + + if (arguments.length === 1) { + return _mongoose.options[key]; + } + + _mongoose.options[key] = value; + + if (key === 'objectIdGetter') { + if (value) { + Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', { + enumerable: false, + configurable: true, + get: function() { + return this; + } + }); + } else { + delete mongoose.Types.ObjectId.prototype._id; + } + } + + return _mongoose; +}; + +/** + * Gets mongoose options + * + * ####Example: + * + * mongoose.get('test') // returns the 'test' value + * + * @param {String} key + * @method get + * @api public + */ + +Mongoose.prototype.get = Mongoose.prototype.set; + +/** + * Creates a Connection instance. + * + * Each `connection` instance maps to a single database. This method is helpful when managing multiple db connections. + * + * + * _Options passed take precedence over options included in connection strings._ + * + * ####Example: + * + * // with mongodb:// URI + * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database'); + * + * // and options + * const opts = { db: { native_parser: true }} + * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database', opts); + * + * // replica sets + * db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database'); + * + * // and options + * const opts = { replset: { strategy: 'ping', rs_name: 'testSet' }} + * db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database', opts); + * + * // and options + * const opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' } + * db = mongoose.createConnection('localhost', 'database', port, opts) + * + * // initialize now, connect later + * db = mongoose.createConnection(); + * db.openUri('localhost', 'database', port, [opts]); + * + * @param {String} [uri] a mongodb:// URI + * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html), except for 4 mongoose-specific options explained below. + * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. + * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. + * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. + * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. + * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. + * @param {Boolean} [options.useNewUrlParser=false] False by default. Set to `true` to make all connections set the `useNewUrlParser` option by default. + * @param {Boolean} [options.useUnifiedTopology=false] False by default. Set to `true` to make all connections set the `useUnifiedTopology` option by default. + * @param {Boolean} [options.useCreateIndex=true] Mongoose-specific option. If `true`, this connection will use [`createIndex()` instead of `ensureIndex()`](/docs/deprecations.html#ensureindex) for automatic index builds via [`Model.init()`](/docs/api.html#model_Model.init). + * @param {Boolean} [options.useFindAndModify=true] True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. + * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections. + * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above. + * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html). + * @param {Number} [options.poolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Number} [options.bufferMaxEntries] This option does nothing if `useUnifiedTopology` is set. The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set `bufferCommands` to `false` on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection. + * @param {Number} [options.connectTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback). + * @param {Number} [options.socketTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. + * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. + * @return {Connection} the created Connection object. Connections are thenable, so you can do `await mongoose.createConnection()` + * @api public + */ + +Mongoose.prototype.createConnection = function(uri, options, callback) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + const conn = new Connection(_mongoose); + if (typeof options === 'function') { + callback = options; + options = null; + } + _mongoose.connections.push(conn); + + if (arguments.length > 0) { + return conn.openUri(uri, options, callback); + } + + return conn; +}; + +/** + * Opens the default mongoose connection. + * + * ####Example: + * + * mongoose.connect('mongodb://user:pass@localhost:port/database'); + * + * // replica sets + * const uri = 'mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/mydatabase'; + * mongoose.connect(uri); + * + * // with options + * mongoose.connect(uri, options); + * + * // optional callback that gets fired when initial connection completed + * const uri = 'mongodb://nonexistent.domain:27000'; + * mongoose.connect(uri, function(error) { + * // if error is truthy, the initial connection failed. + * }) + * + * @param {String} uri(s) + * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html), except for 4 mongoose-specific options explained below. + * @param {Boolean} [options.bufferCommands=true] Mongoose specific option. Set to false to [disable buffering](http://mongoosejs.com/docs/faq.html#callback_never_executes) on all models associated with this connection. + * @param {String} [options.dbName] The name of the database we want to use. If not provided, use database name from connection string. + * @param {String} [options.user] username for authentication, equivalent to `options.auth.user`. Maintained for backwards compatibility. + * @param {String} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility. + * @param {Number} [options.poolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. By default, `poolSize` is 5. Keep in mind that, as of MongoDB 3.4, MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](http://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + * @param {Boolean} [options.useUnifiedTopology=false] False by default. Set to `true` to opt in to the MongoDB driver's replica set and sharded cluster monitoring engine. + * @param {Number} [options.serverSelectionTimeoutMS] If `useUnifiedTopology = true`, the MongoDB driver will try to find a server to send any given operation to, and keep retrying for `serverSelectionTimeoutMS` milliseconds before erroring out. If not set, the MongoDB driver defaults to using `30000` (30 seconds). + * @param {Number} [options.heartbeatFrequencyMS] If `useUnifiedTopology = true`, the MongoDB driver sends a heartbeat every `heartbeatFrequencyMS` to check on the status of the connection. A heartbeat is subject to `serverSelectionTimeoutMS`, so the MongoDB driver will retry failed heartbeats for up to 30 seconds by default. Mongoose only emits a `'disconnected'` event after a heartbeat has failed, so you may want to decrease this setting to reduce the time between when your server goes down and when Mongoose emits `'disconnected'`. We recommend you do **not** set this setting below 1000, too many heartbeats can lead to performance degradation. + * @param {Boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection. + * @param {Boolean} [options.useNewUrlParser=false] False by default. Set to `true` to opt in to the MongoDB driver's new URL parser logic. + * @param {Boolean} [options.useCreateIndex=true] Mongoose-specific option. If `true`, this connection will use [`createIndex()` instead of `ensureIndex()`](/docs/deprecations.html#ensureindex) for automatic index builds via [`Model.init()`](/docs/api.html#model_Model.init). + * @param {Boolean} [options.useFindAndModify=true] True by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. + * @param {Number} [options.reconnectTries=30] If you're connected to a single server or mongos proxy (as opposed to a replica set), the MongoDB driver will try to reconnect every `reconnectInterval` milliseconds for `reconnectTries` times, and give up afterward. When the driver gives up, the mongoose connection emits a `reconnectFailed` event. This option does nothing for replica set connections. + * @param {Number} [options.reconnectInterval=1000] See `reconnectTries` option above. + * @param {Class} [options.promiseLibrary] Sets the [underlying driver's promise library](http://mongodb.github.io/node-mongodb-native/3.1/api/MongoClient.html). + * @param {Number} [options.bufferMaxEntries] This option does nothing if `useUnifiedTopology` is set. The MongoDB driver also has its own buffering mechanism that kicks in when the driver is disconnected. Set this option to 0 and set `bufferCommands` to `false` on your schemas if you want your database operations to fail immediately when the driver is not connected, as opposed to waiting for reconnection. + * @param {Number} [options.connectTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _during initial connection_. Defaults to 30000. This option is passed transparently to [Node.js' `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback). + * @param {Number} [options.socketTimeoutMS=30000] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. A socket may be inactive because of either no activity or a long-running operation. This is set to `30000` by default, you should set this to 2-3x your longest running operation if you expect some of your database operations to run longer than 20 seconds. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes. + * @param {Number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both. + * @param {Function} [callback] + * @see Mongoose#createConnection #index_Mongoose-createConnection + * @api public + * @return {Promise} resolves to `this` if connection succeeded + */ + +Mongoose.prototype.connect = function(uri, options, callback) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + const conn = _mongoose.connection; + + return promiseOrCallback(callback, cb => { + conn.openUri(uri, options, err => { + if (err != null) { + return cb(err); + } + return cb(null, _mongoose); + }); + }); +}; + +/** + * Runs `.close()` on all connections in parallel. + * + * @param {Function} [callback] called after all connection close, or when first error occurred. + * @return {Promise} resolves when all connections are closed, or rejects with the first error that occurred. + * @api public + */ + +Mongoose.prototype.disconnect = function(callback) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + return promiseOrCallback(callback, cb => { + let remaining = _mongoose.connections.length; + if (remaining <= 0) { + return cb(null); + } + _mongoose.connections.forEach(conn => { + conn.close(function(error) { + if (error) { + return cb(error); + } + if (!--remaining) { + cb(null); + } + }); + }); + }); +}; + +/** + * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions) + * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/), + * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). + * + * Calling `mongoose.startSession()` is equivalent to calling `mongoose.connection.startSession()`. + * Sessions are scoped to a connection, so calling `mongoose.startSession()` + * starts a session on the [default mongoose connection](/docs/api.html#mongoose_Mongoose-connection). + * + * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession) + * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency + * @param {Function} [callback] + * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession` + * @api public + */ + +Mongoose.prototype.startSession = function() { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + return _mongoose.connection.startSession.apply(_mongoose.connection, arguments); +}; + +/** + * Getter/setter around function for pluralizing collection names. + * + * @param {Function|null} [fn] overwrites the function used to pluralize collection names + * @return {Function|null} the current function used to pluralize collection names, defaults to the legacy function from `mongoose-legacy-pluralize`. + * @api public + */ + +Mongoose.prototype.pluralize = function(fn) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + if (arguments.length > 0) { + _mongoose._pluralize = fn; + } + return _mongoose._pluralize; +}; + +/** + * Defines a model or retrieves it. + * + * Models defined on the `mongoose` instance are available to all connection + * created by the same `mongoose` instance. + * + * If you call `mongoose.model()` with twice the same name but a different schema, + * you will get an `OverwriteModelError`. If you call `mongoose.model()` with + * the same name and same schema, you'll get the same schema back. + * + * ####Example: + * + * const mongoose = require('mongoose'); + * + * // define an Actor model with this mongoose instance + * const schema = new Schema({ name: String }); + * mongoose.model('Actor', schema); + * + * // create a new connection + * const conn = mongoose.createConnection(..); + * + * // create Actor model + * const Actor = conn.model('Actor', schema); + * conn.model('Actor') === Actor; // true + * conn.model('Actor', schema) === Actor; // true, same schema + * conn.model('Actor', schema, 'actors') === Actor; // true, same schema and collection name + * + * // This throws an `OverwriteModelError` because the schema is different. + * conn.model('Actor', new Schema({ name: String })); + * + * _When no `collection` argument is passed, Mongoose uses the model name. If you don't like this behavior, either pass a collection name, use `mongoose.pluralize()`, or set your schemas collection name option._ + * + * ####Example: + * + * const schema = new Schema({ name: String }, { collection: 'actor' }); + * + * // or + * + * schema.set('collection', 'actor'); + * + * // or + * + * const collectionName = 'actor' + * const M = mongoose.model('Actor', schema, collectionName) + * + * @param {String|Function} name model name or class extending Model + * @param {Schema} [schema] the schema to use. + * @param {String} [collection] name (optional, inferred from model name) + * @param {Boolean} [skipInit] whether to skip initialization (defaults to false) + * @return {Model} The model associated with `name`. Mongoose will create the model if it doesn't already exist. + * @api public + */ + +Mongoose.prototype.model = function(name, schema, collection, skipInit) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + let model; + if (typeof name === 'function') { + model = name; + name = model.name; + if (!(model.prototype instanceof Model)) { + throw new _mongoose.Error('The provided class ' + name + ' must extend Model'); + } + } + + if (typeof schema === 'string') { + collection = schema; + schema = false; + } + + if (utils.isObject(schema) && !(schema instanceof Schema)) { + schema = new Schema(schema); + } + if (schema && !(schema instanceof Schema)) { + throw new Error('The 2nd parameter to `mongoose.model()` should be a ' + + 'schema or a POJO'); + } + + if (typeof collection === 'boolean') { + skipInit = collection; + collection = null; + } + + // handle internal options from connection.model() + let options; + if (skipInit && utils.isObject(skipInit)) { + options = skipInit; + skipInit = true; + } else { + options = {}; + } + + // look up schema for the collection. + if (!_mongoose.modelSchemas[name]) { + if (schema) { + // cache it so we only apply plugins once + _mongoose.modelSchemas[name] = schema; + } else { + throw new mongoose.Error.MissingSchemaError(name); + } + } + + const originalSchema = schema; + if (schema) { + if (_mongoose.get('cloneSchemas')) { + schema = schema.clone(); + } + _mongoose._applyPlugins(schema); + } + + let sub; + + // connection.model() may be passing a different schema for + // an existing model name. in this case don't read from cache. + if (_mongoose.models[name] && options.cache !== false) { + if (originalSchema && + originalSchema.instanceOfSchema && + originalSchema !== _mongoose.models[name].schema) { + throw new _mongoose.Error.OverwriteModelError(name); + } + + if (collection && collection !== _mongoose.models[name].collection.name) { + // subclass current model with alternate collection + model = _mongoose.models[name]; + schema = model.prototype.schema; + sub = model.__subclass(_mongoose.connection, schema, collection); + // do not cache the sub model + return sub; + } + + return _mongoose.models[name]; + } + + // ensure a schema exists + if (!schema) { + schema = this.modelSchemas[name]; + if (!schema) { + throw new mongoose.Error.MissingSchemaError(name); + } + } + + // Apply relevant "global" options to the schema + if (!('pluralization' in schema.options)) { + schema.options.pluralization = _mongoose.options.pluralization; + } + + if (!collection) { + collection = schema.get('collection') || + utils.toCollectionName(name, _mongoose.pluralize()); + } + + const connection = options.connection || _mongoose.connection; + model = _mongoose.Model.compile(model || name, schema, collection, connection, _mongoose); + + if (!skipInit) { + // Errors handled internally, so safe to ignore error + model.init(function $modelInitNoop() {}); + } + + if (options.cache === false) { + return model; + } + + _mongoose.models[name] = model; + return _mongoose.models[name]; +}; + +/** + * Removes the model named `name` from the default connection, if it exists. + * You can use this function to clean up any models you created in your tests to + * prevent OverwriteModelErrors. + * + * Equivalent to `mongoose.connection.deleteModel(name)`. + * + * ####Example: + * + * mongoose.model('User', new Schema({ name: String })); + * console.log(mongoose.model('User')); // Model object + * mongoose.deleteModel('User'); + * console.log(mongoose.model('User')); // undefined + * + * // Usually useful in a Mocha `afterEach()` hook + * afterEach(function() { + * mongoose.deleteModel(/.+/); // Delete every model + * }); + * + * @api public + * @param {String|RegExp} name if string, the name of the model to remove. If regexp, removes all models whose name matches the regexp. + * @return {Mongoose} this + */ + +Mongoose.prototype.deleteModel = function(name) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + _mongoose.connection.deleteModel(name); + return _mongoose; +}; + +/** + * Returns an array of model names created on this instance of Mongoose. + * + * ####Note: + * + * _Does not include names of models created using `connection.model()`._ + * + * @api public + * @return {Array} + */ + +Mongoose.prototype.modelNames = function() { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + const names = Object.keys(_mongoose.models); + return names; +}; + +/** + * Applies global plugins to `schema`. + * + * @param {Schema} schema + * @api private + */ + +Mongoose.prototype._applyPlugins = function(schema, options) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + options = options || {}; + options.applyPluginsToDiscriminators = get(_mongoose, + 'options.applyPluginsToDiscriminators', false); + options.applyPluginsToChildSchemas = get(_mongoose, + 'options.applyPluginsToChildSchemas', true); + applyPlugins(schema, _mongoose.plugins, options, '$globalPluginsApplied'); +}; + +/** + * Declares a global plugin executed on all Schemas. + * + * Equivalent to calling `.plugin(fn)` on each Schema you create. + * + * @param {Function} fn plugin callback + * @param {Object} [opts] optional options + * @return {Mongoose} this + * @see plugins ./plugins.html + * @api public + */ + +Mongoose.prototype.plugin = function(fn, opts) { + const _mongoose = this instanceof Mongoose ? this : mongoose; + + _mongoose.plugins.push([fn, opts]); + return _mongoose; +}; + +/** + * The Mongoose module's default connection. Equivalent to `mongoose.connections[0]`, see [`connections`](#mongoose_Mongoose-connections). + * + * ####Example: + * + * const mongoose = require('mongoose'); + * mongoose.connect(...); + * mongoose.connection.on('error', cb); + * + * This is the connection used by default for every model created using [mongoose.model](#index_Mongoose-model). + * + * To create a new connection, use [`createConnection()`](#mongoose_Mongoose-createConnection). + * + * @memberOf Mongoose + * @instance + * @property {Connection} connection + * @api public + */ + +Mongoose.prototype.__defineGetter__('connection', function() { + return this.connections[0]; +}); + +Mongoose.prototype.__defineSetter__('connection', function(v) { + if (v instanceof Connection) { + this.connections[0] = v; + this.models = v.models; + } +}); + +/** + * An array containing all [connections](connections.html) associated with this + * Mongoose instance. By default, there is 1 connection. Calling + * [`createConnection()`](#mongoose_Mongoose-createConnection) adds a connection + * to this array. + * + * ####Example: + * + * const mongoose = require('mongoose'); + * mongoose.connections.length; // 1, just the default connection + * mongoose.connections[0] === mongoose.connection; // true + * + * mongoose.createConnection('mongodb://localhost:27017/test'); + * mongoose.connections.length; // 2 + * + * @memberOf Mongoose + * @instance + * @property {Array} connections + * @api public + */ + +Mongoose.prototype.connections; + +/*! + * Driver dependent APIs + */ + +const driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native'; + +/*! + * Connection + */ + +const Connection = require(driver + '/connection'); + +/*! + * Collection + */ + +const Collection = require(driver + '/collection'); + +/** + * The Mongoose Aggregate constructor + * + * @method Aggregate + * @api public + */ + +Mongoose.prototype.Aggregate = Aggregate; + +/** + * The Mongoose Collection constructor + * + * @method Collection + * @api public + */ + +Mongoose.prototype.Collection = Collection; + +/** + * The Mongoose [Connection](#connection_Connection) constructor + * + * @memberOf Mongoose + * @instance + * @method Connection + * @api public + */ + +Mongoose.prototype.Connection = Connection; + +/** + * The Mongoose version + * + * #### Example + * + * console.log(mongoose.version); // '5.x.x' + * + * @property version + * @api public + */ + +Mongoose.prototype.version = pkg.version; + +/** + * The Mongoose constructor + * + * The exports of the mongoose module is an instance of this class. + * + * ####Example: + * + * const mongoose = require('mongoose'); + * const mongoose2 = new mongoose.Mongoose(); + * + * @method Mongoose + * @api public + */ + +Mongoose.prototype.Mongoose = Mongoose; + +/** + * The Mongoose [Schema](#schema_Schema) constructor + * + * ####Example: + * + * const mongoose = require('mongoose'); + * const Schema = mongoose.Schema; + * const CatSchema = new Schema(..); + * + * @method Schema + * @api public + */ + +Mongoose.prototype.Schema = Schema; + +/** + * The Mongoose [SchemaType](#schematype_SchemaType) constructor + * + * @method SchemaType + * @api public + */ + +Mongoose.prototype.SchemaType = SchemaType; + +/** + * The various Mongoose SchemaTypes. + * + * ####Note: + * + * _Alias of mongoose.Schema.Types for backwards compatibility._ + * + * @property SchemaTypes + * @see Schema.SchemaTypes #schema_Schema.Types + * @api public + */ + +Mongoose.prototype.SchemaTypes = Schema.Types; + +/** + * The Mongoose [VirtualType](#virtualtype_VirtualType) constructor + * + * @method VirtualType + * @api public + */ + +Mongoose.prototype.VirtualType = VirtualType; + +/** + * The various Mongoose Types. + * + * ####Example: + * + * const mongoose = require('mongoose'); + * const array = mongoose.Types.Array; + * + * ####Types: + * + * - [ObjectId](#types-objectid-js) + * - [Buffer](#types-buffer-js) + * - [SubDocument](#types-embedded-js) + * - [Array](#types-array-js) + * - [DocumentArray](#types-documentarray-js) + * + * Using this exposed access to the `ObjectId` type, we can construct ids on demand. + * + * const ObjectId = mongoose.Types.ObjectId; + * const id1 = new ObjectId; + * + * @property Types + * @api public + */ + +Mongoose.prototype.Types = Types; + +/** + * The Mongoose [Query](#query_Query) constructor. + * + * @method Query + * @api public + */ + +Mongoose.prototype.Query = Query; + +/** + * The Mongoose [Promise](#promise_Promise) constructor. + * + * @memberOf Mongoose + * @instance + * @property Promise + * @api public + */ + +Object.defineProperty(Mongoose.prototype, 'Promise', { + get: function() { + return PromiseProvider.get(); + }, + set: function(lib) { + PromiseProvider.set(lib); + } +}); + +/** + * Storage layer for mongoose promises + * + * @method PromiseProvider + * @api public + */ + +Mongoose.prototype.PromiseProvider = PromiseProvider; + +/** + * The Mongoose [Model](#model_Model) constructor. + * + * @method Model + * @api public + */ + +Mongoose.prototype.Model = Model; + +/** + * The Mongoose [Document](/api/document.html) constructor. + * + * @method Document + * @api public + */ + +Mongoose.prototype.Document = Document; + +/** + * The Mongoose DocumentProvider constructor. Mongoose users should not have to + * use this directly + * + * @method DocumentProvider + * @api public + */ + +Mongoose.prototype.DocumentProvider = require('./document_provider'); + +/** + * The Mongoose ObjectId [SchemaType](/docs/schematypes.html). Used for + * declaring paths in your schema that should be + * [MongoDB ObjectIds](https://docs.mongodb.com/manual/reference/method/ObjectId/). + * Do not use this to create a new ObjectId instance, use `mongoose.Types.ObjectId` + * instead. + * + * ####Example: + * + * const childSchema = new Schema({ parentId: mongoose.ObjectId }); + * + * @property ObjectId + * @api public + */ + +Mongoose.prototype.ObjectId = SchemaTypes.ObjectId; + +/** + * Returns true if Mongoose can cast the given value to an ObjectId, or + * false otherwise. + * + * ####Example: + * + * mongoose.isValidObjectId(new mongoose.Types.ObjectId()); // true + * mongoose.isValidObjectId('0123456789ab'); // true + * mongoose.isValidObjectId(6); // false + * + * @method isValidObjectId + * @api public + */ + +Mongoose.prototype.isValidObjectId = function(v) { + if (v == null) { + return true; + } + const base = this || mongoose; + const ObjectId = base.driver.get().ObjectId; + if (v instanceof ObjectId) { + return true; + } + + if (v._id != null) { + if (v._id instanceof ObjectId) { + return true; + } + if (v._id.toString instanceof Function) { + v = v._id.toString(); + return typeof v === 'string' && (v.length === 12 || v.length === 24); + } + } + + if (v.toString instanceof Function) { + v = v.toString(); + } + + if (typeof v === 'string' && (v.length === 12 || v.length === 24)) { + return true; + } + + return false; +}; + +/** + * The Mongoose Decimal128 [SchemaType](/docs/schematypes.html). Used for + * declaring paths in your schema that should be + * [128-bit decimal floating points](http://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-decimal.html). + * Do not use this to create a new Decimal128 instance, use `mongoose.Types.Decimal128` + * instead. + * + * ####Example: + * + * const vehicleSchema = new Schema({ fuelLevel: mongoose.Decimal128 }); + * + * @property Decimal128 + * @api public + */ + +Mongoose.prototype.Decimal128 = SchemaTypes.Decimal128; + +/** + * The Mongoose Mixed [SchemaType](/docs/schematypes.html). Used for + * declaring paths in your schema that Mongoose's change tracking, casting, + * and validation should ignore. + * + * ####Example: + * + * const schema = new Schema({ arbitrary: mongoose.Mixed }); + * + * @property Mixed + * @api public + */ + +Mongoose.prototype.Mixed = SchemaTypes.Mixed; + +/** + * The Mongoose Date [SchemaType](/docs/schematypes.html). + * + * ####Example: + * + * const schema = new Schema({ test: Date }); + * schema.path('test') instanceof mongoose.Date; // true + * + * @property Date + * @api public + */ + +Mongoose.prototype.Date = SchemaTypes.Date; + +/** + * The Mongoose Number [SchemaType](/docs/schematypes.html). Used for + * declaring paths in your schema that Mongoose should cast to numbers. + * + * ####Example: + * + * const schema = new Schema({ num: mongoose.Number }); + * // Equivalent to: + * const schema = new Schema({ num: 'number' }); + * + * @property Number + * @api public + */ + +Mongoose.prototype.Number = SchemaTypes.Number; + +/** + * The [MongooseError](#error_MongooseError) constructor. + * + * @method Error + * @api public + */ + +Mongoose.prototype.Error = require('./error/index'); + +/** + * Mongoose uses this function to get the current time when setting + * [timestamps](/docs/guide.html#timestamps). You may stub out this function + * using a tool like [Sinon](https://www.npmjs.com/package/sinon) for testing. + * + * @method now + * @returns Date the current time + * @api public + */ + +Mongoose.prototype.now = function now() { return new Date(); }; + +/** + * The Mongoose CastError constructor + * + * @method CastError + * @param {String} type The name of the type + * @param {Any} value The value that failed to cast + * @param {String} path The path `a.b.c` in the doc where this cast error occurred + * @param {Error} [reason] The original error that was thrown + * @api public + */ + +Mongoose.prototype.CastError = require('./error/cast'); + +/** + * The constructor used for schematype options + * + * @method SchemaTypeOptions + * @api public + */ + +Mongoose.prototype.SchemaTypeOptions = require('./options/SchemaTypeOptions'); + +/** + * The [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver Mongoose uses. + * + * @property mongo + * @api public + */ + +Mongoose.prototype.mongo = require('mongodb'); + +/** + * The [mquery](https://github.com/aheckmann/mquery) query builder Mongoose uses. + * + * @property mquery + * @api public + */ + +Mongoose.prototype.mquery = require('mquery'); + +/*! + * The exports object is an instance of Mongoose. + * + * @api public + */ + +const mongoose = module.exports = exports = new Mongoose({ + [defaultMongooseSymbol]: true +}); diff --git a/node_modules/mongoose/lib/internal.js b/node_modules/mongoose/lib/internal.js new file mode 100644 index 0000000..7642477 --- /dev/null +++ b/node_modules/mongoose/lib/internal.js @@ -0,0 +1,38 @@ +/*! + * Dependencies + */ + +'use strict'; + +const StateMachine = require('./statemachine'); +const ActiveRoster = StateMachine.ctor('require', 'modify', 'init', 'default', 'ignore'); + +module.exports = exports = InternalCache; + +function InternalCache() { + this.strictMode = undefined; + this.selected = undefined; + this.shardval = undefined; + this.saveError = undefined; + this.validationError = undefined; + this.adhocPaths = undefined; + this.removing = undefined; + this.inserting = undefined; + this.saving = undefined; + this.version = undefined; + this.getters = {}; + this._id = undefined; + this.populate = undefined; // what we want to populate in this doc + this.populated = undefined;// the _ids that have been populated + this.wasPopulated = false; // if this doc was the result of a population + this.scope = undefined; + this.activePaths = new ActiveRoster; + this.pathsToScopes = {}; + this.cachedRequired = {}; + this.session = null; + this.$setCalled = new Set(); + + // embedded docs + this.ownerDocument = undefined; + this.fullPath = undefined; +} diff --git a/node_modules/mongoose/lib/model.js b/node_modules/mongoose/lib/model.js new file mode 100644 index 0000000..de7ab0a --- /dev/null +++ b/node_modules/mongoose/lib/model.js @@ -0,0 +1,4902 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const Aggregate = require('./aggregate'); +const ChangeStream = require('./cursor/ChangeStream'); +const Document = require('./document'); +const DocumentNotFoundError = require('./error/notFound'); +const DivergentArrayError = require('./error/divergentArray'); +const EventEmitter = require('events').EventEmitter; +const MongooseBuffer = require('./types/buffer'); +const MongooseError = require('./error/index'); +const OverwriteModelError = require('./error/overwriteModel'); +const PromiseProvider = require('./promise_provider'); +const Query = require('./query'); +const RemoveOptions = require('./options/removeOptions'); +const SaveOptions = require('./options/saveOptions'); +const Schema = require('./schema'); +const ServerSelectionError = require('./error/serverSelection'); +const SkipPopulateValue = require('./helpers/populate/SkipPopulateValue'); +const ValidationError = require('./error/validation'); +const VersionError = require('./error/version'); +const ParallelSaveError = require('./error/parallelSave'); +const applyQueryMiddleware = require('./helpers/query/applyQueryMiddleware'); +const applyHooks = require('./helpers/model/applyHooks'); +const applyMethods = require('./helpers/model/applyMethods'); +const applyStaticHooks = require('./helpers/model/applyStaticHooks'); +const applyStatics = require('./helpers/model/applyStatics'); +const applyWriteConcern = require('./helpers/schema/applyWriteConcern'); +const assignVals = require('./helpers/populate/assignVals'); +const castBulkWrite = require('./helpers/model/castBulkWrite'); +const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult'); +const discriminator = require('./helpers/model/discriminator'); +const each = require('./helpers/each'); +const get = require('./helpers/get'); +const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue'); +const getModelsMapForPopulate = require('./helpers/populate/getModelsMapForPopulate'); +const immediate = require('./helpers/immediate'); +const internalToObjectOptions = require('./options').internalToObjectOptions; +const isDefaultIdIndex = require('./helpers/indexes/isDefaultIdIndex'); +const isIndexEqual = require('./helpers/indexes/isIndexEqual'); +const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive'); +const leanPopulateMap = require('./helpers/populate/leanPopulateMap'); +const modifiedPaths = require('./helpers/update/modifiedPaths'); +const parallelLimit = require('./helpers/parallelLimit'); +const promiseOrCallback = require('./helpers/promiseOrCallback'); +const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField'); +const util = require('util'); +const utils = require('./utils'); + +const VERSION_WHERE = 1; +const VERSION_INC = 2; +const VERSION_ALL = VERSION_WHERE | VERSION_INC; + +const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol; +const modelCollectionSymbol = Symbol('mongoose#Model#collection'); +const modelDbSymbol = Symbol('mongoose#Model#db'); +const modelSymbol = require('./helpers/symbols').modelSymbol; +const subclassedSymbol = Symbol('mongoose#Model#subclassed'); + +const saveToObjectOptions = Object.assign({}, internalToObjectOptions, { + bson: true +}); + +/** + * A Model is a class that's your primary tool for interacting with MongoDB. + * An instance of a Model is called a [Document](./api.html#Document). + * + * In Mongoose, the term "Model" refers to subclasses of the `mongoose.Model` + * class. You should not use the `mongoose.Model` class directly. The + * [`mongoose.model()`](./api.html#mongoose_Mongoose-model) and + * [`connection.model()`](./api.html#connection_Connection-model) functions + * create subclasses of `mongoose.Model` as shown below. + * + * ####Example: + * + * // `UserModel` is a "Model", a subclass of `mongoose.Model`. + * const UserModel = mongoose.model('User', new Schema({ name: String })); + * + * // You can use a Model to create new documents using `new`: + * const userDoc = new UserModel({ name: 'Foo' }); + * await userDoc.save(); + * + * // You also use a model to create queries: + * const userFromDb = await UserModel.findOne({ name: 'Foo' }); + * + * @param {Object} doc values for initial set + * @param [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](./api.html#query_Query-select). + * @param {Boolean} [skipId=false] optional boolean. If true, mongoose doesn't add an `_id` field to the document. + * @inherits Document http://mongoosejs.com/docs/api/document.html + * @event `error`: If listening to this event, 'error' is emitted when a document was saved without passing a callback and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model. + * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event. + * @event `index-single-start`: Emitted when an individual index starts within `Model#ensureIndexes`. The fields and options being used to build the index are also passed with the event. + * @event `index-single-done`: Emitted when an individual index finishes within `Model#ensureIndexes`. If an error occurred it is passed with the event. The fields, options, and index name are also passed. + * @api public + */ + +function Model(doc, fields, skipId) { + if (fields instanceof Schema) { + throw new TypeError('2nd argument to `Model` must be a POJO or string, ' + + '**not** a schema. Make sure you\'re calling `mongoose.model()`, not ' + + '`mongoose.Model()`.'); + } + Document.call(this, doc, fields, skipId); +} + +/*! + * Inherits from Document. + * + * All Model.prototype features are available on + * top level (non-sub) documents. + */ + +Model.prototype.__proto__ = Document.prototype; +Model.prototype.$isMongooseModelPrototype = true; + +/** + * Connection the model uses. + * + * @api public + * @property db + * @memberOf Model + * @instance + */ + +Model.prototype.db; + +/** + * Collection the model uses. + * + * This property is read-only. Modifying this property is a no-op. + * + * @api public + * @property collection + * @memberOf Model + * @instance + */ + +Model.prototype.collection; + +/** + * The name of the model + * + * @api public + * @property modelName + * @memberOf Model + * @instance + */ + +Model.prototype.modelName; + +/** + * Additional properties to attach to the query when calling `save()` and + * `isNew` is false. + * + * @api public + * @property $where + * @memberOf Model + * @instance + */ + +Model.prototype.$where; + +/** + * If this is a discriminator model, `baseModelName` is the name of + * the base model. + * + * @api public + * @property baseModelName + * @memberOf Model + * @instance + */ + +Model.prototype.baseModelName; + +/** + * Event emitter that reports any errors that occurred. Useful for global error + * handling. + * + * ####Example: + * + * MyModel.events.on('error', err => console.log(err.message)); + * + * // Prints a 'CastError' because of the above handler + * await MyModel.findOne({ _id: 'notanid' }).catch(noop); + * + * @api public + * @fires error whenever any query or model function errors + * @memberOf Model + * @static events + */ + +Model.events; + +/*! + * Compiled middleware for this model. Set in `applyHooks()`. + * + * @api private + * @property _middleware + * @memberOf Model + * @static + */ + +Model._middleware; + +/*! + * ignore + */ + +function _applyCustomWhere(doc, where) { + if (doc.$where == null) { + return; + } + + const keys = Object.keys(doc.$where); + const len = keys.length; + for (let i = 0; i < len; ++i) { + where[keys[i]] = doc.$where[keys[i]]; + } +} + +/*! + * ignore + */ + +Model.prototype.$__handleSave = function(options, callback) { + const _this = this; + let saveOptions = {}; + + if ('safe' in options) { + _handleSafe(options); + } + applyWriteConcern(this.schema, options); + if ('w' in options) { + saveOptions.w = options.w; + } + if ('j' in options) { + saveOptions.j = options.j; + } + if ('wtimeout' in options) { + saveOptions.wtimeout = options.wtimeout; + } + if ('checkKeys' in options) { + saveOptions.checkKeys = options.checkKeys; + } + + const session = this.$session(); + if (!saveOptions.hasOwnProperty('session')) { + saveOptions.session = session; + } + + if (Object.keys(saveOptions).length === 0) { + saveOptions = null; + } + + if (this.isNew) { + // send entire doc + const obj = this.toObject(saveToObjectOptions); + + if ((obj || {})._id === void 0) { + // documents must have an _id else mongoose won't know + // what to update later if more changes are made. the user + // wouldn't know what _id was generated by mongodb either + // nor would the ObjectId generated by mongodb necessarily + // match the schema definition. + setTimeout(function() { + callback(new MongooseError('document must have an _id before saving')); + }, 0); + return; + } + + this.$__version(true, obj); + this[modelCollectionSymbol].insertOne(obj, saveOptions, function(err, ret) { + if (err) { + _setIsNew(_this, true); + + callback(err, null); + return; + } + + callback(null, ret); + }); + + this.$__reset(); + _setIsNew(this, false); + // Make it possible to retry the insert + this.$__.inserting = true; + } else { + // Make sure we don't treat it as a new object on error, + // since it already exists + this.$__.inserting = false; + + const delta = this.$__delta(); + + if (delta) { + if (delta instanceof MongooseError) { + callback(delta); + return; + } + + const where = this.$__where(delta[0]); + if (where instanceof MongooseError) { + callback(where); + return; + } + + _applyCustomWhere(this, where); + + this[modelCollectionSymbol].updateOne(where, delta[1], saveOptions, (err, ret) => { + if (err) { + this.$__undoReset(); + + callback(err); + return; + } + ret.$where = where; + callback(null, ret); + }); + } else { + const optionsWithCustomValues = Object.assign({}, options, saveOptions); + this.constructor.exists(this.$__where(), optionsWithCustomValues) + .then((documentExists) => { + if (!documentExists) { + throw new DocumentNotFoundError(this.$__where(), this.constructor.modelName); + } + + callback(); + }) + .catch(callback); + return; + } + + // store the modified paths before the document is reset + this.$__.modifiedPaths = this.modifiedPaths(); + + this.$__reset(); + + _setIsNew(this, false); + } +}; + +/*! + * ignore + */ + +Model.prototype.$__save = function(options, callback) { + this.$__handleSave(options, (error, result) => { + const hooks = this.schema.s.hooks; + if (error) { + return hooks.execPost('save:error', this, [this], { error: error }, (error) => { + callback(error, this); + }); + } + + let numAffected = 0; + if (get(options, 'safe.w') !== 0 && get(options, 'w') !== 0) { + // Skip checking if write succeeded if writeConcern is set to + // unacknowledged writes, because otherwise `numAffected` will always be 0 + if (result) { + if (Array.isArray(result)) { + numAffected = result.length; + } else if (result.result && result.result.n !== undefined) { + numAffected = result.result.n; + } else if (result.result && result.result.nModified !== undefined) { + numAffected = result.result.nModified; + } else { + numAffected = result; + } + } + + // was this an update that required a version bump? + if (this.$__.version && !this.$__.inserting) { + const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version); + this.$__.version = undefined; + + const key = this.schema.options.versionKey; + const version = this.$__getValue(key) || 0; + + if (numAffected <= 0) { + // the update failed. pass an error back + this.$__undoReset(); + const err = this.$__.$versionError || + new VersionError(this, version, this.$__.modifiedPaths); + return callback(err); + } + + // increment version if was successful + if (doIncrement) { + this.$__setValue(key, version + 1); + } + } + + if (result != null && numAffected <= 0) { + this.$__undoReset(); + error = new DocumentNotFoundError(result.$where, + this.constructor.modelName, numAffected, result); + return hooks.execPost('save:error', this, [this], { error: error }, (error) => { + callback(error, this); + }); + } + } + this.$__.saving = undefined; + this.emit('save', this, numAffected); + this.constructor.emit('save', this, numAffected); + callback(null, this); + }); +}; + +/*! + * ignore + */ + +function generateVersionError(doc, modifiedPaths) { + const key = doc.schema.options.versionKey; + if (!key) { + return null; + } + const version = doc.$__getValue(key) || 0; + return new VersionError(doc, version, modifiedPaths); +} + +/** + * Saves this document by inserting a new document into the database if [document.isNew](/docs/api.html#document_Document-isNew) is `true`, + * or sends an [updateOne](/docs/api.html#document_Document-updateOne) operation **only** with the modifications to the database, it does not replace the whole document in the latter case. + * + * ####Example: + * + * product.sold = Date.now(); + * product = await product.save(); + * + * If save is successful, the returned promise will fulfill with the document + * saved. + * + * ####Example: + * + * const newProduct = await product.save(); + * newProduct === product; // true + * + * @param {Object} [options] options optional options + * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](api.html#document_Document-$session). + * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead. + * @param {Boolean} [options.validateBeforeSave] set to false to save without validating. + * @param {Boolean} [options.validateModifiedOnly=false] if `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths. + * @param {Number|String} [options.w] set the [write concern](https://docs.mongodb.com/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern) + * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://docs.mongodb.com/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern) + * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](/docs/guide.html#writeConcern). + * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names) + * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](./guide.html#timestamps) are enabled, skip timestamps for this `save()`. + * @param {Function} [fn] optional callback + * @throws {DocumentNotFoundError} if this [save updates an existing document](api.html#document_Document-isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating). + * @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise. + * @api public + * @see middleware http://mongoosejs.com/docs/middleware.html + */ + +Model.prototype.save = function(options, fn) { + let parallelSave; + this.$op = 'save'; + + if (this.$__.saving) { + parallelSave = new ParallelSaveError(this); + } else { + this.$__.saving = new ParallelSaveError(this); + } + + if (typeof options === 'function') { + fn = options; + options = undefined; + } + + options = new SaveOptions(options); + if (options.hasOwnProperty('session')) { + this.$session(options.session); + } + + this.$__.$versionError = generateVersionError(this, this.modifiedPaths()); + + fn = this.constructor.$handleCallbackError(fn); + + return promiseOrCallback(fn, cb => { + cb = this.constructor.$wrapCallback(cb); + + if (parallelSave) { + this.$__handleReject(parallelSave); + return cb(parallelSave); + } + + this.$__.saveOptions = options; + + this.$__save(options, error => { + this.$__.saving = undefined; + delete this.$__.saveOptions; + delete this.$__.$versionError; + this.$op = null; + + if (error) { + this.$__handleReject(error); + return cb(error); + } + cb(null, this); + }); + }, this.constructor.events); +}; + +/*! + * Determines whether versioning should be skipped for the given path + * + * @param {Document} self + * @param {String} path + * @return {Boolean} true if versioning should be skipped for the given path + */ +function shouldSkipVersioning(self, path) { + const skipVersioning = self.schema.options.skipVersioning; + if (!skipVersioning) return false; + + // Remove any array indexes from the path + path = path.replace(/\.\d+\./, '.'); + + return skipVersioning[path]; +} + +/*! + * Apply the operation to the delta (update) clause as + * well as track versioning for our where clause. + * + * @param {Document} self + * @param {Object} where + * @param {Object} delta + * @param {Object} data + * @param {Mixed} val + * @param {String} [operation] + */ + +function operand(self, where, delta, data, val, op) { + // delta + op || (op = '$set'); + if (!delta[op]) delta[op] = {}; + delta[op][data.path] = val; + + // disabled versioning? + if (self.schema.options.versionKey === false) return; + + // path excluded from versioning? + if (shouldSkipVersioning(self, data.path)) return; + + // already marked for versioning? + if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return; + + if (self.schema.options.optimisticConcurrency) { + self.$__.version = VERSION_ALL; + return; + } + + switch (op) { + case '$set': + case '$unset': + case '$pop': + case '$pull': + case '$pullAll': + case '$push': + case '$addToSet': + break; + default: + // nothing to do + return; + } + + // ensure updates sent with positional notation are + // editing the correct array element. + // only increment the version if an array position changes. + // modifying elements of an array is ok if position does not change. + if (op === '$push' || op === '$addToSet' || op === '$pullAll' || op === '$pull') { + self.$__.version = VERSION_INC; + } else if (/^\$p/.test(op)) { + // potentially changing array positions + self.increment(); + } else if (Array.isArray(val)) { + // $set an array + self.increment(); + } else if (/\.\d+\.|\.\d+$/.test(data.path)) { + // now handling $set, $unset + // subpath of array + self.$__.version = VERSION_WHERE; + } +} + +/*! + * Compiles an update and where clause for a `val` with _atomics. + * + * @param {Document} self + * @param {Object} where + * @param {Object} delta + * @param {Object} data + * @param {Array} value + */ + +function handleAtomics(self, where, delta, data, value) { + if (delta.$set && delta.$set[data.path]) { + // $set has precedence over other atomics + return; + } + + if (typeof value.$__getAtomics === 'function') { + value.$__getAtomics().forEach(function(atomic) { + const op = atomic[0]; + const val = atomic[1]; + operand(self, where, delta, data, val, op); + }); + return; + } + + // legacy support for plugins + + const atomics = value[arrayAtomicsSymbol]; + const ops = Object.keys(atomics); + let i = ops.length; + let val; + let op; + + if (i === 0) { + // $set + + if (utils.isMongooseObject(value)) { + value = value.toObject({ depopulate: 1, _isNested: true }); + } else if (value.valueOf) { + value = value.valueOf(); + } + + return operand(self, where, delta, data, value); + } + + function iter(mem) { + return utils.isMongooseObject(mem) + ? mem.toObject({ depopulate: 1, _isNested: true }) + : mem; + } + + while (i--) { + op = ops[i]; + val = atomics[op]; + + if (utils.isMongooseObject(val)) { + val = val.toObject({ depopulate: true, transform: false, _isNested: true }); + } else if (Array.isArray(val)) { + val = val.map(iter); + } else if (val.valueOf) { + val = val.valueOf(); + } + + if (op === '$addToSet') { + val = { $each: val }; + } + + operand(self, where, delta, data, val, op); + } +} + +/** + * Produces a special query document of the modified properties used in updates. + * + * @api private + * @method $__delta + * @memberOf Model + * @instance + */ + +Model.prototype.$__delta = function() { + const dirty = this.$__dirty(); + if (!dirty.length && VERSION_ALL !== this.$__.version) { + return; + } + + const where = {}; + const delta = {}; + const len = dirty.length; + const divergent = []; + let d = 0; + + where._id = this._doc._id; + // If `_id` is an object, need to depopulate, but also need to be careful + // because `_id` can technically be null (see gh-6406) + if (get(where, '_id.$__', null) != null) { + where._id = where._id.toObject({ transform: false, depopulate: true }); + } + + for (; d < len; ++d) { + const data = dirty[d]; + let value = data.value; + + const match = checkDivergentArray(this, data.path, value); + if (match) { + divergent.push(match); + continue; + } + + const pop = this.populated(data.path, true); + if (!pop && this.$__.selected) { + // If any array was selected using an $elemMatch projection, we alter the path and where clause + // NOTE: MongoDB only supports projected $elemMatch on top level array. + const pathSplit = data.path.split('.'); + const top = pathSplit[0]; + if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) { + // If the selected array entry was modified + if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') { + where[top] = this.$__.selected[top]; + pathSplit[1] = '$'; + data.path = pathSplit.join('.'); + } + // if the selected array was modified in any other way throw an error + else { + divergent.push(data.path); + continue; + } + } + } + + if (divergent.length) continue; + + if (value === undefined) { + operand(this, where, delta, data, 1, '$unset'); + } else if (value === null) { + operand(this, where, delta, data, null); + } else if (value.isMongooseArray && value.$path() && value[arrayAtomicsSymbol]) { + // arrays and other custom types (support plugins etc) + handleAtomics(this, where, delta, data, value); + } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) { + // MongooseBuffer + value = value.toObject(); + operand(this, where, delta, data, value); + } else { + value = utils.clone(value, { + depopulate: true, + transform: false, + virtuals: false, + getters: false, + _isNested: true + }); + operand(this, where, delta, data, value); + } + } + + if (divergent.length) { + return new DivergentArrayError(divergent); + } + + if (this.$__.version) { + this.$__version(where, delta); + } + + return [where, delta]; +}; + +/*! + * Determine if array was populated with some form of filter and is now + * being updated in a manner which could overwrite data unintentionally. + * + * @see https://github.com/Automattic/mongoose/issues/1334 + * @param {Document} doc + * @param {String} path + * @return {String|undefined} + */ + +function checkDivergentArray(doc, path, array) { + // see if we populated this path + const pop = doc.populated(path, true); + + if (!pop && doc.$__.selected) { + // If any array was selected using an $elemMatch projection, we deny the update. + // NOTE: MongoDB only supports projected $elemMatch on top level array. + const top = path.split('.')[0]; + if (doc.$__.selected[top + '.$']) { + return top; + } + } + + if (!(pop && array && array.isMongooseArray)) return; + + // If the array was populated using options that prevented all + // documents from being returned (match, skip, limit) or they + // deselected the _id field, $pop and $set of the array are + // not safe operations. If _id was deselected, we do not know + // how to remove elements. $pop will pop off the _id from the end + // of the array in the db which is not guaranteed to be the + // same as the last element we have here. $set of the entire array + // would be similarily destructive as we never received all + // elements of the array and potentially would overwrite data. + const check = pop.options.match || + pop.options.options && utils.object.hasOwnProperty(pop.options.options, 'limit') || // 0 is not permitted + pop.options.options && pop.options.options.skip || // 0 is permitted + pop.options.select && // deselected _id? + (pop.options.select._id === 0 || + /\s?-_id\s?/.test(pop.options.select)); + + if (check) { + const atomics = array[arrayAtomicsSymbol]; + if (Object.keys(atomics).length === 0 || atomics.$set || atomics.$pop) { + return path; + } + } +} + +/** + * Appends versioning to the where and update clauses. + * + * @api private + * @method $__version + * @memberOf Model + * @instance + */ + +Model.prototype.$__version = function(where, delta) { + const key = this.schema.options.versionKey; + + if (where === true) { + // this is an insert + if (key) this.$__setValue(key, delta[key] = 0); + return; + } + + // updates + + // only apply versioning if our versionKey was selected. else + // there is no way to select the correct version. we could fail + // fast here and force them to include the versionKey but + // thats a bit intrusive. can we do this automatically? + if (!this.isSelected(key)) { + return; + } + + // $push $addToSet don't need the where clause set + if (VERSION_WHERE === (VERSION_WHERE & this.$__.version)) { + const value = this.$__getValue(key); + if (value != null) where[key] = value; + } + + if (VERSION_INC === (VERSION_INC & this.$__.version)) { + if (get(delta.$set, key, null) != null) { + // Version key is getting set, means we'll increment the doc's version + // after a successful save, so we should set the incremented version so + // future saves don't fail (gh-5779) + ++delta.$set[key]; + } else { + delta.$inc = delta.$inc || {}; + delta.$inc[key] = 1; + } + } +}; + +/** + * Signal that we desire an increment of this documents version. + * + * ####Example: + * + * Model.findById(id, function (err, doc) { + * doc.increment(); + * doc.save(function (err) { .. }) + * }) + * + * @see versionKeys http://mongoosejs.com/docs/guide.html#versionKey + * @api public + */ + +Model.prototype.increment = function increment() { + this.$__.version = VERSION_ALL; + return this; +}; + +/** + * Returns a query object + * + * @api private + * @method $__where + * @memberOf Model + * @instance + */ + +Model.prototype.$__where = function _where(where) { + where || (where = {}); + + if (!where._id) { + where._id = this._doc._id; + } + + if (this._doc._id === void 0) { + return new MongooseError('No _id found on document!'); + } + + return where; +}; + +/** + * Removes this document from the db. + * + * ####Example: + * product.remove(function (err, product) { + * if (err) return handleError(err); + * Product.findById(product._id, function (err, product) { + * console.log(product) // null + * }) + * }) + * + * + * As an extra measure of flow control, remove will return a Promise (bound to `fn` if passed) so it could be chained, or hooked to recieve errors + * + * ####Example: + * product.remove().then(function (product) { + * ... + * }).catch(function (err) { + * assert.ok(err) + * }) + * + * @param {Object} [options] + * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this operation. If not specified, defaults to the [document's associated session](api.html#document_Document-$session). + * @param {function(err,product)} [fn] optional callback + * @return {Promise} Promise + * @api public + */ + +Model.prototype.remove = function remove(options, fn) { + if (typeof options === 'function') { + fn = options; + options = undefined; + } + + options = new RemoveOptions(options); + if (options.hasOwnProperty('session')) { + this.$session(options.session); + } + this.$op = 'remove'; + + fn = this.constructor.$handleCallbackError(fn); + + return promiseOrCallback(fn, cb => { + cb = this.constructor.$wrapCallback(cb); + this.$__remove(options, (err, res) => { + this.$op = null; + cb(err, res); + }); + }, this.constructor.events); +}; + +/** + * Alias for remove + */ + +Model.prototype.delete = Model.prototype.remove; + +/** + * Removes this document from the db. Equivalent to `.remove()`. + * + * ####Example: + * product = await product.deleteOne(); + * await Product.findById(product._id); // null + * + * @param {function(err,product)} [fn] optional callback + * @return {Promise} Promise + * @api public + */ + +Model.prototype.deleteOne = function deleteOne(options, fn) { + if (typeof options === 'function') { + fn = options; + options = undefined; + } + + if (!options) { + options = {}; + } + + fn = this.constructor.$handleCallbackError(fn); + + return promiseOrCallback(fn, cb => { + cb = this.constructor.$wrapCallback(cb); + this.$__deleteOne(options, cb); + }, this.constructor.events); +}; + +/*! + * ignore + */ + +Model.prototype.$__remove = function $__remove(options, cb) { + if (this.$__.isDeleted) { + return immediate(() => cb(null, this)); + } + + const where = this.$__where(); + if (where instanceof MongooseError) { + return cb(where); + } + + _applyCustomWhere(this, where); + + const session = this.$session(); + if (!options.hasOwnProperty('session')) { + options.session = session; + } + + this[modelCollectionSymbol].deleteOne(where, options, err => { + if (!err) { + this.$__.isDeleted = true; + this.emit('remove', this); + this.constructor.emit('remove', this); + return cb(null, this); + } + this.$__.isDeleted = false; + cb(err); + }); +}; + +/*! + * ignore + */ + +Model.prototype.$__deleteOne = Model.prototype.$__remove; + +/** + * Returns another Model instance. + * + * ####Example: + * + * const doc = new Tank; + * doc.model('User').findById(id, callback); + * + * @param {String} name model name + * @api public + */ + +Model.prototype.model = function model(name) { + return this[modelDbSymbol].model(name); +}; + +/** + * Returns true if at least one document exists in the database that matches + * the given `filter`, and false otherwise. + * + * Under the hood, `MyModel.exists({ answer: 42 })` is equivalent to + * `MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc => !!doc)` + * + * ####Example: + * await Character.deleteMany({}); + * await Character.create({ name: 'Jean-Luc Picard' }); + * + * await Character.exists({ name: /picard/i }); // true + * await Character.exists({ name: /riker/i }); // false + * + * This function triggers the following middleware. + * + * - `findOne()` + * + * @param {Object} filter + * @param {Function} [callback] callback + * @return {Promise} + */ + +Model.exists = function exists(filter, options, callback) { + _checkContext(this, 'exists'); + + if (typeof options === 'function') { + callback = options; + options = null; + } + + const query = this.findOne(filter). + select({ _id: 1 }). + lean(). + setOptions(options); + + if (typeof callback === 'function') { + query.exec(function(err, doc) { + if (err != null) { + return callback(err); + } + callback(null, !!doc); + }); + return; + } + + return query.then(doc => !!doc); +}; + +/** + * Adds a discriminator type. + * + * ####Example: + * + * function BaseSchema() { + * Schema.apply(this, arguments); + * + * this.add({ + * name: String, + * createdAt: Date + * }); + * } + * util.inherits(BaseSchema, Schema); + * + * const PersonSchema = new BaseSchema(); + * const BossSchema = new BaseSchema({ department: String }); + * + * const Person = mongoose.model('Person', PersonSchema); + * const Boss = Person.discriminator('Boss', BossSchema); + * new Boss().__t; // "Boss". `__t` is the default `discriminatorKey` + * + * const employeeSchema = new Schema({ boss: ObjectId }); + * const Employee = Person.discriminator('Employee', employeeSchema, 'staff'); + * new Employee().__t; // "staff" because of 3rd argument above + * + * @param {String} name discriminator model name + * @param {Schema} schema discriminator model schema + * @param {String} [value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter. + * @return {Model} The newly created discriminator model + * @api public + */ + +Model.discriminator = function(name, schema, value) { + let model; + if (typeof name === 'function') { + model = name; + name = utils.getFunctionName(model); + if (!(model.prototype instanceof Model)) { + throw new MongooseError('The provided class ' + name + ' must extend Model'); + } + } + + _checkContext(this, 'discriminator'); + + if (utils.isObject(schema) && !schema.instanceOfSchema) { + schema = new Schema(schema); + } + + schema = discriminator(this, name, schema, value, true); + if (this.db.models[name]) { + throw new OverwriteModelError(name); + } + + schema.$isRootDiscriminator = true; + schema.$globalPluginsApplied = true; + + model = this.db.model(model || name, schema, this.collection.name); + this.discriminators[name] = model; + const d = this.discriminators[name]; + d.prototype.__proto__ = this.prototype; + Object.defineProperty(d, 'baseModelName', { + value: this.modelName, + configurable: true, + writable: false + }); + + // apply methods and statics + applyMethods(d, schema); + applyStatics(d, schema); + + if (this[subclassedSymbol] != null) { + for (const submodel of this[subclassedSymbol]) { + submodel.discriminators = submodel.discriminators || {}; + submodel.discriminators[name] = + model.__subclass(model.db, schema, submodel.collection.name); + } + } + + return d; +}; + +/*! + * Make sure `this` is a model + */ + +function _checkContext(ctx, fnName) { + // Check context, because it is easy to mistakenly type + // `new Model.discriminator()` and get an incomprehensible error + if (ctx == null || ctx === global) { + throw new MongooseError('`Model.' + fnName + '()` cannot run without a ' + + 'model as `this`. Make sure you are calling `MyModel.' + fnName + '()` ' + + 'where `MyModel` is a Mongoose model.'); + } else if (ctx[modelSymbol] == null) { + throw new MongooseError('`Model.' + fnName + '()` cannot run without a ' + + 'model as `this`. Make sure you are not calling ' + + '`new Model.' + fnName + '()`'); + } +} + +// Model (class) features + +/*! + * Give the constructor the ability to emit events. + */ + +for (const i in EventEmitter.prototype) { + Model[i] = EventEmitter.prototype[i]; +} + +/** + * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/), + * unless [`autoIndex`](http://mongoosejs.com/docs/guide.html#autoIndex) is turned off. + * + * Mongoose calls this function automatically when a model is created using + * [`mongoose.model()`](/docs/api.html#mongoose_Mongoose-model) or + * [`connection.model()`](/docs/api.html#connection_Connection-model), so you + * don't need to call it. This function is also idempotent, so you may call it + * to get back a promise that will resolve when your indexes are finished + * building as an alternative to [`MyModel.on('index')`](/docs/guide.html#indexes) + * + * ####Example: + * + * const eventSchema = new Schema({ thing: { type: 'string', unique: true }}) + * // This calls `Event.init()` implicitly, so you don't need to call + * // `Event.init()` on your own. + * const Event = mongoose.model('Event', eventSchema); + * + * Event.init().then(function(Event) { + * // You can also use `Event.on('index')` if you prefer event emitters + * // over promises. + * console.log('Indexes are done building!'); + * }); + * + * @api public + * @param {Function} [callback] + * @returns {Promise} + */ + +Model.init = function init(callback) { + _checkContext(this, 'init'); + + this.schema.emit('init', this); + + if (this.$init != null) { + if (callback) { + this.$init.then(() => callback(), err => callback(err)); + return null; + } + return this.$init; + } + + const Promise = PromiseProvider.get(); + const autoIndex = utils.getOption('autoIndex', + this.schema.options, this.db.config, this.db.base.options); + const autoCreate = utils.getOption('autoCreate', + this.schema.options, this.db.config, this.db.base.options); + + const _ensureIndexes = autoIndex ? + cb => this.ensureIndexes({ _automatic: true }, cb) : + cb => cb(); + const _createCollection = autoCreate ? + cb => this.createCollection({}, cb) : + cb => cb(); + + this.$init = new Promise((resolve, reject) => { + _createCollection(error => { + if (error) { + return reject(error); + } + _ensureIndexes(error => { + if (error) { + return reject(error); + } + resolve(this); + }); + }); + }); + + if (callback) { + this.$init.then(() => callback(), err => callback(err)); + this.$caught = true; + return null; + } else { + const _catch = this.$init.catch; + const _this = this; + this.$init.catch = function() { + this.$caught = true; + return _catch.apply(_this.$init, arguments); + }; + } + + return this.$init; +}; + + +/** + * Create the collection for this model. By default, if no indexes are specified, + * mongoose will not create the collection for the model until any documents are + * created. Use this method to create the collection explicitly. + * + * Note 1: You may need to call this before starting a transaction + * See https://docs.mongodb.com/manual/core/transactions/#transactions-and-operations + * + * Note 2: You don't have to call this if your schema contains index or unique field. + * In that case, just use `Model.init()` + * + * ####Example: + * + * const userSchema = new Schema({ name: String }) + * const User = mongoose.model('User', userSchema); + * + * User.createCollection().then(function(collection) { + * console.log('Collection is created!'); + * }); + * + * @api public + * @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/3.1/api/Db.html#createCollection) + * @param {Function} [callback] + * @returns {Promise} + */ + +Model.createCollection = function createCollection(options, callback) { + _checkContext(this, 'createCollection'); + + if (typeof options === 'string') { + throw new MongooseError('You can\'t specify a new collection name in Model.createCollection.' + + 'This is not like Connection.createCollection. Only options are accepted here.'); + } else if (typeof options === 'function') { + callback = options; + options = null; + } + + const schemaCollation = get(this, 'schema.options.collation', null); + if (schemaCollation != null) { + options = Object.assign({ collation: schemaCollation }, options); + } + + callback = this.$handleCallbackError(callback); + + return promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + + this.db.createCollection(this.collection.collectionName, options, utils.tick((error) => { + if (error != null && error.codeName !== 'NamespaceExists') { + return cb(error); + } + this.collection = this.db.collection(this.collection.collectionName, options); + cb(null, this.collection); + })); + }, this.events); +}; + +/** + * Makes the indexes in MongoDB match the indexes defined in this model's + * schema. This function will drop any indexes that are not defined in + * the model's schema except the `_id` index, and build any indexes that + * are in your schema but not in MongoDB. + * + * See the [introductory blog post](http://thecodebarbarian.com/whats-new-in-mongoose-5-2-syncindexes) + * for more information. + * + * ####Example: + * + * const schema = new Schema({ name: { type: String, unique: true } }); + * const Customer = mongoose.model('Customer', schema); + * await Customer.collection.createIndex({ age: 1 }); // Index is not in schema + * // Will drop the 'age' index and create an index on `name` + * await Customer.syncIndexes(); + * + * @param {Object} [options] options to pass to `ensureIndexes()` + * @param {Boolean} [options.background=null] if specified, overrides each index's `background` property + * @param {Function} [callback] optional callback + * @return {Promise|undefined} Returns `undefined` if callback is specified, returns a promise if no callback. + * @api public + */ + +Model.syncIndexes = function syncIndexes(options, callback) { + _checkContext(this, 'syncIndexes'); + + callback = this.$handleCallbackError(callback); + + return promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + + this.createCollection(err => { + if (err != null && err.codeName !== 'NamespaceExists') { + return cb(err); + } + this.cleanIndexes((err, dropped) => { + if (err != null) { + return cb(err); + } + this.createIndexes(options, err => { + if (err != null) { + return cb(err); + } + cb(null, dropped); + }); + }); + }); + }, this.events); +}; + +/** + * Deletes all indexes that aren't defined in this model's schema. Used by + * `syncIndexes()`. + * + * The returned promise resolves to a list of the dropped indexes' names as an array + * + * @param {Function} [callback] optional callback + * @return {Promise|undefined} Returns `undefined` if callback is specified, returns a promise if no callback. + * @api public + */ + +Model.cleanIndexes = function cleanIndexes(callback) { + _checkContext(this, 'cleanIndexes'); + + callback = this.$handleCallbackError(callback); + + return promiseOrCallback(callback, cb => { + const collection = this.collection; + + this.listIndexes((err, indexes) => { + if (err != null) { + return cb(err); + } + + const schemaIndexes = this.schema.indexes(); + const toDrop = []; + + for (const index of indexes) { + let found = false; + // Never try to drop `_id` index, MongoDB server doesn't allow it + if (isDefaultIdIndex(index)) { + continue; + } + + for (const schemaIndex of schemaIndexes) { + const key = schemaIndex[0]; + const options = _decorateDiscriminatorIndexOptions(this, + utils.clone(schemaIndex[1])); + if (isIndexEqual(key, options, index)) { + found = true; + } + } + + if (!found) { + toDrop.push(index.name); + } + } + + if (toDrop.length === 0) { + return cb(null, []); + } + + dropIndexes(toDrop, cb); + }); + + function dropIndexes(toDrop, cb) { + let remaining = toDrop.length; + let error = false; + toDrop.forEach(indexName => { + collection.dropIndex(indexName, err => { + if (err != null) { + error = true; + return cb(err); + } + if (!error) { + --remaining || cb(null, toDrop); + } + }); + }); + } + }); +}; + +/** + * Lists the indexes currently defined in MongoDB. This may or may not be + * the same as the indexes defined in your schema depending on whether you + * use the [`autoIndex` option](/docs/guide.html#autoIndex) and if you + * build indexes manually. + * + * @param {Function} [cb] optional callback + * @return {Promise|undefined} Returns `undefined` if callback is specified, returns a promise if no callback. + * @api public + */ + +Model.listIndexes = function init(callback) { + _checkContext(this, 'listIndexes'); + + const _listIndexes = cb => { + this.collection.listIndexes().toArray(cb); + }; + + callback = this.$handleCallbackError(callback); + + return promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + + // Buffering + if (this.collection.buffer) { + this.collection.addQueue(_listIndexes, [cb]); + } else { + _listIndexes(cb); + } + }, this.events); +}; + +/** + * Sends `createIndex` commands to mongo for each index declared in the schema. + * The `createIndex` commands are sent in series. + * + * ####Example: + * + * Event.ensureIndexes(function (err) { + * if (err) return handleError(err); + * }); + * + * After completion, an `index` event is emitted on this `Model` passing an error if one occurred. + * + * ####Example: + * + * const eventSchema = new Schema({ thing: { type: 'string', unique: true }}) + * const Event = mongoose.model('Event', eventSchema); + * + * Event.on('index', function (err) { + * if (err) console.error(err); // error occurred during index creation + * }) + * + * _NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution._ + * + * @param {Object} [options] internal options + * @param {Function} [cb] optional callback + * @return {Promise} + * @api public + */ + +Model.ensureIndexes = function ensureIndexes(options, callback) { + _checkContext(this, 'ensureIndexes'); + + if (typeof options === 'function') { + callback = options; + options = null; + } + + callback = this.$handleCallbackError(callback); + + return promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + + _ensureIndexes(this, options || {}, error => { + if (error) { + return cb(error); + } + cb(null); + }); + }, this.events); +}; + +/** + * Similar to `ensureIndexes()`, except for it uses the [`createIndex`](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#createIndex) + * function. + * + * @param {Object} [options] internal options + * @param {Function} [cb] optional callback + * @return {Promise} + * @api public + */ + +Model.createIndexes = function createIndexes(options, callback) { + _checkContext(this, 'createIndexes'); + + if (typeof options === 'function') { + callback = options; + options = {}; + } + options = options || {}; + options.createIndex = true; + return this.ensureIndexes(options, callback); +}; + +/*! + * ignore + */ + +function _ensureIndexes(model, options, callback) { + const indexes = model.schema.indexes(); + let indexError; + + options = options || {}; + + const done = function(err) { + if (err && !model.$caught) { + model.emit('error', err); + } + model.emit('index', err || indexError); + callback && callback(err || indexError); + }; + + for (const index of indexes) { + if (isDefaultIdIndex(index)) { + console.warn('mongoose: Cannot specify a custom index on `_id` for ' + + 'model name "' + model.modelName + '", ' + + 'MongoDB does not allow overwriting the default `_id` index. See ' + + 'http://bit.ly/mongodb-id-index'); + } + } + + if (!indexes.length) { + immediate(function() { + done(); + }); + return; + } + // Indexes are created one-by-one to support how MongoDB < 2.4 deals + // with background indexes. + + const indexSingleDone = function(err, fields, options, name) { + model.emit('index-single-done', err, fields, options, name); + }; + const indexSingleStart = function(fields, options) { + model.emit('index-single-start', fields, options); + }; + + const baseSchema = model.schema._baseSchema; + const baseSchemaIndexes = baseSchema ? baseSchema.indexes() : []; + + const create = function() { + if (options._automatic) { + if (model.schema.options.autoIndex === false || + (model.schema.options.autoIndex == null && model.db.config.autoIndex === false)) { + return done(); + } + } + + const index = indexes.shift(); + if (!index) { + return done(); + } + if (options._automatic && index[1]._autoIndex === false) { + return create(); + } + + if (baseSchemaIndexes.find(i => utils.deepEqual(i, index))) { + return create(); + } + + const indexFields = utils.clone(index[0]); + const indexOptions = utils.clone(index[1]); + delete indexOptions._autoIndex; + + _decorateDiscriminatorIndexOptions(model, indexOptions); + if ('safe' in options) { + _handleSafe(options); + } + applyWriteConcern(model.schema, indexOptions); + + indexSingleStart(indexFields, options); + let useCreateIndex = !!model.base.options.useCreateIndex; + if ('useCreateIndex' in model.db.config) { + useCreateIndex = !!model.db.config.useCreateIndex; + } + if ('createIndex' in options) { + useCreateIndex = !!options.createIndex; + } + if ('background' in options) { + indexOptions.background = options.background; + } + + const methodName = useCreateIndex ? 'createIndex' : 'ensureIndex'; + model.collection[methodName](indexFields, indexOptions, utils.tick(function(err, name) { + indexSingleDone(err, indexFields, indexOptions, name); + if (err) { + if (!indexError) { + indexError = err; + } + if (!model.$caught) { + model.emit('error', err); + } + } + create(); + })); + }; + + immediate(function() { + // If buffering is off, do this manually. + if (options._automatic && !model.collection.collection) { + model.collection.addQueue(create, []); + } else { + create(); + } + }); +} + +function _decorateDiscriminatorIndexOptions(model, indexOptions) { + // If the model is a discriminator and it has a unique index, add a + // partialFilterExpression by default so the unique index will only apply + // to that discriminator. + if (model.baseModelName != null && indexOptions.unique && + !('partialFilterExpression' in indexOptions) && + !('sparse' in indexOptions)) { + + const value = ( + model.schema.discriminatorMapping && + model.schema.discriminatorMapping.value + ) || model.modelName; + + indexOptions.partialFilterExpression = { + [model.schema.options.discriminatorKey]: value + }; + } + return indexOptions; +} + +const safeDeprecationWarning = 'Mongoose: the `safe` option for `save()` is ' + + 'deprecated. Use the `w` option instead: http://bit.ly/mongoose-save'; + +const _handleSafe = util.deprecate(function _handleSafe(options) { + if (options.safe) { + if (typeof options.safe === 'boolean') { + options.w = options.safe; + delete options.safe; + } + if (typeof options.safe === 'object') { + options.w = options.safe.w; + options.j = options.safe.j; + options.wtimeout = options.safe.wtimeout; + delete options.safe; + } + } +}, safeDeprecationWarning); + +/** + * Schema the model uses. + * + * @property schema + * @receiver Model + * @api public + * @memberOf Model + */ + +Model.schema; + +/*! + * Connection instance the model uses. + * + * @property db + * @api public + * @memberOf Model + */ + +Model.db; + +/*! + * Collection the model uses. + * + * @property collection + * @api public + * @memberOf Model + */ + +Model.collection; + +/** + * Base Mongoose instance the model uses. + * + * @property base + * @api public + * @memberOf Model + */ + +Model.base; + +/** + * Registered discriminators for this model. + * + * @property discriminators + * @api public + * @memberOf Model + */ + +Model.discriminators; + +/** + * Translate any aliases fields/conditions so the final query or document object is pure + * + * ####Example: + * + * Character + * .find(Character.translateAliases({ + * '名': 'Eddard Stark' // Alias for 'name' + * }) + * .exec(function(err, characters) {}) + * + * ####Note: + * Only translate arguments of object type anything else is returned raw + * + * @param {Object} raw fields/conditions that may contain aliased keys + * @return {Object} the translated 'pure' fields/conditions + */ +Model.translateAliases = function translateAliases(fields) { + _checkContext(this, 'translateAliases'); + + const translate = (key, value) => { + let alias; + const translated = []; + const fieldKeys = key.split('.'); + let currentSchema = this.schema; + for (const i in fieldKeys) { + const name = fieldKeys[i]; + if (currentSchema && currentSchema.aliases[name]) { + alias = currentSchema.aliases[name]; + // Alias found, + translated.push(alias); + } else { + // Alias not found, so treat as un-aliased key + translated.push(name); + } + + // Check if aliased path is a schema + if (currentSchema && currentSchema.paths[alias]) { + currentSchema = currentSchema.paths[alias].schema; + } + else + currentSchema = null; + } + + const translatedKey = translated.join('.'); + if (fields instanceof Map) + fields.set(translatedKey, value); + else + fields[translatedKey] = value; + + if (translatedKey !== key) { + // We'll be using the translated key instead + if (fields instanceof Map) { + // Delete from map + fields.delete(key); + } else { + // Delete from object + delete fields[key]; // We'll be using the translated key instead + } + } + return fields; + }; + + if (typeof fields === 'object') { + // Fields is an object (query conditions or document fields) + if (fields instanceof Map) { + // A Map was supplied + for (const field of new Map(fields)) { + fields = translate(field[0], field[1]); + } + } else { + // Infer a regular object was supplied + for (const key of Object.keys(fields)) { + fields = translate(key, fields[key]); + if (key[0] === '$') { + if (Array.isArray(fields[key])) { + for (const i in fields[key]) { + // Recursively translate nested queries + fields[key][i] = this.translateAliases(fields[key][i]); + } + } + } + } + } + + return fields; + } else { + // Don't know typeof fields + return fields; + } +}; + +/** + * Removes all documents that match `conditions` from the collection. + * To remove just the first document that matches `conditions`, set the `single` + * option to true. + * + * ####Example: + * + * const res = await Character.remove({ name: 'Eddard Stark' }); + * res.deletedCount; // Number of documents removed + * + * ####Note: + * + * This method sends a remove command directly to MongoDB, no Mongoose documents + * are involved. Because no Mongoose documents are involved, Mongoose does + * not execute [document middleware](/docs/middleware.html#types-of-middleware). + * + * @param {Object} conditions + * @param {Object} [options] + * @param {Session} [options.session=null] the [session](https://docs.mongodb.com/manual/reference/server-sessions/) associated with this operation. + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.remove = function remove(conditions, options, callback) { + _checkContext(this, 'remove'); + + if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + options = null; + } else if (typeof options === 'function') { + callback = options; + options = null; + } + + // get the mongodb collection object + const mq = new this.Query({}, {}, this, this.collection); + mq.setOptions(options); + + callback = this.$handleCallbackError(callback); + + return mq.remove(conditions, callback); +}; + +/** + * Deletes the first document that matches `conditions` from the collection. + * Behaves like `remove()`, but deletes at most one document regardless of the + * `single` option. + * + * ####Example: + * + * await Character.deleteOne({ name: 'Eddard Stark' }); + * + * ####Note: + * + * This function triggers `deleteOne` query hooks. Read the + * [middleware docs](/docs/middleware.html#naming) to learn more. + * + * @param {Object} conditions + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.deleteOne = function deleteOne(conditions, options, callback) { + _checkContext(this, 'deleteOne'); + + if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + options = null; + } + else if (typeof options === 'function') { + callback = options; + options = null; + } + + const mq = new this.Query({}, {}, this, this.collection); + mq.setOptions(options); + + callback = this.$handleCallbackError(callback); + + return mq.deleteOne(conditions, callback); +}; + +/** + * Deletes all of the documents that match `conditions` from the collection. + * Behaves like `remove()`, but deletes all documents that match `conditions` + * regardless of the `single` option. + * + * ####Example: + * + * await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }); + * + * ####Note: + * + * This function triggers `deleteMany` query hooks. Read the + * [middleware docs](/docs/middleware.html#naming) to learn more. + * + * @param {Object} conditions + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.deleteMany = function deleteMany(conditions, options, callback) { + _checkContext(this, 'deleteMany'); + + if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + options = null; + } else if (typeof options === 'function') { + callback = options; + options = null; + } + + const mq = new this.Query({}, {}, this, this.collection); + mq.setOptions(options); + + callback = this.$handleCallbackError(callback); + + return mq.deleteMany(conditions, callback); +}; + +/** + * Finds documents. + * + * Mongoose casts the `filter` to match the model's schema before the command is sent. + * See our [query casting tutorial](/docs/tutorials/query_casting.html) for + * more information on how Mongoose casts `filter`. + * + * ####Examples: + * + * // find all documents + * await MyModel.find({}); + * + * // find all documents named john and at least 18 + * await MyModel.find({ name: 'john', age: { $gte: 18 } }).exec(); + * + * // executes, passing results to callback + * MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {}); + * + * // executes, name LIKE john and only selecting the "name" and "friends" fields + * await MyModel.find({ name: /john/i }, 'name friends').exec(); + * + * // passing options + * await MyModel.find({ name: /john/i }, null, { skip: 10 }).exec(); + * + * @param {Object|ObjectId} filter + * @param {Object|String|Array<String>} [projection] optional fields to return, see [`Query.prototype.select()`](http://mongoosejs.com/docs/api.html#query_Query-select) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Function} [callback] + * @return {Query} + * @see field selection #query_Query-select + * @see query casting /docs/tutorials/query_casting.html + * @api public + */ + +Model.find = function find(conditions, projection, options, callback) { + _checkContext(this, 'find'); + + if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + projection = null; + options = null; + } else if (typeof projection === 'function') { + callback = projection; + projection = null; + options = null; + } else if (typeof options === 'function') { + callback = options; + options = null; + } + + const mq = new this.Query({}, {}, this, this.collection); + mq.select(projection); + + mq.setOptions(options); + if (this.schema.discriminatorMapping && + this.schema.discriminatorMapping.isRoot && + mq.selectedInclusively()) { + // Need to select discriminator key because original schema doesn't have it + mq.select(this.schema.options.discriminatorKey); + } + + callback = this.$handleCallbackError(callback); + + return mq.find(conditions, callback); +}; + +/** + * Finds a single document by its _id field. `findById(id)` is almost* + * equivalent to `findOne({ _id: id })`. If you want to query by a document's + * `_id`, use `findById()` instead of `findOne()`. + * + * The `id` is cast based on the Schema before sending the command. + * + * This function triggers the following middleware. + * + * - `findOne()` + * + * \* Except for how it treats `undefined`. If you use `findOne()`, you'll see + * that `findOne(undefined)` and `findOne({ _id: undefined })` are equivalent + * to `findOne({})` and return arbitrary documents. However, mongoose + * translates `findById(undefined)` into `findOne({ _id: null })`. + * + * ####Example: + * + * // Find the adventure with the given `id`, or `null` if not found + * await Adventure.findById(id).exec(); + * + * // using callback + * Adventure.findById(id, function (err, adventure) {}); + * + * // select only the adventures name and length + * await Adventure.findById(id, 'name length').exec(); + * + * @param {Any} id value of `_id` to query by + * @param {Object|String|Array<String>} [projection] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Function} [callback] + * @return {Query} + * @see field selection #query_Query-select + * @see lean queries /docs/tutorials/lean.html + * @see findById in Mongoose https://masteringjs.io/tutorials/mongoose/find-by-id + * @api public + */ + +Model.findById = function findById(id, projection, options, callback) { + _checkContext(this, 'findById'); + + if (typeof id === 'undefined') { + id = null; + } + + callback = this.$handleCallbackError(callback); + + return this.findOne({ _id: id }, projection, options, callback); +}; + +/** + * Finds one document. + * + * The `conditions` are cast to their respective SchemaTypes before the command is sent. + * + * *Note:* `conditions` is optional, and if `conditions` is null or undefined, + * mongoose will send an empty `findOne` command to MongoDB, which will return + * an arbitrary document. If you're querying by `_id`, use `findById()` instead. + * + * ####Example: + * + * // Find one adventure whose `country` is 'Croatia', otherwise `null` + * await Adventure.findOne({ country: 'Croatia' }).exec(); + * + * // using callback + * Adventure.findOne({ country: 'Croatia' }, function (err, adventure) {}); + * + * // select only the adventures name and length + * await Adventure.findOne({ country: 'Croatia' }, 'name length').exec(); + * + * @param {Object} [conditions] + * @param {Object|String|Array<String>} [projection] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Function} [callback] + * @return {Query} + * @see field selection #query_Query-select + * @see lean queries /docs/tutorials/lean.html + * @api public + */ + +Model.findOne = function findOne(conditions, projection, options, callback) { + _checkContext(this, 'findOne'); + + if (typeof options === 'function') { + callback = options; + options = null; + } else if (typeof projection === 'function') { + callback = projection; + projection = null; + options = null; + } else if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + projection = null; + options = null; + } + + const mq = new this.Query({}, {}, this, this.collection); + mq.select(projection); + + mq.setOptions(options); + if (this.schema.discriminatorMapping && + this.schema.discriminatorMapping.isRoot && + mq.selectedInclusively()) { + mq.select(this.schema.options.discriminatorKey); + } + + callback = this.$handleCallbackError(callback); + + return mq.findOne(conditions, callback); +}; + +/** + * Estimates the number of documents in the MongoDB collection. Faster than + * using `countDocuments()` for large collections because + * `estimatedDocumentCount()` uses collection metadata rather than scanning + * the entire collection. + * + * ####Example: + * + * const numAdventures = Adventure.estimatedDocumentCount(); + * + * @param {Object} [options] + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.estimatedDocumentCount = function estimatedDocumentCount(options, callback) { + _checkContext(this, 'estimatedDocumentCount'); + + const mq = new this.Query({}, {}, this, this.collection); + + callback = this.$handleCallbackError(callback); + + return mq.estimatedDocumentCount(options, callback); +}; + +/** + * Counts number of documents matching `filter` in a database collection. + * + * ####Example: + * + * Adventure.countDocuments({ type: 'jungle' }, function (err, count) { + * console.log('there are %d jungle adventures', count); + * }); + * + * If you want to count all documents in a large collection, + * use the [`estimatedDocumentCount()` function](/docs/api.html#model_Model.estimatedDocumentCount) + * instead. If you call `countDocuments({})`, MongoDB will always execute + * a full collection scan and **not** use any indexes. + * + * The `countDocuments()` function is similar to `count()`, but there are a + * [few operators that `countDocuments()` does not support](https://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#countDocuments). + * Below are the operators that `count()` supports but `countDocuments()` does not, + * and the suggested replacement: + * + * - `$where`: [`$expr`](https://docs.mongodb.com/manual/reference/operator/query/expr/) + * - `$near`: [`$geoWithin`](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/) with [`$center`](https://docs.mongodb.com/manual/reference/operator/query/center/#op._S_center) + * - `$nearSphere`: [`$geoWithin`](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/) with [`$centerSphere`](https://docs.mongodb.com/manual/reference/operator/query/centerSphere/#op._S_centerSphere) + * + * @param {Object} filter + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.countDocuments = function countDocuments(conditions, callback) { + _checkContext(this, 'countDocuments'); + + if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + } + + const mq = new this.Query({}, {}, this, this.collection); + + callback = this.$handleCallbackError(callback); + + return mq.countDocuments(conditions, callback); +}; + +/** + * Counts number of documents that match `filter` in a database collection. + * + * This method is deprecated. If you want to count the number of documents in + * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](/docs/api.html#model_Model.estimatedDocumentCount) + * instead. Otherwise, use the [`countDocuments()`](/docs/api.html#model_Model.countDocuments) function instead. + * + * ####Example: + * + * Adventure.count({ type: 'jungle' }, function (err, count) { + * if (err) .. + * console.log('there are %d jungle adventures', count); + * }); + * + * @deprecated + * @param {Object} filter + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.count = function count(conditions, callback) { + _checkContext(this, 'count'); + + if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + } + + const mq = new this.Query({}, {}, this, this.collection); + + callback = this.$handleCallbackError(callback); + + return mq.count(conditions, callback); +}; + +/** + * Creates a Query for a `distinct` operation. + * + * Passing a `callback` executes the query. + * + * ####Example + * + * Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) { + * if (err) return handleError(err); + * + * assert(Array.isArray(result)); + * console.log('unique urls with more than 100 clicks', result); + * }) + * + * const query = Link.distinct('url'); + * query.exec(callback); + * + * @param {String} field + * @param {Object} [conditions] optional + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.distinct = function distinct(field, conditions, callback) { + _checkContext(this, 'distinct'); + + const mq = new this.Query({}, {}, this, this.collection); + + if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + } + callback = this.$handleCallbackError(callback); + + return mq.distinct(field, conditions, callback); +}; + +/** + * Creates a Query, applies the passed conditions, and returns the Query. + * + * For example, instead of writing: + * + * User.find({age: {$gte: 21, $lte: 65}}, callback); + * + * we can instead write: + * + * User.where('age').gte(21).lte(65).exec(callback); + * + * Since the Query class also supports `where` you can continue chaining + * + * User + * .where('age').gte(21).lte(65) + * .where('name', /^b/i) + * ... etc + * + * @param {String} path + * @param {Object} [val] optional value + * @return {Query} + * @api public + */ + +Model.where = function where(path, val) { + _checkContext(this, 'where'); + + void val; // eslint + const mq = new this.Query({}, {}, this, this.collection).find({}); + return mq.where.apply(mq, arguments); +}; + +/** + * Creates a `Query` and specifies a `$where` condition. + * + * Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model. + * + * Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {}); + * + * @param {String|Function} argument is a javascript string or anonymous function + * @method $where + * @memberOf Model + * @return {Query} + * @see Query.$where #query_Query-%24where + * @api public + */ + +Model.$where = function $where() { + _checkContext(this, '$where'); + + const mq = new this.Query({}, {}, this, this.collection).find({}); + return mq.$where.apply(mq, arguments); +}; + +/** + * Issues a mongodb findAndModify update command. + * + * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes if `callback` is passed else a Query object is returned. + * + * ####Options: + * + * - `new`: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0) + * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. + * - `overwrite`: bool - if true, replace the entire document. + * - `fields`: {Object|String} - Field selection. Equivalent to `.select(fields).findOneAndUpdate()` + * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. + * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/). + * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update + * + * ####Examples: + * + * A.findOneAndUpdate(conditions, update, options, callback) // executes + * A.findOneAndUpdate(conditions, update, options) // returns Query + * A.findOneAndUpdate(conditions, update, callback) // executes + * A.findOneAndUpdate(conditions, update) // returns Query + * A.findOneAndUpdate() // returns Query + * + * ####Note: + * + * All top level update keys which are not `atomic` operation names are treated as set operations: + * + * ####Example: + * + * const query = { name: 'borne' }; + * Model.findOneAndUpdate(query, { name: 'jason bourne' }, options, callback) + * + * // is sent as + * Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options, callback) + * + * This helps prevent accidentally overwriting your document with `{ name: 'jason bourne' }`. + * + * ####Note: + * + * Values are cast to their appropriate types when using the findAndModify helpers. + * However, the below are not executed by default. + * + * - defaults. Use the `setDefaultsOnInsert` option to override. + * + * `findAndModify` helpers support limited validation. You can + * enable these by setting the `runValidators` options, + * respectively. + * + * If you need full-fledged validation, use the traditional approach of first + * retrieving the document. + * + * Model.findById(id, function (err, doc) { + * if (err) .. + * doc.name = 'jason bourne'; + * doc.save(callback); + * }); + * + * @param {Object} [conditions] + * @param {Object} [update] + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. To change the default to `true`, use `mongoose.set('returnOriginal', false);`. + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. + * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://docs.mongodb.com/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace(conditions, update, options, callback)](https://mongoosejs.com/docs/api/model.html#model_Model.findOneAndReplace). + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Function} [callback] + * @return {Query} + * @see Tutorial /docs/tutorials/findoneandupdate.html + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + * @api public + */ + +Model.findOneAndUpdate = function(conditions, update, options, callback) { + _checkContext(this, 'findOneAndUpdate'); + + if (typeof options === 'function') { + callback = options; + options = null; + } else if (arguments.length === 1) { + if (typeof conditions === 'function') { + const msg = 'Model.findOneAndUpdate(): First argument must not be a function.\n\n' + + ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options, callback)\n' + + ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options)\n' + + ' ' + this.modelName + '.findOneAndUpdate(conditions, update)\n' + + ' ' + this.modelName + '.findOneAndUpdate(update)\n' + + ' ' + this.modelName + '.findOneAndUpdate()\n'; + throw new TypeError(msg); + } + update = conditions; + conditions = undefined; + } + callback = this.$handleCallbackError(callback); + + let fields; + if (options) { + fields = options.fields || options.projection; + } + + update = utils.clone(update, { + depopulate: true, + _isNested: true + }); + + _decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey); + + const mq = new this.Query({}, {}, this, this.collection); + mq.select(fields); + + return mq.findOneAndUpdate(conditions, update, options, callback); +}; + +/*! + * Decorate the update with a version key, if necessary + */ + +function _decorateUpdateWithVersionKey(update, options, versionKey) { + if (!versionKey || !get(options, 'upsert', false)) { + return; + } + + const updatedPaths = modifiedPaths(update); + if (!updatedPaths[versionKey]) { + if (options.overwrite) { + update[versionKey] = 0; + } else { + if (!update.$setOnInsert) { + update.$setOnInsert = {}; + } + update.$setOnInsert[versionKey] = 0; + } + } +} + +/** + * Issues a mongodb findAndModify update command by a document's _id field. + * `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`. + * + * Finds a matching document, updates it according to the `update` arg, + * passing any `options`, and returns the found document (if any) to the + * callback. The query executes if `callback` is passed. + * + * This function triggers the following middleware. + * + * - `findOneAndUpdate()` + * + * ####Options: + * + * - `new`: bool - true to return the modified document rather than the original. defaults to false + * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. + * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. + * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/). + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `select`: sets the document fields to return + * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update + * + * ####Examples: + * + * A.findByIdAndUpdate(id, update, options, callback) // executes + * A.findByIdAndUpdate(id, update, options) // returns Query + * A.findByIdAndUpdate(id, update, callback) // executes + * A.findByIdAndUpdate(id, update) // returns Query + * A.findByIdAndUpdate() // returns Query + * + * ####Note: + * + * All top level update keys which are not `atomic` operation names are treated as set operations: + * + * ####Example: + * + * Model.findByIdAndUpdate(id, { name: 'jason bourne' }, options, callback) + * + * // is sent as + * Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options, callback) + * + * This helps prevent accidentally overwriting your document with `{ name: 'jason bourne' }`. + * + * ####Note: + * + * Values are cast to their appropriate types when using the findAndModify helpers. + * However, the below are not executed by default. + * + * - defaults. Use the `setDefaultsOnInsert` option to override. + * + * `findAndModify` helpers support limited validation. You can + * enable these by setting the `runValidators` options, + * respectively. + * + * If you need full-fledged validation, use the traditional approach of first + * retrieving the document. + * + * Model.findById(id, function (err, doc) { + * if (err) .. + * doc.name = 'jason bourne'; + * doc.save(callback); + * }); + * + * @param {Object|Number|String} id value of `_id` to query by + * @param {Object} [update] + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean} [options.new=false] By default, `findByIdAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. To change the default to `true`, use `mongoose.set('returnOriginal', false);`. + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. + * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://docs.mongodb.com/manual/reference/operator/update/) in `update`, Mongoose will wrap `update` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. An alternative to this would be using [Model.findOneAndReplace({ _id: id }, update, options, callback)](https://mongoosejs.com/docs/api/model.html#model_Model.findOneAndReplace). + * @param {Function} [callback] + * @return {Query} + * @see Model.findOneAndUpdate #model_Model.findOneAndUpdate + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + * @api public + */ + +Model.findByIdAndUpdate = function(id, update, options, callback) { + _checkContext(this, 'findByIdAndUpdate'); + + callback = this.$handleCallbackError(callback); + if (arguments.length === 1) { + if (typeof id === 'function') { + const msg = 'Model.findByIdAndUpdate(): First argument must not be a function.\n\n' + + ' ' + this.modelName + '.findByIdAndUpdate(id, callback)\n' + + ' ' + this.modelName + '.findByIdAndUpdate(id)\n' + + ' ' + this.modelName + '.findByIdAndUpdate()\n'; + throw new TypeError(msg); + } + return this.findOneAndUpdate({ _id: id }, undefined); + } + + // if a model is passed in instead of an id + if (id instanceof Document) { + id = id._id; + } + + return this.findOneAndUpdate.call(this, { _id: id }, update, options, callback); +}; + +/** + * Issue a MongoDB `findOneAndDelete()` command. + * + * Finds a matching document, removes it, and passes the found document + * (if any) to the callback. + * + * Executes the query if `callback` is passed. + * + * This function triggers the following middleware. + * + * - `findOneAndDelete()` + * + * This function differs slightly from `Model.findOneAndRemove()` in that + * `findOneAndRemove()` becomes a [MongoDB `findAndModify()` command](https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/), + * as opposed to a `findOneAndDelete()` command. For most mongoose use cases, + * this distinction is purely pedantic. You should use `findOneAndDelete()` + * unless you have a good reason not to. + * + * ####Options: + * + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 + * - `select`: sets the document fields to return, ex. `{ projection: { _id: 0 } }` + * - `projection`: equivalent to `select` + * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update + * + * ####Examples: + * + * A.findOneAndDelete(conditions, options, callback) // executes + * A.findOneAndDelete(conditions, options) // return Query + * A.findOneAndDelete(conditions, callback) // executes + * A.findOneAndDelete(conditions) // returns Query + * A.findOneAndDelete() // returns Query + * + * Values are cast to their appropriate types when using the findAndModify helpers. + * However, the below are not executed by default. + * + * - defaults. Use the `setDefaultsOnInsert` option to override. + * + * `findAndModify` helpers support limited validation. You can + * enable these by setting the `runValidators` options, + * respectively. + * + * If you need full-fledged validation, use the traditional approach of first + * retrieving the document. + * + * Model.findById(id, function (err, doc) { + * if (err) .. + * doc.name = 'jason bourne'; + * doc.save(callback); + * }); + * + * @param {Object} conditions + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.findOneAndDelete = function(conditions, options, callback) { + _checkContext(this, 'findOneAndDelete'); + + if (arguments.length === 1 && typeof conditions === 'function') { + const msg = 'Model.findOneAndDelete(): First argument must not be a function.\n\n' + + ' ' + this.modelName + '.findOneAndDelete(conditions, callback)\n' + + ' ' + this.modelName + '.findOneAndDelete(conditions)\n' + + ' ' + this.modelName + '.findOneAndDelete()\n'; + throw new TypeError(msg); + } + + if (typeof options === 'function') { + callback = options; + options = undefined; + } + callback = this.$handleCallbackError(callback); + + let fields; + if (options) { + fields = options.select; + options.select = undefined; + } + + const mq = new this.Query({}, {}, this, this.collection); + mq.select(fields); + + return mq.findOneAndDelete(conditions, options, callback); +}; + +/** + * Issue a MongoDB `findOneAndDelete()` command by a document's _id field. + * In other words, `findByIdAndDelete(id)` is a shorthand for + * `findOneAndDelete({ _id: id })`. + * + * This function triggers the following middleware. + * + * - `findOneAndDelete()` + * + * @param {Object|Number|String} id value of `_id` to query by + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Function} [callback] + * @return {Query} + * @see Model.findOneAndRemove #model_Model.findOneAndRemove + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + */ + +Model.findByIdAndDelete = function(id, options, callback) { + _checkContext(this, 'findByIdAndDelete'); + + if (arguments.length === 1 && typeof id === 'function') { + const msg = 'Model.findByIdAndDelete(): First argument must not be a function.\n\n' + + ' ' + this.modelName + '.findByIdAndDelete(id, callback)\n' + + ' ' + this.modelName + '.findByIdAndDelete(id)\n' + + ' ' + this.modelName + '.findByIdAndDelete()\n'; + throw new TypeError(msg); + } + callback = this.$handleCallbackError(callback); + + return this.findOneAndDelete({ _id: id }, options, callback); +}; + +/** + * Issue a MongoDB `findOneAndReplace()` command. + * + * Finds a matching document, replaces it with the provided doc, and passes the + * returned doc to the callback. + * + * Executes the query if `callback` is passed. + * + * This function triggers the following query middleware. + * + * - `findOneAndReplace()` + * + * ####Options: + * + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 + * - `select`: sets the document fields to return + * - `projection`: like select, it determines which fields to return, ex. `{ projection: { _id: 0 } }` + * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update + * + * ####Examples: + * + * A.findOneAndReplace(conditions, options, callback) // executes + * A.findOneAndReplace(conditions, options) // return Query + * A.findOneAndReplace(conditions, callback) // executes + * A.findOneAndReplace(conditions) // returns Query + * A.findOneAndReplace() // returns Query + * + * Values are cast to their appropriate types when using the findAndModify helpers. + * However, the below are not executed by default. + * + * - defaults. Use the `setDefaultsOnInsert` option to override. + * + * @param {Object} filter Replace the first document that matches this filter + * @param {Object} [replacement] Replace with this document + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean} [options.new=false] By default, `findOneAndReplace()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndReplace()` will instead give you the object after `update` was applied. To change the default to `true`, use `mongoose.set('returnOriginal', false);`. + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. + * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Function} [callback] + * @return {Query} + * @api public + */ + +Model.findOneAndReplace = function(filter, replacement, options, callback) { + _checkContext(this, 'findOneAndReplace'); + + if (arguments.length === 1 && typeof filter === 'function') { + const msg = 'Model.findOneAndReplace(): First argument must not be a function.\n\n' + + ' ' + this.modelName + '.findOneAndReplace(conditions, callback)\n' + + ' ' + this.modelName + '.findOneAndReplace(conditions)\n' + + ' ' + this.modelName + '.findOneAndReplace()\n'; + throw new TypeError(msg); + } + + if (arguments.length === 3 && typeof options === 'function') { + callback = options; + options = replacement; + replacement = void 0; + } + if (arguments.length === 2 && typeof replacement === 'function') { + callback = replacement; + replacement = void 0; + options = void 0; + } + callback = this.$handleCallbackError(callback); + + let fields; + if (options) { + fields = options.select; + options.select = undefined; + } + + const mq = new this.Query({}, {}, this, this.collection); + mq.select(fields); + + return mq.findOneAndReplace(filter, replacement, options, callback); +}; + +/** + * Issue a mongodb findAndModify remove command. + * + * Finds a matching document, removes it, passing the found document (if any) to the callback. + * + * Executes the query if `callback` is passed. + * + * This function triggers the following middleware. + * + * - `findOneAndRemove()` + * + * ####Options: + * + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 + * - `select`: sets the document fields to return + * - `projection`: like select, it determines which fields to return, ex. `{ projection: { _id: 0 } }` + * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update + * + * ####Examples: + * + * A.findOneAndRemove(conditions, options, callback) // executes + * A.findOneAndRemove(conditions, options) // return Query + * A.findOneAndRemove(conditions, callback) // executes + * A.findOneAndRemove(conditions) // returns Query + * A.findOneAndRemove() // returns Query + * + * Values are cast to their appropriate types when using the findAndModify helpers. + * However, the below are not executed by default. + * + * - defaults. Use the `setDefaultsOnInsert` option to override. + * + * `findAndModify` helpers support limited validation. You can + * enable these by setting the `runValidators` options, + * respectively. + * + * If you need full-fledged validation, use the traditional approach of first + * retrieving the document. + * + * Model.findById(id, function (err, doc) { + * if (err) .. + * doc.name = 'jason bourne'; + * doc.save(callback); + * }); + * + * @param {Object} conditions + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Function} [callback] + * @return {Query} + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + * @api public + */ + +Model.findOneAndRemove = function(conditions, options, callback) { + _checkContext(this, 'findOneAndRemove'); + + if (arguments.length === 1 && typeof conditions === 'function') { + const msg = 'Model.findOneAndRemove(): First argument must not be a function.\n\n' + + ' ' + this.modelName + '.findOneAndRemove(conditions, callback)\n' + + ' ' + this.modelName + '.findOneAndRemove(conditions)\n' + + ' ' + this.modelName + '.findOneAndRemove()\n'; + throw new TypeError(msg); + } + + if (typeof options === 'function') { + callback = options; + options = undefined; + } + callback = this.$handleCallbackError(callback); + + let fields; + if (options) { + fields = options.select; + options.select = undefined; + } + + const mq = new this.Query({}, {}, this, this.collection); + mq.select(fields); + + return mq.findOneAndRemove(conditions, options, callback); +}; + +/** + * Issue a mongodb findAndModify remove command by a document's _id field. `findByIdAndRemove(id, ...)` is equivalent to `findOneAndRemove({ _id: id }, ...)`. + * + * Finds a matching document, removes it, passing the found document (if any) to the callback. + * + * Executes the query if `callback` is passed. + * + * This function triggers the following middleware. + * + * - `findOneAndRemove()` + * + * ####Options: + * + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `select`: sets the document fields to return + * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update + * + * ####Examples: + * + * A.findByIdAndRemove(id, options, callback) // executes + * A.findByIdAndRemove(id, options) // return Query + * A.findByIdAndRemove(id, callback) // executes + * A.findByIdAndRemove(id) // returns Query + * A.findByIdAndRemove() // returns Query + * + * @param {Object|Number|String} id value of `_id` to query by + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Object|String|Array<String>} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](#query_Query-select) + * @param {Function} [callback] + * @return {Query} + * @see Model.findOneAndRemove #model_Model.findOneAndRemove + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + */ + +Model.findByIdAndRemove = function(id, options, callback) { + _checkContext(this, 'findByIdAndRemove'); + + if (arguments.length === 1 && typeof id === 'function') { + const msg = 'Model.findByIdAndRemove(): First argument must not be a function.\n\n' + + ' ' + this.modelName + '.findByIdAndRemove(id, callback)\n' + + ' ' + this.modelName + '.findByIdAndRemove(id)\n' + + ' ' + this.modelName + '.findByIdAndRemove()\n'; + throw new TypeError(msg); + } + callback = this.$handleCallbackError(callback); + + return this.findOneAndRemove({ _id: id }, options, callback); +}; + +/** + * Shortcut for saving one or more documents to the database. + * `MyModel.create(docs)` does `new MyModel(doc).save()` for every doc in + * docs. + * + * This function triggers the following middleware. + * + * - `save()` + * + * ####Example: + * + * // Insert one new `Character` document + * await Character.create({ name: 'Jean-Luc Picard' }); + * + * // Insert multiple new `Character` documents + * await Character.create([{ name: 'Will Riker' }, { name: 'Geordi LaForge' }]); + * + * // Create a new character within a transaction. Note that you **must** + * // pass an array as the first parameter to `create()` if you want to + * // specify options. + * await Character.create([{ name: 'Jean-Luc Picard' }], { session }); + * + * @param {Array|Object} docs Documents to insert, as a spread or array + * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. + * @param {Function} [callback] callback + * @return {Promise} + * @api public + */ + +Model.create = function create(doc, options, callback) { + _checkContext(this, 'create'); + + let args; + let cb; + const discriminatorKey = this.schema.options.discriminatorKey; + + if (Array.isArray(doc)) { + args = doc; + cb = typeof options === 'function' ? options : callback; + options = options != null && typeof options === 'object' ? options : {}; + } else { + const last = arguments[arguments.length - 1]; + options = {}; + // Handle falsy callbacks re: #5061 + if (typeof last === 'function' || !last) { + cb = last; + args = utils.args(arguments, 0, arguments.length - 1); + } else { + args = utils.args(arguments); + } + + if (args.length === 2 && + args[0] != null && + args[1] != null && + args[0].session == null && + last.session != null && + last.session.constructor.name === 'ClientSession' && + !this.schema.path('session')) { + // Probably means the user is running into the common mistake of trying + // to use a spread to specify options, see gh-7535 + console.warn('WARNING: to pass a `session` to `Model.create()` in ' + + 'Mongoose, you **must** pass an array as the first argument. See: ' + + 'https://mongoosejs.com/docs/api.html#model_Model.create'); + } + } + + return promiseOrCallback(cb, cb => { + cb = this.$wrapCallback(cb); + if (args.length === 0) { + return cb(null); + } + + const toExecute = []; + let firstError; + args.forEach(doc => { + toExecute.push(callback => { + const Model = this.discriminators && doc[discriminatorKey] != null ? + this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this, doc[discriminatorKey]) : + this; + if (Model == null) { + throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` + + `found for model "${this.modelName}"`); + } + let toSave = doc; + const callbackWrapper = (error, doc) => { + if (error) { + if (!firstError) { + firstError = error; + } + return callback(null, { error: error }); + } + callback(null, { doc: doc }); + }; + + if (!(toSave instanceof Model)) { + try { + toSave = new Model(toSave); + } catch (error) { + return callbackWrapper(error); + } + } + + toSave.save(options, callbackWrapper); + }); + }); + + let numFns = toExecute.length; + if (numFns === 0) { + return cb(null, []); + } + const _done = (error, res) => { + const savedDocs = []; + const len = res.length; + for (let i = 0; i < len; ++i) { + if (res[i].doc) { + savedDocs.push(res[i].doc); + } + } + + if (firstError) { + return cb(firstError, savedDocs); + } + + if (doc instanceof Array) { + cb(null, savedDocs); + } else { + cb.apply(this, [null].concat(savedDocs)); + } + }; + + const _res = []; + toExecute.forEach((fn, i) => { + fn((err, res) => { + _res[i] = res; + if (--numFns <= 0) { + return _done(null, _res); + } + }); + }); + }, this.events); +}; + +/** + * _Requires a replica set running MongoDB >= 3.6.0._ Watches the + * underlying collection for changes using + * [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/). + * + * This function does **not** trigger any middleware. In particular, it + * does **not** trigger aggregate middleware. + * + * The ChangeStream object is an event emitter that emits the following events: + * + * - 'change': A change occurred, see below example + * - 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow [this GitHub issue](https://github.com/Automattic/mongoose/issues/6799) for updates. + * - 'end': Emitted if the underlying stream is closed + * - 'close': Emitted if the underlying stream is closed + * + * ####Example: + * + * const doc = await Person.create({ name: 'Ned Stark' }); + * const changeStream = Person.watch().on('change', change => console.log(change)); + * // Will print from the above `console.log()`: + * // { _id: { _data: ... }, + * // operationType: 'delete', + * // ns: { db: 'mydb', coll: 'Person' }, + * // documentKey: { _id: 5a51b125c5500f5aa094c7bd } } + * await doc.remove(); + * + * @param {Array} [pipeline] + * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#watch) + * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter + * @api public + */ + +Model.watch = function(pipeline, options) { + _checkContext(this, 'watch'); + + const changeStreamThunk = cb => { + if (this.collection.buffer) { + this.collection.addQueue(() => { + if (this.closed) { + return; + } + const driverChangeStream = this.collection.watch(pipeline, options); + cb(null, driverChangeStream); + }); + } else { + const driverChangeStream = this.collection.watch(pipeline, options); + cb(null, driverChangeStream); + } + }; + + return new ChangeStream(changeStreamThunk, pipeline, options); +}; + +/** + * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions) + * for benefits like causal consistency, [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/), + * and [transactions](http://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html). + * + * Calling `MyModel.startSession()` is equivalent to calling `MyModel.db.startSession()`. + * + * This function does not trigger any middleware. + * + * ####Example: + * + * const session = await Person.startSession(); + * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session }); + * await doc.remove(); + * // `doc` will always be null, even if reading from a replica set + * // secondary. Without causal consistency, it is possible to + * // get a doc back from the below query if the query reads from a + * // secondary that is experiencing replication lag. + * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' }); + * + * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession) + * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency + * @param {Function} [callback] + * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession` + * @api public + */ + +Model.startSession = function() { + _checkContext(this, 'startSession'); + + return this.db.startSession.apply(this.db, arguments); +}; + +/** + * Shortcut for validating an array of documents and inserting them into + * MongoDB if they're all valid. This function is faster than `.create()` + * because it only sends one operation to the server, rather than one for each + * document. + * + * Mongoose always validates each document **before** sending `insertMany` + * to MongoDB. So if one document has a validation error, no documents will + * be saved, unless you set + * [the `ordered` option to false](https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#error-handling). + * + * This function does **not** trigger save middleware. + * + * This function triggers the following middleware. + * + * - `insertMany()` + * + * ####Example: + * + * const arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }]; + * Movies.insertMany(arr, function(error, docs) {}); + * + * @param {Array|Object|*} doc(s) + * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#insertMany) + * @param {Boolean} [options.ordered = true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`. + * @param {Boolean} [options.rawResult = false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~insertWriteOpCallback) with a `mongoose` property that contains `validationErrors` if this is an unordered `insertMany`. + * @param {Boolean} [options.lean = false] if `true`, skips hydrating and validating the documents. This option is useful if you need the extra performance, but Mongoose won't validate the documents before inserting. + * @param {Number} [options.limit = null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory. + * @param {Function} [callback] callback + * @return {Promise} resolving to the raw result from the MongoDB driver if `options.rawResult` was `true`, or the documents that passed validation, otherwise + * @api public + */ + +Model.insertMany = function(arr, options, callback) { + _checkContext(this, 'insertMany'); + + if (typeof options === 'function') { + callback = options; + options = null; + } + return promiseOrCallback(callback, cb => { + this.$__insertMany(arr, options, cb); + }, this.events); +}; + +/*! + * ignore + */ + +Model.$__insertMany = function(arr, options, callback) { + const _this = this; + if (typeof options === 'function') { + callback = options; + options = null; + } + if (callback) { + callback = this.$handleCallbackError(callback); + callback = this.$wrapCallback(callback); + } + callback = callback || utils.noop; + options = options || {}; + const limit = get(options, 'limit', 1000); + const rawResult = get(options, 'rawResult', false); + const ordered = get(options, 'ordered', true); + const lean = get(options, 'lean', false); + + if (!Array.isArray(arr)) { + arr = [arr]; + } + + const validationErrors = []; + const toExecute = arr.map(doc => + callback => { + if (!(doc instanceof _this)) { + try { + doc = new _this(doc); + } catch (err) { + return callback(err); + } + } + if (options.session != null) { + doc.$session(options.session); + } + // If option `lean` is set to true bypass validation + if (lean) { + // we have to execute callback at the nextTick to be compatible + // with parallelLimit, as `results` variable has TDZ issue if we + // execute the callback synchronously + return process.nextTick(() => callback(null, doc)); + } + doc.validate({ __noPromise: true }, function(error) { + if (error) { + // Option `ordered` signals that insert should be continued after reaching + // a failing insert. Therefore we delegate "null", meaning the validation + // failed. It's up to the next function to filter out all failed models + if (ordered === false) { + validationErrors.push(error); + return callback(null, null); + } + return callback(error); + } + callback(null, doc); + }); + }); + + parallelLimit(toExecute, limit, function(error, docs) { + if (error) { + callback(error, null); + return; + } + // We filter all failed pre-validations by removing nulls + const docAttributes = docs.filter(function(doc) { + return doc != null; + }); + // Quickly escape while there aren't any valid docAttributes + if (docAttributes.length < 1) { + if (rawResult) { + const res = { + mongoose: { + validationErrors: validationErrors + } + }; + return callback(null, res); + } + callback(null, []); + return; + } + const docObjects = docAttributes.map(function(doc) { + if (doc.schema.options.versionKey) { + doc[doc.schema.options.versionKey] = 0; + } + if (doc.initializeTimestamps) { + return doc.initializeTimestamps().toObject(internalToObjectOptions); + } + return doc.toObject(internalToObjectOptions); + }); + + _this.collection.insertMany(docObjects, options, function(error, res) { + if (error) { + // `writeErrors` is a property reported by the MongoDB driver, + // just not if there's only 1 error. + if (error.writeErrors == null && + get(error, 'result.result.writeErrors') != null) { + error.writeErrors = error.result.result.writeErrors; + } + + // `insertedDocs` is a Mongoose-specific property + const erroredIndexes = new Set(get(error, 'writeErrors', []).map(err => err.index)); + error.insertedDocs = docAttributes.filter((doc, i) => { + return !erroredIndexes.has(i); + }); + + callback(error, null); + return; + } + + for (const attribute of docAttributes) { + attribute.$__reset(); + _setIsNew(attribute, false); + } + + if (rawResult) { + if (ordered === false) { + // Decorate with mongoose validation errors in case of unordered, + // because then still do `insertMany()` + res.mongoose = { + validationErrors: validationErrors + }; + } + return callback(null, res); + } + callback(null, docAttributes); + }); + }); +}; + +/*! + * ignore + */ + +function _setIsNew(doc, val) { + doc.isNew = val; + doc.emit('isNew', val); + doc.constructor.emit('isNew', val); + + const subdocs = doc.$__getAllSubdocs(); + for (const subdoc of subdocs) { + subdoc.isNew = val; + } +} + +/** + * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`, + * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one + * command. This is faster than sending multiple independent operations (e.g. + * if you use `create()`) because with `bulkWrite()` there is only one round + * trip to MongoDB. + * + * Mongoose will perform casting on all operations you provide. + * + * This function does **not** trigger any middleware, neither `save()`, nor `update()`. + * If you need to trigger + * `save()` middleware for every document use [`create()`](http://mongoosejs.com/docs/api.html#model_Model.create) instead. + * + * ####Example: + * + * Character.bulkWrite([ + * { + * insertOne: { + * document: { + * name: 'Eddard Stark', + * title: 'Warden of the North' + * } + * } + * }, + * { + * updateOne: { + * filter: { name: 'Eddard Stark' }, + * // If you were using the MongoDB driver directly, you'd need to do + * // `update: { $set: { title: ... } }` but mongoose adds $set for + * // you. + * update: { title: 'Hand of the King' } + * } + * }, + * { + * deleteOne: { + * { + * filter: { name: 'Eddard Stark' } + * } + * } + * } + * ]).then(res => { + * // Prints "1 1 1" + * console.log(res.insertedCount, res.modifiedCount, res.deletedCount); + * }); + * + * The [supported operations](https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are: + * + * - `insertOne` + * - `updateOne` + * - `updateMany` + * - `deleteOne` + * - `deleteMany` + * - `replaceOne` + * + * @param {Array} ops + * @param {Object} [ops.insertOne.document] The document to insert + * @param {Object} [opts.updateOne.filter] Update the first document that matches this filter + * @param {Object} [opts.updateOne.update] An object containing [update operators](https://docs.mongodb.com/manual/reference/operator/update/) + * @param {Boolean} [opts.updateOne.upsert=false] If true, insert a doc if none match + * @param {Boolean} [opts.updateOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation + * @param {Object} [opts.updateOne.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use + * @param {Array} [opts.updateOne.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update` + * @param {Object} [opts.updateMany.filter] Update all the documents that match this filter + * @param {Object} [opts.updateMany.update] An object containing [update operators](https://docs.mongodb.com/manual/reference/operator/update/) + * @param {Boolean} [opts.updateMany.upsert=false] If true, insert a doc if no documents match `filter` + * @param {Boolean} [opts.updateMany.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation + * @param {Object} [opts.updateMany.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use + * @param {Array} [opts.updateMany.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update` + * @param {Object} [opts.deleteOne.filter] Delete the first document that matches this filter + * @param {Object} [opts.deleteMany.filter] Delete all documents that match this filter + * @param {Object} [opts.replaceOne.filter] Replace the first document that matches this filter + * @param {Object} [opts.replaceOne.replacement] The replacement document + * @param {Boolean} [opts.replaceOne.upsert=false] If true, insert a doc if no documents match `filter` + * @param {Object} [options] + * @param {Boolean} [options.ordered=true] If true, execute writes in order and stop at the first error. If false, execute writes in parallel and continue until all writes have either succeeded or errored. + * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](/docs/transactions.html). + * @param {String|number} [options.w=1] The [write concern](https://docs.mongodb.com/manual/reference/write-concern/). See [`Query#w()`](/docs/api.html#query_Query-w) for more information. + * @param {number} [options.wtimeout=null] The [write concern timeout](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout). + * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://docs.mongodb.com/manual/reference/write-concern/#j-option) + * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://docs.mongodb.com/manual/core/schema-validation/) for all writes in this bulk. + * @param {Boolean} [options.strict=null] Overwrites the [`strict` option](/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk. + * @param {Function} [callback] callback `function(error, bulkWriteOpResult) {}` + * @return {Promise} resolves to a [`BulkWriteOpResult`](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#~BulkWriteOpResult) if the operation succeeds + * @api public + */ + +Model.bulkWrite = function(ops, options, callback) { + _checkContext(this, 'bulkWrite'); + + if (typeof options === 'function') { + callback = options; + options = null; + } + options = options || {}; + + const validations = ops.map(op => castBulkWrite(this, op, options)); + + callback = this.$handleCallbackError(callback); + return promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + each(validations, (fn, cb) => fn(cb), error => { + if (error) { + return cb(error); + } + + if (ops.length === 0) { + return cb(null, getDefaultBulkwriteResult()); + } + + this.collection.bulkWrite(ops, options, (error, res) => { + if (error) { + return cb(error); + } + + cb(null, res); + }); + }); + }, this.events); +}; + +/** + * Shortcut for creating a new Document from existing raw data, pre-saved in the DB. + * The document returned has no paths marked as modified initially. + * + * ####Example: + * + * // hydrate previous data into a Mongoose document + * const mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' }); + * + * @param {Object} obj + * @param {Object|String|Array<String>} [projection] optional projection containing which fields should be selected for this document + * @return {Document} document instance + * @api public + */ + +Model.hydrate = function(obj, projection) { + _checkContext(this, 'hydrate'); + + const model = require('./queryhelpers').createModel(this, obj, projection); + model.init(obj); + return model; +}; + +/** + * Updates one document in the database without returning it. + * + * This function triggers the following middleware. + * + * - `update()` + * + * ####Examples: + * + * MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn); + * + * const res = await MyModel.update({ name: 'Tobi' }, { ferret: true }); + * res.n; // Number of documents that matched `{ name: 'Tobi' }` + * // Number of documents that were changed. If every doc matched already + * // had `ferret` set to `true`, `nModified` will be 0. + * res.nModified; + * + * ####Valid options: + * + * - `strict` (boolean): overrides the [schema-level `strict` option](/docs/guide.html#strict) for this update + * - `upsert` (boolean): whether to create the doc if it doesn't match (false) + * - `writeConcern` (object): sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * - `omitUndefined` (boolean): If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * - `multi` (boolean): whether multiple documents should be updated (false) + * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. + * - `setDefaultsOnInsert` (boolean): if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/). + * - `timestamps` (boolean): If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * - `overwrite` (boolean): disables update-only mode, allowing you to overwrite the doc (false) + * + * All `update` values are cast to their appropriate SchemaTypes before being sent. + * + * The `callback` function receives `(err, rawResponse)`. + * + * - `err` is the error if any occurred + * - `rawResponse` is the full response from Mongo + * + * ####Note: + * + * All top level keys which are not `atomic` operation names are treated as set operations: + * + * ####Example: + * + * const query = { name: 'borne' }; + * Model.update(query, { name: 'jason bourne' }, options, callback); + * + * // is sent as + * Model.update(query, { $set: { name: 'jason bourne' }}, options, function(err, res)); + * // if overwrite option is false. If overwrite is true, sent without the $set wrapper. + * + * This helps prevent accidentally overwriting all documents in your collection with `{ name: 'jason bourne' }`. + * + * ####Note: + * + * Be careful to not use an existing model instance for the update clause (this won't work and can cause weird behavior like infinite loops). Also, ensure that the update clause does not have an _id property, which causes Mongo to return a "Mod on _id not allowed" error. + * + * ####Note: + * + * Mongoose casts values and runs setters when using update. The following + * features are **not** applied by default. + * + * - [defaults](/docs/defaults.html#the-setdefaultsoninsert-option) + * - [validators](/docs/validation.html#update-validators) + * - middleware + * + * If you need document middleware and fully-featured validation, load the + * document first and then use [`save()`](/docs/api.html#model_Model-save). + * + * Model.findOne({ name: 'borne' }, function (err, doc) { + * if (err) .. + * doc.name = 'jason bourne'; + * doc.save(callback); + * }) + * + * @see strict http://mongoosejs.com/docs/guide.html#strict + * @see response http://docs.mongodb.org/v2.6/reference/command/update/#output + * @param {Object} filter + * @param {Object} doc + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](/docs/api.html#query_Query-setOptions) + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](/docs/guide.html#strict) + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.multi=false] whether multiple documents should be updated or just the first one that matches `filter`. + * @param {Boolean} [options.runValidators=false] if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. + * @param {Boolean} [options.setDefaultsOnInsert=false] if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/). + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.overwrite=false] By default, if you don't include any [update operators](https://docs.mongodb.com/manual/reference/operator/update/) in `doc`, Mongoose will wrap `doc` in `$set` for you. This prevents you from accidentally overwriting the document. This option tells Mongoose to skip adding `$set`. + * @param {Function} [callback] params are (error, [updateWriteOpResult](https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#~updateWriteOpResult)) + * @param {Function} [callback] + * @return {Query} + * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output + * @see writeOpResult https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#~updateWriteOpResult + * @see Query docs https://mongoosejs.com/docs/queries.html + * @api public + */ + +Model.update = function update(conditions, doc, options, callback) { + _checkContext(this, 'update'); + + return _update(this, 'update', conditions, doc, options, callback); +}; + +/** + * Same as `update()`, except MongoDB will update _all_ documents that match + * `filter` (as opposed to just the first one) regardless of the value of + * the `multi` option. + * + * **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')` + * and `post('updateMany')` instead. + * + * ####Example: + * const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true }); + * res.n; // Number of documents matched + * res.nModified; // Number of documents modified + * + * This function triggers the following middleware. + * + * - `updateMany()` + * + * @param {Object} filter + * @param {Object} doc + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Function} [callback] `function(error, res) {}` where `res` has 3 properties: `n`, `nModified`, `ok`. + * @return {Query} + * @see Query docs https://mongoosejs.com/docs/queries.html + * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output + * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult + * @api public + */ + +Model.updateMany = function updateMany(conditions, doc, options, callback) { + _checkContext(this, 'updateMany'); + + return _update(this, 'updateMany', conditions, doc, options, callback); +}; + +/** + * Same as `update()`, except it does not support the `multi` or `overwrite` + * options. + * + * - MongoDB will update _only_ the first document that matches `filter` regardless of the value of the `multi` option. + * - Use `replaceOne()` if you want to overwrite an entire document rather than using atomic operators like `$set`. + * + * ####Example: + * const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' }); + * res.n; // Number of documents matched + * res.nModified; // Number of documents modified + * + * This function triggers the following middleware. + * + * - `updateOne()` + * + * @param {Object} filter + * @param {Object} doc + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Function} [callback] params are (error, writeOpResult) + * @return {Query} + * @see Query docs https://mongoosejs.com/docs/queries.html + * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output + * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult + * @api public + */ + +Model.updateOne = function updateOne(conditions, doc, options, callback) { + _checkContext(this, 'updateOne'); + + return _update(this, 'updateOne', conditions, doc, options, callback); +}; + +/** + * Same as `update()`, except MongoDB replace the existing document with the + * given document (no atomic operators like `$set`). + * + * ####Example: + * const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' }); + * res.n; // Number of documents matched + * res.nModified; // Number of documents modified + * + * This function triggers the following middleware. + * + * - `replaceOne()` + * + * @param {Object} filter + * @param {Object} doc + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Function} [callback] `function(error, res) {}` where `res` has 3 properties: `n`, `nModified`, `ok`. + * @return {Query} + * @see Query docs https://mongoosejs.com/docs/queries.html + * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult + * @return {Query} + * @api public + */ + +Model.replaceOne = function replaceOne(conditions, doc, options, callback) { + _checkContext(this, 'replaceOne'); + + const versionKey = get(this, 'schema.options.versionKey', null); + if (versionKey && !doc[versionKey]) { + doc[versionKey] = 0; + } + + return _update(this, 'replaceOne', conditions, doc, options, callback); +}; + +/*! + * Common code for `updateOne()`, `updateMany()`, `replaceOne()`, and `update()` + * because they need to do the same thing + */ + +function _update(model, op, conditions, doc, options, callback) { + const mq = new model.Query({}, {}, model, model.collection); + + callback = model.$handleCallbackError(callback); + // gh-2406 + // make local deep copy of conditions + if (conditions instanceof Document) { + conditions = conditions.toObject(); + } else { + conditions = utils.clone(conditions); + } + options = typeof options === 'function' ? options : utils.clone(options); + + const versionKey = get(model, 'schema.options.versionKey', null); + _decorateUpdateWithVersionKey(doc, options, versionKey); + + return mq[op](conditions, doc, options, callback); +} + +/** + * Executes a mapReduce command. + * + * `o` is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. See [node-mongodb-native mapReduce() documentation](http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#mapreduce) for more detail about options. + * + * This function does not trigger any middleware. + * + * ####Example: + * + * const o = {}; + * // `map()` and `reduce()` are run on the MongoDB server, not Node.js, + * // these functions are converted to strings + * o.map = function () { emit(this.name, 1) }; + * o.reduce = function (k, vals) { return vals.length }; + * User.mapReduce(o, function (err, results) { + * console.log(results) + * }) + * + * ####Other options: + * + * - `query` {Object} query filter object. + * - `sort` {Object} sort input objects using this key + * - `limit` {Number} max number of documents + * - `keeptemp` {Boolean, default:false} keep temporary data + * - `finalize` {Function} finalize function + * - `scope` {Object} scope variables exposed to map/reduce/finalize during execution + * - `jsMode` {Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X + * - `verbose` {Boolean, default:false} provide statistics on job execution time. + * - `readPreference` {String} + * - `out*` {Object, default: {inline:1}} sets the output target for the map reduce job. + * + * ####* out options: + * + * - `{inline:1}` the results are returned in an array + * - `{replace: 'collectionName'}` add the results to collectionName: the results replace the collection + * - `{reduce: 'collectionName'}` add the results to collectionName: if dups are detected, uses the reducer / finalize functions + * - `{merge: 'collectionName'}` add the results to collectionName: if dups exist the new docs overwrite the old + * + * If `options.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the [`lean` option](/docs/tutorials/lean.html); meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc). + * + * ####Example: + * + * const o = {}; + * // You can also define `map()` and `reduce()` as strings if your + * // linter complains about `emit()` not being defined + * o.map = 'function () { emit(this.name, 1) }'; + * o.reduce = 'function (k, vals) { return vals.length }'; + * o.out = { replace: 'createdCollectionNameForResults' } + * o.verbose = true; + * + * User.mapReduce(o, function (err, model, stats) { + * console.log('map reduce took %d ms', stats.processtime) + * model.find().where('value').gt(10).exec(function (err, docs) { + * console.log(docs); + * }); + * }) + * + * // `mapReduce()` returns a promise. However, ES6 promises can only + * // resolve to exactly one value, + * o.resolveToObject = true; + * const promise = User.mapReduce(o); + * promise.then(function (res) { + * const model = res.model; + * const stats = res.stats; + * console.log('map reduce took %d ms', stats.processtime) + * return model.find().where('value').gt(10).exec(); + * }).then(function (docs) { + * console.log(docs); + * }).then(null, handleError).end() + * + * @param {Object} o an object specifying map-reduce options + * @param {Function} [callback] optional callback + * @see http://www.mongodb.org/display/DOCS/MapReduce + * @return {Promise} + * @api public + */ + +Model.mapReduce = function mapReduce(o, callback) { + _checkContext(this, 'mapReduce'); + + callback = this.$handleCallbackError(callback); + + return promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + + if (!Model.mapReduce.schema) { + const opts = { noId: true, noVirtualId: true, strict: false }; + Model.mapReduce.schema = new Schema({}, opts); + } + + if (!o.out) o.out = { inline: 1 }; + if (o.verbose !== false) o.verbose = true; + + o.map = String(o.map); + o.reduce = String(o.reduce); + + if (o.query) { + let q = new this.Query(o.query); + q.cast(this); + o.query = q._conditions; + q = undefined; + } + + this.collection.mapReduce(null, null, o, (err, res) => { + if (err) { + return cb(err); + } + if (res.collection) { + // returned a collection, convert to Model + const model = Model.compile('_mapreduce_' + res.collection.collectionName, + Model.mapReduce.schema, res.collection.collectionName, this.db, + this.base); + + model._mapreduce = true; + res.model = model; + + return cb(null, res); + } + + cb(null, res); + }); + }, this.events); +}; + +/** + * Performs [aggregations](http://docs.mongodb.org/manual/applications/aggregation/) on the models collection. + * + * If a `callback` is passed, the `aggregate` is executed and a `Promise` is returned. If a callback is not passed, the `aggregate` itself is returned. + * + * This function triggers the following middleware. + * + * - `aggregate()` + * + * ####Example: + * + * // Find the max balance of all accounts + * Users.aggregate([ + * { $group: { _id: null, maxBalance: { $max: '$balance' }}}, + * { $project: { _id: 0, maxBalance: 1 }} + * ]). + * then(function (res) { + * console.log(res); // [ { maxBalance: 98000 } ] + * }); + * + * // Or use the aggregation pipeline builder. + * Users.aggregate(). + * group({ _id: null, maxBalance: { $max: '$balance' } }). + * project('-id maxBalance'). + * exec(function (err, res) { + * if (err) return handleError(err); + * console.log(res); // [ { maxBalance: 98 } ] + * }); + * + * ####NOTE: + * + * - Mongoose does **not** cast aggregation pipelines to the model's schema because `$project` and `$group` operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format. You can use the [mongoose-cast-aggregation plugin](https://github.com/AbdelrahmanHafez/mongoose-cast-aggregation) to enable minimal casting for aggregation pipelines. + * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned). + * + * #### More About Aggregations: + * + * - [Mongoose `Aggregate`](/docs/api/aggregate.html) + * - [An Introduction to Mongoose Aggregate](https://masteringjs.io/tutorials/mongoose/aggregate) + * - [MongoDB Aggregation docs](http://docs.mongodb.org/manual/applications/aggregation/) + * + * @see Aggregate #aggregate_Aggregate + * @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/ + * @param {Array} [pipeline] aggregation pipeline as an array of objects + * @param {Function} [callback] + * @return {Aggregate} + * @api public + */ + +Model.aggregate = function aggregate(pipeline, callback) { + _checkContext(this, 'aggregate'); + + if (arguments.length > 2 || get(pipeline, 'constructor.name') === 'Object') { + throw new MongooseError('Mongoose 5.x disallows passing a spread of operators ' + + 'to `Model.aggregate()`. Instead of ' + + '`Model.aggregate({ $match }, { $skip })`, do ' + + '`Model.aggregate([{ $match }, { $skip }])`'); + } + + if (typeof pipeline === 'function') { + callback = pipeline; + pipeline = []; + } + + const aggregate = new Aggregate(pipeline || []); + aggregate.model(this); + + if (typeof callback === 'undefined') { + return aggregate; + } + + callback = this.$handleCallbackError(callback); + callback = this.$wrapCallback(callback); + + aggregate.exec(callback); + return aggregate; +}; + +/** + * Casts and validates the given object against this model's schema, passing the + * given `context` to custom validators. + * + * ####Example: + * + * const Model = mongoose.model('Test', Schema({ + * name: { type: String, required: true }, + * age: { type: Number, required: true } + * }); + * + * try { + * await Model.validate({ name: null }, ['name']) + * } catch (err) { + * err instanceof mongoose.Error.ValidationError; // true + * Object.keys(err.errors); // ['name'] + * } + * + * @param {Object} obj + * @param {Array} pathsToValidate + * @param {Object} [context] + * @param {Function} [callback] + * @return {Promise|undefined} + * @api public + */ + +Model.validate = function validate(obj, pathsToValidate, context, callback) { + return promiseOrCallback(callback, cb => { + const schema = this.schema; + let paths = Object.keys(schema.paths); + + if (pathsToValidate != null) { + const _pathsToValidate = new Set(pathsToValidate); + paths = paths.filter(p => { + const pieces = p.split('.'); + let cur = pieces[0]; + + for (const piece of pieces) { + if (_pathsToValidate.has(cur)) { + return true; + } + cur += '.' + piece; + } + + return _pathsToValidate.has(p); + }); + } + + for (const path of paths) { + const schemaType = schema.path(path); + if (!schemaType || !schemaType.$isMongooseArray) { + continue; + } + + const val = get(obj, path); + pushNestedArrayPaths(val, path); + } + + let remaining = paths.length; + let error = null; + + for (const path of paths) { + const schemaType = schema.path(path); + if (schemaType == null) { + _checkDone(); + continue; + } + + const pieces = path.split('.'); + let cur = obj; + for (let i = 0; i < pieces.length - 1; ++i) { + cur = cur[pieces[i]]; + } + + let val = get(obj, path, void 0); + + if (val != null) { + try { + val = schemaType.cast(val); + cur[pieces[pieces.length - 1]] = val; + } catch (err) { + error = error || new ValidationError(); + error.addError(path, err); + + _checkDone(); + continue; + } + } + + schemaType.doValidate(val, err => { + if (err) { + error = error || new ValidationError(); + if (err instanceof ValidationError) { + for (const _err of Object.keys(err.errors)) { + error.addError(`${path}.${err.errors[_err].path}`, _err); + } + } else { + error.addError(err.path, err); + } + } + _checkDone(); + }, context, { path: path }); + } + + function pushNestedArrayPaths(nestedArray, path) { + if (nestedArray == null) { + return; + } + + for (let i = 0; i < nestedArray.length; ++i) { + if (Array.isArray(nestedArray[i])) { + pushNestedArrayPaths(nestedArray[i], path + '.' + i); + } else { + paths.push(path + '.' + i); + } + } + } + + function _checkDone() { + if (--remaining <= 0) { + return cb(error); + } + } + }); +}; + +/** + * Implements `$geoSearch` functionality for Mongoose + * + * This function does not trigger any middleware + * + * ####Example: + * + * const options = { near: [10, 10], maxDistance: 5 }; + * Locations.geoSearch({ type : "house" }, options, function(err, res) { + * console.log(res); + * }); + * + * ####Options: + * - `near` {Array} x,y point to search for + * - `maxDistance` {Number} the maximum distance from the point near that a result can be + * - `limit` {Number} The maximum number of results to return + * - `lean` {Object|Boolean} return the raw object instead of the Mongoose Model + * + * @param {Object} conditions an object that specifies the match condition (required) + * @param {Object} options for the geoSearch, some (near, maxDistance) are required + * @param {Object|Boolean} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and the [Mongoose lean tutorial](/docs/tutorials/lean.html). + * @param {Function} [callback] optional callback + * @return {Promise} + * @see http://docs.mongodb.org/manual/reference/command/geoSearch/ + * @see http://docs.mongodb.org/manual/core/geohaystack/ + * @api public + */ + +Model.geoSearch = function(conditions, options, callback) { + _checkContext(this, 'geoSearch'); + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + callback = this.$handleCallbackError(callback); + + return promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + let error; + if (conditions === undefined || !utils.isObject(conditions)) { + error = new MongooseError('Must pass conditions to geoSearch'); + } else if (!options.near) { + error = new MongooseError('Must specify the near option in geoSearch'); + } else if (!Array.isArray(options.near)) { + error = new MongooseError('near option must be an array [x, y]'); + } + + if (error) { + return cb(error); + } + + // send the conditions in the options object + options.search = conditions; + + this.collection.geoHaystackSearch(options.near[0], options.near[1], options, (err, res) => { + if (err) { + return cb(err); + } + + let count = res.results.length; + if (options.lean || count === 0) { + return cb(null, res.results); + } + + const errSeen = false; + + function init(err) { + if (err && !errSeen) { + return cb(err); + } + + if (!--count && !errSeen) { + cb(null, res.results); + } + } + + for (let i = 0; i < res.results.length; ++i) { + const temp = res.results[i]; + res.results[i] = new this(); + res.results[i].init(temp, {}, init); + } + }); + }, this.events); +}; + +/** + * Populates document references. + * + * ####Available top-level options: + * + * - path: space delimited path(s) to populate + * - select: optional fields to select + * - match: optional query conditions to match + * - model: optional name of the model to use for population + * - options: optional query options like sort, limit, etc + * - justOne: optional boolean, if true Mongoose will always set `path` to an array. Inferred from schema by default. + * + * ####Examples: + * + * // populates a single object + * User.findById(id, function (err, user) { + * const opts = [ + * { path: 'company', match: { x: 1 }, select: 'name' }, + * { path: 'notes', options: { limit: 10 }, model: 'override' } + * ]; + * + * User.populate(user, opts, function (err, user) { + * console.log(user); + * }); + * }); + * + * // populates an array of objects + * User.find(match, function (err, users) { + * const opts = [{ path: 'company', match: { x: 1 }, select: 'name' }]; + * + * const promise = User.populate(users, opts); + * promise.then(console.log).end(); + * }) + * + * // imagine a Weapon model exists with two saved documents: + * // { _id: 389, name: 'whip' } + * // { _id: 8921, name: 'boomerang' } + * // and this schema: + * // new Schema({ + * // name: String, + * // weapon: { type: ObjectId, ref: 'Weapon' } + * // }); + * + * const user = { name: 'Indiana Jones', weapon: 389 }; + * Weapon.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, user) { + * console.log(user.weapon.name); // whip + * }) + * + * // populate many plain objects + * const users = [{ name: 'Indiana Jones', weapon: 389 }] + * users.push({ name: 'Batman', weapon: 8921 }) + * Weapon.populate(users, { path: 'weapon' }, function (err, users) { + * users.forEach(function (user) { + * console.log('%s uses a %s', users.name, user.weapon.name) + * // Indiana Jones uses a whip + * // Batman uses a boomerang + * }); + * }); + * // Note that we didn't need to specify the Weapon model because + * // it is in the schema's ref + * + * @param {Document|Array} docs Either a single document or array of documents to populate. + * @param {Object|String} options Either the paths to populate or an object specifying all parameters + * @param {string} [options.path=null] The path to populate. + * @param {boolean} [options.retainNullValues=false] By default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries. + * @param {boolean} [options.getters=false] If true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options). + * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them. + * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://docs.mongodb.com/manual/tutorial/query-documents/), or a function that returns a filter object. + * @param {Boolean} [options.skipInvalidIds=false] By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type. + * @param {Number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents. + * @param {Object} [options.options=null] Additional options like `limit` and `lean`. + * @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`. + * @return {Promise} + * @api public + */ + +Model.populate = function(docs, paths, callback) { + _checkContext(this, 'populate'); + + const _this = this; + + // normalized paths + paths = utils.populate(paths); + + // data that should persist across subPopulate calls + const cache = {}; + + callback = this.$handleCallbackError(callback); + + return promiseOrCallback(callback, cb => { + cb = this.$wrapCallback(cb); + _populate(_this, docs, paths, cache, cb); + }, this.events); +}; + +/*! + * Populate helper + * + * @param {Model} model the model to use + * @param {Document|Array} docs Either a single document or array of documents to populate. + * @param {Object} paths + * @param {Function} [cb(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`. + * @return {Function} + * @api private + */ + +function _populate(model, docs, paths, cache, callback) { + let pending = paths.length; + + if (paths.length === 0) { + return callback(null, docs); + } + + // each path has its own query options and must be executed separately + for (const path of paths) { + populate(model, docs, path, next); + } + + function next(err) { + if (err) { + return callback(err, null); + } + if (--pending) { + return; + } + callback(null, docs); + } +} + +/*! + * Populates `docs` + */ +const excludeIdReg = /\s?-_id\s?/; +const excludeIdRegGlobal = /\s?-_id\s?/g; + +function populate(model, docs, options, callback) { + // normalize single / multiple docs passed + if (!Array.isArray(docs)) { + docs = [docs]; + } + + if (docs.length === 0 || docs.every(utils.isNullOrUndefined)) { + return callback(); + } + + const modelsMap = getModelsMapForPopulate(model, docs, options); + + if (modelsMap instanceof MongooseError) { + return immediate(function() { + callback(modelsMap); + }); + } + + const len = modelsMap.length; + let vals = []; + + function flatten(item) { + // no need to include undefined values in our query + return undefined !== item; + } + + let _remaining = len; + let hasOne = false; + const params = []; + for (let i = 0; i < len; ++i) { + const mod = modelsMap[i]; + let select = mod.options.select; + const match = _formatMatch(mod.match); + + let ids = utils.array.flatten(mod.ids, flatten); + ids = utils.array.unique(ids); + + const assignmentOpts = {}; + assignmentOpts.sort = get(mod, 'options.options.sort', void 0); + assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0); + + if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) { + // Ensure that we set populate virtuals to 0 or empty array even + // if we don't actually execute a query because they don't have + // a value by default. See gh-7731, gh-8230 + --_remaining; + if (mod.count || mod.isVirtual) { + _assign(model, [], mod, assignmentOpts); + } + continue; + } + + hasOne = true; + if (mod.foreignField.size === 1) { + const foreignField = Array.from(mod.foreignField)[0]; + const foreignSchemaType = mod.model.schema.path(foreignField); + if (foreignField !== '_id' || !match['_id']) { + ids = _filterInvalidIds(ids, foreignSchemaType, mod.options.skipInvalidIds); + match[foreignField] = { $in: ids }; + } + } else { + const $or = []; + if (Array.isArray(match.$or)) { + match.$and = [{ $or: match.$or }, { $or: $or }]; + delete match.$or; + } else { + match.$or = $or; + } + for (const foreignField of mod.foreignField) { + if (foreignField !== '_id' || !match['_id']) { + const foreignSchemaType = mod.model.schema.path(foreignField); + ids = _filterInvalidIds(ids, foreignSchemaType, mod.options.skipInvalidIds); + $or.push({ [foreignField]: { $in: ids } }); + } + } + } + + if (assignmentOpts.excludeId) { + // override the exclusion from the query so we can use the _id + // for document matching during assignment. we'll delete the + // _id back off before returning the result. + if (typeof select === 'string') { + select = select.replace(excludeIdRegGlobal, ' '); + } else { + // preserve original select conditions by copying + select = utils.object.shallowCopy(select); + delete select._id; + } + } + + if (mod.options.options && mod.options.options.limit != null) { + assignmentOpts.originalLimit = mod.options.options.limit; + } else if (mod.options.limit != null) { + assignmentOpts.originalLimit = mod.options.limit; + } + + params.push([mod, match, select, assignmentOpts, _next]); + } + + if (!hasOne) { + // If no models to populate but we have a nested populate, + // keep trying, re: gh-8946 + if (options.populate != null) { + const opts = utils.populate(options.populate).map(pop => Object.assign({}, pop, { + path: options.path + '.' + pop.path + })); + return model.populate(docs, opts, callback); + } + return callback(); + } + + for (const arr of params) { + _execPopulateQuery.apply(null, arr); + } + + function _next(err, valsFromDb) { + if (err != null) { + return callback(err, null); + } + vals = vals.concat(valsFromDb); + if (--_remaining === 0) { + _done(); + } + } + + function _done() { + for (const arr of params) { + const mod = arr[0]; + const assignmentOpts = arr[3]; + for (const val of vals) { + mod.options._childDocs.push(val); + } + _assign(model, vals, mod, assignmentOpts); + } + + for (const arr of params) { + removeDeselectedForeignField(arr[0].foreignField, arr[0].options, vals); + } + callback(); + } +} + +/*! + * ignore + */ + +function _execPopulateQuery(mod, match, select, assignmentOpts, callback) { + const subPopulate = utils.clone(mod.options.populate); + + const queryOptions = Object.assign({ + skip: mod.options.skip, + limit: mod.options.limit, + perDocumentLimit: mod.options.perDocumentLimit + }, mod.options.options); + + if (mod.count) { + delete queryOptions.skip; + } + + if (queryOptions.perDocumentLimit != null) { + queryOptions.limit = queryOptions.perDocumentLimit; + delete queryOptions.perDocumentLimit; + } else if (queryOptions.limit != null) { + queryOptions.limit = queryOptions.limit * mod.ids.length; + } + + const query = mod.model.find(match, select, queryOptions); + // If we're doing virtual populate and projection is inclusive and foreign + // field is not selected, automatically select it because mongoose needs it. + // If projection is exclusive and client explicitly unselected the foreign + // field, that's the client's fault. + for (const foreignField of mod.foreignField) { + if (foreignField !== '_id' && query.selectedInclusively() && + !isPathSelectedInclusive(query._fields, foreignField)) { + query.select(foreignField); + } + } + + // If using count, still need the `foreignField` so we can match counts + // to documents, otherwise we would need a separate `count()` for every doc. + if (mod.count) { + for (const foreignField of mod.foreignField) { + query.select(foreignField); + } + } + + // If we need to sub-populate, call populate recursively + if (subPopulate) { + query.populate(subPopulate); + } + + query.exec((err, docs) => { + if (err != null) { + return callback(err); + } + for (const val of docs) { + leanPopulateMap.set(val, mod.model); + } + + callback(null, docs); + }); +} + +/*! + * ignore + */ + +function _assign(model, vals, mod, assignmentOpts) { + const options = mod.options; + const isVirtual = mod.isVirtual; + const justOne = mod.justOne; + let _val; + const lean = get(options, 'options.lean', false); + const len = vals.length; + const rawOrder = {}; + const rawDocs = {}; + let key; + let val; + + // Clone because `assignRawDocsToIdStructure` will mutate the array + const allIds = utils.clone(mod.allIds); + + // optimization: + // record the document positions as returned by + // the query result. + for (let i = 0; i < len; i++) { + val = vals[i]; + if (val == null) { + continue; + } + for (const foreignField of mod.foreignField) { + _val = utils.getValue(foreignField, val); + if (Array.isArray(_val)) { + _val = utils.array.flatten(_val); + + for (let __val of _val) { + if (__val instanceof Document) { + __val = __val._id; + } + key = String(__val); + if (rawDocs[key]) { + if (Array.isArray(rawDocs[key])) { + rawDocs[key].push(val); + rawOrder[key].push(i); + } else { + rawDocs[key] = [rawDocs[key], val]; + rawOrder[key] = [rawOrder[key], i]; + } + } else { + if (isVirtual && !justOne) { + rawDocs[key] = [val]; + rawOrder[key] = [i]; + } else { + rawDocs[key] = val; + rawOrder[key] = i; + } + } + } + } else { + if (_val instanceof Document) { + _val = _val._id; + } + key = String(_val); + if (rawDocs[key]) { + if (Array.isArray(rawDocs[key])) { + rawDocs[key].push(val); + rawOrder[key].push(i); + } else { + rawDocs[key] = [rawDocs[key], val]; + rawOrder[key] = [rawOrder[key], i]; + } + } else { + rawDocs[key] = val; + rawOrder[key] = i; + } + } + // flag each as result of population + if (!lean) { + val.$__.wasPopulated = true; + } + } + } + + assignVals({ + originalModel: model, + // If virtual, make sure to not mutate original field + rawIds: mod.isVirtual ? allIds : mod.allIds, + allIds: allIds, + foreignField: mod.foreignField, + rawDocs: rawDocs, + rawOrder: rawOrder, + docs: mod.docs, + path: options.path, + options: assignmentOpts, + justOne: mod.justOne, + isVirtual: mod.isVirtual, + allOptions: mod, + populatedModel: mod.model, + lean: lean, + virtual: mod.virtual, + count: mod.count, + match: mod.match + }); +} + +/*! + * Optionally filter out invalid ids that don't conform to foreign field's schema + * to avoid cast errors (gh-7706) + */ + +function _filterInvalidIds(ids, foreignSchemaType, skipInvalidIds) { + ids = ids.filter(v => !(v instanceof SkipPopulateValue)); + if (!skipInvalidIds) { + return ids; + } + return ids.filter(id => { + try { + foreignSchemaType.cast(id); + return true; + } catch (err) { + return false; + } + }); +} + +/*! + * Format `mod.match` given that it may be an array that we need to $or if + * the client has multiple docs with match functions + */ + +function _formatMatch(match) { + if (Array.isArray(match)) { + if (match.length > 1) { + return { $or: [].concat(match.map(m => Object.assign({}, m))) }; + } + return Object.assign({}, match[0]); + } + return Object.assign({}, match); +} + +/*! + * Compiler utility. + * + * @param {String|Function} name model name or class extending Model + * @param {Schema} schema + * @param {String} collectionName + * @param {Connection} connection + * @param {Mongoose} base mongoose instance + */ + +Model.compile = function compile(name, schema, collectionName, connection, base) { + const versioningEnabled = schema.options.versionKey !== false; + + if (versioningEnabled && !schema.paths[schema.options.versionKey]) { + // add versioning to top level documents only + const o = {}; + o[schema.options.versionKey] = Number; + schema.add(o); + } + + let model; + if (typeof name === 'function' && name.prototype instanceof Model) { + model = name; + name = model.name; + schema.loadClass(model, false); + model.prototype.$isMongooseModelPrototype = true; + } else { + // generate new class + model = function model(doc, fields, skipId) { + model.hooks.execPreSync('createModel', doc); + if (!(this instanceof model)) { + return new model(doc, fields, skipId); + } + const discriminatorKey = model.schema.options.discriminatorKey; + + if (model.discriminators == null || doc == null || doc[discriminatorKey] == null) { + Model.call(this, doc, fields, skipId); + return; + } + + // If discriminator key is set, use the discriminator instead (gh-7586) + const Discriminator = model.discriminators[doc[discriminatorKey]] || + getDiscriminatorByValue(model, doc[discriminatorKey]); + if (Discriminator != null) { + return new Discriminator(doc, fields, skipId); + } + + // Otherwise, just use the top-level model + Model.call(this, doc, fields, skipId); + }; + } + + model.hooks = schema.s.hooks.clone(); + model.base = base; + model.modelName = name; + + if (!(model.prototype instanceof Model)) { + model.__proto__ = Model; + model.prototype.__proto__ = Model.prototype; + } + model.model = function model(name) { + return this.db.model(name); + }; + model.db = connection; + model.prototype.db = connection; + model.prototype[modelDbSymbol] = connection; + model.discriminators = model.prototype.discriminators = undefined; + model[modelSymbol] = true; + model.events = new EventEmitter(); + + model.prototype.$__setSchema(schema); + + const _userProvidedOptions = schema._userProvidedOptions || {}; + + const collectionOptions = { + schemaUserProvidedOptions: _userProvidedOptions, + capped: schema.options.capped, + autoCreate: schema.options.autoCreate, + Promise: model.base.Promise + }; + + model.prototype.collection = connection.collection( + collectionName, + collectionOptions + ); + model.prototype[modelCollectionSymbol] = model.prototype.collection; + + // apply methods and statics + applyMethods(model, schema); + applyStatics(model, schema); + applyHooks(model, schema); + applyStaticHooks(model, schema.s.hooks, schema.statics); + + model.schema = model.prototype.schema; + model.collection = model.prototype.collection; + + // Create custom query constructor + model.Query = function() { + Query.apply(this, arguments); + }; + model.Query.prototype = Object.create(Query.prototype); + model.Query.base = Query.base; + applyQueryMiddleware(model.Query, model); + applyQueryMethods(model, schema.query); + + return model; +}; + +/*! + * Register custom query methods for this model + * + * @param {Model} model + * @param {Schema} schema + */ + +function applyQueryMethods(model, methods) { + for (const i in methods) { + model.Query.prototype[i] = methods[i]; + } +} + +/*! + * Subclass this model with `conn`, `schema`, and `collection` settings. + * + * @param {Connection} conn + * @param {Schema} [schema] + * @param {String} [collection] + * @return {Model} + */ + +Model.__subclass = function subclass(conn, schema, collection) { + // subclass model using this connection and collection name + const _this = this; + + const Model = function Model(doc, fields, skipId) { + if (!(this instanceof Model)) { + return new Model(doc, fields, skipId); + } + _this.call(this, doc, fields, skipId); + }; + + Model.__proto__ = _this; + Model.prototype.__proto__ = _this.prototype; + Model.db = conn; + Model.prototype.db = conn; + Model.prototype[modelDbSymbol] = conn; + + _this[subclassedSymbol] = _this[subclassedSymbol] || []; + _this[subclassedSymbol].push(Model); + if (_this.discriminators != null) { + Model.discriminators = {}; + for (const key of Object.keys(_this.discriminators)) { + Model.discriminators[key] = _this.discriminators[key]. + __subclass(_this.db, _this.discriminators[key].schema, collection); + } + } + + const s = schema && typeof schema !== 'string' + ? schema + : _this.prototype.schema; + + const options = s.options || {}; + const _userProvidedOptions = s._userProvidedOptions || {}; + + if (!collection) { + collection = _this.prototype.schema.get('collection') || + utils.toCollectionName(_this.modelName, this.base.pluralize()); + } + + const collectionOptions = { + schemaUserProvidedOptions: _userProvidedOptions, + capped: s && options.capped + }; + + Model.prototype.collection = conn.collection(collection, collectionOptions); + Model.prototype[modelCollectionSymbol] = Model.prototype.collection; + Model.collection = Model.prototype.collection; + // Errors handled internally, so ignore + Model.init(() => {}); + return Model; +}; + +Model.$handleCallbackError = function(callback) { + if (callback == null) { + return callback; + } + if (typeof callback !== 'function') { + throw new MongooseError('Callback must be a function, got ' + callback); + } + + const _this = this; + return function() { + try { + callback.apply(null, arguments); + } catch (error) { + _this.emit('error', error); + } + }; +}; + +/*! + * ignore + */ + +Model.$wrapCallback = function(callback) { + const serverSelectionError = new ServerSelectionError(); + const _this = this; + + return function(err) { + if (err != null && err.name === 'MongoServerSelectionError') { + arguments[0] = serverSelectionError.assimilateError(err); + } + if (err != null && err.name === 'MongoNetworkTimeoutError' && err.message.endsWith('timed out')) { + _this.db.emit('timeout'); + } + + return callback.apply(null, arguments); + }; +}; + +/** + * Helper for console.log. Given a model named 'MyModel', returns the string + * `'Model { MyModel }'`. + * + * ####Example: + * + * const MyModel = mongoose.model('Test', Schema({ name: String })); + * MyModel.inspect(); // 'Model { Test }' + * console.log(MyModel); // Prints 'Model { Test }' + * + * @api public + */ + +Model.inspect = function() { + return `Model { ${this.modelName} }`; +}; + +if (util.inspect.custom) { + /*! + * Avoid Node deprecation warning DEP0079 + */ + + Model[util.inspect.custom] = Model.inspect; +} + +/*! + * Module exports. + */ + +module.exports = exports = Model; diff --git a/node_modules/mongoose/lib/options.js b/node_modules/mongoose/lib/options.js new file mode 100644 index 0000000..8e9fc29 --- /dev/null +++ b/node_modules/mongoose/lib/options.js @@ -0,0 +1,14 @@ +'use strict'; + +/*! + * ignore + */ + +exports.internalToObjectOptions = { + transform: false, + virtuals: false, + getters: false, + _skipDepopulateTopLevel: true, + depopulate: true, + flattenDecimals: false +}; diff --git a/node_modules/mongoose/lib/options/PopulateOptions.js b/node_modules/mongoose/lib/options/PopulateOptions.js new file mode 100644 index 0000000..5b98194 --- /dev/null +++ b/node_modules/mongoose/lib/options/PopulateOptions.js @@ -0,0 +1,36 @@ +'use strict'; + +const clone = require('../helpers/clone'); + +class PopulateOptions { + constructor(obj) { + this._docs = {}; + this._childDocs = []; + + if (obj == null) { + return; + } + obj = clone(obj); + Object.assign(this, obj); + if (typeof obj.subPopulate === 'object') { + this.populate = obj.subPopulate; + } + + + if (obj.perDocumentLimit != null && obj.limit != null) { + throw new Error('Can not use `limit` and `perDocumentLimit` at the same time. Path: `' + obj.path + '`.'); + } + } +} + +/** + * The connection used to look up models by name. If not specified, Mongoose + * will default to using the connection associated with the model in + * `PopulateOptions#model`. + * + * @memberOf PopulateOptions + * @property {Connection} connection + * @api public + */ + +module.exports = PopulateOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaArrayOptions.js b/node_modules/mongoose/lib/options/SchemaArrayOptions.js new file mode 100644 index 0000000..ddd0b37 --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaArrayOptions.js @@ -0,0 +1,39 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on an Array schematype. + * + * ####Example: + * + * const schema = new Schema({ tags: [String] }); + * schema.path('tags').options; // SchemaArrayOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaArrayOptions + */ + +class SchemaArrayOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * If this is an array of strings, an array of allowed values for this path. + * Throws an error if this array isn't an array of strings. + * + * @api public + * @property enum + * @memberOf SchemaArrayOptions + * @type Array + * @instance + */ + +Object.defineProperty(SchemaArrayOptions.prototype, 'enum', opts); + +/*! + * ignore + */ + +module.exports = SchemaArrayOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaBufferOptions.js b/node_modules/mongoose/lib/options/SchemaBufferOptions.js new file mode 100644 index 0000000..258130d --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaBufferOptions.js @@ -0,0 +1,38 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on a Buffer schematype. + * + * ####Example: + * + * const schema = new Schema({ bitmap: Buffer }); + * schema.path('bitmap').options; // SchemaBufferOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaBufferOptions + */ + +class SchemaBufferOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * Set the default subtype for this buffer. + * + * @api public + * @property subtype + * @memberOf SchemaBufferOptions + * @type Number + * @instance + */ + +Object.defineProperty(SchemaBufferOptions.prototype, 'subtype', opts); + +/*! + * ignore + */ + +module.exports = SchemaBufferOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaDateOptions.js b/node_modules/mongoose/lib/options/SchemaDateOptions.js new file mode 100644 index 0000000..09bf27f --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaDateOptions.js @@ -0,0 +1,64 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on a Date schematype. + * + * ####Example: + * + * const schema = new Schema({ startedAt: Date }); + * schema.path('startedAt').options; // SchemaDateOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaDateOptions + */ + +class SchemaDateOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * If set, Mongoose adds a validator that checks that this path is after the + * given `min`. + * + * @api public + * @property min + * @memberOf SchemaDateOptions + * @type Date + * @instance + */ + +Object.defineProperty(SchemaDateOptions.prototype, 'min', opts); + +/** + * If set, Mongoose adds a validator that checks that this path is before the + * given `max`. + * + * @api public + * @property max + * @memberOf SchemaDateOptions + * @type Date + * @instance + */ + +Object.defineProperty(SchemaDateOptions.prototype, 'max', opts); + +/** + * If set, Mongoose creates a TTL index on this path. + * + * @api public + * @property expires + * @memberOf SchemaDateOptions + * @type Date + * @instance + */ + +Object.defineProperty(SchemaDateOptions.prototype, 'expires', opts); + +/*! + * ignore + */ + +module.exports = SchemaDateOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaDocumentArrayOptions.js b/node_modules/mongoose/lib/options/SchemaDocumentArrayOptions.js new file mode 100644 index 0000000..f3fb7a9 --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaDocumentArrayOptions.js @@ -0,0 +1,68 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on an Document Array schematype. + * + * ####Example: + * + * const schema = new Schema({ users: [{ name: string }] }); + * schema.path('users').options; // SchemaDocumentArrayOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaDocumentOptions + */ + +class SchemaDocumentArrayOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * If `true`, Mongoose will skip building any indexes defined in this array's schema. + * If not set, Mongoose will build all indexes defined in this array's schema. + * + * ####Example: + * + * const childSchema = Schema({ name: { type: String, index: true } }); + * // If `excludeIndexes` is `true`, Mongoose will skip building an index + * // on `arr.name`. Otherwise, Mongoose will build an index on `arr.name`. + * const parentSchema = Schema({ + * arr: { type: [childSchema], excludeIndexes: true } + * }); + * + * @api public + * @property excludeIndexes + * @memberOf SchemaDocumentArrayOptions + * @type Array + * @instance + */ + +Object.defineProperty(SchemaDocumentArrayOptions.prototype, 'excludeIndexes', opts); + +/** + * If set, overwrites the child schema's `_id` option. + * + * ####Example: + * + * const childSchema = Schema({ name: String }); + * const parentSchema = Schema({ + * child: { type: childSchema, _id: false } + * }); + * parentSchema.path('child').schema.options._id; // false + * + * @api public + * @property _id + * @memberOf SchemaDocumentArrayOptions + * @type Array + * @instance + */ + +Object.defineProperty(SchemaDocumentArrayOptions.prototype, '_id', opts); + +/*! + * ignore + */ + +module.exports = SchemaDocumentArrayOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaMapOptions.js b/node_modules/mongoose/lib/options/SchemaMapOptions.js new file mode 100644 index 0000000..335d84a --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaMapOptions.js @@ -0,0 +1,43 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on a Map schematype. + * + * ####Example: + * + * const schema = new Schema({ socialMediaHandles: { type: Map, of: String } }); + * schema.path('socialMediaHandles').options; // SchemaMapOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaMapOptions + */ + +class SchemaMapOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * If set, specifies the type of this map's values. Mongoose will cast + * this map's values to the given type. + * + * If not set, Mongoose will not cast the map's values. + * + * ####Example: + * + * // Mongoose will cast `socialMediaHandles` values to strings + * const schema = new Schema({ socialMediaHandles: { type: Map, of: String } }); + * schema.path('socialMediaHandles').options.of; // String + * + * @api public + * @property of + * @memberOf SchemaMapOptions + * @type Function|string + * @instance + */ + +Object.defineProperty(SchemaMapOptions.prototype, 'of', opts); + +module.exports = SchemaMapOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaNumberOptions.js b/node_modules/mongoose/lib/options/SchemaNumberOptions.js new file mode 100644 index 0000000..42b0a01 --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaNumberOptions.js @@ -0,0 +1,74 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on a Number schematype. + * + * ####Example: + * + * const schema = new Schema({ count: Number }); + * schema.path('count').options; // SchemaNumberOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaNumberOptions + */ + +class SchemaNumberOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * If set, Mongoose adds a validator that checks that this path is at least the + * given `min`. + * + * @api public + * @property min + * @memberOf SchemaNumberOptions + * @type Number + * @instance + */ + +Object.defineProperty(SchemaNumberOptions.prototype, 'min', opts); + +/** + * If set, Mongoose adds a validator that checks that this path is less than the + * given `max`. + * + * @api public + * @property max + * @memberOf SchemaNumberOptions + * @type Number + * @instance + */ + +Object.defineProperty(SchemaNumberOptions.prototype, 'max', opts); + +/** + * If set, Mongoose adds a validator that checks that this path is strictly + * equal to one of the given values. + * + * ####Example: + * const schema = new Schema({ + * favoritePrime: { + * type: Number, + * enum: [3, 5, 7] + * } + * }); + * schema.path('favoritePrime').options.enum; // [3, 5, 7] + * + * @api public + * @property enum + * @memberOf SchemaNumberOptions + * @type Array + * @instance + */ + +Object.defineProperty(SchemaNumberOptions.prototype, 'enum', opts); + +/*! + * ignore + */ + +module.exports = SchemaNumberOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaObjectIdOptions.js b/node_modules/mongoose/lib/options/SchemaObjectIdOptions.js new file mode 100644 index 0000000..cf887d0 --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaObjectIdOptions.js @@ -0,0 +1,38 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on an ObjectId schematype. + * + * ####Example: + * + * const schema = new Schema({ testId: mongoose.ObjectId }); + * schema.path('testId').options; // SchemaObjectIdOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaObjectIdOptions + */ + +class SchemaObjectIdOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * If truthy, uses Mongoose's default built-in ObjectId path. + * + * @api public + * @property auto + * @memberOf SchemaObjectIdOptions + * @type Boolean + * @instance + */ + +Object.defineProperty(SchemaObjectIdOptions.prototype, 'auto', opts); + +/*! + * ignore + */ + +module.exports = SchemaObjectIdOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaSingleNestedOptions.js b/node_modules/mongoose/lib/options/SchemaSingleNestedOptions.js new file mode 100644 index 0000000..c916d0f --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaSingleNestedOptions.js @@ -0,0 +1,42 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on a single nested schematype. + * + * ####Example: + * + * const schema = Schema({ child: Schema({ name: String }) }); + * schema.path('child').options; // SchemaSingleNestedOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaSingleNestedOptions + */ + +class SchemaSingleNestedOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * If set, overwrites the child schema's `_id` option. + * + * ####Example: + * + * const childSchema = Schema({ name: String }); + * const parentSchema = Schema({ + * child: { type: childSchema, _id: false } + * }); + * parentSchema.path('child').schema.options._id; // false + * + * @api public + * @property of + * @memberOf SchemaSingleNestedOptions + * @type Function|string + * @instance + */ + +Object.defineProperty(SchemaSingleNestedOptions.prototype, '_id', opts); + +module.exports = SchemaSingleNestedOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaStringOptions.js b/node_modules/mongoose/lib/options/SchemaStringOptions.js new file mode 100644 index 0000000..7654993 --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaStringOptions.js @@ -0,0 +1,116 @@ +'use strict'; + +const SchemaTypeOptions = require('./SchemaTypeOptions'); + +/** + * The options defined on a string schematype. + * + * ####Example: + * + * const schema = new Schema({ name: String }); + * schema.path('name').options; // SchemaStringOptions instance + * + * @api public + * @inherits SchemaTypeOptions + * @constructor SchemaStringOptions + */ + +class SchemaStringOptions extends SchemaTypeOptions {} + +const opts = require('./propertyOptions'); + +/** + * Array of allowed values for this path + * + * @api public + * @property enum + * @memberOf SchemaStringOptions + * @type Array + * @instance + */ + +Object.defineProperty(SchemaStringOptions.prototype, 'enum', opts); + +/** + * Attach a validator that succeeds if the data string matches the given regular + * expression, and fails otherwise. + * + * @api public + * @property match + * @memberOf SchemaStringOptions + * @type RegExp + * @instance + */ + +Object.defineProperty(SchemaStringOptions.prototype, 'match', opts); + +/** + * If truthy, Mongoose will add a custom setter that lowercases this string + * using JavaScript's built-in `String#toLowerCase()`. + * + * @api public + * @property lowercase + * @memberOf SchemaStringOptions + * @type Boolean + * @instance + */ + +Object.defineProperty(SchemaStringOptions.prototype, 'lowercase', opts); + +/** + * If truthy, Mongoose will add a custom setter that removes leading and trailing + * whitespace using JavaScript's built-in `String#trim()`. + * + * @api public + * @property trim + * @memberOf SchemaStringOptions + * @type Boolean + * @instance + */ + +Object.defineProperty(SchemaStringOptions.prototype, 'trim', opts); + +/** + * If truthy, Mongoose will add a custom setter that uppercases this string + * using JavaScript's built-in `String#toUpperCase()`. + * + * @api public + * @property uppercase + * @memberOf SchemaStringOptions + * @type Boolean + * @instance + */ + +Object.defineProperty(SchemaStringOptions.prototype, 'uppercase', opts); + +/** + * If set, Mongoose will add a custom validator that ensures the given + * string's `length` is at least the given number. + * + * @api public + * @property minlength + * @memberOf SchemaStringOptions + * @type Number + * @instance + */ + +Object.defineProperty(SchemaStringOptions.prototype, 'minlength', opts); + +/** + * If set, Mongoose will add a custom validator that ensures the given + * string's `length` is at most the given number. + * + * @api public + * @property maxlength + * @memberOf SchemaStringOptions + * @type Number + * @instance + */ + +Object.defineProperty(SchemaStringOptions.prototype, 'maxlength', opts); + +/*! + * ignore + */ + +module.exports = SchemaStringOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/SchemaTypeOptions.js b/node_modules/mongoose/lib/options/SchemaTypeOptions.js new file mode 100644 index 0000000..5dc5995 --- /dev/null +++ b/node_modules/mongoose/lib/options/SchemaTypeOptions.js @@ -0,0 +1,232 @@ +'use strict'; + +const clone = require('../helpers/clone'); + +/** + * The options defined on a schematype. + * + * ####Example: + * + * const schema = new Schema({ name: String }); + * schema.path('name').options instanceof mongoose.SchemaTypeOptions; // true + * + * @api public + * @constructor SchemaTypeOptions + */ + +class SchemaTypeOptions { + constructor(obj) { + if (obj == null) { + return this; + } + Object.assign(this, clone(obj)); + } +} + +const opts = require('./propertyOptions'); + +/** + * The type to cast this path to. + * + * @api public + * @property type + * @memberOf SchemaTypeOptions + * @type Function|String|Object + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'type', opts); + +/** + * Function or object describing how to validate this schematype. + * + * @api public + * @property validate + * @memberOf SchemaTypeOptions + * @type Function|Object + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'validate', opts); + +/** + * Allows overriding casting logic for this individual path. If a string, the + * given string overwrites Mongoose's default cast error message. + * + * ####Example: + * + * const schema = new Schema({ + * num: { + * type: Number, + * cast: '{VALUE} is not a valid number' + * } + * }); + * + * // Throws 'CastError: "bad" is not a valid number' + * schema.path('num').cast('bad'); + * + * const Model = mongoose.model('Test', schema); + * const doc = new Model({ num: 'fail' }); + * const err = doc.validateSync(); + * + * err.errors['num']; // 'CastError: "fail" is not a valid number' + * + * @api public + * @property cast + * @memberOf SchemaTypeOptions + * @type String + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'cast', opts); + +/** + * If true, attach a required validator to this path, which ensures this path + * path cannot be set to a nullish value. If a function, Mongoose calls the + * function and only checks for nullish values if the function returns a truthy value. + * + * @api public + * @property required + * @memberOf SchemaTypeOptions + * @type Function|Boolean + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'required', opts); + +/** + * The default value for this path. If a function, Mongoose executes the function + * and uses the return value as the default. + * + * @api public + * @property default + * @memberOf SchemaTypeOptions + * @type Function|Any + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'default', opts); + +/** + * The model that `populate()` should use if populating this path. + * + * @api public + * @property ref + * @memberOf SchemaTypeOptions + * @type Function|String + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'ref', opts); + +/** + * Whether to include or exclude this path by default when loading documents + * using `find()`, `findOne()`, etc. + * + * @api public + * @property select + * @memberOf SchemaTypeOptions + * @type Boolean|Number + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'select', opts); + +/** + * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will + * build an index on this path when the model is + * compiled. + * + * @api public + * @property index + * @memberOf SchemaTypeOptions + * @type Boolean|Number|Object + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'index', opts); + +/** + * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose + * will build a unique index on this path when the + * model is compiled. [The `unique` option is **not** a validator](/docs/validation.html#the-unique-option-is-not-a-validator). + * + * @api public + * @property unique + * @memberOf SchemaTypeOptions + * @type Boolean|Number + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'unique', opts); + +/** + * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will + * disallow changes to this path once the document + * is saved to the database for the first time. Read more about [immutability in Mongoose here](http://thecodebarbarian.com/whats-new-in-mongoose-5-6-immutable-properties.html). + * + * @api public + * @property immutable + * @memberOf SchemaTypeOptions + * @type Function|Boolean + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'immutable', opts); + +/** + * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose will + * build a sparse index on this path. + * + * @api public + * @property sparse + * @memberOf SchemaTypeOptions + * @type Boolean|Number + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'sparse', opts); + +/** + * If [truthy](https://masteringjs.io/tutorials/fundamentals/truthy), Mongoose + * will build a text index on this path. + * + * @api public + * @property text + * @memberOf SchemaTypeOptions + * @type Boolean|Number|Object + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'text', opts); + +/** + * Define a transform function for this individual schema type. + * Only called when calling `toJSON()` or `toObject()`. + * + * ####Example: + * + * const schema = Schema({ + * myDate: { + * type: Date, + * transform: v => v.getFullYear() + * } + * }); + * const Model = mongoose.model('Test', schema); + * + * const doc = new Model({ myDate: new Date('2019/06/01') }); + * doc.myDate instanceof Date; // true + * + * const res = doc.toObject({ transform: true }); + * res.myDate; // 2019 + * + * @api public + * @property transform + * @memberOf SchemaTypeOptions + * @type Function + * @instance + */ + +Object.defineProperty(SchemaTypeOptions.prototype, 'transform', opts); + +module.exports = SchemaTypeOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/VirtualOptions.js b/node_modules/mongoose/lib/options/VirtualOptions.js new file mode 100644 index 0000000..a266414 --- /dev/null +++ b/node_modules/mongoose/lib/options/VirtualOptions.js @@ -0,0 +1,164 @@ +'use strict'; + +const opts = require('./propertyOptions'); + +class VirtualOptions { + constructor(obj) { + Object.assign(this, obj); + + if (obj != null && obj.options != null) { + this.options = Object.assign({}, obj.options); + } + } +} + +/** + * Marks this virtual as a populate virtual, and specifies the model to + * use for populate. + * + * @api public + * @property ref + * @memberOf VirtualOptions + * @type String|Model|Function + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'ref', opts); + +/** + * Marks this virtual as a populate virtual, and specifies the path that + * contains the name of the model to populate + * + * @api public + * @property refPath + * @memberOf VirtualOptions + * @type String|Function + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'refPath', opts); + +/** + * The name of the property in the local model to match to `foreignField` + * in the foreign model. + * + * @api public + * @property localField + * @memberOf VirtualOptions + * @type String|Function + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'localField', opts); + +/** + * The name of the property in the foreign model to match to `localField` + * in the local model. + * + * @api public + * @property foreignField + * @memberOf VirtualOptions + * @type String|Function + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'foreignField', opts); + +/** + * Whether to populate this virtual as a single document (true) or an + * array of documents (false). + * + * @api public + * @property justOne + * @memberOf VirtualOptions + * @type Boolean + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'justOne', opts); + +/** + * If true, populate just the number of documents where `localField` + * matches `foreignField`, as opposed to the documents themselves. + * + * If `count` is set, it overrides `justOne`. + * + * @api public + * @property count + * @memberOf VirtualOptions + * @type Boolean + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'count', opts); + +/** + * Add an additional filter to populate, in addition to `localField` + * matches `foreignField`. + * + * @api public + * @property match + * @memberOf VirtualOptions + * @type Object|Function + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'match', opts); + +/** + * Additional options to pass to the query used to `populate()`: + * + * - `sort` + * - `skip` + * - `limit` + * + * @api public + * @property options + * @memberOf VirtualOptions + * @type Object + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'options', opts); + +/** + * If true, add a `skip` to the query used to `populate()`. + * + * @api public + * @property skip + * @memberOf VirtualOptions + * @type Number + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'skip', opts); + +/** + * If true, add a `limit` to the query used to `populate()`. + * + * @api public + * @property limit + * @memberOf VirtualOptions + * @type Number + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'limit', opts); + +/** + * The `limit` option for `populate()` has [some unfortunate edge cases](/docs/populate.html#query-conditions) + * when working with multiple documents, like `.find().populate()`. The + * `perDocumentLimit` option makes `populate()` execute a separate query + * for each document returned from `find()` to ensure each document + * gets up to `perDocumentLimit` populated docs if possible. + * + * @api public + * @property perDocumentLimit + * @memberOf VirtualOptions + * @type Number + * @instance + */ + +Object.defineProperty(VirtualOptions.prototype, 'perDocumentLimit', opts); + +module.exports = VirtualOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/propertyOptions.js b/node_modules/mongoose/lib/options/propertyOptions.js new file mode 100644 index 0000000..b7488a3 --- /dev/null +++ b/node_modules/mongoose/lib/options/propertyOptions.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = Object.freeze({ + enumerable: true, + configurable: true, + writable: true, + value: void 0 +});
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/removeOptions.js b/node_modules/mongoose/lib/options/removeOptions.js new file mode 100644 index 0000000..0d91586 --- /dev/null +++ b/node_modules/mongoose/lib/options/removeOptions.js @@ -0,0 +1,14 @@ +'use strict'; + +const clone = require('../helpers/clone'); + +class RemoveOptions { + constructor(obj) { + if (obj == null) { + return; + } + Object.assign(this, clone(obj)); + } +} + +module.exports = RemoveOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/options/saveOptions.js b/node_modules/mongoose/lib/options/saveOptions.js new file mode 100644 index 0000000..22ef437 --- /dev/null +++ b/node_modules/mongoose/lib/options/saveOptions.js @@ -0,0 +1,14 @@ +'use strict'; + +const clone = require('../helpers/clone'); + +class SaveOptions { + constructor(obj) { + if (obj == null) { + return; + } + Object.assign(this, clone(obj)); + } +} + +module.exports = SaveOptions;
\ No newline at end of file diff --git a/node_modules/mongoose/lib/plugins/idGetter.js b/node_modules/mongoose/lib/plugins/idGetter.js new file mode 100644 index 0000000..f4e6356 --- /dev/null +++ b/node_modules/mongoose/lib/plugins/idGetter.js @@ -0,0 +1,28 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = function(schema) { + // ensure the documents receive an id getter unless disabled + const autoIdGetter = !schema.paths['id'] && + (!schema.options.noVirtualId && schema.options.id); + if (!autoIdGetter) { + return; + } + + schema.virtual('id').get(idGetter); +}; + +/*! + * Returns this documents _id cast to a string. + */ + +function idGetter() { + if (this._id != null) { + return String(this._id); + } + + return null; +} diff --git a/node_modules/mongoose/lib/plugins/removeSubdocs.js b/node_modules/mongoose/lib/plugins/removeSubdocs.js new file mode 100644 index 0000000..44b2ea6 --- /dev/null +++ b/node_modules/mongoose/lib/plugins/removeSubdocs.js @@ -0,0 +1,31 @@ +'use strict'; + +const each = require('../helpers/each'); + +/*! + * ignore + */ + +module.exports = function(schema) { + const unshift = true; + schema.s.hooks.pre('remove', false, function(next) { + if (this.ownerDocument) { + next(); + return; + } + + const _this = this; + const subdocs = this.$__getAllSubdocs(); + + each(subdocs, function(subdoc, cb) { + subdoc.$__remove(cb); + }, function(error) { + if (error) { + return _this.schema.s.hooks.execPost('remove:error', _this, [_this], { error: error }, function(error) { + next(error); + }); + } + next(); + }); + }, null, unshift); +}; diff --git a/node_modules/mongoose/lib/plugins/saveSubdocs.js b/node_modules/mongoose/lib/plugins/saveSubdocs.js new file mode 100644 index 0000000..c0a3144 --- /dev/null +++ b/node_modules/mongoose/lib/plugins/saveSubdocs.js @@ -0,0 +1,66 @@ +'use strict'; + +const each = require('../helpers/each'); + +/*! + * ignore + */ + +module.exports = function(schema) { + const unshift = true; + schema.s.hooks.pre('save', false, function(next) { + if (this.ownerDocument) { + next(); + return; + } + + const _this = this; + const subdocs = this.$__getAllSubdocs(); + + if (!subdocs.length) { + next(); + return; + } + + each(subdocs, function(subdoc, cb) { + subdoc.schema.s.hooks.execPre('save', subdoc, function(err) { + cb(err); + }); + }, function(error) { + if (error) { + return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { + next(error); + }); + } + next(); + }); + }, null, unshift); + + schema.s.hooks.post('save', function(doc, next) { + if (this.ownerDocument) { + next(); + return; + } + + const _this = this; + const subdocs = this.$__getAllSubdocs(); + + if (!subdocs.length) { + next(); + return; + } + + each(subdocs, function(subdoc, cb) { + subdoc.schema.s.hooks.execPost('save', subdoc, [subdoc], function(err) { + cb(err); + }); + }, function(error) { + if (error) { + return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { + next(error); + }); + } + next(); + }); + }, null, unshift); +}; diff --git a/node_modules/mongoose/lib/plugins/sharding.js b/node_modules/mongoose/lib/plugins/sharding.js new file mode 100644 index 0000000..560053e --- /dev/null +++ b/node_modules/mongoose/lib/plugins/sharding.js @@ -0,0 +1,83 @@ +'use strict'; + +const objectIdSymbol = require('../helpers/symbols').objectIdSymbol; +const utils = require('../utils'); + +/*! + * ignore + */ + +module.exports = function shardingPlugin(schema) { + schema.post('init', function() { + storeShard.call(this); + return this; + }); + schema.pre('save', function(next) { + applyWhere.call(this); + next(); + }); + schema.pre('remove', function(next) { + applyWhere.call(this); + next(); + }); + schema.post('save', function() { + storeShard.call(this); + }); +}; + +/*! + * ignore + */ + +function applyWhere() { + let paths; + let len; + + if (this.$__.shardval) { + paths = Object.keys(this.$__.shardval); + len = paths.length; + + this.$where = this.$where || {}; + for (let i = 0; i < len; ++i) { + this.$where[paths[i]] = this.$__.shardval[paths[i]]; + } + } +} + +/*! + * ignore + */ + +module.exports.storeShard = storeShard; + +/*! + * ignore + */ + +function storeShard() { + // backwards compat + const key = this.schema.options.shardKey || this.schema.options.shardkey; + if (!utils.isPOJO(key)) { + return; + } + + const orig = this.$__.shardval = {}; + const paths = Object.keys(key); + const len = paths.length; + let val; + + for (let i = 0; i < len; ++i) { + val = this.$__getValue(paths[i]); + if (val == null) { + orig[paths[i]] = val; + } else if (utils.isMongooseObject(val)) { + orig[paths[i]] = val.toObject({ depopulate: true, _isNested: true }); + } else if (val instanceof Date || val[objectIdSymbol]) { + orig[paths[i]] = val; + } else if (typeof val.valueOf === 'function') { + orig[paths[i]] = val.valueOf(); + } else { + orig[paths[i]] = val; + } + } +} diff --git a/node_modules/mongoose/lib/plugins/trackTransaction.js b/node_modules/mongoose/lib/plugins/trackTransaction.js new file mode 100644 index 0000000..410a596 --- /dev/null +++ b/node_modules/mongoose/lib/plugins/trackTransaction.js @@ -0,0 +1,91 @@ +'use strict'; + +const arrayAtomicsSymbol = require('../helpers/symbols').arrayAtomicsSymbol; +const sessionNewDocuments = require('../helpers/symbols').sessionNewDocuments; + +module.exports = function trackTransaction(schema) { + schema.pre('save', function() { + const session = this.$session(); + if (session == null) { + return; + } + if (session.transaction == null || session[sessionNewDocuments] == null) { + return; + } + + if (!session[sessionNewDocuments].has(this)) { + const initialState = {}; + if (this.isNew) { + initialState.isNew = true; + } + if (this.schema.options.versionKey) { + initialState.versionKey = this.get(this.schema.options.versionKey); + } + + initialState.modifiedPaths = new Set(Object.keys(this.$__.activePaths.states.modify)); + initialState.atomics = _getAtomics(this); + + session[sessionNewDocuments].set(this, initialState); + } else { + const state = session[sessionNewDocuments].get(this); + + for (const path of Object.keys(this.$__.activePaths.states.modify)) { + state.modifiedPaths.add(path); + } + state.atomics = _getAtomics(this, state.atomics); + } + }); +}; + +function _getAtomics(doc, previous) { + const pathToAtomics = new Map(); + previous = previous || new Map(); + + const pathsToCheck = Object.keys(doc.$__.activePaths.init).concat(Object.keys(doc.$__.activePaths.modify)); + + for (const path of pathsToCheck) { + const val = doc.$__getValue(path); + if (val != null && + val instanceof Array && + val.isMongooseDocumentArray && + val.length && + val[arrayAtomicsSymbol] != null && + Object.keys(val[arrayAtomicsSymbol]).length > 0) { + const existing = previous.get(path) || {}; + pathToAtomics.set(path, mergeAtomics(existing, val[arrayAtomicsSymbol])); + } + } + + const dirty = doc.$__dirty(); + for (const dirt of dirty) { + const path = dirt.path; + + const val = dirt.value; + if (val != null && val[arrayAtomicsSymbol] != null && Object.keys(val[arrayAtomicsSymbol]).length > 0) { + const existing = previous.get(path) || {}; + pathToAtomics.set(path, mergeAtomics(existing, val[arrayAtomicsSymbol])); + } + } + + return pathToAtomics; +} + +function mergeAtomics(destination, source) { + destination = destination || {}; + + if (source.$pullAll != null) { + destination.$pullAll = (destination.$pullAll || []).concat(source.$pullAll); + } + if (source.$push != null) { + destination.$push = destination.$push || {}; + destination.$push.$each = (destination.$push.$each || []).concat(source.$push.$each); + } + if (source.$addToSet != null) { + destination.$addToSet = (destination.$addToSet || []).concat(source.$addToSet); + } + if (source.$set != null) { + destination.$set = Object.assign(destination.$set || {}, source.$set); + } + + return destination; +}
\ No newline at end of file diff --git a/node_modules/mongoose/lib/plugins/validateBeforeSave.js b/node_modules/mongoose/lib/plugins/validateBeforeSave.js new file mode 100644 index 0000000..4635de1 --- /dev/null +++ b/node_modules/mongoose/lib/plugins/validateBeforeSave.js @@ -0,0 +1,45 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = function(schema) { + const unshift = true; + schema.pre('save', false, function validateBeforeSave(next, options) { + const _this = this; + // Nested docs have their own presave + if (this.ownerDocument) { + return next(); + } + + const hasValidateBeforeSaveOption = options && + (typeof options === 'object') && + ('validateBeforeSave' in options); + + let shouldValidate; + if (hasValidateBeforeSaveOption) { + shouldValidate = !!options.validateBeforeSave; + } else { + shouldValidate = this.schema.options.validateBeforeSave; + } + + // Validate + if (shouldValidate) { + const hasValidateModifiedOnlyOption = options && + (typeof options === 'object') && + ('validateModifiedOnly' in options); + const validateOptions = hasValidateModifiedOnlyOption ? + { validateModifiedOnly: options.validateModifiedOnly } : + null; + this.validate(validateOptions, function(error) { + return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) { + _this.$op = 'save'; + next(error); + }); + }); + } else { + next(); + } + }, null, unshift); +}; diff --git a/node_modules/mongoose/lib/promise_provider.js b/node_modules/mongoose/lib/promise_provider.js new file mode 100644 index 0000000..3febf36 --- /dev/null +++ b/node_modules/mongoose/lib/promise_provider.js @@ -0,0 +1,49 @@ +/*! + * ignore + */ + +'use strict'; + +const assert = require('assert'); +const mquery = require('mquery'); + +/** + * Helper for multiplexing promise implementations + * + * @api private + */ + +const store = { + _promise: null +}; + +/** + * Get the current promise constructor + * + * @api private + */ + +store.get = function() { + return store._promise; +}; + +/** + * Set the current promise constructor + * + * @api private + */ + +store.set = function(lib) { + assert.ok(typeof lib === 'function', + `mongoose.Promise must be a function, got ${lib}`); + store._promise = lib; + mquery.Promise = lib; +}; + +/*! + * Use native promises by default + */ + +store.set(global.Promise); + +module.exports = store; diff --git a/node_modules/mongoose/lib/query.js b/node_modules/mongoose/lib/query.js new file mode 100644 index 0000000..d270efb --- /dev/null +++ b/node_modules/mongoose/lib/query.js @@ -0,0 +1,5380 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const CastError = require('./error/cast'); +const DocumentNotFoundError = require('./error/notFound'); +const Kareem = require('kareem'); +const MongooseError = require('./error/mongooseError'); +const ObjectParameterError = require('./error/objectParameter'); +const QueryCursor = require('./cursor/QueryCursor'); +const ReadPreference = require('./driver').get().ReadPreference; +const applyGlobalMaxTimeMS = require('./helpers/query/applyGlobalMaxTimeMS'); +const applyWriteConcern = require('./helpers/schema/applyWriteConcern'); +const cast = require('./cast'); +const castArrayFilters = require('./helpers/update/castArrayFilters'); +const castUpdate = require('./helpers/query/castUpdate'); +const completeMany = require('./helpers/query/completeMany'); +const get = require('./helpers/get'); +const promiseOrCallback = require('./helpers/promiseOrCallback'); +const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue'); +const hasDollarKeys = require('./helpers/query/hasDollarKeys'); +const helpers = require('./queryhelpers'); +const isInclusive = require('./helpers/projection/isInclusive'); +const mquery = require('mquery'); +const parseProjection = require('./helpers/projection/parseProjection'); +const removeUnusedArrayFilters = require('./helpers/update/removeUnusedArrayFilters'); +const selectPopulatedFields = require('./helpers/query/selectPopulatedFields'); +const setDefaultsOnInsert = require('./helpers/setDefaultsOnInsert'); +const slice = require('sliced'); +const updateValidators = require('./helpers/updateValidators'); +const util = require('util'); +const utils = require('./utils'); +const wrapThunk = require('./helpers/query/wrapThunk'); + +/** + * Query constructor used for building queries. You do not need + * to instantiate a `Query` directly. Instead use Model functions like + * [`Model.find()`](/docs/api.html#find_find). + * + * ####Example: + * + * const query = MyModel.find(); // `query` is an instance of `Query` + * query.setOptions({ lean : true }); + * query.collection(MyModel.collection); + * query.where('age').gte(21).exec(callback); + * + * // You can instantiate a query directly. There is no need to do + * // this unless you're an advanced user with a very good reason to. + * const query = new mongoose.Query(); + * + * @param {Object} [options] + * @param {Object} [model] + * @param {Object} [conditions] + * @param {Object} [collection] Mongoose collection + * @api public + */ + +function Query(conditions, options, model, collection) { + // this stuff is for dealing with custom queries created by #toConstructor + if (!this._mongooseOptions) { + this._mongooseOptions = {}; + } + options = options || {}; + + this._transforms = []; + this._hooks = new Kareem(); + this._executionCount = 0; + + // this is the case where we have a CustomQuery, we need to check if we got + // options passed in, and if we did, merge them in + const keys = Object.keys(options); + for (const key of keys) { + this._mongooseOptions[key] = options[key]; + } + + if (collection) { + this.mongooseCollection = collection; + } + + if (model) { + this.model = model; + this.schema = model.schema; + } + + // this is needed because map reduce returns a model that can be queried, but + // all of the queries on said model should be lean + if (this.model && this.model._mapreduce) { + this.lean(); + } + + // inherit mquery + mquery.call(this, this.mongooseCollection, options); + + if (conditions) { + this.find(conditions); + } + + this.options = this.options || {}; + + // For gh-6880. mquery still needs to support `fields` by default for old + // versions of MongoDB + this.$useProjection = true; + + const collation = get(this, 'schema.options.collation', null); + if (collation != null) { + this.options.collation = collation; + } +} + +/*! + * inherit mquery + */ + +Query.prototype = new mquery; +Query.prototype.constructor = Query; +Query.base = mquery.prototype; + +/** + * Flag to opt out of using `$geoWithin`. + * + * mongoose.Query.use$geoWithin = false; + * + * MongoDB 2.4 deprecated the use of `$within`, replacing it with `$geoWithin`. Mongoose uses `$geoWithin` by default (which is 100% backward compatible with `$within`). If you are running an older version of MongoDB, set this flag to `false` so your `within()` queries continue to work. + * + * @see http://docs.mongodb.org/manual/reference/operator/geoWithin/ + * @default true + * @property use$geoWithin + * @memberOf Query + * @receiver Query + * @api public + */ + +Query.use$geoWithin = mquery.use$geoWithin; + +/** + * Converts this query to a customized, reusable query constructor with all arguments and options retained. + * + * ####Example + * + * // Create a query for adventure movies and read from the primary + * // node in the replica-set unless it is down, in which case we'll + * // read from a secondary node. + * const query = Movie.find({ tags: 'adventure' }).read('primaryPreferred'); + * + * // create a custom Query constructor based off these settings + * const Adventure = query.toConstructor(); + * + * // Adventure is now a subclass of mongoose.Query and works the same way but with the + * // default query parameters and options set. + * Adventure().exec(callback) + * + * // further narrow down our query results while still using the previous settings + * Adventure().where({ name: /^Life/ }).exec(callback); + * + * // since Adventure is a stand-alone constructor we can also add our own + * // helper methods and getters without impacting global queries + * Adventure.prototype.startsWith = function (prefix) { + * this.where({ name: new RegExp('^' + prefix) }) + * return this; + * } + * Object.defineProperty(Adventure.prototype, 'highlyRated', { + * get: function () { + * this.where({ rating: { $gt: 4.5 }}); + * return this; + * } + * }) + * Adventure().highlyRated.startsWith('Life').exec(callback) + * + * @return {Query} subclass-of-Query + * @api public + */ + +Query.prototype.toConstructor = function toConstructor() { + const model = this.model; + const coll = this.mongooseCollection; + + const CustomQuery = function(criteria, options) { + if (!(this instanceof CustomQuery)) { + return new CustomQuery(criteria, options); + } + this._mongooseOptions = utils.clone(p._mongooseOptions); + Query.call(this, criteria, options || null, model, coll); + }; + + util.inherits(CustomQuery, model.Query); + + // set inherited defaults + const p = CustomQuery.prototype; + + p.options = {}; + + // Need to handle `sort()` separately because entries-style `sort()` syntax + // `sort([['prop1', 1]])` confuses mquery into losing the outer nested array. + // See gh-8159 + const options = Object.assign({}, this.options); + if (options.sort != null) { + p.sort(options.sort); + delete options.sort; + } + p.setOptions(options); + + p.op = this.op; + p._conditions = utils.clone(this._conditions); + p._fields = utils.clone(this._fields); + p._update = utils.clone(this._update, { + flattenDecimals: false + }); + p._path = this._path; + p._distinct = this._distinct; + p._collection = this._collection; + p._mongooseOptions = this._mongooseOptions; + + return CustomQuery; +}; + +/** + * Specifies a javascript function or expression to pass to MongoDBs query system. + * + * ####Example + * + * query.$where('this.comments.length === 10 || this.name.length === 5') + * + * // or + * + * query.$where(function () { + * return this.comments.length === 10 || this.name.length === 5; + * }) + * + * ####NOTE: + * + * Only use `$where` when you have a condition that cannot be met using other MongoDB operators like `$lt`. + * **Be sure to read about all of [its caveats](http://docs.mongodb.org/manual/reference/operator/where/) before using.** + * + * @see $where http://docs.mongodb.org/manual/reference/operator/where/ + * @method $where + * @param {String|Function} js javascript string or function + * @return {Query} this + * @memberOf Query + * @instance + * @method $where + * @api public + */ + +/** + * Specifies a `path` for use with chaining. + * + * ####Example + * + * // instead of writing: + * User.find({age: {$gte: 21, $lte: 65}}, callback); + * + * // we can instead write: + * User.where('age').gte(21).lte(65); + * + * // passing query conditions is permitted + * User.find().where({ name: 'vonderful' }) + * + * // chaining + * User + * .where('age').gte(21).lte(65) + * .where('name', /^vonderful/i) + * .where('friends').slice(10) + * .exec(callback) + * + * @method where + * @memberOf Query + * @instance + * @param {String|Object} [path] + * @param {any} [val] + * @return {Query} this + * @api public + */ + +Query.prototype.slice = function() { + if (arguments.length === 0) { + return this; + } + + this._validate('slice'); + + let path; + let val; + + if (arguments.length === 1) { + const arg = arguments[0]; + if (typeof arg === 'object' && !Array.isArray(arg)) { + const keys = Object.keys(arg); + const numKeys = keys.length; + for (let i = 0; i < numKeys; ++i) { + this.slice(keys[i], arg[keys[i]]); + } + return this; + } + this._ensurePath('slice'); + path = this._path; + val = arguments[0]; + } else if (arguments.length === 2) { + if ('number' === typeof arguments[0]) { + this._ensurePath('slice'); + path = this._path; + val = slice(arguments); + } else { + path = arguments[0]; + val = arguments[1]; + } + } else if (arguments.length === 3) { + path = arguments[0]; + val = slice(arguments, 1); + } + + const p = {}; + p[path] = { $slice: val }; + this.select(p); + + return this; +}; + + +/** + * Specifies the complementary comparison value for paths specified with `where()` + * + * ####Example + * + * User.where('age').equals(49); + * + * // is the same as + * + * User.where('age', 49); + * + * @method equals + * @memberOf Query + * @instance + * @param {Object} val + * @return {Query} this + * @api public + */ + +/** + * Specifies arguments for an `$or` condition. + * + * ####Example + * + * query.or([{ color: 'red' }, { status: 'emergency' }]) + * + * @see $or http://docs.mongodb.org/manual/reference/operator/or/ + * @method or + * @memberOf Query + * @instance + * @param {Array} array array of conditions + * @return {Query} this + * @api public + */ + +/** + * Specifies arguments for a `$nor` condition. + * + * ####Example + * + * query.nor([{ color: 'green' }, { status: 'ok' }]) + * + * @see $nor http://docs.mongodb.org/manual/reference/operator/nor/ + * @method nor + * @memberOf Query + * @instance + * @param {Array} array array of conditions + * @return {Query} this + * @api public + */ + +/** + * Specifies arguments for a `$and` condition. + * + * ####Example + * + * query.and([{ color: 'green' }, { status: 'ok' }]) + * + * @method and + * @memberOf Query + * @instance + * @see $and http://docs.mongodb.org/manual/reference/operator/and/ + * @param {Array} array array of conditions + * @return {Query} this + * @api public + */ + +/** + * Specifies a `$gt` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * ####Example + * + * Thing.find().where('age').gt(21) + * + * // or + * Thing.find().gt('age', 21) + * + * @method gt + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @see $gt http://docs.mongodb.org/manual/reference/operator/gt/ + * @api public + */ + +/** + * Specifies a `$gte` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * @method gte + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @see $gte http://docs.mongodb.org/manual/reference/operator/gte/ + * @api public + */ + +/** + * Specifies a `$lt` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * @method lt + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @see $lt http://docs.mongodb.org/manual/reference/operator/lt/ + * @api public + */ + +/** + * Specifies a `$lte` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * @method lte + * @see $lte http://docs.mongodb.org/manual/reference/operator/lte/ + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @api public + */ + +/** + * Specifies a `$ne` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * @see $ne http://docs.mongodb.org/manual/reference/operator/ne/ + * @method ne + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @api public + */ + +/** + * Specifies an `$in` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * @see $in http://docs.mongodb.org/manual/reference/operator/in/ + * @method in + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @api public + */ + +/** + * Specifies an `$nin` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * @see $nin http://docs.mongodb.org/manual/reference/operator/nin/ + * @method nin + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @api public + */ + +/** + * Specifies an `$all` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * ####Example: + * + * MyModel.find().where('pets').all(['dog', 'cat', 'ferret']); + * // Equivalent: + * MyModel.find().all('pets', ['dog', 'cat', 'ferret']); + * + * @see $all http://docs.mongodb.org/manual/reference/operator/all/ + * @method all + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Array} val + * @api public + */ + +/** + * Specifies a `$size` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * ####Example + * + * MyModel.where('tags').size(0).exec(function (err, docs) { + * if (err) return handleError(err); + * + * assert(Array.isArray(docs)); + * console.log('documents with 0 tags', docs); + * }) + * + * @see $size http://docs.mongodb.org/manual/reference/operator/size/ + * @method size + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @api public + */ + +/** + * Specifies a `$regex` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * @see $regex http://docs.mongodb.org/manual/reference/operator/regex/ + * @method regex + * @memberOf Query + * @instance + * @param {String} [path] + * @param {String|RegExp} val + * @api public + */ + +/** + * Specifies a `maxDistance` query condition. + * + * When called with one argument, the most recent path passed to `where()` is used. + * + * @see $maxDistance http://docs.mongodb.org/manual/reference/operator/maxDistance/ + * @method maxDistance + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @api public + */ + +/** + * Specifies a `$mod` condition, filters documents for documents whose + * `path` property is a number that is equal to `remainder` modulo `divisor`. + * + * ####Example + * + * // All find products whose inventory is odd + * Product.find().mod('inventory', [2, 1]); + * Product.find().where('inventory').mod([2, 1]); + * // This syntax is a little strange, but supported. + * Product.find().where('inventory').mod(2, 1); + * + * @method mod + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Array} val must be of length 2, first element is `divisor`, 2nd element is `remainder`. + * @return {Query} this + * @see $mod http://docs.mongodb.org/manual/reference/operator/mod/ + * @api public + */ + +Query.prototype.mod = function() { + let val; + let path; + + if (arguments.length === 1) { + this._ensurePath('mod'); + val = arguments[0]; + path = this._path; + } else if (arguments.length === 2 && !Array.isArray(arguments[1])) { + this._ensurePath('mod'); + val = slice(arguments); + path = this._path; + } else if (arguments.length === 3) { + val = slice(arguments, 1); + path = arguments[0]; + } else { + val = arguments[1]; + path = arguments[0]; + } + + const conds = this._conditions[path] || (this._conditions[path] = {}); + conds.$mod = val; + return this; +}; + +/** + * Specifies an `$exists` condition + * + * ####Example + * + * // { name: { $exists: true }} + * Thing.where('name').exists() + * Thing.where('name').exists(true) + * Thing.find().exists('name') + * + * // { name: { $exists: false }} + * Thing.where('name').exists(false); + * Thing.find().exists('name', false); + * + * @method exists + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val + * @return {Query} this + * @see $exists http://docs.mongodb.org/manual/reference/operator/exists/ + * @api public + */ + +/** + * Specifies an `$elemMatch` condition + * + * ####Example + * + * query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}}) + * + * query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}}) + * + * query.elemMatch('comment', function (elem) { + * elem.where('author').equals('autobot'); + * elem.where('votes').gte(5); + * }) + * + * query.where('comment').elemMatch(function (elem) { + * elem.where({ author: 'autobot' }); + * elem.where('votes').gte(5); + * }) + * + * @method elemMatch + * @memberOf Query + * @instance + * @param {String|Object|Function} path + * @param {Object|Function} filter + * @return {Query} this + * @see $elemMatch http://docs.mongodb.org/manual/reference/operator/elemMatch/ + * @api public + */ + +/** + * Defines a `$within` or `$geoWithin` argument for geo-spatial queries. + * + * ####Example + * + * query.where(path).within().box() + * query.where(path).within().circle() + * query.where(path).within().geometry() + * + * query.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true }); + * query.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] }); + * query.where('loc').within({ polygon: [[],[],[],[]] }); + * + * query.where('loc').within([], [], []) // polygon + * query.where('loc').within([], []) // box + * query.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry + * + * **MUST** be used after `where()`. + * + * ####NOTE: + * + * As of Mongoose 3.7, `$geoWithin` is always used for queries. To change this behavior, see [Query.use$geoWithin](#query_Query-use%2524geoWithin). + * + * ####NOTE: + * + * In Mongoose 3.7, `within` changed from a getter to a function. If you need the old syntax, use [this](https://github.com/ebensing/mongoose-within). + * + * @method within + * @see $polygon http://docs.mongodb.org/manual/reference/operator/polygon/ + * @see $box http://docs.mongodb.org/manual/reference/operator/box/ + * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/ + * @see $center http://docs.mongodb.org/manual/reference/operator/center/ + * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/ + * @memberOf Query + * @instance + * @return {Query} this + * @api public + */ + +/** + * Specifies a `$slice` projection for an array. + * + * ####Example + * + * query.slice('comments', 5) + * query.slice('comments', -5) + * query.slice('comments', [10, 5]) + * query.where('comments').slice(5) + * query.where('comments').slice([-10, 5]) + * + * @method slice + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Number} val number/range of elements to slice + * @return {Query} this + * @see mongodb http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields#RetrievingaSubsetofFields-RetrievingaSubrangeofArrayElements + * @see $slice http://docs.mongodb.org/manual/reference/projection/slice/#prj._S_slice + * @api public + */ + +/** + * Specifies the maximum number of documents the query will return. + * + * ####Example + * + * query.limit(20) + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @method limit + * @memberOf Query + * @instance + * @param {Number} val + * @api public + */ + +/** + * Specifies the number of documents to skip. + * + * ####Example + * + * query.skip(100).limit(20) + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @method skip + * @memberOf Query + * @instance + * @param {Number} val + * @see cursor.skip http://docs.mongodb.org/manual/reference/method/cursor.skip/ + * @api public + */ + +/** + * Specifies the maxScan option. + * + * ####Example + * + * query.maxScan(100) + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @method maxScan + * @memberOf Query + * @instance + * @param {Number} val + * @see maxScan http://docs.mongodb.org/manual/reference/operator/maxScan/ + * @api public + */ + +/** + * Specifies the batchSize option. + * + * ####Example + * + * query.batchSize(100) + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @method batchSize + * @memberOf Query + * @instance + * @param {Number} val + * @see batchSize http://docs.mongodb.org/manual/reference/method/cursor.batchSize/ + * @api public + */ + +/** + * Specifies the `comment` option. + * + * ####Example + * + * query.comment('login query') + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @method comment + * @memberOf Query + * @instance + * @param {String} val + * @see comment http://docs.mongodb.org/manual/reference/operator/comment/ + * @api public + */ + +/** + * Specifies this query as a `snapshot` query. + * + * ####Example + * + * query.snapshot() // true + * query.snapshot(true) + * query.snapshot(false) + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @method snapshot + * @memberOf Query + * @instance + * @see snapshot http://docs.mongodb.org/manual/reference/operator/snapshot/ + * @return {Query} this + * @api public + */ + +/** + * Sets query hints. + * + * ####Example + * + * query.hint({ indexA: 1, indexB: -1}) + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @method hint + * @memberOf Query + * @instance + * @param {Object} val a hint object + * @return {Query} this + * @see $hint http://docs.mongodb.org/manual/reference/operator/hint/ + * @api public + */ + +/** + * Get/set the current projection (AKA fields). Pass `null` to remove the + * current projection. + * + * Unlike `projection()`, the `select()` function modifies the current + * projection in place. This function overwrites the existing projection. + * + * ####Example: + * + * const q = Model.find(); + * q.projection(); // null + * + * q.select('a b'); + * q.projection(); // { a: 1, b: 1 } + * + * q.projection({ c: 1 }); + * q.projection(); // { c: 1 } + * + * q.projection(null); + * q.projection(); // null + * + * + * @method projection + * @memberOf Query + * @instance + * @param {Object|null} arg + * @return {Object} the current projection + * @api public + */ + +Query.prototype.projection = function(arg) { + if (arguments.length === 0) { + return this._fields; + } + + this._fields = {}; + this._userProvidedFields = {}; + this.select(arg); + return this._fields; +}; + +/** + * Specifies which document fields to include or exclude (also known as the query "projection") + * + * When using string syntax, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. Lastly, if a path is prefixed with `+`, it forces inclusion of the path, which is useful for paths excluded at the [schema level](/docs/api.html#schematype_SchemaType-select). + * + * A projection _must_ be either inclusive or exclusive. In other words, you must + * either list the fields to include (which excludes all others), or list the fields + * to exclude (which implies all other fields are included). The [`_id` field is the only exception because MongoDB includes it by default](https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/#suppress-id-field). + * + * ####Example + * + * // include a and b, exclude other fields + * query.select('a b'); + * // Equivalent syntaxes: + * query.select(['a', 'b']); + * query.select({ a: 1, b: 1 }); + * + * // exclude c and d, include other fields + * query.select('-c -d'); + * + * // Use `+` to override schema-level `select: false` without making the + * // projection inclusive. + * const schema = new Schema({ + * foo: { type: String, select: false }, + * bar: String + * }); + * // ... + * query.select('+foo'); // Override foo's `select: false` without excluding `bar` + * + * // or you may use object notation, useful when + * // you have keys already prefixed with a "-" + * query.select({ a: 1, b: 1 }); + * query.select({ c: 0, d: 0 }); + * + * + * @method select + * @memberOf Query + * @instance + * @param {Object|String|Array<String>} arg + * @return {Query} this + * @see SchemaType + * @api public + */ + +Query.prototype.select = function select() { + let arg = arguments[0]; + if (!arg) return this; + let i; + + if (arguments.length !== 1) { + throw new Error('Invalid select: select only takes 1 argument'); + } + + this._validate('select'); + + const fields = this._fields || (this._fields = {}); + const userProvidedFields = this._userProvidedFields || (this._userProvidedFields = {}); + + arg = parseProjection(arg); + + if (utils.isObject(arg)) { + const keys = Object.keys(arg); + for (i = 0; i < keys.length; ++i) { + fields[keys[i]] = arg[keys[i]]; + userProvidedFields[keys[i]] = arg[keys[i]]; + } + return this; + } + + throw new TypeError('Invalid select() argument. Must be string or object.'); +}; + +/** + * _DEPRECATED_ Sets the slaveOk option. + * + * **Deprecated** in MongoDB 2.2 in favor of [read preferences](#query_Query-read). + * + * ####Example: + * + * query.slaveOk() // true + * query.slaveOk(true) + * query.slaveOk(false) + * + * @method slaveOk + * @memberOf Query + * @instance + * @deprecated use read() preferences instead if on mongodb >= 2.2 + * @param {Boolean} v defaults to true + * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference + * @see slaveOk http://docs.mongodb.org/manual/reference/method/rs.slaveOk/ + * @see read() #query_Query-read + * @return {Query} this + * @api public + */ + +/** + * Determines the MongoDB nodes from which to read. + * + * ####Preferences: + * + * primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags. + * secondary Read from secondary if available, otherwise error. + * primaryPreferred Read from primary if available, otherwise a secondary. + * secondaryPreferred Read from a secondary if available, otherwise read from the primary. + * nearest All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection. + * + * Aliases + * + * p primary + * pp primaryPreferred + * s secondary + * sp secondaryPreferred + * n nearest + * + * ####Example: + * + * new Query().read('primary') + * new Query().read('p') // same as primary + * + * new Query().read('primaryPreferred') + * new Query().read('pp') // same as primaryPreferred + * + * new Query().read('secondary') + * new Query().read('s') // same as secondary + * + * new Query().read('secondaryPreferred') + * new Query().read('sp') // same as secondaryPreferred + * + * new Query().read('nearest') + * new Query().read('n') // same as nearest + * + * // read from secondaries with matching tags + * new Query().read('s', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }]) + * + * Read more about how to use read preferrences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences). + * + * @method read + * @memberOf Query + * @instance + * @param {String} pref one of the listed preference options or aliases + * @param {Array} [tags] optional tags for this query + * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference + * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences + * @return {Query} this + * @api public + */ + +Query.prototype.read = function read(pref, tags) { + // first cast into a ReadPreference object to support tags + const read = new ReadPreference(pref, tags); + this.options.readPreference = read; + return this; +}; + +/** + * Sets the [MongoDB session](https://docs.mongodb.com/manual/reference/server-sessions/) + * associated with this query. Sessions are how you mark a query as part of a + * [transaction](/docs/transactions.html). + * + * Calling `session(null)` removes the session from this query. + * + * ####Example: + * + * const s = await mongoose.startSession(); + * await mongoose.model('Person').findOne({ name: 'Axl Rose' }).session(s); + * + * @method session + * @memberOf Query + * @instance + * @param {ClientSession} [session] from `await conn.startSession()` + * @see Connection.prototype.startSession() /docs/api.html#connection_Connection-startSession + * @see mongoose.startSession() /docs/api.html#mongoose_Mongoose-startSession + * @return {Query} this + * @api public + */ + +Query.prototype.session = function session(v) { + if (v == null) { + delete this.options.session; + } + this.options.session = v; + return this; +}; + +/** + * Sets the specified number of `mongod` servers, or tag set of `mongod` servers, + * that must acknowledge this write before this write is considered successful. + * This option is only valid for operations that write to the database: + * + * - `deleteOne()` + * - `deleteMany()` + * - `findOneAndDelete()` + * - `findOneAndReplace()` + * - `findOneAndUpdate()` + * - `remove()` + * - `update()` + * - `updateOne()` + * - `updateMany()` + * + * Defaults to the schema's [`writeConcern.w` option](/docs/guide.html#writeConcern) + * + * ####Example: + * + * // The 'majority' option means the `deleteOne()` promise won't resolve + * // until the `deleteOne()` has propagated to the majority of the replica set + * await mongoose.model('Person'). + * deleteOne({ name: 'Ned Stark' }). + * w('majority'); + * + * @method w + * @memberOf Query + * @instance + * @param {String|number} val 0 for fire-and-forget, 1 for acknowledged by one server, 'majority' for majority of the replica set, or [any of the more advanced options](https://docs.mongodb.com/manual/reference/write-concern/#w-option). + * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#w-option + * @return {Query} this + * @api public + */ + +Query.prototype.w = function w(val) { + if (val == null) { + delete this.options.w; + } + this.options.w = val; + return this; +}; + +/** + * Requests acknowledgement that this operation has been persisted to MongoDB's + * on-disk journal. + * This option is only valid for operations that write to the database: + * + * - `deleteOne()` + * - `deleteMany()` + * - `findOneAndDelete()` + * - `findOneAndReplace()` + * - `findOneAndUpdate()` + * - `remove()` + * - `update()` + * - `updateOne()` + * - `updateMany()` + * + * Defaults to the schema's [`writeConcern.j` option](/docs/guide.html#writeConcern) + * + * ####Example: + * + * await mongoose.model('Person').deleteOne({ name: 'Ned Stark' }).j(true); + * + * @method j + * @memberOf Query + * @instance + * @param {boolean} val + * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#j-option + * @return {Query} this + * @api public + */ + +Query.prototype.j = function j(val) { + if (val == null) { + delete this.options.j; + } + this.options.j = val; + return this; +}; + +/** + * If [`w > 1`](/docs/api.html#query_Query-w), the maximum amount of time to + * wait for this write to propagate through the replica set before this + * operation fails. The default is `0`, which means no timeout. + * + * This option is only valid for operations that write to the database: + * + * - `deleteOne()` + * - `deleteMany()` + * - `findOneAndDelete()` + * - `findOneAndReplace()` + * - `findOneAndUpdate()` + * - `remove()` + * - `update()` + * - `updateOne()` + * - `updateMany()` + * + * Defaults to the schema's [`writeConcern.wtimeout` option](/docs/guide.html#writeConcern) + * + * ####Example: + * + * // The `deleteOne()` promise won't resolve until this `deleteOne()` has + * // propagated to at least `w = 2` members of the replica set. If it takes + * // longer than 1 second, this `deleteOne()` will fail. + * await mongoose.model('Person'). + * deleteOne({ name: 'Ned Stark' }). + * w(2). + * wtimeout(1000); + * + * @method wtimeout + * @memberOf Query + * @instance + * @param {number} ms number of milliseconds to wait + * @see mongodb https://docs.mongodb.com/manual/reference/write-concern/#wtimeout + * @return {Query} this + * @api public + */ + +Query.prototype.wtimeout = function wtimeout(ms) { + if (ms == null) { + delete this.options.wtimeout; + } + this.options.wtimeout = ms; + return this; +}; + +/** + * Sets the readConcern option for the query. + * + * ####Example: + * + * new Query().readConcern('local') + * new Query().readConcern('l') // same as local + * + * new Query().readConcern('available') + * new Query().readConcern('a') // same as available + * + * new Query().readConcern('majority') + * new Query().readConcern('m') // same as majority + * + * new Query().readConcern('linearizable') + * new Query().readConcern('lz') // same as linearizable + * + * new Query().readConcern('snapshot') + * new Query().readConcern('s') // same as snapshot + * + * + * ####Read Concern Level: + * + * local MongoDB 3.2+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back). + * available MongoDB 3.6+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back). + * majority MongoDB 3.2+ The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure. + * linearizable MongoDB 3.4+ The query returns data that reflects all successful majority-acknowledged writes that completed prior to the start of the read operation. The query may wait for concurrently executing writes to propagate to a majority of replica set members before returning results. + * snapshot MongoDB 4.0+ Only available for operations within multi-document transactions. Upon transaction commit with write concern "majority", the transaction operations are guaranteed to have read from a snapshot of majority-committed data. + * + * Aliases + * + * l local + * a available + * m majority + * lz linearizable + * s snapshot + * + * Read more about how to use read concern [here](https://docs.mongodb.com/manual/reference/read-concern/). + * + * @memberOf Query + * @method readConcern + * @param {String} level one of the listed read concern level or their aliases + * @see mongodb https://docs.mongodb.com/manual/reference/read-concern/ + * @return {Query} this + * @api public + */ + +/** + * Gets query options. + * + * ####Example: + * + * const query = new Query(); + * query.limit(10); + * query.setOptions({ maxTimeMS: 1000 }) + * query.getOptions(); // { limit: 10, maxTimeMS: 1000 } + * + * @return {Object} the options + * @api public + */ + +Query.prototype.getOptions = function() { + return this.options; +}; + +/** + * Sets query options. Some options only make sense for certain operations. + * + * ####Options: + * + * The following options are only for `find()`: + * + * - [tailable](http://www.mongodb.org/display/DOCS/Tailable+Cursors) + * - [sort](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsort(\)%7D%7D) + * - [limit](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D) + * - [skip](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D) + * - [maxscan](https://docs.mongodb.org/v3.2/reference/operator/meta/maxScan/#metaOp._S_maxScan) + * - [batchSize](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D) + * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment) + * - [snapshot](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D) + * - [readPreference](http://docs.mongodb.org/manual/applications/replication/#read-preference) + * - [hint](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint) + * + * The following options are only for write operations: `update()`, `updateOne()`, `updateMany()`, `replaceOne()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`: + * + * - [upsert](https://docs.mongodb.com/manual/reference/method/db.collection.update/) + * - [writeConcern](https://docs.mongodb.com/manual/reference/method/db.collection.update/) + * - [timestamps](https://mongoosejs.com/docs/guide.html#timestamps): If `timestamps` is set in the schema, set this option to `false` to skip timestamps for that particular update. Has no effect if `timestamps` is not enabled in the schema options. + * - omitUndefined: delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * - overwriteDiscriminatorKey: allow setting the discriminator key in the update. Will use the correct discriminator schema if the update changes the discriminator key. + * - overwrite: replace the entire document + * + * The following options are only for `find()`, `findOne()`, `findById()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`: + * + * - [lean](./api.html#query_Query-lean) + * - [populate](/docs/populate.html) + * - [projection](/docs/api/query.html#query_Query-projection) + * + * The following options are only for all operations **except** `update()`, `updateOne()`, `updateMany()`, `remove()`, `deleteOne()`, and `deleteMany()`: + * + * - [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) + * + * The following options are for `findOneAndUpdate()` and `findOneAndRemove()` + * + * - [useFindAndModify](/docs/deprecations.html#findandmodify) + * - rawResult + * + * The following options are for all operations: + * + * - [collation](https://docs.mongodb.com/manual/reference/collation/) + * - [session](https://docs.mongodb.com/manual/reference/server-sessions/) + * - [explain](https://docs.mongodb.com/manual/reference/method/cursor.explain/) + * + * @param {Object} options + * @return {Query} this + * @api public + */ + +Query.prototype.setOptions = function(options, overwrite) { + // overwrite is only for internal use + if (overwrite) { + // ensure that _mongooseOptions & options are two different objects + this._mongooseOptions = (options && utils.clone(options)) || {}; + this.options = options || {}; + + if ('populate' in options) { + this.populate(this._mongooseOptions); + } + return this; + } + + if (options == null) { + return this; + } + if (typeof options !== 'object') { + throw new Error('Options must be an object, got "' + options + '"'); + } + + if (Array.isArray(options.populate)) { + const populate = options.populate; + delete options.populate; + const _numPopulate = populate.length; + for (let i = 0; i < _numPopulate; ++i) { + this.populate(populate[i]); + } + } + + if ('useFindAndModify' in options) { + this._mongooseOptions.useFindAndModify = options.useFindAndModify; + delete options.useFindAndModify; + } + if ('omitUndefined' in options) { + this._mongooseOptions.omitUndefined = options.omitUndefined; + delete options.omitUndefined; + } + if ('setDefaultsOnInsert' in options) { + this._mongooseOptions.setDefaultsOnInsert = options.setDefaultsOnInsert; + delete options.setDefaultsOnInsert; + } + if ('overwriteDiscriminatorKey' in options) { + this._mongooseOptions.overwriteDiscriminatorKey = options.overwriteDiscriminatorKey; + delete options.overwriteDiscriminatorKey; + } + + return Query.base.setOptions.call(this, options); +}; + +/** + * Sets the [`explain` option](https://docs.mongodb.com/manual/reference/method/cursor.explain/), + * which makes this query return detailed execution stats instead of the actual + * query result. This method is useful for determining what index your queries + * use. + * + * Calling `query.explain(v)` is equivalent to `query.setOption({ explain: v })` + * + * ####Example: + * + * const query = new Query(); + * const res = await query.find({ a: 1 }).explain('queryPlanner'); + * console.log(res); + * + * @param {String} [verbose] The verbosity mode. Either 'queryPlanner', 'executionStats', or 'allPlansExecution'. The default is 'queryPlanner' + * @return {Query} this + * @api public + */ + +Query.prototype.explain = function(verbose) { + if (arguments.length === 0) { + this.options.explain = true; + return this; + } + this.options.explain = verbose; + return this; +}; + +/** + * Sets the [maxTimeMS](https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS/) + * option. This will tell the MongoDB server to abort if the query or write op + * has been running for more than `ms` milliseconds. + * + * Calling `query.maxTimeMS(v)` is equivalent to `query.setOption({ maxTimeMS: v })` + * + * ####Example: + * + * const query = new Query(); + * // Throws an error 'operation exceeded time limit' as long as there's + * // >= 1 doc in the queried collection + * const res = await query.find({ $where: 'sleep(1000) || true' }).maxTimeMS(100); + * + * @param {Number} [ms] The number of milliseconds + * @return {Query} this + * @api public + */ + +Query.prototype.maxTimeMS = function(ms) { + this.options.maxTimeMS = ms; + return this; +}; + +/** + * Returns the current query filter (also known as conditions) as a POJO. + * + * ####Example: + * + * const query = new Query(); + * query.find({ a: 1 }).where('b').gt(2); + * query.getFilter(); // { a: 1, b: { $gt: 2 } } + * + * @return {Object} current query filter + * @api public + */ + +Query.prototype.getFilter = function() { + return this._conditions; +}; + +/** + * Returns the current query filter. Equivalent to `getFilter()`. + * + * You should use `getFilter()` instead of `getQuery()` where possible. `getQuery()` + * will likely be deprecated in a future release. + * + * ####Example: + * + * const query = new Query(); + * query.find({ a: 1 }).where('b').gt(2); + * query.getQuery(); // { a: 1, b: { $gt: 2 } } + * + * @return {Object} current query filter + * @api public + */ + +Query.prototype.getQuery = function() { + return this._conditions; +}; + +/** + * Sets the query conditions to the provided JSON object. + * + * ####Example: + * + * const query = new Query(); + * query.find({ a: 1 }) + * query.setQuery({ a: 2 }); + * query.getQuery(); // { a: 2 } + * + * @param {Object} new query conditions + * @return {undefined} + * @api public + */ + +Query.prototype.setQuery = function(val) { + this._conditions = val; +}; + +/** + * Returns the current update operations as a JSON object. + * + * ####Example: + * + * const query = new Query(); + * query.update({}, { $set: { a: 5 } }); + * query.getUpdate(); // { $set: { a: 5 } } + * + * @return {Object} current update operations + * @api public + */ + +Query.prototype.getUpdate = function() { + return this._update; +}; + +/** + * Sets the current update operation to new value. + * + * ####Example: + * + * const query = new Query(); + * query.update({}, { $set: { a: 5 } }); + * query.setUpdate({ $set: { b: 6 } }); + * query.getUpdate(); // { $set: { b: 6 } } + * + * @param {Object} new update operation + * @return {undefined} + * @api public + */ + +Query.prototype.setUpdate = function(val) { + this._update = val; +}; + +/** + * Returns fields selection for this query. + * + * @method _fieldsForExec + * @return {Object} + * @api private + * @receiver Query + */ + +Query.prototype._fieldsForExec = function() { + return utils.clone(this._fields); +}; + + +/** + * Return an update document with corrected `$set` operations. + * + * @method _updateForExec + * @api private + * @receiver Query + */ + +Query.prototype._updateForExec = function() { + const update = utils.clone(this._update, { + transform: false, + depopulate: true + }); + const ops = Object.keys(update); + let i = ops.length; + const ret = {}; + + while (i--) { + const op = ops[i]; + + if (this.options.overwrite) { + ret[op] = update[op]; + continue; + } + + if ('$' !== op[0]) { + // fix up $set sugar + if (!ret.$set) { + if (update.$set) { + ret.$set = update.$set; + } else { + ret.$set = {}; + } + } + ret.$set[op] = update[op]; + ops.splice(i, 1); + if (!~ops.indexOf('$set')) ops.push('$set'); + } else if ('$set' === op) { + if (!ret.$set) { + ret[op] = update[op]; + } + } else { + ret[op] = update[op]; + } + } + + return ret; +}; + +/** + * Makes sure _path is set. + * + * @method _ensurePath + * @param {String} method + * @api private + * @receiver Query + */ + +/** + * Determines if `conds` can be merged using `mquery().merge()` + * + * @method canMerge + * @memberOf Query + * @instance + * @param {Object} conds + * @return {Boolean} + * @api private + */ + +/** + * Returns default options for this query. + * + * @param {Model} model + * @api private + */ + +Query.prototype._optionsForExec = function(model) { + const options = utils.clone(this.options); + + delete options.populate; + model = model || this.model; + + if (!model) { + return options; + } + + const safe = get(model, 'schema.options.safe', null); + if (!('safe' in options) && safe != null) { + setSafe(options, safe); + } + + // Apply schema-level `writeConcern` option + applyWriteConcern(model.schema, options); + + const readPreference = get(model, 'schema.options.read'); + if (!('readPreference' in options) && readPreference) { + options.readPreference = readPreference; + } + + if (options.upsert !== void 0) { + options.upsert = !!options.upsert; + } + + return options; +}; + +/*! + * ignore + */ + +const safeDeprecationWarning = 'Mongoose: the `safe` option is deprecated. ' + + 'Use write concerns instead: http://bit.ly/mongoose-w'; + +const setSafe = util.deprecate(function setSafe(options, safe) { + options.safe = safe; +}, safeDeprecationWarning); + +/** + * Sets the lean option. + * + * Documents returned from queries with the `lean` option enabled are plain + * javascript objects, not [Mongoose Documents](/api/document.html). They have no + * `save` method, getters/setters, virtuals, or other Mongoose features. + * + * ####Example: + * + * new Query().lean() // true + * new Query().lean(true) + * new Query().lean(false) + * + * const docs = await Model.find().lean(); + * docs[0] instanceof mongoose.Document; // false + * + * [Lean is great for high-performance, read-only cases](/docs/tutorials/lean.html), + * especially when combined + * with [cursors](/docs/queries.html#streaming). + * + * If you need virtuals, getters/setters, or defaults with `lean()`, you need + * to use a plugin. See: + * + * - [mongoose-lean-virtuals](https://plugins.mongoosejs.io/plugins/lean-virtuals) + * - [mongoose-lean-getters](https://plugins.mongoosejs.io/plugins/lean-getters) + * - [mongoose-lean-defaults](https://www.npmjs.com/package/mongoose-lean-defaults) + * + * @param {Boolean|Object} bool defaults to true + * @return {Query} this + * @api public + */ + +Query.prototype.lean = function(v) { + this._mongooseOptions.lean = arguments.length ? v : true; + return this; +}; + +/** + * Adds a `$set` to this query's update without changing the operation. + * This is useful for query middleware so you can add an update regardless + * of whether you use `updateOne()`, `updateMany()`, `findOneAndUpdate()`, etc. + * + * ####Example: + * + * // Updates `{ $set: { updatedAt: new Date() } }` + * new Query().updateOne({}, {}).set('updatedAt', new Date()); + * new Query().updateMany({}, {}).set({ updatedAt: new Date() }); + * + * @param {String|Object} path path or object of key/value pairs to set + * @param {Any} [val] the value to set + * @return {Query} this + * @api public + */ + +Query.prototype.set = function(path, val) { + if (typeof path === 'object') { + const keys = Object.keys(path); + for (const key of keys) { + this.set(key, path[key]); + } + return this; + } + + this._update = this._update || {}; + this._update.$set = this._update.$set || {}; + this._update.$set[path] = val; + return this; +}; + +/** + * For update operations, returns the value of a path in the update's `$set`. + * Useful for writing getters/setters that can work with both update operations + * and `save()`. + * + * ####Example: + * + * const query = Model.updateOne({}, { $set: { name: 'Jean-Luc Picard' } }); + * query.get('name'); // 'Jean-Luc Picard' + * + * @param {String|Object} path path or object of key/value pairs to get + * @return {Query} this + * @api public + */ + +Query.prototype.get = function get(path) { + const update = this._update; + if (update == null) { + return void 0; + } + const $set = update.$set; + if ($set == null) { + return update[path]; + } + + if (utils.hasUserDefinedProperty(update, path)) { + return update[path]; + } + if (utils.hasUserDefinedProperty($set, path)) { + return $set[path]; + } + + return void 0; +}; + +/** + * Gets/sets the error flag on this query. If this flag is not null or + * undefined, the `exec()` promise will reject without executing. + * + * ####Example: + * + * Query().error(); // Get current error value + * Query().error(null); // Unset the current error + * Query().error(new Error('test')); // `exec()` will resolve with test + * Schema.pre('find', function() { + * if (!this.getQuery().userId) { + * this.error(new Error('Not allowed to query without setting userId')); + * } + * }); + * + * Note that query casting runs **after** hooks, so cast errors will override + * custom errors. + * + * ####Example: + * const TestSchema = new Schema({ num: Number }); + * const TestModel = db.model('Test', TestSchema); + * TestModel.find({ num: 'not a number' }).error(new Error('woops')).exec(function(error) { + * // `error` will be a cast error because `num` failed to cast + * }); + * + * @param {Error|null} err if set, `exec()` will fail fast before sending the query to MongoDB + * @return {Query} this + * @api public + */ + +Query.prototype.error = function error(err) { + if (arguments.length === 0) { + return this._error; + } + + this._error = err; + return this; +}; + +/*! + * ignore + */ + +Query.prototype._unsetCastError = function _unsetCastError() { + if (this._error != null && !(this._error instanceof CastError)) { + return; + } + return this.error(null); +}; + +/** + * Getter/setter around the current mongoose-specific options for this query + * Below are the current Mongoose-specific options. + * + * - `populate`: an array representing what paths will be populated. Should have one entry for each call to [`Query.prototype.populate()`](/docs/api.html#query_Query-populate) + * - `lean`: if truthy, Mongoose will not [hydrate](/docs/api.html#model_Model.hydrate) any documents that are returned from this query. See [`Query.prototype.lean()`](/docs/api.html#query_Query-lean) for more information. + * - `strict`: controls how Mongoose handles keys that aren't in the schema for updates. This option is `true` by default, which means Mongoose will silently strip any paths in the update that aren't in the schema. See the [`strict` mode docs](/docs/guide.html#strict) for more information. + * - `strictQuery`: controls how Mongoose handles keys that aren't in the schema for the query `filter`. This option is `false` by default for backwards compatibility, which means Mongoose will allow `Model.find({ foo: 'bar' })` even if `foo` is not in the schema. See the [`strictQuery` docs](/docs/guide.html#strictQuery) for more information. + * - `useFindAndModify`: used to work around the [`findAndModify()` deprecation warning](/docs/deprecations.html#findandmodify) + * - `omitUndefined`: delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * - `nearSphere`: use `$nearSphere` instead of `near()`. See the [`Query.prototype.nearSphere()` docs](/docs/api.html#query_Query-nearSphere) + * + * Mongoose maintains a separate object for internal options because + * Mongoose sends `Query.prototype.options` to the MongoDB server, and the + * above options are not relevant for the MongoDB server. + * + * @param {Object} options if specified, overwrites the current options + * @return {Object} the options + * @api public + */ + +Query.prototype.mongooseOptions = function(v) { + if (arguments.length > 0) { + this._mongooseOptions = v; + } + return this._mongooseOptions; +}; + +/*! + * ignore + */ + +Query.prototype._castConditions = function() { + try { + this.cast(this.model); + this._unsetCastError(); + } catch (err) { + this.error(err); + } +}; + +/*! + * ignore + */ + +function _castArrayFilters(query) { + try { + castArrayFilters(query); + } catch (err) { + query.error(err); + } +} + +/** + * Thunk around find() + * + * @param {Function} [callback] + * @return {Query} this + * @api private + */ +Query.prototype._find = wrapThunk(function(callback) { + this._castConditions(); + + if (this.error() != null) { + callback(this.error()); + return null; + } + + callback = _wrapThunkCallback(this, callback); + + this._applyPaths(); + this._fields = this._castFields(this._fields); + + const fields = this._fieldsForExec(); + const mongooseOptions = this._mongooseOptions; + const _this = this; + const userProvidedFields = _this._userProvidedFields || {}; + + applyGlobalMaxTimeMS(this.options, this.model); + + // Separate options to pass down to `completeMany()` in case we need to + // set a session on the document + const completeManyOptions = Object.assign({}, { + session: get(this, 'options.session', null) + }); + + const cb = (err, docs) => { + if (err) { + return callback(err); + } + + if (docs.length === 0) { + return callback(null, docs); + } + if (this.options.explain) { + return callback(null, docs); + } + + if (!mongooseOptions.populate) { + return mongooseOptions.lean ? + callback(null, docs) : + completeMany(_this.model, docs, fields, userProvidedFields, completeManyOptions, callback); + } + + const pop = helpers.preparePopulationOptionsMQ(_this, mongooseOptions); + completeManyOptions.populated = pop; + _this.model.populate(docs, pop, function(err, docs) { + if (err) return callback(err); + return mongooseOptions.lean ? + callback(null, docs) : + completeMany(_this.model, docs, fields, userProvidedFields, completeManyOptions, callback); + }); + }; + + const options = this._optionsForExec(); + options.projection = this._fieldsForExec(); + const filter = this._conditions; + + this._collection.find(filter, options, cb); + return null; +}); + +/** + * Find all documents that match `selector`. The result will be an array of documents. + * + * If there are too many documents in the result to fit in memory, use + * [`Query.prototype.cursor()`](api.html#query_Query-cursor) + * + * ####Example + * + * // Using async/await + * const arr = await Movie.find({ year: { $gte: 1980, $lte: 1989 } }); + * + * // Using callbacks + * Movie.find({ year: { $gte: 1980, $lte: 1989 } }, function(err, arr) {}); + * + * @param {Object|ObjectId} [filter] mongodb selector. If not specified, returns all documents. + * @param {Function} [callback] + * @return {Query} this + * @api public + */ + +Query.prototype.find = function(conditions, callback) { + this.op = 'find'; + + if (typeof conditions === 'function') { + callback = conditions; + conditions = {}; + } + + conditions = utils.toObject(conditions); + + if (mquery.canMerge(conditions)) { + this.merge(conditions); + + prepareDiscriminatorCriteria(this); + } else if (conditions != null) { + this.error(new ObjectParameterError(conditions, 'filter', 'find')); + } + + // if we don't have a callback, then just return the query object + if (!callback) { + return Query.base.find.call(this); + } + + this.exec(callback); + + return this; +}; + +/** + * Merges another Query or conditions object into this one. + * + * When a Query is passed, conditions, field selection and options are merged. + * + * @param {Query|Object} source + * @return {Query} this + */ + +Query.prototype.merge = function(source) { + if (!source) { + return this; + } + + const opts = { overwrite: true }; + + if (source instanceof Query) { + // if source has a feature, apply it to ourselves + + if (source._conditions) { + utils.merge(this._conditions, source._conditions, opts); + } + + if (source._fields) { + this._fields || (this._fields = {}); + utils.merge(this._fields, source._fields, opts); + } + + if (source.options) { + this.options || (this.options = {}); + utils.merge(this.options, source.options, opts); + } + + if (source._update) { + this._update || (this._update = {}); + utils.mergeClone(this._update, source._update); + } + + if (source._distinct) { + this._distinct = source._distinct; + } + + utils.merge(this._mongooseOptions, source._mongooseOptions); + + return this; + } + + // plain object + utils.merge(this._conditions, source, opts); + + return this; +}; + +/** + * Adds a collation to this op (MongoDB 3.4 and up) + * + * @param {Object} value + * @return {Query} this + * @see MongoDB docs https://docs.mongodb.com/manual/reference/method/cursor.collation/#cursor.collation + * @api public + */ + +Query.prototype.collation = function(value) { + if (this.options == null) { + this.options = {}; + } + this.options.collation = value; + return this; +}; + +/** + * Hydrate a single doc from `findOne()`, `findOneAndUpdate()`, etc. + * + * @api private + */ + +Query.prototype._completeOne = function(doc, res, callback) { + if (!doc && !this.options.rawResult) { + return callback(null, null); + } + + const model = this.model; + const projection = utils.clone(this._fields); + const userProvidedFields = this._userProvidedFields || {}; + // `populate`, `lean` + const mongooseOptions = this._mongooseOptions; + // `rawResult` + const options = this.options; + + if (options.explain) { + return callback(null, doc); + } + + if (!mongooseOptions.populate) { + return mongooseOptions.lean ? + _completeOneLean(doc, res, options, callback) : + completeOne(model, doc, res, options, projection, userProvidedFields, + null, callback); + } + + const pop = helpers.preparePopulationOptionsMQ(this, this._mongooseOptions); + model.populate(doc, pop, (err, doc) => { + if (err) { + return callback(err); + } + return mongooseOptions.lean ? + _completeOneLean(doc, res, options, callback) : + completeOne(model, doc, res, options, projection, userProvidedFields, + pop, callback); + }); +}; + +/** + * Thunk around findOne() + * + * @param {Function} [callback] + * @see findOne http://docs.mongodb.org/manual/reference/method/db.collection.findOne/ + * @api private + */ + +Query.prototype._findOne = wrapThunk(function(callback) { + this._castConditions(); + + if (this.error()) { + callback(this.error()); + return null; + } + + this._applyPaths(); + this._fields = this._castFields(this._fields); + + applyGlobalMaxTimeMS(this.options, this.model); + + // don't pass in the conditions because we already merged them in + Query.base.findOne.call(this, {}, (err, doc) => { + if (err) { + callback(err); + return null; + } + + this._completeOne(doc, null, _wrapThunkCallback(this, callback)); + }); +}); + +/** + * Declares the query a findOne operation. When executed, the first found document is passed to the callback. + * + * Passing a `callback` executes the query. The result of the query is a single document. + * + * * *Note:* `conditions` is optional, and if `conditions` is null or undefined, + * mongoose will send an empty `findOne` command to MongoDB, which will return + * an arbitrary document. If you're querying by `_id`, use `Model.findById()` + * instead. + * + * This function triggers the following middleware. + * + * - `findOne()` + * + * ####Example + * + * const query = Kitten.where({ color: 'white' }); + * query.findOne(function (err, kitten) { + * if (err) return handleError(err); + * if (kitten) { + * // doc may be null if no document matched + * } + * }); + * + * @param {Object} [filter] mongodb selector + * @param {Object} [projection] optional fields to return + * @param {Object} [options] see [`setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Function} [callback] optional params are (error, document) + * @return {Query} this + * @see findOne http://docs.mongodb.org/manual/reference/method/db.collection.findOne/ + * @see Query.select #query_Query-select + * @api public + */ + +Query.prototype.findOne = function(conditions, projection, options, callback) { + this.op = 'findOne'; + + if (typeof conditions === 'function') { + callback = conditions; + conditions = null; + projection = null; + options = null; + } else if (typeof projection === 'function') { + callback = projection; + options = null; + projection = null; + } else if (typeof options === 'function') { + callback = options; + options = null; + } + + // make sure we don't send in the whole Document to merge() + conditions = utils.toObject(conditions); + + if (options) { + this.setOptions(options); + } + + if (projection) { + this.select(projection); + } + + if (mquery.canMerge(conditions)) { + this.merge(conditions); + + prepareDiscriminatorCriteria(this); + } else if (conditions != null) { + this.error(new ObjectParameterError(conditions, 'filter', 'findOne')); + } + + if (!callback) { + // already merged in the conditions, don't need to send them in. + return Query.base.findOne.call(this); + } + + this.exec(callback); + + return this; +}; + +/** + * Thunk around count() + * + * @param {Function} [callback] + * @see count http://docs.mongodb.org/manual/reference/method/db.collection.count/ + * @api private + */ + +Query.prototype._count = wrapThunk(function(callback) { + try { + this.cast(this.model); + } catch (err) { + this.error(err); + } + + if (this.error()) { + return callback(this.error()); + } + + const conds = this._conditions; + const options = this._optionsForExec(); + + this._collection.count(conds, options, utils.tick(callback)); +}); + +/** + * Thunk around countDocuments() + * + * @param {Function} [callback] + * @see countDocuments http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#countDocuments + * @api private + */ + +Query.prototype._countDocuments = wrapThunk(function(callback) { + try { + this.cast(this.model); + } catch (err) { + this.error(err); + } + + if (this.error()) { + return callback(this.error()); + } + + const conds = this._conditions; + const options = this._optionsForExec(); + + this._collection.collection.countDocuments(conds, options, utils.tick(callback)); +}); + +/** + * Thunk around estimatedDocumentCount() + * + * @param {Function} [callback] + * @see estimatedDocumentCount http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#estimatedDocumentCount + * @api private + */ + +Query.prototype._estimatedDocumentCount = wrapThunk(function(callback) { + if (this.error()) { + return callback(this.error()); + } + + const options = this._optionsForExec(); + + this._collection.collection.estimatedDocumentCount(options, utils.tick(callback)); +}); + +/** + * Specifies this query as a `count` query. + * + * This method is deprecated. If you want to count the number of documents in + * a collection, e.g. `count({})`, use the [`estimatedDocumentCount()` function](/docs/api.html#query_Query-estimatedDocumentCount) + * instead. Otherwise, use the [`countDocuments()`](/docs/api.html#query_Query-countDocuments) function instead. + * + * Passing a `callback` executes the query. + * + * This function triggers the following middleware. + * + * - `count()` + * + * ####Example: + * + * const countQuery = model.where({ 'color': 'black' }).count(); + * + * query.count({ color: 'black' }).count(callback) + * + * query.count({ color: 'black' }, callback) + * + * query.where('color', 'black').count(function (err, count) { + * if (err) return handleError(err); + * console.log('there are %d kittens', count); + * }) + * + * @deprecated + * @param {Object} [filter] count documents that match this object + * @param {Function} [callback] optional params are (error, count) + * @return {Query} this + * @see count http://docs.mongodb.org/manual/reference/method/db.collection.count/ + * @api public + */ + +Query.prototype.count = function(filter, callback) { + this.op = 'count'; + if (typeof filter === 'function') { + callback = filter; + filter = undefined; + } + + filter = utils.toObject(filter); + + if (mquery.canMerge(filter)) { + this.merge(filter); + } + + if (!callback) { + return this; + } + + this.exec(callback); + + return this; +}; + +/** + * Specifies this query as a `estimatedDocumentCount()` query. Faster than + * using `countDocuments()` for large collections because + * `estimatedDocumentCount()` uses collection metadata rather than scanning + * the entire collection. + * + * `estimatedDocumentCount()` does **not** accept a filter. `Model.find({ foo: bar }).estimatedDocumentCount()` + * is equivalent to `Model.find().estimatedDocumentCount()` + * + * This function triggers the following middleware. + * + * - `estimatedDocumentCount()` + * + * ####Example: + * + * await Model.find().estimatedDocumentCount(); + * + * @param {Object} [options] passed transparently to the [MongoDB driver](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#estimatedDocumentCount) + * @param {Function} [callback] optional params are (error, count) + * @return {Query} this + * @see estimatedDocumentCount http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#estimatedDocumentCount + * @api public + */ + +Query.prototype.estimatedDocumentCount = function(options, callback) { + this.op = 'estimatedDocumentCount'; + if (typeof options === 'function') { + callback = options; + options = undefined; + } + + if (typeof options === 'object' && options != null) { + this.setOptions(options); + } + + if (!callback) { + return this; + } + + this.exec(callback); + + return this; +}; + +/** + * Specifies this query as a `countDocuments()` query. Behaves like `count()`, + * except it always does a full collection scan when passed an empty filter `{}`. + * + * There are also minor differences in how `countDocuments()` handles + * [`$where` and a couple geospatial operators](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#countDocuments). + * versus `count()`. + * + * Passing a `callback` executes the query. + * + * This function triggers the following middleware. + * + * - `countDocuments()` + * + * ####Example: + * + * const countQuery = model.where({ 'color': 'black' }).countDocuments(); + * + * query.countDocuments({ color: 'black' }).count(callback); + * + * query.countDocuments({ color: 'black' }, callback); + * + * query.where('color', 'black').countDocuments(function(err, count) { + * if (err) return handleError(err); + * console.log('there are %d kittens', count); + * }); + * + * The `countDocuments()` function is similar to `count()`, but there are a + * [few operators that `countDocuments()` does not support](https://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#countDocuments). + * Below are the operators that `count()` supports but `countDocuments()` does not, + * and the suggested replacement: + * + * - `$where`: [`$expr`](https://docs.mongodb.com/manual/reference/operator/query/expr/) + * - `$near`: [`$geoWithin`](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/) with [`$center`](https://docs.mongodb.com/manual/reference/operator/query/center/#op._S_center) + * - `$nearSphere`: [`$geoWithin`](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/) with [`$centerSphere`](https://docs.mongodb.com/manual/reference/operator/query/centerSphere/#op._S_centerSphere) + * + * @param {Object} [filter] mongodb selector + * @param {Function} [callback] optional params are (error, count) + * @return {Query} this + * @see countDocuments http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#countDocuments + * @api public + */ + +Query.prototype.countDocuments = function(conditions, callback) { + this.op = 'countDocuments'; + if (typeof conditions === 'function') { + callback = conditions; + conditions = undefined; + } + + conditions = utils.toObject(conditions); + + if (mquery.canMerge(conditions)) { + this.merge(conditions); + } + + if (!callback) { + return this; + } + + this.exec(callback); + + return this; +}; + +/** + * Thunk around distinct() + * + * @param {Function} [callback] + * @see distinct http://docs.mongodb.org/manual/reference/method/db.collection.distinct/ + * @api private + */ + +Query.prototype.__distinct = wrapThunk(function __distinct(callback) { + this._castConditions(); + + if (this.error()) { + callback(this.error()); + return null; + } + + const options = this._optionsForExec(); + + // don't pass in the conditions because we already merged them in + this._collection.collection. + distinct(this._distinct, this._conditions, options, callback); +}); + +/** + * Declares or executes a distinct() operation. + * + * Passing a `callback` executes the query. + * + * This function does not trigger any middleware. + * + * ####Example + * + * distinct(field, conditions, callback) + * distinct(field, conditions) + * distinct(field, callback) + * distinct(field) + * distinct(callback) + * distinct() + * + * @param {String} [field] + * @param {Object|Query} [filter] + * @param {Function} [callback] optional params are (error, arr) + * @return {Query} this + * @see distinct http://docs.mongodb.org/manual/reference/method/db.collection.distinct/ + * @api public + */ + +Query.prototype.distinct = function(field, conditions, callback) { + this.op = 'distinct'; + if (!callback) { + if (typeof conditions === 'function') { + callback = conditions; + conditions = undefined; + } else if (typeof field === 'function') { + callback = field; + field = undefined; + conditions = undefined; + } + } + + conditions = utils.toObject(conditions); + + if (mquery.canMerge(conditions)) { + this.merge(conditions); + + prepareDiscriminatorCriteria(this); + } else if (conditions != null) { + this.error(new ObjectParameterError(conditions, 'filter', 'distinct')); + } + + if (field != null) { + this._distinct = field; + } + + if (callback != null) { + this.exec(callback); + } + + return this; +}; + +/** + * Sets the sort order + * + * If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`. + * + * If a string is passed, it must be a space delimited list of path names. The + * sort order of each path is ascending unless the path name is prefixed with `-` + * which will be treated as descending. + * + * ####Example + * + * // sort by "field" ascending and "test" descending + * query.sort({ field: 'asc', test: -1 }); + * + * // equivalent + * query.sort('field -test'); + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @param {Object|String} arg + * @return {Query} this + * @see cursor.sort http://docs.mongodb.org/manual/reference/method/cursor.sort/ + * @api public + */ + +Query.prototype.sort = function(arg) { + if (arguments.length > 1) { + throw new Error('sort() only takes 1 Argument'); + } + + return Query.base.sort.call(this, arg); +}; + +/** + * Declare and/or execute this query as a remove() operation. `remove()` is + * deprecated, you should use [`deleteOne()`](#query_Query-deleteOne) + * or [`deleteMany()`](#query_Query-deleteMany) instead. + * + * This function does not trigger any middleware + * + * ####Example + * + * Character.remove({ name: /Stark/ }, callback); + * + * This function calls the MongoDB driver's [`Collection#remove()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#remove). + * The returned [promise](https://mongoosejs.com/docs/queries.html) resolves to an + * object that contains 3 properties: + * + * - `ok`: `1` if no errors occurred + * - `deletedCount`: the number of documents deleted + * - `n`: the number of documents deleted. Equal to `deletedCount`. + * + * ####Example + * + * const res = await Character.remove({ name: /Stark/ }); + * // Number of docs deleted + * res.deletedCount; + * + * ####Note + * + * Calling `remove()` creates a [Mongoose query](./queries.html), and a query + * does not execute until you either pass a callback, call [`Query#then()`](#query_Query-then), + * or call [`Query#exec()`](#query_Query-exec). + * + * // not executed + * const query = Character.remove({ name: /Stark/ }); + * + * // executed + * Character.remove({ name: /Stark/ }, callback); + * Character.remove({ name: /Stark/ }).remove(callback); + * + * // executed without a callback + * Character.exec(); + * + * @param {Object|Query} [filter] mongodb selector + * @param {Function} [callback] optional params are (error, mongooseDeleteResult) + * @return {Query} this + * @deprecated + * @see deleteWriteOpResult http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#~deleteWriteOpResult + * @see MongoDB driver remove http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#remove + * @api public + */ + +Query.prototype.remove = function(filter, callback) { + this.op = 'remove'; + if (typeof filter === 'function') { + callback = filter; + filter = null; + } + + filter = utils.toObject(filter); + + if (mquery.canMerge(filter)) { + this.merge(filter); + + prepareDiscriminatorCriteria(this); + } else if (filter != null) { + this.error(new ObjectParameterError(filter, 'filter', 'remove')); + } + + if (!callback) { + return Query.base.remove.call(this); + } + + this.exec(callback); + return this; +}; + +/*! + * ignore + */ + +Query.prototype._remove = wrapThunk(function(callback) { + this._castConditions(); + + if (this.error() != null) { + callback(this.error()); + return this; + } + + callback = _wrapThunkCallback(this, callback); + + return Query.base.remove.call(this, helpers.handleDeleteWriteOpResult(callback)); +}); + +/** + * Declare and/or execute this query as a `deleteOne()` operation. Works like + * remove, except it deletes at most one document regardless of the `single` + * option. + * + * This function triggers `deleteOne` middleware. + * + * ####Example + * + * await Character.deleteOne({ name: 'Eddard Stark' }); + * + * // Using callbacks: + * Character.deleteOne({ name: 'Eddard Stark' }, callback); + * + * This function calls the MongoDB driver's [`Collection#deleteOne()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#deleteOne). + * The returned [promise](https://mongoosejs.com/docs/queries.html) resolves to an + * object that contains 3 properties: + * + * - `ok`: `1` if no errors occurred + * - `deletedCount`: the number of documents deleted + * - `n`: the number of documents deleted. Equal to `deletedCount`. + * + * ####Example + * + * const res = await Character.deleteOne({ name: 'Eddard Stark' }); + * // `1` if MongoDB deleted a doc, `0` if no docs matched the filter `{ name: ... }` + * res.deletedCount; + * + * @param {Object|Query} [filter] mongodb selector + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Function} [callback] optional params are (error, mongooseDeleteResult) + * @return {Query} this + * @see deleteWriteOpResult http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#~deleteWriteOpResult + * @see MongoDB Driver deleteOne http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#deleteOne + * @api public + */ + +Query.prototype.deleteOne = function(filter, options, callback) { + this.op = 'deleteOne'; + if (typeof filter === 'function') { + callback = filter; + filter = null; + options = null; + } else if (typeof options === 'function') { + callback = options; + options = null; + } else { + this.setOptions(options); + } + + filter = utils.toObject(filter); + + if (mquery.canMerge(filter)) { + this.merge(filter); + + prepareDiscriminatorCriteria(this); + } else if (filter != null) { + this.error(new ObjectParameterError(filter, 'filter', 'deleteOne')); + } + + if (!callback) { + return Query.base.deleteOne.call(this); + } + + this.exec.call(this, callback); + + return this; +}; + +/*! + * Internal thunk for `deleteOne()` + */ + +Query.prototype._deleteOne = wrapThunk(function(callback) { + this._castConditions(); + + if (this.error() != null) { + callback(this.error()); + return this; + } + + callback = _wrapThunkCallback(this, callback); + + return Query.base.deleteOne.call(this, helpers.handleDeleteWriteOpResult(callback)); +}); + +/** + * Declare and/or execute this query as a `deleteMany()` operation. Works like + * remove, except it deletes _every_ document that matches `filter` in the + * collection, regardless of the value of `single`. + * + * This function triggers `deleteMany` middleware. + * + * ####Example + * + * await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }); + * + * // Using callbacks: + * Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }, callback); + * + * This function calls the MongoDB driver's [`Collection#deleteMany()` function](http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#deleteMany). + * The returned [promise](https://mongoosejs.com/docs/queries.html) resolves to an + * object that contains 3 properties: + * + * - `ok`: `1` if no errors occurred + * - `deletedCount`: the number of documents deleted + * - `n`: the number of documents deleted. Equal to `deletedCount`. + * + * ####Example + * + * const res = await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }); + * // `0` if no docs matched the filter, number of docs deleted otherwise + * res.deletedCount; + * + * @param {Object|Query} [filter] mongodb selector + * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) + * @param {Function} [callback] optional params are (error, mongooseDeleteResult) + * @return {Query} this + * @see deleteWriteOpResult http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#~deleteWriteOpResult + * @see MongoDB Driver deleteMany http://mongodb.github.io/node-mongodb-native/3.1/api/Collection.html#deleteMany + * @api public + */ + +Query.prototype.deleteMany = function(filter, options, callback) { + this.op = 'deleteMany'; + if (typeof filter === 'function') { + callback = filter; + filter = null; + options = null; + } else if (typeof options === 'function') { + callback = options; + options = null; + } else { + this.setOptions(options); + } + + filter = utils.toObject(filter); + + if (mquery.canMerge(filter)) { + this.merge(filter); + + prepareDiscriminatorCriteria(this); + } else if (filter != null) { + this.error(new ObjectParameterError(filter, 'filter', 'deleteMany')); + } + + if (!callback) { + return Query.base.deleteMany.call(this); + } + + this.exec.call(this, callback); + + return this; +}; + +/*! + * Internal thunk around `deleteMany()` + */ + +Query.prototype._deleteMany = wrapThunk(function(callback) { + this._castConditions(); + + if (this.error() != null) { + callback(this.error()); + return this; + } + + callback = _wrapThunkCallback(this, callback); + + return Query.base.deleteMany.call(this, helpers.handleDeleteWriteOpResult(callback)); +}); + +/*! + * hydrates a document + * + * @param {Model} model + * @param {Document} doc + * @param {Object} res 3rd parameter to callback + * @param {Object} fields + * @param {Query} self + * @param {Array} [pop] array of paths used in population + * @param {Function} callback + */ + +function completeOne(model, doc, res, options, fields, userProvidedFields, pop, callback) { + const opts = pop ? + { populated: pop } + : undefined; + + if (options.rawResult && doc == null) { + _init(null); + return null; + } + + const casted = helpers.createModel(model, doc, fields, userProvidedFields); + try { + casted.init(doc, opts, _init); + } catch (error) { + _init(error); + } + + function _init(err) { + if (err) { + return process.nextTick(() => callback(err)); + } + + + if (options.rawResult) { + if (doc && casted) { + casted.$session(options.session); + res.value = casted; + } else { + res.value = null; + } + return process.nextTick(() => callback(null, res)); + } + casted.$session(options.session); + process.nextTick(() => callback(null, casted)); + } +} + +/*! + * If the model is a discriminator type and not root, then add the key & value to the criteria. + */ + +function prepareDiscriminatorCriteria(query) { + if (!query || !query.model || !query.model.schema) { + return; + } + + const schema = query.model.schema; + + if (schema && schema.discriminatorMapping && !schema.discriminatorMapping.isRoot) { + query._conditions[schema.discriminatorMapping.key] = schema.discriminatorMapping.value; + } +} + +/** + * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) update command. + * + * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found + * document (if any) to the callback. The query executes if + * `callback` is passed. + * + * This function triggers the following middleware. + * + * - `findOneAndUpdate()` + * + * ####Available options + * + * - `new`: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0) + * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. + * - `fields`: {Object|String} - Field selection. Equivalent to `.select(fields).findOneAndUpdate()` + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 + * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. + * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/). + * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * - `context` (string) if set to 'query' and `runValidators` is on, `this` will refer to the query in custom validator functions that update validation runs. Does nothing if `runValidators` is false. + * + * ####Callback Signature + * function(error, doc) { + * // error: any errors that occurred + * // doc: the document before updates are applied if `new: false`, or after updates if `new = true` + * } + * + * ####Examples + * + * query.findOneAndUpdate(conditions, update, options, callback) // executes + * query.findOneAndUpdate(conditions, update, options) // returns Query + * query.findOneAndUpdate(conditions, update, callback) // executes + * query.findOneAndUpdate(conditions, update) // returns Query + * query.findOneAndUpdate(update, callback) // returns Query + * query.findOneAndUpdate(update) // returns Query + * query.findOneAndUpdate(callback) // executes + * query.findOneAndUpdate() // returns Query + * + * @method findOneAndUpdate + * @memberOf Query + * @instance + * @param {Object|Query} [filter] + * @param {Object} [doc] + * @param {Object} [options] + * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. + * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. + * @param {Function} [callback] optional params are (error, doc), _unless_ `rawResult` is used, in which case params are (error, writeOpResult) + * @see Tutorial /docs/tutorials/findoneandupdate.html + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult + * @return {Query} this + * @api public + */ + +Query.prototype.findOneAndUpdate = function(criteria, doc, options, callback) { + this.op = 'findOneAndUpdate'; + this._validate(); + + switch (arguments.length) { + case 3: + if (typeof options === 'function') { + callback = options; + options = {}; + } + break; + case 2: + if (typeof doc === 'function') { + callback = doc; + doc = criteria; + criteria = undefined; + } + options = undefined; + break; + case 1: + if (typeof criteria === 'function') { + callback = criteria; + criteria = options = doc = undefined; + } else { + doc = criteria; + criteria = options = undefined; + } + } + + if (mquery.canMerge(criteria)) { + this.merge(criteria); + } + + // apply doc + if (doc) { + this._mergeUpdate(doc); + } + + options = options ? utils.clone(options) : {}; + + if (options.projection) { + this.select(options.projection); + delete options.projection; + } + if (options.fields) { + this.select(options.fields); + delete options.fields; + } + + + const returnOriginal = get(this, 'model.base.options.returnOriginal'); + if (options.returnOriginal == null && returnOriginal != null) { + options.returnOriginal = returnOriginal; + } + + this.setOptions(options); + + if (!callback) { + return this; + } + + this.exec(callback); + + return this; +}; + +/*! + * Thunk around findOneAndUpdate() + * + * @param {Function} [callback] + * @api private + */ + +Query.prototype._findOneAndUpdate = wrapThunk(function(callback) { + if (this.error() != null) { + return callback(this.error()); + } + + this._findAndModify('update', callback); +}); + +/** + * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) remove command. + * + * Finds a matching document, removes it, passing the found document (if any) to + * the callback. Executes if `callback` is passed. + * + * This function triggers the following middleware. + * + * - `findOneAndRemove()` + * + * ####Available options + * + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 + * - `rawResult`: if true, resolves to the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * + * ####Callback Signature + * function(error, doc) { + * // error: any errors that occurred + * // doc: the document before updates are applied if `new: false`, or after updates if `new = true` + * } + * + * ####Examples + * + * A.where().findOneAndRemove(conditions, options, callback) // executes + * A.where().findOneAndRemove(conditions, options) // return Query + * A.where().findOneAndRemove(conditions, callback) // executes + * A.where().findOneAndRemove(conditions) // returns Query + * A.where().findOneAndRemove(callback) // executes + * A.where().findOneAndRemove() // returns Query + * + * @method findOneAndRemove + * @memberOf Query + * @instance + * @param {Object} [conditions] + * @param {Object} [options] + * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Function} [callback] optional params are (error, document) + * @return {Query} this + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + * @api public + */ + +Query.prototype.findOneAndRemove = function(conditions, options, callback) { + this.op = 'findOneAndRemove'; + this._validate(); + + switch (arguments.length) { + case 2: + if (typeof options === 'function') { + callback = options; + options = {}; + } + break; + case 1: + if (typeof conditions === 'function') { + callback = conditions; + conditions = undefined; + options = undefined; + } + break; + } + + if (mquery.canMerge(conditions)) { + this.merge(conditions); + } + + options && this.setOptions(options); + + if (!callback) { + return this; + } + + this.exec(callback); + + return this; +}; + +/** + * Issues a MongoDB [findOneAndDelete](https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndDelete/) command. + * + * Finds a matching document, removes it, and passes the found document (if any) + * to the callback. Executes if `callback` is passed. + * + * This function triggers the following middleware. + * + * - `findOneAndDelete()` + * + * This function differs slightly from `Model.findOneAndRemove()` in that + * `findOneAndRemove()` becomes a [MongoDB `findAndModify()` command](https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/), + * as opposed to a `findOneAndDelete()` command. For most mongoose use cases, + * this distinction is purely pedantic. You should use `findOneAndDelete()` + * unless you have a good reason not to. + * + * ####Available options + * + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 + * - `rawResult`: if true, resolves to the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * + * ####Callback Signature + * function(error, doc) { + * // error: any errors that occurred + * // doc: the document before updates are applied if `new: false`, or after updates if `new = true` + * } + * + * ####Examples + * + * A.where().findOneAndDelete(conditions, options, callback) // executes + * A.where().findOneAndDelete(conditions, options) // return Query + * A.where().findOneAndDelete(conditions, callback) // executes + * A.where().findOneAndDelete(conditions) // returns Query + * A.where().findOneAndDelete(callback) // executes + * A.where().findOneAndDelete() // returns Query + * + * @method findOneAndDelete + * @memberOf Query + * @param {Object} [conditions] + * @param {Object} [options] + * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Function} [callback] optional params are (error, document) + * @return {Query} this + * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command + * @api public + */ + +Query.prototype.findOneAndDelete = function(conditions, options, callback) { + this.op = 'findOneAndDelete'; + this._validate(); + + switch (arguments.length) { + case 2: + if (typeof options === 'function') { + callback = options; + options = {}; + } + break; + case 1: + if (typeof conditions === 'function') { + callback = conditions; + conditions = undefined; + options = undefined; + } + break; + } + + if (mquery.canMerge(conditions)) { + this.merge(conditions); + } + + options && this.setOptions(options); + + if (!callback) { + return this; + } + + this.exec(callback); + + return this; +}; + +/*! + * Thunk around findOneAndDelete() + * + * @param {Function} [callback] + * @return {Query} this + * @api private + */ +Query.prototype._findOneAndDelete = wrapThunk(function(callback) { + this._castConditions(); + + if (this.error() != null) { + callback(this.error()); + return null; + } + + const filter = this._conditions; + const options = this._optionsForExec(); + let fields = null; + + if (this._fields != null) { + options.projection = this._castFields(utils.clone(this._fields)); + fields = options.projection; + if (fields instanceof Error) { + callback(fields); + return null; + } + } + + this._collection.collection.findOneAndDelete(filter, options, _wrapThunkCallback(this, (err, res) => { + if (err) { + return callback(err); + } + + const doc = res.value; + + return this._completeOne(doc, res, callback); + })); +}); + +/** + * Issues a MongoDB [findOneAndReplace](https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/) command. + * + * Finds a matching document, removes it, and passes the found document (if any) + * to the callback. Executes if `callback` is passed. + * + * This function triggers the following middleware. + * + * - `findOneAndReplace()` + * + * ####Available options + * + * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update + * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0 + * - `rawResult`: if true, resolves to the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * + * ####Callback Signature + * function(error, doc) { + * // error: any errors that occurred + * // doc: the document before updates are applied if `new: false`, or after updates if `new = true` + * } + * + * ####Examples + * + * A.where().findOneAndReplace(filter, replacement, options, callback); // executes + * A.where().findOneAndReplace(filter, replacement, options); // return Query + * A.where().findOneAndReplace(filter, replacement, callback); // executes + * A.where().findOneAndReplace(filter); // returns Query + * A.where().findOneAndReplace(callback); // executes + * A.where().findOneAndReplace(); // returns Query + * + * @method findOneAndReplace + * @memberOf Query + * @param {Object} [filter] + * @param {Object} [replacement] + * @param {Object} [options] + * @param {Boolean} [options.rawResult] if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify) + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. + * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). + * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. + * @param {Function} [callback] optional params are (error, document) + * @return {Query} this + * @api public + */ + +Query.prototype.findOneAndReplace = function(filter, replacement, options, callback) { + this.op = 'findOneAndReplace'; + this._validate(); + + switch (arguments.length) { + case 3: + if (typeof options === 'function') { + callback = options; + options = void 0; + } + break; + case 2: + if (typeof replacement === 'function') { + callback = replacement; + replacement = void 0; + } + break; + case 1: + if (typeof filter === 'function') { + callback = filter; + filter = void 0; + replacement = void 0; + options = void 0; + } + break; + } + + if (mquery.canMerge(filter)) { + this.merge(filter); + } + + if (replacement != null) { + if (hasDollarKeys(replacement)) { + throw new Error('The replacement document must not contain atomic operators.'); + } + this._mergeUpdate(replacement); + } + + options = options || {}; + + const returnOriginal = get(this, 'model.base.options.returnOriginal'); + if (options.returnOriginal == null && returnOriginal != null) { + options.returnOriginal = returnOriginal; + } + + this.setOptions(options); + + if (!callback) { + return this; + } + + this.exec(callback); + + return this; +}; + +/*! + * Thunk around findOneAndReplace() + * + * @param {Function} [callback] + * @return {Query} this + * @api private + */ +Query.prototype._findOneAndReplace = wrapThunk(function(callback) { + this._castConditions(); + + if (this.error() != null) { + callback(this.error()); + return null; + } + + const filter = this._conditions; + const options = this._optionsForExec(); + convertNewToReturnOriginal(options); + let fields = null; + + let castedDoc = new this.model(this._update, null, true); + this._update = castedDoc; + + this._applyPaths(); + if (this._fields != null) { + options.projection = this._castFields(utils.clone(this._fields)); + fields = options.projection; + if (fields instanceof Error) { + callback(fields); + return null; + } + } + + castedDoc.validate(err => { + if (err != null) { + return callback(err); + } + + if (castedDoc.toBSON) { + castedDoc = castedDoc.toBSON(); + } + + this._collection.collection.findOneAndReplace(filter, castedDoc, options, _wrapThunkCallback(this, (err, res) => { + if (err) { + return callback(err); + } + + const doc = res.value; + + return this._completeOne(doc, res, callback); + })); + }); +}); + +/*! + * Support the `new` option as an alternative to `returnOriginal` for backwards + * compat. + */ + +function convertNewToReturnOriginal(options) { + if ('new' in options) { + options.returnOriginal = !options['new']; + delete options['new']; + } +} + +/*! + * Thunk around findOneAndRemove() + * + * @param {Function} [callback] + * @return {Query} this + * @api private + */ +Query.prototype._findOneAndRemove = wrapThunk(function(callback) { + if (this.error() != null) { + callback(this.error()); + return; + } + + this._findAndModify('remove', callback); +}); + +/*! + * Get options from query opts, falling back to the base mongoose object. + */ + +function _getOption(query, option, def) { + const opts = query._optionsForExec(query.model); + + if (option in opts) { + return opts[option]; + } + if (option in query.model.base.options) { + return query.model.base.options[option]; + } + return def; +} + +/*! + * Override mquery.prototype._findAndModify to provide casting etc. + * + * @param {String} type - either "remove" or "update" + * @param {Function} callback + * @api private + */ + +Query.prototype._findAndModify = function(type, callback) { + if (typeof callback !== 'function') { + throw new Error('Expected callback in _findAndModify'); + } + + const model = this.model; + const schema = model.schema; + const _this = this; + let fields; + + const castedQuery = castQuery(this); + if (castedQuery instanceof Error) { + return callback(castedQuery); + } + + _castArrayFilters(this); + + const opts = this._optionsForExec(model); + + if ('strict' in opts) { + this._mongooseOptions.strict = opts.strict; + } + + const isOverwriting = this.options.overwrite && !hasDollarKeys(this._update); + if (isOverwriting) { + this._update = new this.model(this._update, null, true); + } + + if (type === 'remove') { + opts.remove = true; + } else { + if (!('new' in opts) && !('returnOriginal' in opts)) { + opts.new = false; + } + if (!('upsert' in opts)) { + opts.upsert = false; + } + if (opts.upsert || opts['new']) { + opts.remove = false; + } + + if (!isOverwriting) { + this._update = castDoc(this, opts.overwrite); + const _opts = Object.assign({}, opts, { + setDefaultsOnInsert: this._mongooseOptions.setDefaultsOnInsert + }); + this._update = setDefaultsOnInsert(this._conditions, schema, this._update, _opts); + if (!this._update || Object.keys(this._update).length === 0) { + if (opts.upsert) { + // still need to do the upsert to empty doc + const doc = utils.clone(castedQuery); + delete doc._id; + this._update = { $set: doc }; + } else { + this.findOne(callback); + return this; + } + } else if (this._update instanceof Error) { + return callback(this._update); + } else { + // In order to make MongoDB 2.6 happy (see + // https://jira.mongodb.org/browse/SERVER-12266 and related issues) + // if we have an actual update document but $set is empty, junk the $set. + if (this._update.$set && Object.keys(this._update.$set).length === 0) { + delete this._update.$set; + } + } + } + + if (Array.isArray(opts.arrayFilters)) { + opts.arrayFilters = removeUnusedArrayFilters(this._update, opts.arrayFilters); + } + } + + this._applyPaths(); + + const options = this._mongooseOptions; + + if (this._fields) { + fields = utils.clone(this._fields); + opts.projection = this._castFields(fields); + if (opts.projection instanceof Error) { + return callback(opts.projection); + } + } + + if (opts.sort) convertSortToArray(opts); + + const cb = function(err, doc, res) { + if (err) { + return callback(err); + } + + _this._completeOne(doc, res, callback); + }; + + let useFindAndModify = true; + const runValidators = _getOption(this, 'runValidators', false); + const base = _this.model && _this.model.base; + const conn = get(model, 'collection.conn', {}); + if ('useFindAndModify' in base.options) { + useFindAndModify = base.get('useFindAndModify'); + } + if ('useFindAndModify' in conn.config) { + useFindAndModify = conn.config.useFindAndModify; + } + if ('useFindAndModify' in options) { + useFindAndModify = options.useFindAndModify; + } + if (useFindAndModify === false) { + // Bypass mquery + const collection = _this._collection.collection; + convertNewToReturnOriginal(opts); + + if (type === 'remove') { + collection.findOneAndDelete(castedQuery, opts, _wrapThunkCallback(_this, function(error, res) { + return cb(error, res ? res.value : res, res); + })); + + return this; + } + + // honors legacy overwrite option for backward compatibility + const updateMethod = isOverwriting ? 'findOneAndReplace' : 'findOneAndUpdate'; + + if (runValidators) { + this.validate(this._update, opts, isOverwriting, error => { + if (error) { + return callback(error); + } + if (this._update && this._update.toBSON) { + this._update = this._update.toBSON(); + } + + collection[updateMethod](castedQuery, this._update, opts, _wrapThunkCallback(_this, function(error, res) { + return cb(error, res ? res.value : res, res); + })); + }); + } else { + if (this._update && this._update.toBSON) { + this._update = this._update.toBSON(); + } + collection[updateMethod](castedQuery, this._update, opts, _wrapThunkCallback(_this, function(error, res) { + return cb(error, res ? res.value : res, res); + })); + } + + return this; + } + + if (runValidators) { + this.validate(this._update, opts, isOverwriting, function(error) { + if (error) { + return callback(error); + } + _legacyFindAndModify.call(_this, castedQuery, _this._update, opts, cb); + }); + } else { + _legacyFindAndModify.call(_this, castedQuery, _this._update, opts, cb); + } + + return this; +}; + +/*! + * ignore + */ + +function _completeOneLean(doc, res, opts, callback) { + if (opts.rawResult) { + return callback(null, res); + } + return callback(null, doc); +} + + +/*! + * ignore + */ + +const _legacyFindAndModify = util.deprecate(function(filter, update, opts, cb) { + if (update && update.toBSON) { + update = update.toBSON(); + } + const collection = this._collection; + const sort = opts != null && Array.isArray(opts.sort) ? opts.sort : []; + const _cb = _wrapThunkCallback(this, function(error, res) { + return cb(error, res ? res.value : res, res); + }); + collection.collection._findAndModify(filter, sort, update, opts, _cb); +}, 'Mongoose: `findOneAndUpdate()` and `findOneAndDelete()` without the ' + + '`useFindAndModify` option set to false are deprecated. See: ' + + 'https://mongoosejs.com/docs/deprecations.html#findandmodify'); + +/*! + * Override mquery.prototype._mergeUpdate to handle mongoose objects in + * updates. + * + * @param {Object} doc + * @api private + */ + +Query.prototype._mergeUpdate = function(doc) { + if (doc == null || (typeof doc === 'object' && Object.keys(doc).length === 0)) { + return; + } + + if (!this._update) { + this._update = Array.isArray(doc) ? [] : {}; + } + if (doc instanceof Query) { + if (Array.isArray(this._update)) { + throw new Error('Cannot mix array and object updates'); + } + if (doc._update) { + utils.mergeClone(this._update, doc._update); + } + } else if (Array.isArray(doc)) { + if (!Array.isArray(this._update)) { + throw new Error('Cannot mix array and object updates'); + } + this._update = this._update.concat(doc); + } else { + if (Array.isArray(this._update)) { + throw new Error('Cannot mix array and object updates'); + } + utils.mergeClone(this._update, doc); + } +}; + +/*! + * The mongodb driver 1.3.23 only supports the nested array sort + * syntax. We must convert it or sorting findAndModify will not work. + */ + +function convertSortToArray(opts) { + if (Array.isArray(opts.sort)) { + return; + } + if (!utils.isObject(opts.sort)) { + return; + } + + const sort = []; + + for (const key in opts.sort) { + if (utils.object.hasOwnProperty(opts.sort, key)) { + sort.push([key, opts.sort[key]]); + } + } + + opts.sort = sort; +} + +/*! + * ignore + */ + +function _updateThunk(op, callback) { + this._castConditions(); + + _castArrayFilters(this); + + if (this.error() != null) { + callback(this.error()); + return null; + } + + callback = _wrapThunkCallback(this, callback); + const oldCb = callback; + callback = function(error, result) { + oldCb(error, result ? result.result : { ok: 0, n: 0, nModified: 0 }); + }; + + const castedQuery = this._conditions; + const options = this._optionsForExec(this.model); + + ++this._executionCount; + + this._update = utils.clone(this._update, options); + const isOverwriting = this.options.overwrite && !hasDollarKeys(this._update); + if (isOverwriting) { + if (op === 'updateOne' || op === 'updateMany') { + return callback(new MongooseError('The MongoDB server disallows ' + + 'overwriting documents using `' + op + '`. See: ' + + 'https://mongoosejs.com/docs/deprecations.html#update')); + } + this._update = new this.model(this._update, null, true); + } else { + this._update = castDoc(this, options.overwrite); + + if (this._update instanceof Error) { + callback(this._update); + return null; + } + + if (this._update == null || Object.keys(this._update).length === 0) { + callback(null, 0); + return null; + } + + const _opts = Object.assign({}, options, { + setDefaultsOnInsert: this._mongooseOptions.setDefaultsOnInsert + }); + this._update = setDefaultsOnInsert(this._conditions, this.model.schema, + this._update, _opts); + } + + if (Array.isArray(options.arrayFilters)) { + options.arrayFilters = removeUnusedArrayFilters(this._update, options.arrayFilters); + } + + const runValidators = _getOption(this, 'runValidators', false); + if (runValidators) { + this.validate(this._update, options, isOverwriting, err => { + if (err) { + return callback(err); + } + + if (this._update.toBSON) { + this._update = this._update.toBSON(); + } + this._collection[op](castedQuery, this._update, options, callback); + }); + return null; + } + + if (this._update.toBSON) { + this._update = this._update.toBSON(); + } + + this._collection[op](castedQuery, this._update, options, callback); + return null; +} + +/*! + * Mongoose calls this function internally to validate the query if + * `runValidators` is set + * + * @param {Object} castedDoc the update, after casting + * @param {Object} options the options from `_optionsForExec()` + * @param {Function} callback + * @api private + */ + +Query.prototype.validate = function validate(castedDoc, options, isOverwriting, callback) { + return promiseOrCallback(callback, cb => { + try { + if (isOverwriting) { + castedDoc.validate(cb); + } else { + updateValidators(this, this.model.schema, castedDoc, options, cb); + } + } catch (err) { + process.nextTick(function() { + cb(err); + }); + } + }); +}; + +/*! + * Internal thunk for .update() + * + * @param {Function} callback + * @see Model.update #model_Model.update + * @api private + */ +Query.prototype._execUpdate = wrapThunk(function(callback) { + return _updateThunk.call(this, 'update', callback); +}); + +/*! + * Internal thunk for .updateMany() + * + * @param {Function} callback + * @see Model.update #model_Model.update + * @api private + */ +Query.prototype._updateMany = wrapThunk(function(callback) { + return _updateThunk.call(this, 'updateMany', callback); +}); + +/*! + * Internal thunk for .updateOne() + * + * @param {Function} callback + * @see Model.update #model_Model.update + * @api private + */ +Query.prototype._updateOne = wrapThunk(function(callback) { + return _updateThunk.call(this, 'updateOne', callback); +}); + +/*! + * Internal thunk for .replaceOne() + * + * @param {Function} callback + * @see Model.replaceOne #model_Model.replaceOne + * @api private + */ +Query.prototype._replaceOne = wrapThunk(function(callback) { + return _updateThunk.call(this, 'replaceOne', callback); +}); + +/** + * Declare and/or execute this query as an update() operation. + * + * _All paths passed that are not [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) operations will become `$set` ops._ + * + * This function triggers the following middleware. + * + * - `update()` + * + * ####Example + * + * Model.where({ _id: id }).update({ title: 'words' }) + * + * // becomes + * + * Model.where({ _id: id }).update({ $set: { title: 'words' }}) + * + * ####Valid options: + * + * - `upsert` (boolean) whether to create the doc if it doesn't match (false) + * - `multi` (boolean) whether multiple documents should be updated (false) + * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema. + * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/). + * - `strict` (boolean) overrides the `strict` option for this update + * - `overwrite` (boolean) disables update-only mode, allowing you to overwrite the doc (false) + * - `context` (string) if set to 'query' and `runValidators` is on, `this` will refer to the query in custom validator functions that update validation runs. Does nothing if `runValidators` is false. + * - `read` + * - `writeConcern` + * + * ####Note + * + * Passing an empty object `{}` as the doc will result in a no-op unless the `overwrite` option is passed. Without the `overwrite` option set, the update operation will be ignored and the callback executed without sending the command to MongoDB so as to prevent accidently overwritting documents in the collection. + * + * ####Note + * + * The operation is only executed when a callback is passed. To force execution without a callback, we must first call update() and then execute it by using the `exec()` method. + * + * const q = Model.where({ _id: id }); + * q.update({ $set: { name: 'bob' }}).update(); // not executed + * + * q.update({ $set: { name: 'bob' }}).exec(); // executed + * + * // keys that are not [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) ops become `$set`. + * // this executes the same command as the previous example. + * q.update({ name: 'bob' }).exec(); + * + * // overwriting with empty docs + * const q = Model.where({ _id: id }).setOptions({ overwrite: true }) + * q.update({ }, callback); // executes + * + * // multi update with overwrite to empty doc + * const q = Model.where({ _id: id }); + * q.setOptions({ multi: true, overwrite: true }) + * q.update({ }); + * q.update(callback); // executed + * + * // multi updates + * Model.where() + * .update({ name: /^match/ }, { $set: { arr: [] }}, { multi: true }, callback) + * + * // more multi updates + * Model.where() + * .setOptions({ multi: true }) + * .update({ $set: { arr: [] }}, callback) + * + * // single update by default + * Model.where({ email: 'address@example.com' }) + * .update({ $inc: { counter: 1 }}, callback) + * + * API summary + * + * update(filter, doc, options, cb) // executes + * update(filter, doc, options) + * update(filter, doc, cb) // executes + * update(filter, doc) + * update(doc, cb) // executes + * update(doc) + * update(cb) // executes + * update(true) // executes + * update() + * + * @param {Object} [filter] + * @param {Object} [doc] the update command + * @param {Object} [options] + * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Function} [callback] params are (error, writeOpResult) + * @return {Query} this + * @see Model.update #model_Model.update + * @see Query docs https://mongoosejs.com/docs/queries.html + * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/ + * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult + * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output + * @api public + */ + +Query.prototype.update = function(conditions, doc, options, callback) { + if (typeof options === 'function') { + // .update(conditions, doc, callback) + callback = options; + options = null; + } else if (typeof doc === 'function') { + // .update(doc, callback); + callback = doc; + doc = conditions; + conditions = {}; + options = null; + } else if (typeof conditions === 'function') { + // .update(callback) + callback = conditions; + conditions = undefined; + doc = undefined; + options = undefined; + } else if (typeof conditions === 'object' && !doc && !options && !callback) { + // .update(doc) + doc = conditions; + conditions = undefined; + options = undefined; + callback = undefined; + } + + return _update(this, 'update', conditions, doc, options, callback); +}; + +/** + * Declare and/or execute this query as an updateMany() operation. Same as + * `update()`, except MongoDB will update _all_ documents that match + * `filter` (as opposed to just the first one) regardless of the value of + * the `multi` option. + * + * **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')` + * and `post('updateMany')` instead. + * + * ####Example: + * const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true }); + * res.n; // Number of documents matched + * res.nModified; // Number of documents modified + * + * This function triggers the following middleware. + * + * - `updateMany()` + * + * @param {Object} [filter] + * @param {Object} [doc] the update command + * @param {Object} [options] + * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Function} [callback] params are (error, writeOpResult) + * @return {Query} this + * @see Model.update #model_Model.update + * @see Query docs https://mongoosejs.com/docs/queries.html + * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/ + * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult + * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output + * @api public + */ + +Query.prototype.updateMany = function(conditions, doc, options, callback) { + if (typeof options === 'function') { + // .update(conditions, doc, callback) + callback = options; + options = null; + } else if (typeof doc === 'function') { + // .update(doc, callback); + callback = doc; + doc = conditions; + conditions = {}; + options = null; + } else if (typeof conditions === 'function') { + // .update(callback) + callback = conditions; + conditions = undefined; + doc = undefined; + options = undefined; + } else if (typeof conditions === 'object' && !doc && !options && !callback) { + // .update(doc) + doc = conditions; + conditions = undefined; + options = undefined; + callback = undefined; + } + + return _update(this, 'updateMany', conditions, doc, options, callback); +}; + +/** + * Declare and/or execute this query as an updateOne() operation. Same as + * `update()`, except it does not support the `multi` or `overwrite` options. + * + * - MongoDB will update _only_ the first document that matches `filter` regardless of the value of the `multi` option. + * - Use `replaceOne()` if you want to overwrite an entire document rather than using [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) operators like `$set`. + * + * **Note** updateOne will _not_ fire update middleware. Use `pre('updateOne')` + * and `post('updateOne')` instead. + * + * ####Example: + * const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' }); + * res.n; // Number of documents matched + * res.nModified; // Number of documents modified + * + * This function triggers the following middleware. + * + * - `updateOne()` + * + * @param {Object} [filter] + * @param {Object} [doc] the update command + * @param {Object} [options] + * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set. + * @param {Function} [callback] params are (error, writeOpResult) + * @return {Query} this + * @see Model.update #model_Model.update + * @see Query docs https://mongoosejs.com/docs/queries.html + * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/ + * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult + * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output + * @api public + */ + +Query.prototype.updateOne = function(conditions, doc, options, callback) { + if (typeof options === 'function') { + // .update(conditions, doc, callback) + callback = options; + options = null; + } else if (typeof doc === 'function') { + // .update(doc, callback); + callback = doc; + doc = conditions; + conditions = {}; + options = null; + } else if (typeof conditions === 'function') { + // .update(callback) + callback = conditions; + conditions = undefined; + doc = undefined; + options = undefined; + } else if (typeof conditions === 'object' && !doc && !options && !callback) { + // .update(doc) + doc = conditions; + conditions = undefined; + options = undefined; + callback = undefined; + } + + return _update(this, 'updateOne', conditions, doc, options, callback); +}; + +/** + * Declare and/or execute this query as a replaceOne() operation. Same as + * `update()`, except MongoDB will replace the existing document and will + * not accept any [atomic](https://docs.mongodb.com/manual/tutorial/model-data-for-atomic-operations/#pattern) operators (`$set`, etc.) + * + * **Note** replaceOne will _not_ fire update middleware. Use `pre('replaceOne')` + * and `post('replaceOne')` instead. + * + * ####Example: + * const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' }); + * res.n; // Number of documents matched + * res.nModified; // Number of documents modified + * + * This function triggers the following middleware. + * + * - `replaceOne()` + * + * @param {Object} [filter] + * @param {Object} [doc] the update command + * @param {Object} [options] + * @param {Boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors. + * @param {Boolean} [options.omitUndefined=false] If true, delete any properties whose value is `undefined` when casting an update. In other words, if this is set, Mongoose will delete `baz` from the update in `Model.updateOne({}, { foo: 'bar', baz: undefined })` before sending the update to the server. + * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) + * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document + * @param {Object} [options.writeConcern=null] sets the [write concern](https://docs.mongodb.com/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](/docs/guide.html#writeConcern) + * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set. + * @param {Function} [callback] params are (error, writeOpResult) + * @return {Query} this + * @see Model.update #model_Model.update + * @see Query docs https://mongoosejs.com/docs/queries.html + * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/ + * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult + * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output + * @api public + */ + +Query.prototype.replaceOne = function(conditions, doc, options, callback) { + if (typeof options === 'function') { + // .update(conditions, doc, callback) + callback = options; + options = null; + } else if (typeof doc === 'function') { + // .update(doc, callback); + callback = doc; + doc = conditions; + conditions = {}; + options = null; + } else if (typeof conditions === 'function') { + // .update(callback) + callback = conditions; + conditions = undefined; + doc = undefined; + options = undefined; + } else if (typeof conditions === 'object' && !doc && !options && !callback) { + // .update(doc) + doc = conditions; + conditions = undefined; + options = undefined; + callback = undefined; + } + + this.setOptions({ overwrite: true }); + return _update(this, 'replaceOne', conditions, doc, options, callback); +}; + +/*! + * Internal helper for update, updateMany, updateOne, replaceOne + */ + +function _update(query, op, filter, doc, options, callback) { + // make sure we don't send in the whole Document to merge() + query.op = op; + filter = utils.toObject(filter); + doc = doc || {}; + + // strict is an option used in the update checking, make sure it gets set + if (options != null) { + if ('strict' in options) { + query._mongooseOptions.strict = options.strict; + } + } + + if (!(filter instanceof Query) && + filter != null && + filter.toString() !== '[object Object]') { + query.error(new ObjectParameterError(filter, 'filter', op)); + } else { + query.merge(filter); + } + + if (utils.isObject(options)) { + query.setOptions(options); + } + + query._mergeUpdate(doc); + + // Hooks + if (callback) { + query.exec(callback); + + return query; + } + + return Query.base[op].call(query, filter, void 0, options, callback); +} + +/** + * Runs a function `fn` and treats the return value of `fn` as the new value + * for the query to resolve to. + * + * Any functions you pass to `map()` will run **after** any post hooks. + * + * ####Example: + * + * const res = await MyModel.findOne().map(res => { + * // Sets a `loadedAt` property on the doc that tells you the time the + * // document was loaded. + * return res == null ? + * res : + * Object.assign(res, { loadedAt: new Date() }); + * }); + * + * @method map + * @memberOf Query + * @instance + * @param {Function} fn function to run to transform the query result + * @return {Query} this + */ + +Query.prototype.map = function(fn) { + this._transforms.push(fn); + return this; +}; + +/** + * Make this query throw an error if no documents match the given `filter`. + * This is handy for integrating with async/await, because `orFail()` saves you + * an extra `if` statement to check if no document was found. + * + * ####Example: + * + * // Throws if no doc returned + * await Model.findOne({ foo: 'bar' }).orFail(); + * + * // Throws if no document was updated + * await Model.updateOne({ foo: 'bar' }, { name: 'test' }).orFail(); + * + * // Throws "No docs found!" error if no docs match `{ foo: 'bar' }` + * await Model.find({ foo: 'bar' }).orFail(new Error('No docs found!')); + * + * // Throws "Not found" error if no document was found + * await Model.findOneAndUpdate({ foo: 'bar' }, { name: 'test' }). + * orFail(() => Error('Not found')); + * + * @method orFail + * @memberOf Query + * @instance + * @param {Function|Error} [err] optional error to throw if no docs match `filter`. If not specified, `orFail()` will throw a `DocumentNotFoundError` + * @return {Query} this + */ + +Query.prototype.orFail = function(err) { + this.map(res => { + switch (this.op) { + case 'find': + if (res.length === 0) { + throw _orFailError(err, this); + } + break; + case 'findOne': + if (res == null) { + throw _orFailError(err, this); + } + break; + case 'update': + case 'updateMany': + case 'updateOne': + if (get(res, 'nModified') === 0) { + throw _orFailError(err, this); + } + break; + case 'findOneAndDelete': + case 'findOneAndRemove': + if (get(res, 'lastErrorObject.n') === 0) { + throw _orFailError(err, this); + } + break; + case 'findOneAndUpdate': + case 'findOneAndReplace': + if (get(res, 'lastErrorObject.updatedExisting') === false) { + throw _orFailError(err, this); + } + break; + case 'deleteMany': + case 'deleteOne': + case 'remove': + if (res.n === 0) { + throw _orFailError(err, this); + } + break; + default: + break; + } + + return res; + }); + return this; +}; + +/*! + * Get the error to throw for `orFail()` + */ + +function _orFailError(err, query) { + if (typeof err === 'function') { + err = err.call(query); + } + + if (err == null) { + err = new DocumentNotFoundError(query.getQuery(), query.model.modelName); + } + + return err; +} + +/** + * Executes the query + * + * ####Examples: + * + * const promise = query.exec(); + * const promise = query.exec('update'); + * + * query.exec(callback); + * query.exec('find', callback); + * + * @param {String|Function} [operation] + * @param {Function} [callback] optional params depend on the function being called + * @return {Promise} + * @api public + */ + +Query.prototype.exec = function exec(op, callback) { + const _this = this; + // Ensure that `exec()` is the first thing that shows up in + // the stack when cast errors happen. + const castError = new CastError(); + + if (typeof op === 'function') { + callback = op; + op = null; + } else if (typeof op === 'string') { + this.op = op; + } + + callback = this.model.$handleCallbackError(callback); + + return promiseOrCallback(callback, (cb) => { + cb = this.model.$wrapCallback(cb); + + if (!_this.op) { + cb(); + return; + } + + this._hooks.execPre('exec', this, [], (error) => { + if (error != null) { + return cb(_cleanCastErrorStack(castError, error)); + } + let thunk = '_' + this.op; + if (this.op === 'update') { + thunk = '_execUpdate'; + } else if (this.op === 'distinct') { + thunk = '__distinct'; + } + this[thunk].call(this, (error, res) => { + if (error) { + return cb(_cleanCastErrorStack(castError, error)); + } + + this._hooks.execPost('exec', this, [], {}, (error) => { + if (error) { + return cb(_cleanCastErrorStack(castError, error)); + } + + cb(null, res); + }); + }); + }); + }, this.model.events); +}; + +/*! + * ignore + */ + +function _cleanCastErrorStack(castError, error) { + if (error instanceof CastError) { + castError.copy(error); + return castError; + } + + return error; +} + +/*! + * ignore + */ + +function _wrapThunkCallback(query, cb) { + return function(error, res) { + if (error != null) { + return cb(error); + } + + for (const fn of query._transforms) { + try { + res = fn(res); + } catch (error) { + return cb(error); + } + } + + return cb(null, res); + }; +} + +/** + * Executes the query returning a `Promise` which will be + * resolved with either the doc(s) or rejected with the error. + * + * @param {Function} [resolve] + * @param {Function} [reject] + * @return {Promise} + * @api public + */ + +Query.prototype.then = function(resolve, reject) { + return this.exec().then(resolve, reject); +}; + +/** + * Executes the query returning a `Promise` which will be + * resolved with either the doc(s) or rejected with the error. + * Like `.then()`, but only takes a rejection handler. + * + * @param {Function} [reject] + * @return {Promise} + * @api public + */ + +Query.prototype.catch = function(reject) { + return this.exec().then(null, reject); +}; + +/*! + * ignore + */ + +Query.prototype._pre = function(fn) { + this._hooks.pre('exec', fn); + return this; +}; + +/*! + * ignore + */ + +Query.prototype._post = function(fn) { + this._hooks.post('exec', fn); + return this; +}; + +/*! + * Casts obj for an update command. + * + * @param {Object} obj + * @return {Object} obj after casting its values + * @api private + */ + +Query.prototype._castUpdate = function _castUpdate(obj, overwrite) { + let strict; + let schema = this.schema; + + const discriminatorKey = schema.options.discriminatorKey; + const baseSchema = schema._baseSchema ? schema._baseSchema : schema; + if (this._mongooseOptions.overwriteDiscriminatorKey && + obj[discriminatorKey] != null && + baseSchema.discriminators) { + const _schema = baseSchema.discriminators[obj[discriminatorKey]]; + if (_schema != null) { + schema = _schema; + } + } + + if ('strict' in this._mongooseOptions) { + strict = this._mongooseOptions.strict; + } else if (this.schema && this.schema.options) { + strict = this.schema.options.strict; + } else { + strict = true; + } + + let omitUndefined = false; + if ('omitUndefined' in this._mongooseOptions) { + omitUndefined = this._mongooseOptions.omitUndefined; + } + + let useNestedStrict; + if ('useNestedStrict' in this.options) { + useNestedStrict = this.options.useNestedStrict; + } + + let upsert; + if ('upsert' in this.options) { + upsert = this.options.upsert; + } + + const filter = this._conditions; + if (schema != null && + utils.hasUserDefinedProperty(filter, schema.options.discriminatorKey) && + typeof filter[schema.options.discriminatorKey] !== 'object' && + schema.discriminators != null) { + const discriminatorValue = filter[schema.options.discriminatorKey]; + const byValue = getDiscriminatorByValue(this.model, discriminatorValue); + schema = schema.discriminators[discriminatorValue] || + (byValue && byValue.schema) || + schema; + } + + return castUpdate(schema, obj, { + overwrite: overwrite, + strict: strict, + omitUndefined, + useNestedStrict: useNestedStrict, + upsert: upsert + }, this, this._conditions); +}; + +/*! + * castQuery + * @api private + */ + +function castQuery(query) { + try { + return query.cast(query.model); + } catch (err) { + return err; + } +} + +/*! + * castDoc + * @api private + */ + +function castDoc(query, overwrite) { + try { + return query._castUpdate(query._update, overwrite); + } catch (err) { + return err; + } +} + +/** + * Specifies paths which should be populated with other documents. + * + * ####Example: + * + * let book = await Book.findOne().populate('authors'); + * book.title; // 'Node.js in Action' + * book.authors[0].name; // 'TJ Holowaychuk' + * book.authors[1].name; // 'Nathan Rajlich' + * + * let books = await Book.find().populate({ + * path: 'authors', + * // `match` and `sort` apply to the Author model, + * // not the Book model. These options do not affect + * // which documents are in `books`, just the order and + * // contents of each book document's `authors`. + * match: { name: new RegExp('.*h.*', 'i') }, + * sort: { name: -1 } + * }); + * books[0].title; // 'Node.js in Action' + * // Each book's `authors` are sorted by name, descending. + * books[0].authors[0].name; // 'TJ Holowaychuk' + * books[0].authors[1].name; // 'Marc Harter' + * + * books[1].title; // 'Professional AngularJS' + * // Empty array, no authors' name has the letter 'h' + * books[1].authors; // [] + * + * Paths are populated after the query executes and a response is received. A + * separate query is then executed for each path specified for population. After + * a response for each query has also been returned, the results are passed to + * the callback. + * + * @param {Object|String} path either the path to populate or an object specifying all parameters + * @param {Object|String} [select] Field selection for the population query + * @param {Model} [model] The model you wish to use for population. If not specified, populate will look up the model by the name in the Schema's `ref` field. + * @param {Object} [match] Conditions for the population query + * @param {Object} [options] Options for the population query (sort, etc) + * @param {String} [options.path=null] The path to populate. + * @param {boolean} [options.retainNullValues=false] by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries. + * @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](/docs/schematypes.html#schematype-options). + * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them. + * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://docs.mongodb.com/manual/tutorial/query-documents/), or a function that returns a filter object. + * @param {Object} [options.options=null] Additional options like `limit` and `lean`. + * @see population ./populate.html + * @see Query#select #query_Query-select + * @see Model.populate #model_Model.populate + * @return {Query} this + * @api public + */ + +Query.prototype.populate = function() { + // Bail when given no truthy arguments + if (!Array.from(arguments).some(Boolean)) { + return this; + } + + const res = utils.populate.apply(null, arguments); + + // Propagate readConcern and readPreference and lean from parent query, + // unless one already specified + if (this.options != null) { + const readConcern = this.options.readConcern; + const readPref = this.options.readPreference; + + for (const populateOptions of res) { + if (readConcern != null && get(populateOptions, 'options.readConcern') == null) { + populateOptions.options = populateOptions.options || {}; + populateOptions.options.readConcern = readConcern; + } + if (readPref != null && get(populateOptions, 'options.readPreference') == null) { + populateOptions.options = populateOptions.options || {}; + populateOptions.options.readPreference = readPref; + } + } + } + + const opts = this._mongooseOptions; + + if (opts.lean != null) { + const lean = opts.lean; + for (const populateOptions of res) { + if (get(populateOptions, 'options.lean') == null) { + populateOptions.options = populateOptions.options || {}; + populateOptions.options.lean = lean; + } + } + } + + if (!utils.isObject(opts.populate)) { + opts.populate = {}; + } + + const pop = opts.populate; + + for (const populateOptions of res) { + const path = populateOptions.path; + if (pop[path] && pop[path].populate && populateOptions.populate) { + populateOptions.populate = pop[path].populate.concat(populateOptions.populate); + } + + pop[populateOptions.path] = populateOptions; + } + + return this; +}; + +/** + * Gets a list of paths to be populated by this query + * + * ####Example: + * bookSchema.pre('findOne', function() { + * let keys = this.getPopulatedPaths(); // ['author'] + * }); + * ... + * Book.findOne({}).populate('author'); + * + * ####Example: + * // Deep populate + * const q = L1.find().populate({ + * path: 'level2', + * populate: { path: 'level3' } + * }); + * q.getPopulatedPaths(); // ['level2', 'level2.level3'] + * + * @return {Array} an array of strings representing populated paths + * @api public + */ + +Query.prototype.getPopulatedPaths = function getPopulatedPaths() { + const obj = this._mongooseOptions.populate || {}; + const ret = Object.keys(obj); + for (const path of Object.keys(obj)) { + const pop = obj[path]; + if (!Array.isArray(pop.populate)) { + continue; + } + _getPopulatedPaths(ret, pop.populate, path + '.'); + } + return ret; +}; + +/*! + * ignore + */ + +function _getPopulatedPaths(list, arr, prefix) { + for (const pop of arr) { + list.push(prefix + pop.path); + if (!Array.isArray(pop.populate)) { + continue; + } + _getPopulatedPaths(list, pop.populate, prefix + pop.path + '.'); + } +} + +/** + * Casts this query to the schema of `model` + * + * ####Note + * + * If `obj` is present, it is cast instead of this query. + * + * @param {Model} [model] the model to cast to. If not set, defaults to `this.model` + * @param {Object} [obj] + * @return {Object} + * @api public + */ + +Query.prototype.cast = function(model, obj) { + obj || (obj = this._conditions); + + model = model || this.model; + + const discriminatorKey = model.schema.options.discriminatorKey; + if (obj != null && + obj.hasOwnProperty(discriminatorKey)) { + model = getDiscriminatorByValue(model, obj[discriminatorKey]) || model; + } + + try { + return cast(model.schema, obj, { + upsert: this.options && this.options.upsert, + strict: (this.options && 'strict' in this.options) ? + this.options.strict : + get(model, 'schema.options.strict', null), + strictQuery: (this.options && this.options.strictQuery) || + get(model, 'schema.options.strictQuery', null) + }, this); + } catch (err) { + // CastError, assign model + if (typeof err.setModel === 'function') { + err.setModel(model); + } + throw err; + } +}; + +/** + * Casts selected field arguments for field selection with mongo 2.2 + * + * query.select({ ids: { $elemMatch: { $in: [hexString] }}) + * + * @param {Object} fields + * @see https://github.com/Automattic/mongoose/issues/1091 + * @see http://docs.mongodb.org/manual/reference/projection/elemMatch/ + * @api private + */ + +Query.prototype._castFields = function _castFields(fields) { + let selected, + elemMatchKeys, + keys, + key, + out, + i; + + if (fields) { + keys = Object.keys(fields); + elemMatchKeys = []; + i = keys.length; + + // collect $elemMatch args + while (i--) { + key = keys[i]; + if (fields[key].$elemMatch) { + selected || (selected = {}); + selected[key] = fields[key]; + elemMatchKeys.push(key); + } + } + } + + if (selected) { + // they passed $elemMatch, cast em + try { + out = this.cast(this.model, selected); + } catch (err) { + return err; + } + + // apply the casted field args + i = elemMatchKeys.length; + while (i--) { + key = elemMatchKeys[i]; + fields[key] = out[key]; + } + } + + return fields; +}; + +/** + * Applies schematype selected options to this query. + * @api private + */ + +Query.prototype._applyPaths = function applyPaths() { + this._fields = this._fields || {}; + helpers.applyPaths(this._fields, this.model.schema); + + let _selectPopulatedPaths = true; + + if ('selectPopulatedPaths' in this.model.base.options) { + _selectPopulatedPaths = this.model.base.options.selectPopulatedPaths; + } + if ('selectPopulatedPaths' in this.model.schema.options) { + _selectPopulatedPaths = this.model.schema.options.selectPopulatedPaths; + } + + if (_selectPopulatedPaths) { + selectPopulatedFields(this); + } +}; + +/** + * Returns a wrapper around a [mongodb driver cursor](http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html). + * A QueryCursor exposes a Streams3 interface, as well as a `.next()` function. + * + * The `.cursor()` function triggers pre find hooks, but **not** post find hooks. + * + * ####Example + * + * // There are 2 ways to use a cursor. First, as a stream: + * Thing. + * find({ name: /^hello/ }). + * cursor(). + * on('data', function(doc) { console.log(doc); }). + * on('end', function() { console.log('Done!'); }); + * + * // Or you can use `.next()` to manually get the next doc in the stream. + * // `.next()` returns a promise, so you can use promises or callbacks. + * const cursor = Thing.find({ name: /^hello/ }).cursor(); + * cursor.next(function(error, doc) { + * console.log(doc); + * }); + * + * // Because `.next()` returns a promise, you can use co + * // to easily iterate through all documents without loading them + * // all into memory. + * co(function*() { + * const cursor = Thing.find({ name: /^hello/ }).cursor(); + * for (let doc = yield cursor.next(); doc != null; doc = yield cursor.next()) { + * console.log(doc); + * } + * }); + * + * ####Valid options + * + * - `transform`: optional function which accepts a mongoose document. The return value of the function will be emitted on `data` and returned by `.next()`. + * + * @return {QueryCursor} + * @param {Object} [options] + * @see QueryCursor + * @api public + */ + +Query.prototype.cursor = function cursor(opts) { + this._applyPaths(); + this._fields = this._castFields(this._fields); + this.setOptions({ projection: this._fieldsForExec() }); + if (opts) { + this.setOptions(opts); + } + + const options = Object.assign({}, this._optionsForExec(), { + projection: this.projection() + }); + try { + this.cast(this.model); + } catch (err) { + return (new QueryCursor(this, options))._markError(err); + } + + return new QueryCursor(this, options); +}; + +// the rest of these are basically to support older Mongoose syntax with mquery + +/** + * _DEPRECATED_ Alias of `maxScan` + * + * @deprecated + * @see maxScan #query_Query-maxScan + * @method maxscan + * @memberOf Query + * @instance + */ + +Query.prototype.maxscan = Query.base.maxScan; + +/** + * Sets the tailable option (for use with capped collections). + * + * ####Example + * + * query.tailable() // true + * query.tailable(true) + * query.tailable(false) + * + * ####Note + * + * Cannot be used with `distinct()` + * + * @param {Boolean} bool defaults to true + * @param {Object} [opts] options to set + * @param {Number} [opts.numberOfRetries] if cursor is exhausted, retry this many times before giving up + * @param {Number} [opts.tailableRetryInterval] if cursor is exhausted, wait this many milliseconds before retrying + * @see tailable http://docs.mongodb.org/manual/tutorial/create-tailable-cursor/ + * @api public + */ + +Query.prototype.tailable = function(val, opts) { + // we need to support the tailable({ awaitdata : true }) as well as the + // tailable(true, {awaitdata :true}) syntax that mquery does not support + if (val && val.constructor.name === 'Object') { + opts = val; + val = true; + } + + if (val === undefined) { + val = true; + } + + if (opts && typeof opts === 'object') { + for (const key in opts) { + if (key === 'awaitdata') { + // For backwards compatibility + this.options[key] = !!opts[key]; + } else { + this.options[key] = opts[key]; + } + } + } + + return Query.base.tailable.call(this, val); +}; + +/** + * Declares an intersects query for `geometry()`. + * + * ####Example + * + * query.where('path').intersects().geometry({ + * type: 'LineString' + * , coordinates: [[180.0, 11.0], [180, 9.0]] + * }) + * + * query.where('path').intersects({ + * type: 'LineString' + * , coordinates: [[180.0, 11.0], [180, 9.0]] + * }) + * + * ####NOTE: + * + * **MUST** be used after `where()`. + * + * ####NOTE: + * + * In Mongoose 3.7, `intersects` changed from a getter to a function. If you need the old syntax, use [this](https://github.com/ebensing/mongoose-within). + * + * @method intersects + * @memberOf Query + * @instance + * @param {Object} [arg] + * @return {Query} this + * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/ + * @see geoIntersects http://docs.mongodb.org/manual/reference/operator/geoIntersects/ + * @api public + */ + +/** + * Specifies a `$geometry` condition + * + * ####Example + * + * const polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]] + * query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA }) + * + * // or + * const polyB = [[ 0, 0 ], [ 1, 1 ]] + * query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB }) + * + * // or + * const polyC = [ 0, 0 ] + * query.where('loc').within().geometry({ type: 'Point', coordinates: polyC }) + * + * // or + * query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC }) + * + * The argument is assigned to the most recent path passed to `where()`. + * + * ####NOTE: + * + * `geometry()` **must** come after either `intersects()` or `within()`. + * + * The `object` argument must contain `type` and `coordinates` properties. + * - type {String} + * - coordinates {Array} + * + * @method geometry + * @memberOf Query + * @instance + * @param {Object} object Must contain a `type` property which is a String and a `coordinates` property which is an Array. See the examples. + * @return {Query} this + * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/ + * @see http://docs.mongodb.org/manual/release-notes/2.4/#new-geospatial-indexes-with-geojson-and-improved-spherical-geometry + * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing + * @api public + */ + +/** + * Specifies a `$near` or `$nearSphere` condition + * + * These operators return documents sorted by distance. + * + * ####Example + * + * query.where('loc').near({ center: [10, 10] }); + * query.where('loc').near({ center: [10, 10], maxDistance: 5 }); + * query.where('loc').near({ center: [10, 10], maxDistance: 5, spherical: true }); + * query.near('loc', { center: [10, 10], maxDistance: 5 }); + * + * @method near + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Object} val + * @return {Query} this + * @see $near http://docs.mongodb.org/manual/reference/operator/near/ + * @see $nearSphere http://docs.mongodb.org/manual/reference/operator/nearSphere/ + * @see $maxDistance http://docs.mongodb.org/manual/reference/operator/maxDistance/ + * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing + * @api public + */ + +/*! + * Overwriting mquery is needed to support a couple different near() forms found in older + * versions of mongoose + * near([1,1]) + * near(1,1) + * near(field, [1,2]) + * near(field, 1, 2) + * In addition to all of the normal forms supported by mquery + */ + +Query.prototype.near = function() { + const params = []; + const sphere = this._mongooseOptions.nearSphere; + + // TODO refactor + + if (arguments.length === 1) { + if (Array.isArray(arguments[0])) { + params.push({ center: arguments[0], spherical: sphere }); + } else if (typeof arguments[0] === 'string') { + // just passing a path + params.push(arguments[0]); + } else if (utils.isObject(arguments[0])) { + if (typeof arguments[0].spherical !== 'boolean') { + arguments[0].spherical = sphere; + } + params.push(arguments[0]); + } else { + throw new TypeError('invalid argument'); + } + } else if (arguments.length === 2) { + if (typeof arguments[0] === 'number' && typeof arguments[1] === 'number') { + params.push({ center: [arguments[0], arguments[1]], spherical: sphere }); + } else if (typeof arguments[0] === 'string' && Array.isArray(arguments[1])) { + params.push(arguments[0]); + params.push({ center: arguments[1], spherical: sphere }); + } else if (typeof arguments[0] === 'string' && utils.isObject(arguments[1])) { + params.push(arguments[0]); + if (typeof arguments[1].spherical !== 'boolean') { + arguments[1].spherical = sphere; + } + params.push(arguments[1]); + } else { + throw new TypeError('invalid argument'); + } + } else if (arguments.length === 3) { + if (typeof arguments[0] === 'string' && typeof arguments[1] === 'number' + && typeof arguments[2] === 'number') { + params.push(arguments[0]); + params.push({ center: [arguments[1], arguments[2]], spherical: sphere }); + } else { + throw new TypeError('invalid argument'); + } + } else { + throw new TypeError('invalid argument'); + } + + return Query.base.near.apply(this, params); +}; + +/** + * _DEPRECATED_ Specifies a `$nearSphere` condition + * + * ####Example + * + * query.where('loc').nearSphere({ center: [10, 10], maxDistance: 5 }); + * + * **Deprecated.** Use `query.near()` instead with the `spherical` option set to `true`. + * + * ####Example + * + * query.where('loc').near({ center: [10, 10], spherical: true }); + * + * @deprecated + * @see near() #query_Query-near + * @see $near http://docs.mongodb.org/manual/reference/operator/near/ + * @see $nearSphere http://docs.mongodb.org/manual/reference/operator/nearSphere/ + * @see $maxDistance http://docs.mongodb.org/manual/reference/operator/maxDistance/ + */ + +Query.prototype.nearSphere = function() { + this._mongooseOptions.nearSphere = true; + this.near.apply(this, arguments); + return this; +}; + +/** + * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js) + * This function *only* works for `find()` queries. + * You do not need to call this function explicitly, the JavaScript runtime + * will call it for you. + * + * ####Example + * + * for await (const doc of Model.aggregate([{ $sort: { name: 1 } }])) { + * console.log(doc.name); + * } + * + * Node.js 10.x supports async iterators natively without any flags. You can + * enable async iterators in Node.js 8.x using the [`--harmony_async_iteration` flag](https://github.com/tc39/proposal-async-iteration/issues/117#issuecomment-346695187). + * + * **Note:** This function is not if `Symbol.asyncIterator` is undefined. If + * `Symbol.asyncIterator` is undefined, that means your Node.js version does not + * support async iterators. + * + * @method Symbol.asyncIterator + * @memberOf Query + * @instance + * @api public + */ + +if (Symbol.asyncIterator != null) { + Query.prototype[Symbol.asyncIterator] = function() { + return this.cursor().transformNull()._transformForAsyncIterator(); + }; +} + +/** + * Specifies a `$polygon` condition + * + * ####Example + * + * query.where('loc').within().polygon([10,20], [13, 25], [7,15]) + * query.polygon('loc', [10,20], [13, 25], [7,15]) + * + * @method polygon + * @memberOf Query + * @instance + * @param {String|Array} [path] + * @param {Array|Object} [coordinatePairs...] + * @return {Query} this + * @see $polygon http://docs.mongodb.org/manual/reference/operator/polygon/ + * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing + * @api public + */ + +/** + * Specifies a `$box` condition + * + * ####Example + * + * const lowerLeft = [40.73083, -73.99756] + * const upperRight= [40.741404, -73.988135] + * + * query.where('loc').within().box(lowerLeft, upperRight) + * query.box({ ll : lowerLeft, ur : upperRight }) + * + * @method box + * @memberOf Query + * @instance + * @see $box http://docs.mongodb.org/manual/reference/operator/box/ + * @see within() Query#within #query_Query-within + * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing + * @param {Object} val + * @param [Array] Upper Right Coords + * @return {Query} this + * @api public + */ + +/*! + * this is needed to support the mongoose syntax of: + * box(field, { ll : [x,y], ur : [x2,y2] }) + * box({ ll : [x,y], ur : [x2,y2] }) + */ + +Query.prototype.box = function(ll, ur) { + if (!Array.isArray(ll) && utils.isObject(ll)) { + ur = ll.ur; + ll = ll.ll; + } + return Query.base.box.call(this, ll, ur); +}; + +/** + * Specifies a `$center` or `$centerSphere` condition. + * + * ####Example + * + * const area = { center: [50, 50], radius: 10, unique: true } + * query.where('loc').within().circle(area) + * // alternatively + * query.circle('loc', area); + * + * // spherical calculations + * const area = { center: [50, 50], radius: 10, unique: true, spherical: true } + * query.where('loc').within().circle(area) + * // alternatively + * query.circle('loc', area); + * + * @method circle + * @memberOf Query + * @instance + * @param {String} [path] + * @param {Object} area + * @return {Query} this + * @see $center http://docs.mongodb.org/manual/reference/operator/center/ + * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/ + * @see $geoWithin http://docs.mongodb.org/manual/reference/operator/geoWithin/ + * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing + * @api public + */ + +/** + * _DEPRECATED_ Alias for [circle](#query_Query-circle) + * + * **Deprecated.** Use [circle](#query_Query-circle) instead. + * + * @deprecated + * @method center + * @memberOf Query + * @instance + * @api public + */ + +Query.prototype.center = Query.base.circle; + +/** + * _DEPRECATED_ Specifies a `$centerSphere` condition + * + * **Deprecated.** Use [circle](#query_Query-circle) instead. + * + * ####Example + * + * const area = { center: [50, 50], radius: 10 }; + * query.where('loc').within().centerSphere(area); + * + * @deprecated + * @param {String} [path] + * @param {Object} val + * @return {Query} this + * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing + * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/ + * @api public + */ + +Query.prototype.centerSphere = function() { + if (arguments[0] && arguments[0].constructor.name === 'Object') { + arguments[0].spherical = true; + } + + if (arguments[1] && arguments[1].constructor.name === 'Object') { + arguments[1].spherical = true; + } + + Query.base.circle.apply(this, arguments); +}; + +/** + * Determines if field selection has been made. + * + * @method selected + * @memberOf Query + * @instance + * @return {Boolean} + * @api public + */ + +/** + * Determines if inclusive field selection has been made. + * + * query.selectedInclusively() // false + * query.select('name') + * query.selectedInclusively() // true + * + * @method selectedInclusively + * @memberOf Query + * @instance + * @return {Boolean} + * @api public + */ + +Query.prototype.selectedInclusively = function selectedInclusively() { + return isInclusive(this._fields); +}; + +/** + * Determines if exclusive field selection has been made. + * + * query.selectedExclusively() // false + * query.select('-name') + * query.selectedExclusively() // true + * query.selectedInclusively() // false + * + * @method selectedExclusively + * @memberOf Query + * @instance + * @return {Boolean} + * @api public + */ + +Query.prototype.selectedExclusively = function selectedExclusively() { + if (!this._fields) { + return false; + } + + const keys = Object.keys(this._fields); + for (const key of keys) { + if (key === '_id') { + continue; + } + if (this._fields[key] === 0 || this._fields[key] === false) { + return true; + } + } + + return false; +}; + +/*! + * Export + */ + +module.exports = Query; diff --git a/node_modules/mongoose/lib/queryhelpers.js b/node_modules/mongoose/lib/queryhelpers.js new file mode 100644 index 0000000..d0e93b6 --- /dev/null +++ b/node_modules/mongoose/lib/queryhelpers.js @@ -0,0 +1,307 @@ +'use strict'; + +/*! + * Module dependencies + */ + +const checkEmbeddedDiscriminatorKeyProjection = + require('./helpers/discriminator/checkEmbeddedDiscriminatorKeyProjection'); +const get = require('./helpers/get'); +const getDiscriminatorByValue = + require('./helpers/discriminator/getDiscriminatorByValue'); +const isDefiningProjection = require('./helpers/projection/isDefiningProjection'); +const clone = require('./helpers/clone'); + +/*! + * Prepare a set of path options for query population. + * + * @param {Query} query + * @param {Object} options + * @return {Array} + */ + +exports.preparePopulationOptions = function preparePopulationOptions(query, options) { + const _populate = query.options.populate; + const pop = Object.keys(_populate).reduce((vals, key) => vals.concat([_populate[key]]), []); + + // lean options should trickle through all queries + if (options.lean != null) { + pop. + filter(p => get(p, 'options.lean') == null). + forEach(makeLean(options.lean)); + } + + return pop; +}; + +/*! + * Prepare a set of path options for query population. This is the MongooseQuery + * version + * + * @param {Query} query + * @param {Object} options + * @return {Array} + */ + +exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ(query, options) { + const _populate = query._mongooseOptions.populate; + const pop = Object.keys(_populate).reduce((vals, key) => vals.concat([_populate[key]]), []); + + // lean options should trickle through all queries + if (options.lean != null) { + pop. + filter(p => get(p, 'options.lean') == null). + forEach(makeLean(options.lean)); + } + + const session = get(query, 'options.session', null); + if (session != null) { + pop.forEach(path => { + if (path.options == null) { + path.options = { session: session }; + return; + } + if (!('session' in path.options)) { + path.options.session = session; + } + }); + } + + const projection = query._fieldsForExec(); + pop.forEach(p => { + p._queryProjection = projection; + }); + + return pop; +}; + +/*! + * If the document is a mapped discriminator type, it returns a model instance for that type, otherwise, + * it returns an instance of the given model. + * + * @param {Model} model + * @param {Object} doc + * @param {Object} fields + * + * @return {Document} + */ +exports.createModel = function createModel(model, doc, fields, userProvidedFields) { + model.hooks.execPreSync('createModel', doc); + const discriminatorMapping = model.schema ? + model.schema.discriminatorMapping : + null; + + const key = discriminatorMapping && discriminatorMapping.isRoot ? + discriminatorMapping.key : + null; + + const value = doc[key]; + if (key && value && model.discriminators) { + const discriminator = model.discriminators[value] || getDiscriminatorByValue(model, value); + if (discriminator) { + const _fields = clone(userProvidedFields); + exports.applyPaths(_fields, discriminator.schema); + return new discriminator(undefined, _fields, true); + } + } + + return new model(undefined, fields, { + skipId: true, + isNew: false, + willInit: true + }); +}; + +/*! + * ignore + */ + +exports.applyPaths = function applyPaths(fields, schema) { + // determine if query is selecting or excluding fields + let exclude; + let keys; + let keyIndex; + + if (fields) { + keys = Object.keys(fields); + keyIndex = keys.length; + + while (keyIndex--) { + if (keys[keyIndex][0] === '+') { + continue; + } + const field = fields[keys[keyIndex]]; + // Skip `$meta` and `$slice` + if (!isDefiningProjection(field)) { + continue; + } + exclude = !field; + break; + } + } + + // if selecting, apply default schematype select:true fields + // if excluding, apply schematype select:false fields + + const selected = []; + const excluded = []; + const stack = []; + + analyzeSchema(schema); + + switch (exclude) { + case true: + for (const fieldName of excluded) { + fields[fieldName] = 0; + } + break; + case false: + if (schema && + schema.paths['_id'] && + schema.paths['_id'].options && + schema.paths['_id'].options.select === false) { + fields._id = 0; + } + + for (const fieldName of selected) { + fields[fieldName] = fields[fieldName] || 1; + } + break; + case undefined: + if (fields == null) { + break; + } + // Any leftover plus paths must in the schema, so delete them (gh-7017) + for (const key of Object.keys(fields || {})) { + if (key.startsWith('+')) { + delete fields[key]; + } + } + + // user didn't specify fields, implies returning all fields. + // only need to apply excluded fields and delete any plus paths + for (const fieldName of excluded) { + fields[fieldName] = 0; + } + break; + } + + function analyzeSchema(schema, prefix) { + prefix || (prefix = ''); + + // avoid recursion + if (stack.indexOf(schema) !== -1) { + return []; + } + stack.push(schema); + + const addedPaths = []; + schema.eachPath(function(path, type) { + if (prefix) path = prefix + '.' + path; + + const addedPath = analyzePath(path, type); + if (addedPath != null) { + addedPaths.push(addedPath); + } + + // nested schemas + if (type.schema) { + const _addedPaths = analyzeSchema(type.schema, path); + + // Special case: if discriminator key is the only field that would + // be projected in, remove it. + if (exclude === false) { + checkEmbeddedDiscriminatorKeyProjection(fields, path, type.schema, + selected, _addedPaths); + } + } + }); + + stack.pop(); + return addedPaths; + } + + function analyzePath(path, type) { + const plusPath = '+' + path; + const hasPlusPath = fields && plusPath in fields; + if (hasPlusPath) { + // forced inclusion + delete fields[plusPath]; + } + + if (typeof type.selected !== 'boolean') return; + + if (hasPlusPath) { + // forced inclusion + delete fields[plusPath]; + + // if there are other fields being included, add this one + // if no other included fields, leave this out (implied inclusion) + if (exclude === false && keys.length > 1 && !~keys.indexOf(path)) { + fields[path] = 1; + } + + return; + } + + // check for parent exclusions + const pieces = path.split('.'); + let cur = ''; + for (let i = 0; i < pieces.length; ++i) { + cur += cur.length ? '.' + pieces[i] : pieces[i]; + if (excluded.indexOf(cur) !== -1) { + return; + } + } + + // Special case: if user has included a parent path of a discriminator key, + // don't explicitly project in the discriminator key because that will + // project out everything else under the parent path + if (!exclude && get(type, 'options.$skipDiscriminatorCheck', false)) { + let cur = ''; + for (let i = 0; i < pieces.length; ++i) { + cur += (cur.length === 0 ? '' : '.') + pieces[i]; + const projection = get(fields, cur, false) || get(fields, cur + '.$', false); + if (projection && typeof projection !== 'object') { + return; + } + } + } + + (type.selected ? selected : excluded).push(path); + return path; + } +}; + +/*! + * Set each path query option to lean + * + * @param {Object} option + */ + +function makeLean(val) { + return function(option) { + option.options || (option.options = {}); + option.options.lean = val; + }; +} + +/*! + * Handle the `WriteOpResult` from the server + */ + +exports.handleDeleteWriteOpResult = function handleDeleteWriteOpResult(callback) { + return function _handleDeleteWriteOpResult(error, res) { + if (error) { + return callback(error); + } + const mongooseResult = Object.assign({}, res.result); + if (get(res, 'result.n', null) != null) { + mongooseResult.deletedCount = res.result.n; + } + if (res.deletedCount != null) { + mongooseResult.deletedCount = res.deletedCount; + } + return callback(null, mongooseResult); + }; +}; diff --git a/node_modules/mongoose/lib/schema.js b/node_modules/mongoose/lib/schema.js new file mode 100644 index 0000000..2abd0a0 --- /dev/null +++ b/node_modules/mongoose/lib/schema.js @@ -0,0 +1,2144 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const EventEmitter = require('events').EventEmitter; +const Kareem = require('kareem'); +const MongooseError = require('./error/mongooseError'); +const SchemaType = require('./schematype'); +const SchemaTypeOptions = require('./options/SchemaTypeOptions'); +const VirtualOptions = require('./options/VirtualOptions'); +const VirtualType = require('./virtualtype'); +const addAutoId = require('./helpers/schema/addAutoId'); +const arrayParentSymbol = require('./helpers/symbols').arrayParentSymbol; +const get = require('./helpers/get'); +const getIndexes = require('./helpers/schema/getIndexes'); +const merge = require('./helpers/schema/merge'); +const mpath = require('mpath'); +const readPref = require('./driver').get().ReadPreference; +const setupTimestamps = require('./helpers/timestamps/setupTimestamps'); +const util = require('util'); +const utils = require('./utils'); +const validateRef = require('./helpers/populate/validateRef'); + +let MongooseTypes; + +const queryHooks = require('./helpers/query/applyQueryMiddleware'). + middlewareFunctions; +const documentHooks = require('./helpers/model/applyHooks').middlewareFunctions; +const hookNames = queryHooks.concat(documentHooks). + reduce((s, hook) => s.add(hook), new Set()); + +let id = 0; + +/** + * Schema constructor. + * + * ####Example: + * + * const child = new Schema({ name: String }); + * const schema = new Schema({ name: String, age: Number, children: [child] }); + * const Tree = mongoose.model('Tree', schema); + * + * // setting schema options + * new Schema({ name: String }, { _id: false, autoIndex: false }) + * + * ####Options: + * + * - [autoIndex](/docs/guide.html#autoIndex): bool - defaults to null (which means use the connection's autoIndex option) + * - [autoCreate](/docs/guide.html#autoCreate): bool - defaults to null (which means use the connection's autoCreate option) + * - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true + * - [capped](/docs/guide.html#capped): bool - defaults to false + * - [collection](/docs/guide.html#collection): string - no default + * - [id](/docs/guide.html#id): bool - defaults to true + * - [_id](/docs/guide.html#_id): bool - defaults to true + * - [minimize](/docs/guide.html#minimize): bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true + * - [read](/docs/guide.html#read): string + * - [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/) + * - [shardKey](/docs/guide.html#shardKey): object - defaults to `null` + * - [strict](/docs/guide.html#strict): bool - defaults to true + * - [strictQuery](/docs/guide.html#strictQuery): bool - defaults to false + * - [toJSON](/docs/guide.html#toJSON) - object - no default + * - [toObject](/docs/guide.html#toObject) - object - no default + * - [typeKey](/docs/guide.html#typeKey) - string - defaults to 'type' + * - [typePojoToMixed](/docs/guide.html#typePojoToMixed) - boolean - defaults to true. Determines whether a type set to a POJO becomes a Mixed path or a Subdocument + * - [useNestedStrict](/docs/guide.html#useNestedStrict) - boolean - defaults to false + * - [validateBeforeSave](/docs/guide.html#validateBeforeSave) - bool - defaults to `true` + * - [versionKey](/docs/guide.html#versionKey): string or object - defaults to "__v" + * - [collation](/docs/guide.html#collation): object - defaults to null (which means use no collation) + * - [selectPopulatedPaths](/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true` + * - [skipVersioning](/docs/guide.html#skipVersioning): object - paths to exclude from versioning + * - [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. + * - [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. + * + * ####Options for Nested Schemas: + * - `excludeIndexes`: bool - defaults to `false`. If `true`, skip building indexes on this schema's paths. + * + * ####Note: + * + * _When nesting schemas, (`children` in the example above), always declare the child schema first before passing it into its parent._ + * + * @param {Object|Schema|Array} [definition] Can be one of: object describing schema paths, or schema to copy, or array of objects and schemas + * @param {Object} [options] + * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter + * @event `init`: Emitted after the schema is compiled into a `Model`. + * @api public + */ + +function Schema(obj, options) { + if (!(this instanceof Schema)) { + return new Schema(obj, options); + } + + this.obj = obj; + this.paths = {}; + this.aliases = {}; + this.subpaths = {}; + this.virtuals = {}; + this.singleNestedPaths = {}; + this.nested = {}; + this.inherits = {}; + this.callQueue = []; + this._indexes = []; + this.methods = {}; + this.methodOptions = {}; + this.statics = {}; + this.tree = {}; + this.query = {}; + this.childSchemas = []; + this.plugins = []; + // For internal debugging. Do not use this to try to save a schema in MDB. + this.$id = ++id; + + this.s = { + hooks: new Kareem() + }; + + this.options = this.defaultOptions(options); + + // build paths + if (Array.isArray(obj)) { + for (const definition of obj) { + this.add(definition); + } + } else if (obj) { + this.add(obj); + } + + // check if _id's value is a subdocument (gh-2276) + const _idSubDoc = obj && obj._id && utils.isObject(obj._id); + + // ensure the documents get an auto _id unless disabled + const auto_id = !this.paths['_id'] && + (!this.options.noId && this.options._id) && !_idSubDoc; + + if (auto_id) { + addAutoId(this); + } + + this.setupTimestamp(this.options.timestamps); +} + +/*! + * Create virtual properties with alias field + */ +function aliasFields(schema, paths) { + paths = paths || Object.keys(schema.paths); + for (const path of paths) { + const options = get(schema.paths[path], 'options'); + if (options == null) { + continue; + } + + const prop = schema.paths[path].path; + const alias = options.alias; + + if (!alias) { + continue; + } + + if (typeof alias !== 'string') { + throw new Error('Invalid value for alias option on ' + prop + ', got ' + alias); + } + + schema.aliases[alias] = prop; + + schema. + virtual(alias). + get((function(p) { + return function() { + if (typeof this.get === 'function') { + return this.get(p); + } + return this[p]; + }; + })(prop)). + set((function(p) { + return function(v) { + return this.$set(p, v); + }; + })(prop)); + } +} + +/*! + * Inherit from EventEmitter. + */ +Schema.prototype = Object.create(EventEmitter.prototype); +Schema.prototype.constructor = Schema; +Schema.prototype.instanceOfSchema = true; + +/*! + * ignore + */ + +Object.defineProperty(Schema.prototype, '$schemaType', { + configurable: false, + enumerable: false, + writable: true +}); + +/** + * Array of child schemas (from document arrays and single nested subdocs) + * and their corresponding compiled models. Each element of the array is + * an object with 2 properties: `schema` and `model`. + * + * This property is typically only useful for plugin authors and advanced users. + * You do not need to interact with this property at all to use mongoose. + * + * @api public + * @property childSchemas + * @memberOf Schema + * @instance + */ + +Object.defineProperty(Schema.prototype, 'childSchemas', { + configurable: false, + enumerable: true, + writable: true +}); + +/** + * The original object passed to the schema constructor + * + * ####Example: + * + * const schema = new Schema({ a: String }).add({ b: String }); + * schema.obj; // { a: String } + * + * @api public + * @property obj + * @memberOf Schema + * @instance + */ + +Schema.prototype.obj; + +/** + * The paths defined on this schema. The keys are the top-level paths + * in this schema, and the values are instances of the SchemaType class. + * + * ####Example: + * const schema = new Schema({ name: String }, { _id: false }); + * schema.paths; // { name: SchemaString { ... } } + * + * schema.add({ age: Number }); + * schema.paths; // { name: SchemaString { ... }, age: SchemaNumber { ... } } + * + * @api public + * @property paths + * @memberOf Schema + * @instance + */ + +Schema.prototype.paths; + +/** + * Schema as a tree + * + * ####Example: + * { + * '_id' : ObjectId + * , 'nested' : { + * 'key' : String + * } + * } + * + * @api private + * @property tree + * @memberOf Schema + * @instance + */ + +Schema.prototype.tree; + +/** + * Returns a deep copy of the schema + * + * ####Example: + * + * const schema = new Schema({ name: String }); + * const clone = schema.clone(); + * clone === schema; // false + * clone.path('name'); // SchemaString { ... } + * + * @return {Schema} the cloned schema + * @api public + * @memberOf Schema + * @instance + */ + +Schema.prototype.clone = function() { + const Constructor = this.base == null ? Schema : this.base.Schema; + + const s = new Constructor({}, this._userProvidedOptions); + s.base = this.base; + s.obj = this.obj; + s.options = utils.clone(this.options); + s.callQueue = this.callQueue.map(function(f) { return f; }); + s.methods = utils.clone(this.methods); + s.methodOptions = utils.clone(this.methodOptions); + s.statics = utils.clone(this.statics); + s.query = utils.clone(this.query); + s.plugins = Array.prototype.slice.call(this.plugins); + s._indexes = utils.clone(this._indexes); + s.s.hooks = this.s.hooks.clone(); + + s.tree = utils.clone(this.tree); + s.paths = utils.clone(this.paths); + s.nested = utils.clone(this.nested); + s.subpaths = utils.clone(this.subpaths); + s.singleNestedPaths = utils.clone(this.singleNestedPaths); + s.childSchemas = gatherChildSchemas(s); + + s.virtuals = utils.clone(this.virtuals); + s.$globalPluginsApplied = this.$globalPluginsApplied; + s.$isRootDiscriminator = this.$isRootDiscriminator; + s.$implicitlyCreated = this.$implicitlyCreated; + + if (this.discriminatorMapping != null) { + s.discriminatorMapping = Object.assign({}, this.discriminatorMapping); + } + if (this.discriminators != null) { + s.discriminators = Object.assign({}, this.discriminators); + } + + s.aliases = Object.assign({}, this.aliases); + + // Bubble up `init` for backwards compat + s.on('init', v => this.emit('init', v)); + + return s; +}; + +/** + * Returns a new schema that has the picked `paths` from this schema. + * + * This method is analagous to [Lodash's `pick()` function](https://lodash.com/docs/4.17.15#pick) for Mongoose schemas. + * + * ####Example: + * + * const schema = Schema({ name: String, age: Number }); + * // Creates a new schema with the same `name` path as `schema`, + * // but no `age` path. + * const newSchema = schema.pick(['name']); + * + * newSchema.path('name'); // SchemaString { ... } + * newSchema.path('age'); // undefined + * + * @param {Array} paths list of paths to pick + * @param {Object} [options] options to pass to the schema constructor. Defaults to `this.options` if not set. + * @return {Schema} + * @api public + */ + +Schema.prototype.pick = function(paths, options) { + const newSchema = new Schema({}, options || this.options); + if (!Array.isArray(paths)) { + throw new MongooseError('Schema#pick() only accepts an array argument, ' + + 'got "' + typeof paths + '"'); + } + + for (const path of paths) { + if (this.nested[path]) { + newSchema.add({ [path]: get(this.tree, path) }); + } else { + const schematype = this.path(path); + if (schematype == null) { + throw new MongooseError('Path `' + path + '` is not in the schema'); + } + newSchema.add({ [path]: schematype }); + } + } + + return newSchema; +}; + +/** + * Returns default options for this schema, merged with `options`. + * + * @param {Object} options + * @return {Object} + * @api private + */ + +Schema.prototype.defaultOptions = function(options) { + if (options && options.safe === false) { + options.safe = { w: 0 }; + } + + if (options && options.safe && options.safe.w === 0) { + // if you turn off safe writes, then versioning goes off as well + options.versionKey = false; + } + + this._userProvidedOptions = options == null ? {} : utils.clone(options); + + const baseOptions = get(this, 'base.options', {}); + options = utils.options({ + strict: 'strict' in baseOptions ? baseOptions.strict : true, + strictQuery: 'strictQuery' in baseOptions ? baseOptions.strictQuery : false, + bufferCommands: true, + capped: false, // { size, max, autoIndexId } + versionKey: '__v', + optimisticConcurrency: false, + discriminatorKey: '__t', + minimize: true, + autoIndex: null, + shardKey: null, + read: null, + validateBeforeSave: true, + // the following are only applied at construction time + noId: false, // deprecated, use { _id: false } + _id: true, + noVirtualId: false, // deprecated, use { id: false } + id: true, + typeKey: 'type', + typePojoToMixed: 'typePojoToMixed' in baseOptions ? baseOptions.typePojoToMixed : true + }, utils.clone(options)); + + if (options.read) { + options.read = readPref(options.read); + } + + if (options.optimisticConcurrency && !options.versionKey) { + throw new MongooseError('Must set `versionKey` if using `optimisticConcurrency`'); + } + + return options; +}; + +/** + * Adds key path / schema type pairs to this schema. + * + * ####Example: + * + * const ToySchema = new Schema(); + * ToySchema.add({ name: 'string', color: 'string', price: 'number' }); + * + * const TurboManSchema = new Schema(); + * // You can also `add()` another schema and copy over all paths, virtuals, + * // getters, setters, indexes, methods, and statics. + * TurboManSchema.add(ToySchema).add({ year: Number }); + * + * @param {Object|Schema} obj plain object with paths to add, or another schema + * @param {String} [prefix] path to prefix the newly added paths with + * @return {Schema} the Schema instance + * @api public + */ + +Schema.prototype.add = function add(obj, prefix) { + if (obj instanceof Schema || (obj != null && obj.instanceOfSchema)) { + merge(this, obj); + + return this; + } + + // Special case: setting top-level `_id` to false should convert to disabling + // the `_id` option. This behavior never worked before 5.4.11 but numerous + // codebases use it (see gh-7516, gh-7512). + if (obj._id === false && prefix == null) { + this.options._id = false; + } + + prefix = prefix || ''; + const keys = Object.keys(obj); + + for (const key of keys) { + const fullPath = prefix + key; + + if (obj[key] == null) { + throw new TypeError('Invalid value for schema path `' + fullPath + + '`, got value "' + obj[key] + '"'); + } + // Retain `_id: false` but don't set it as a path, re: gh-8274. + if (key === '_id' && obj[key] === false) { + continue; + } + if (obj[key] instanceof VirtualType || get(obj[key], 'constructor.name', null) === 'VirtualType') { + this.virtual(obj[key]); + continue; + } + + if (Array.isArray(obj[key]) && obj[key].length === 1 && obj[key][0] == null) { + throw new TypeError('Invalid value for schema Array path `' + fullPath + + '`, got value "' + obj[key][0] + '"'); + } + + if (!(utils.isPOJO(obj[key]) || obj[key] instanceof SchemaTypeOptions)) { + // Special-case: Non-options definitely a path so leaf at this node + // Examples: Schema instances, SchemaType instances + if (prefix) { + this.nested[prefix.substr(0, prefix.length - 1)] = true; + } + this.path(prefix + key, obj[key]); + } else if (Object.keys(obj[key]).length < 1) { + // Special-case: {} always interpreted as Mixed path so leaf at this node + if (prefix) { + this.nested[prefix.substr(0, prefix.length - 1)] = true; + } + this.path(fullPath, obj[key]); // mixed type + } else if (!obj[key][this.options.typeKey] || (this.options.typeKey === 'type' && obj[key].type.type)) { + // Special-case: POJO with no bona-fide type key - interpret as tree of deep paths so recurse + // nested object { last: { name: String }} + this.nested[fullPath] = true; + this.add(obj[key], fullPath + '.'); + } else { + // There IS a bona-fide type key that may also be a POJO + if (!this.options.typePojoToMixed && utils.isPOJO(obj[key][this.options.typeKey])) { + // If a POJO is the value of a type key, make it a subdocument + if (prefix) { + this.nested[prefix.substr(0, prefix.length - 1)] = true; + } + // Propage `typePojoToMixed` to implicitly created schemas + const opts = { typePojoToMixed: false }; + const _schema = new Schema(obj[key][this.options.typeKey], opts); + const schemaWrappedPath = Object.assign({}, obj[key], { [this.options.typeKey]: _schema }); + this.path(prefix + key, schemaWrappedPath); + } else { + // Either the type is non-POJO or we interpret it as Mixed anyway + if (prefix) { + this.nested[prefix.substr(0, prefix.length - 1)] = true; + } + this.path(prefix + key, obj[key]); + } + } + } + + const addedKeys = Object.keys(obj). + map(key => prefix ? prefix + key : key); + aliasFields(this, addedKeys); + return this; +}; + +/** + * Reserved document keys. + * + * Keys in this object are names that are rejected in schema declarations + * because they conflict with Mongoose functionality. If you create a schema + * using `new Schema()` with one of these property names, Mongoose will throw + * an error. + * + * - _posts + * - _pres + * - collection + * - emit + * - errors + * - get + * - init + * - isModified + * - isNew + * - listeners + * - modelName + * - on + * - once + * - populated + * - prototype + * - remove + * - removeListener + * - save + * - schema + * - toObject + * - validate + * + * _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. + * + * const schema = new Schema(..); + * schema.methods.init = function () {} // potentially breaking + */ + +Schema.reserved = Object.create(null); +Schema.prototype.reserved = Schema.reserved; +const reserved = Schema.reserved; +// Core object +reserved['prototype'] = +// EventEmitter +reserved.emit = +reserved.listeners = +reserved.on = +reserved.removeListener = +// document properties and functions +reserved.collection = +reserved.errors = +reserved.get = +reserved.init = +reserved.isModified = +reserved.isNew = +reserved.populated = +reserved.remove = +reserved.save = +reserved.schema = +reserved.toObject = +reserved.validate = 1; + +/*! + * Document keys to print warnings for + */ + +const warnings = {}; +warnings.increment = '`increment` should not be used as a schema path name ' + + 'unless you have disabled versioning.'; + +/** + * Gets/sets schema paths. + * + * Sets a path (if arity 2) + * Gets a path (if arity 1) + * + * ####Example + * + * schema.path('name') // returns a SchemaType + * schema.path('name', Number) // changes the schemaType of `name` to Number + * + * @param {String} path + * @param {Object} constructor + * @api public + */ + +Schema.prototype.path = function(path, obj) { + // Convert to '.$' to check subpaths re: gh-6405 + const cleanPath = _pathToPositionalSyntax(path); + if (obj === undefined) { + let schematype = _getPath(this, path, cleanPath); + if (schematype != null) { + return schematype; + } + + // Look for maps + const mapPath = getMapPath(this, path); + if (mapPath != null) { + return mapPath; + } + + // Look if a parent of this path is mixed + schematype = this.hasMixedParent(cleanPath); + if (schematype != null) { + return schematype; + } + + // subpaths? + return /\.\d+\.?.*$/.test(path) + ? getPositionalPath(this, path) + : undefined; + } + + // some path names conflict with document methods + const firstPieceOfPath = path.split('.')[0]; + if (reserved[firstPieceOfPath]) { + throw new Error('`' + firstPieceOfPath + '` may not be used as a schema pathname'); + } + + if (warnings[path]) { + console.log('WARN: ' + warnings[path]); + } + + if (typeof obj === 'object' && utils.hasUserDefinedProperty(obj, 'ref')) { + validateRef(obj.ref, path); + } + + // update the tree + const subpaths = path.split(/\./); + const last = subpaths.pop(); + let branch = this.tree; + let fullPath = ''; + + for (const sub of subpaths) { + fullPath = fullPath += (fullPath.length > 0 ? '.' : '') + sub; + if (!branch[sub]) { + this.nested[fullPath] = true; + branch[sub] = {}; + } + if (typeof branch[sub] !== 'object') { + const msg = 'Cannot set nested path `' + path + '`. ' + + 'Parent path `' + + fullPath + + '` already set to type ' + branch[sub].name + + '.'; + throw new Error(msg); + } + branch = branch[sub]; + } + + branch[last] = utils.clone(obj); + + this.paths[path] = this.interpretAsType(path, obj, this.options); + const schemaType = this.paths[path]; + + if (schemaType.$isSchemaMap) { + // Maps can have arbitrary keys, so `$*` is internal shorthand for "any key" + // The '$' is to imply this path should never be stored in MongoDB so we + // can easily build a regexp out of this path, and '*' to imply "any key." + const mapPath = path + '.$*'; + let _mapType = { type: {} }; + if (utils.hasUserDefinedProperty(obj, 'of')) { + const isInlineSchema = utils.isPOJO(obj.of) && + Object.keys(obj.of).length > 0 && + !utils.hasUserDefinedProperty(obj.of, this.options.typeKey); + _mapType = isInlineSchema ? new Schema(obj.of) : obj.of; + } + this.paths[mapPath] = this.interpretAsType(mapPath, + _mapType, this.options); + schemaType.$__schemaType = this.paths[mapPath]; + } + + if (schemaType.$isSingleNested) { + for (const key in schemaType.schema.paths) { + this.singleNestedPaths[path + '.' + key] = schemaType.schema.paths[key]; + } + for (const key in schemaType.schema.singleNestedPaths) { + this.singleNestedPaths[path + '.' + key] = + schemaType.schema.singleNestedPaths[key]; + } + for (const key in schemaType.schema.subpaths) { + this.singleNestedPaths[path + '.' + key] = + schemaType.schema.subpaths[key]; + } + for (const key in schemaType.schema.nested) { + this.singleNestedPaths[path + '.' + key] = 'nested'; + } + + Object.defineProperty(schemaType.schema, 'base', { + configurable: true, + enumerable: false, + writable: false, + value: this.base + }); + + schemaType.caster.base = this.base; + this.childSchemas.push({ + schema: schemaType.schema, + model: schemaType.caster + }); + } else if (schemaType.$isMongooseDocumentArray) { + Object.defineProperty(schemaType.schema, 'base', { + configurable: true, + enumerable: false, + writable: false, + value: this.base + }); + + schemaType.casterConstructor.base = this.base; + this.childSchemas.push({ + schema: schemaType.schema, + model: schemaType.casterConstructor + }); + } + + if (schemaType.$isMongooseArray && schemaType.caster instanceof SchemaType) { + let arrayPath = path; + let _schemaType = schemaType; + + const toAdd = []; + while (_schemaType.$isMongooseArray) { + arrayPath = arrayPath + '.$'; + + // Skip arrays of document arrays + if (_schemaType.$isMongooseDocumentArray) { + _schemaType.$embeddedSchemaType._arrayPath = arrayPath; + _schemaType = _schemaType.$embeddedSchemaType.clone(); + } else { + _schemaType.caster._arrayPath = arrayPath; + _schemaType = _schemaType.caster.clone(); + } + + _schemaType.path = arrayPath; + toAdd.push(_schemaType); + } + + for (const _schemaType of toAdd) { + this.subpaths[_schemaType.path] = _schemaType; + } + } + + if (schemaType.$isMongooseDocumentArray) { + for (const key of Object.keys(schemaType.schema.paths)) { + this.subpaths[path + '.' + key] = schemaType.schema.paths[key]; + schemaType.schema.paths[key].$isUnderneathDocArray = true; + } + for (const key of Object.keys(schemaType.schema.subpaths)) { + this.subpaths[path + '.' + key] = schemaType.schema.subpaths[key]; + schemaType.schema.subpaths[key].$isUnderneathDocArray = true; + } + for (const key of Object.keys(schemaType.schema.singleNestedPaths)) { + if (typeof schemaType.schema.singleNestedPaths[cleanPath] !== 'object') { + continue; + } + this.subpaths[path + '.' + key] = schemaType.schema.singleNestedPaths[key]; + schemaType.schema.singleNestedPaths[key].$isUnderneathDocArray = true; + } + } + + return this; +}; + +/*! + * ignore + */ + +function gatherChildSchemas(schema) { + const childSchemas = []; + + for (const path of Object.keys(schema.paths)) { + const schematype = schema.paths[path]; + if (schematype.$isMongooseDocumentArray || schematype.$isSingleNested) { + childSchemas.push({ schema: schematype.schema, model: schematype.caster }); + } + } + + return childSchemas; +} + +/*! + * ignore + */ + +function _getPath(schema, path, cleanPath) { + if (schema.paths.hasOwnProperty(path)) { + return schema.paths[path]; + } + if (schema.subpaths.hasOwnProperty(cleanPath)) { + return schema.subpaths[cleanPath]; + } + if (schema.singleNestedPaths.hasOwnProperty(cleanPath) && typeof schema.singleNestedPaths[cleanPath] === 'object') { + return schema.singleNestedPaths[cleanPath]; + } + + return null; +} + +/*! + * ignore + */ + +function _pathToPositionalSyntax(path) { + if (!/\.\d+/.test(path)) { + return path; + } + return path.replace(/\.\d+\./g, '.$.').replace(/\.\d+$/, '.$'); +} + +/*! + * ignore + */ + +function getMapPath(schema, path) { + for (const _path of Object.keys(schema.paths)) { + if (!_path.includes('.$*')) { + continue; + } + const re = new RegExp('^' + _path.replace(/\.\$\*/g, '\\.[^.]+') + '$'); + if (re.test(path)) { + return schema.paths[_path]; + } + } + + return null; +} + +/** + * The Mongoose instance this schema is associated with + * + * @property base + * @api private + */ + +Object.defineProperty(Schema.prototype, 'base', { + configurable: true, + enumerable: false, + writable: true, + value: null +}); + +/** + * Converts type arguments into Mongoose Types. + * + * @param {String} path + * @param {Object} obj constructor + * @api private + */ + +Schema.prototype.interpretAsType = function(path, obj, options) { + if (obj instanceof SchemaType) { + const clone = obj.clone(); + clone.path = path; + return clone; + } + + // If this schema has an associated Mongoose object, use the Mongoose object's + // copy of SchemaTypes re: gh-7158 gh-6933 + const MongooseTypes = this.base != null ? this.base.Schema.Types : Schema.Types; + + if (!utils.isPOJO(obj) && !(obj instanceof SchemaTypeOptions)) { + const constructorName = utils.getFunctionName(obj.constructor); + if (constructorName !== 'Object') { + const oldObj = obj; + obj = {}; + obj[options.typeKey] = oldObj; + } + } + + // Get the type making sure to allow keys named "type" + // and default to mixed if not specified. + // { type: { type: String, default: 'freshcut' } } + let type = obj[options.typeKey] && (options.typeKey !== 'type' || !obj.type.type) + ? obj[options.typeKey] + : {}; + let name; + + if (utils.isPOJO(type) || type === 'mixed') { + return new MongooseTypes.Mixed(path, obj); + } + + if (Array.isArray(type) || type === Array || type === 'array' || type === MongooseTypes.Array) { + // if it was specified through { type } look for `cast` + let cast = (type === Array || type === 'array') + ? obj.cast + : type[0]; + + if (cast && cast.instanceOfSchema) { + return new MongooseTypes.DocumentArray(path, cast, obj); + } + if (cast && + cast[options.typeKey] && + cast[options.typeKey].instanceOfSchema) { + return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast); + } + + if (Array.isArray(cast)) { + return new MongooseTypes.Array(path, this.interpretAsType(path, cast, options), obj); + } + + if (typeof cast === 'string') { + cast = MongooseTypes[cast.charAt(0).toUpperCase() + cast.substring(1)]; + } else if (cast && (!cast[options.typeKey] || (options.typeKey === 'type' && cast.type.type)) + && utils.isPOJO(cast)) { + if (Object.keys(cast).length) { + // The `minimize` and `typeKey` options propagate to child schemas + // declared inline, like `{ arr: [{ val: { $type: String } }] }`. + // See gh-3560 + const childSchemaOptions = { minimize: options.minimize }; + if (options.typeKey) { + childSchemaOptions.typeKey = options.typeKey; + } + // propagate 'strict' option to child schema + if (options.hasOwnProperty('strict')) { + childSchemaOptions.strict = options.strict; + } + if (options.hasOwnProperty('typePojoToMixed')) { + childSchemaOptions.typePojoToMixed = options.typePojoToMixed; + } + + if (this._userProvidedOptions.hasOwnProperty('_id')) { + childSchemaOptions._id = this._userProvidedOptions._id; + } else if (Schema.Types.DocumentArray.defaultOptions && + Schema.Types.DocumentArray.defaultOptions._id != null) { + childSchemaOptions._id = Schema.Types.DocumentArray.defaultOptions._id; + } + + const childSchema = new Schema(cast, childSchemaOptions); + childSchema.$implicitlyCreated = true; + return new MongooseTypes.DocumentArray(path, childSchema, obj); + } else { + // Special case: empty object becomes mixed + return new MongooseTypes.Array(path, MongooseTypes.Mixed, obj); + } + } + + if (cast) { + type = cast[options.typeKey] && (options.typeKey !== 'type' || !cast.type.type) + ? cast[options.typeKey] + : cast; + + name = typeof type === 'string' + ? type + : type.schemaName || utils.getFunctionName(type); + + if (!MongooseTypes.hasOwnProperty(name)) { + throw new TypeError('Invalid schema configuration: ' + + `\`${name}\` is not a valid type within the array \`${path}\`.` + + 'See http://bit.ly/mongoose-schematypes for a list of valid schema types.'); + } + } + + return new MongooseTypes.Array(path, cast || MongooseTypes.Mixed, obj, options); + } + + if (type && type.instanceOfSchema) { + return new MongooseTypes.Embedded(type, path, obj); + } + + if (Buffer.isBuffer(type)) { + name = 'Buffer'; + } else if (typeof type === 'function' || typeof type === 'object') { + name = type.schemaName || utils.getFunctionName(type); + } else { + name = type == null ? '' + type : type.toString(); + } + + if (name) { + name = name.charAt(0).toUpperCase() + name.substring(1); + } + // Special case re: gh-7049 because the bson `ObjectID` class' capitalization + // doesn't line up with Mongoose's. + if (name === 'ObjectID') { + name = 'ObjectId'; + } + + if (MongooseTypes[name] == null) { + throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` + + `a valid type at path \`${path}\`. See ` + + 'http://bit.ly/mongoose-schematypes for a list of valid schema types.'); + } + + return new MongooseTypes[name](path, obj); +}; + +/** + * Iterates the schemas paths similar to Array#forEach. + * + * The callback is passed the pathname and the schemaType instance. + * + * ####Example: + * + * const userSchema = new Schema({ name: String, registeredAt: Date }); + * userSchema.eachPath((pathname, schematype) => { + * // Prints twice: + * // name SchemaString { ... } + * // registeredAt SchemaDate { ... } + * console.log(pathname, schematype); + * }); + * + * @param {Function} fn callback function + * @return {Schema} this + * @api public + */ + +Schema.prototype.eachPath = function(fn) { + const keys = Object.keys(this.paths); + const len = keys.length; + + for (let i = 0; i < len; ++i) { + fn(keys[i], this.paths[keys[i]]); + } + + return this; +}; + +/** + * Returns an Array of path strings that are required by this schema. + * + * ####Example: + * const s = new Schema({ + * name: { type: String, required: true }, + * age: { type: String, required: true }, + * notes: String + * }); + * s.requiredPaths(); // [ 'age', 'name' ] + * + * @api public + * @param {Boolean} invalidate refresh the cache + * @return {Array} + */ + +Schema.prototype.requiredPaths = function requiredPaths(invalidate) { + if (this._requiredpaths && !invalidate) { + return this._requiredpaths; + } + + const paths = Object.keys(this.paths); + let i = paths.length; + const ret = []; + + while (i--) { + const path = paths[i]; + if (this.paths[path].isRequired) { + ret.push(path); + } + } + this._requiredpaths = ret; + return this._requiredpaths; +}; + +/** + * Returns indexes from fields and schema-level indexes (cached). + * + * @api private + * @return {Array} + */ + +Schema.prototype.indexedPaths = function indexedPaths() { + if (this._indexedpaths) { + return this._indexedpaths; + } + this._indexedpaths = this.indexes(); + return this._indexedpaths; +}; + +/** + * Returns the pathType of `path` for this schema. + * + * Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path. + * + * ####Example: + * const s = new Schema({ name: String, nested: { foo: String } }); + * s.virtual('foo').get(() => 42); + * s.pathType('name'); // "real" + * s.pathType('nested'); // "nested" + * s.pathType('foo'); // "virtual" + * s.pathType('fail'); // "adhocOrUndefined" + * + * @param {String} path + * @return {String} + * @api public + */ + +Schema.prototype.pathType = function(path) { + // Convert to '.$' to check subpaths re: gh-6405 + const cleanPath = _pathToPositionalSyntax(path); + + if (this.paths.hasOwnProperty(path)) { + return 'real'; + } + if (this.virtuals.hasOwnProperty(path)) { + return 'virtual'; + } + if (this.nested.hasOwnProperty(path)) { + return 'nested'; + } + if (this.subpaths.hasOwnProperty(cleanPath) || this.subpaths.hasOwnProperty(path)) { + return 'real'; + } + + const singleNestedPath = this.singleNestedPaths.hasOwnProperty(cleanPath) || this.singleNestedPaths.hasOwnProperty(path); + if (singleNestedPath) { + return singleNestedPath === 'nested' ? 'nested' : 'real'; + } + + // Look for maps + const mapPath = getMapPath(this, path); + if (mapPath != null) { + return 'real'; + } + + if (/\.\d+\.|\.\d+$/.test(path)) { + return getPositionalPathType(this, path); + } + return 'adhocOrUndefined'; +}; + +/** + * Returns true iff this path is a child of a mixed schema. + * + * @param {String} path + * @return {Boolean} + * @api private + */ + +Schema.prototype.hasMixedParent = function(path) { + const subpaths = path.split(/\./g); + path = ''; + for (let i = 0; i < subpaths.length; ++i) { + path = i > 0 ? path + '.' + subpaths[i] : subpaths[i]; + if (path in this.paths && + this.paths[path] instanceof MongooseTypes.Mixed) { + return this.paths[path]; + } + } + + return null; +}; + +/** + * Setup updatedAt and createdAt timestamps to documents if enabled + * + * @param {Boolean|Object} timestamps timestamps options + * @api private + */ +Schema.prototype.setupTimestamp = function(timestamps) { + return setupTimestamps(this, timestamps); +}; + +/*! + * ignore. Deprecated re: #6405 + */ + +function getPositionalPathType(self, path) { + const subpaths = path.split(/\.(\d+)\.|\.(\d+)$/).filter(Boolean); + if (subpaths.length < 2) { + return self.paths.hasOwnProperty(subpaths[0]) ? + self.paths[subpaths[0]] : + 'adhocOrUndefined'; + } + + let val = self.path(subpaths[0]); + let isNested = false; + if (!val) { + return 'adhocOrUndefined'; + } + + const last = subpaths.length - 1; + + for (let i = 1; i < subpaths.length; ++i) { + isNested = false; + const subpath = subpaths[i]; + + if (i === last && val && !/\D/.test(subpath)) { + if (val.$isMongooseDocumentArray) { + val = val.$embeddedSchemaType; + } else if (val instanceof MongooseTypes.Array) { + // StringSchema, NumberSchema, etc + val = val.caster; + } else { + val = undefined; + } + break; + } + + // ignore if its just a position segment: path.0.subpath + if (!/\D/.test(subpath)) { + // Nested array + if (val instanceof MongooseTypes.Array && i !== last) { + val = val.caster; + } + continue; + } + + if (!(val && val.schema)) { + val = undefined; + break; + } + + const type = val.schema.pathType(subpath); + isNested = (type === 'nested'); + val = val.schema.path(subpath); + } + + self.subpaths[path] = val; + if (val) { + return 'real'; + } + if (isNested) { + return 'nested'; + } + return 'adhocOrUndefined'; +} + + +/*! + * ignore + */ + +function getPositionalPath(self, path) { + getPositionalPathType(self, path); + return self.subpaths[path]; +} + +/** + * Adds a method call to the queue. + * + * ####Example: + * + * schema.methods.print = function() { console.log(this); }; + * schema.queue('print', []); // Print the doc every one is instantiated + * + * const Model = mongoose.model('Test', schema); + * new Model({ name: 'test' }); // Prints '{"_id": ..., "name": "test" }' + * + * @param {String} name name of the document method to call later + * @param {Array} args arguments to pass to the method + * @api public + */ + +Schema.prototype.queue = function(name, args) { + this.callQueue.push([name, args]); + return this; +}; + +/** + * Defines a pre hook for the document. + * + * ####Example + * + * const toySchema = new Schema({ name: String, created: Date }); + * + * toySchema.pre('save', function(next) { + * if (!this.created) this.created = new Date; + * next(); + * }); + * + * toySchema.pre('validate', function(next) { + * if (this.name !== 'Woody') this.name = 'Woody'; + * next(); + * }); + * + * // Equivalent to calling `pre()` on `find`, `findOne`, `findOneAndUpdate`. + * toySchema.pre(/^find/, function(next) { + * console.log(this.getFilter()); + * }); + * + * // Equivalent to calling `pre()` on `updateOne`, `findOneAndUpdate`. + * toySchema.pre(['updateOne', 'findOneAndUpdate'], function(next) { + * console.log(this.getFilter()); + * }); + * + * toySchema.pre('deleteOne', function() { + * // Runs when you call `Toy.deleteOne()` + * }); + * + * toySchema.pre('deleteOne', { document: true }, function() { + * // Runs when you call `doc.deleteOne()` + * }); + * + * @param {String|RegExp} The method name or regular expression to match method name + * @param {Object} [options] + * @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()`. + * @param {Boolean} [options.query] If `name` is a hook for both document and query middleware, set to `true` to run on query middleware. + * @param {Function} callback + * @api public + */ + +Schema.prototype.pre = function(name) { + if (name instanceof RegExp) { + const remainingArgs = Array.prototype.slice.call(arguments, 1); + for (const fn of hookNames) { + if (name.test(fn)) { + this.pre.apply(this, [fn].concat(remainingArgs)); + } + } + return this; + } + if (Array.isArray(name)) { + const remainingArgs = Array.prototype.slice.call(arguments, 1); + for (const el of name) { + this.pre.apply(this, [el].concat(remainingArgs)); + } + return this; + } + this.s.hooks.pre.apply(this.s.hooks, arguments); + return this; +}; + +/** + * Defines a post hook for the document + * + * const schema = new Schema(..); + * schema.post('save', function (doc) { + * console.log('this fired after a document was saved'); + * }); + * + * schema.post('find', function(docs) { + * console.log('this fired after you ran a find query'); + * }); + * + * schema.post(/Many$/, function(res) { + * console.log('this fired after you ran `updateMany()` or `deleteMany()`); + * }); + * + * const Model = mongoose.model('Model', schema); + * + * const m = new Model(..); + * m.save(function(err) { + * console.log('this fires after the `post` hook'); + * }); + * + * m.find(function(err, docs) { + * console.log('this fires after the post find hook'); + * }); + * + * @param {String|RegExp} The method name or regular expression to match method name + * @param {Object} [options] + * @param {Boolean} [options.document] If `name` is a hook for both document and query middleware, set to `true` to run on document middleware. + * @param {Boolean} [options.query] If `name` is a hook for both document and query middleware, set to `true` to run on query middleware. + * @param {Function} fn callback + * @see middleware http://mongoosejs.com/docs/middleware.html + * @see kareem http://npmjs.org/package/kareem + * @api public + */ + +Schema.prototype.post = function(name) { + if (name instanceof RegExp) { + const remainingArgs = Array.prototype.slice.call(arguments, 1); + for (const fn of hookNames) { + if (name.test(fn)) { + this.post.apply(this, [fn].concat(remainingArgs)); + } + } + return this; + } + if (Array.isArray(name)) { + const remainingArgs = Array.prototype.slice.call(arguments, 1); + for (const el of name) { + this.post.apply(this, [el].concat(remainingArgs)); + } + return this; + } + this.s.hooks.post.apply(this.s.hooks, arguments); + return this; +}; + +/** + * Registers a plugin for this schema. + * + * ####Example: + * + * const s = new Schema({ name: String }); + * s.plugin(schema => console.log(schema.path('name').path)); + * mongoose.model('Test', s); // Prints 'name' + * + * @param {Function} plugin callback + * @param {Object} [opts] + * @see plugins + * @api public + */ + +Schema.prototype.plugin = function(fn, opts) { + if (typeof fn !== 'function') { + throw new Error('First param to `schema.plugin()` must be a function, ' + + 'got "' + (typeof fn) + '"'); + } + + if (opts && opts.deduplicate) { + for (const plugin of this.plugins) { + if (plugin.fn === fn) { + return this; + } + } + } + this.plugins.push({ fn: fn, opts: opts }); + + fn(this, opts); + return this; +}; + +/** + * Adds an instance method to documents constructed from Models compiled from this schema. + * + * ####Example + * + * const schema = kittySchema = new Schema(..); + * + * schema.method('meow', function () { + * console.log('meeeeeoooooooooooow'); + * }) + * + * const Kitty = mongoose.model('Kitty', schema); + * + * const fizz = new Kitty; + * fizz.meow(); // meeeeeooooooooooooow + * + * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods. + * + * schema.method({ + * purr: function () {} + * , scratch: function () {} + * }); + * + * // later + * fizz.purr(); + * fizz.scratch(); + * + * 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) + * + * @param {String|Object} method name + * @param {Function} [fn] + * @api public + */ + +Schema.prototype.method = function(name, fn, options) { + if (typeof name !== 'string') { + for (const i in name) { + this.methods[i] = name[i]; + this.methodOptions[i] = utils.clone(options); + } + } else { + this.methods[name] = fn; + this.methodOptions[name] = utils.clone(options); + } + return this; +}; + +/** + * Adds static "class" methods to Models compiled from this schema. + * + * ####Example + * + * const schema = new Schema(..); + * // Equivalent to `schema.statics.findByName = function(name) {}`; + * schema.static('findByName', function(name) { + * return this.find({ name: name }); + * }); + * + * const Drink = mongoose.model('Drink', schema); + * await Drink.findByName('LaCroix'); + * + * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics. + * + * @param {String|Object} name + * @param {Function} [fn] + * @api public + * @see Statics /docs/guide.html#statics + */ + +Schema.prototype.static = function(name, fn) { + if (typeof name !== 'string') { + for (const i in name) { + this.statics[i] = name[i]; + } + } else { + this.statics[name] = fn; + } + return this; +}; + +/** + * Defines an index (most likely compound) for this schema. + * + * ####Example + * + * schema.index({ first: 1, last: -1 }) + * + * @param {Object} fields + * @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) + * @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. + * @api public + */ + +Schema.prototype.index = function(fields, options) { + fields || (fields = {}); + options || (options = {}); + + if (options.expires) { + utils.expires(options); + } + + this._indexes.push([fields, options]); + return this; +}; + +/** + * Sets/gets a schema option. + * + * ####Example + * + * schema.set('strict'); // 'true' by default + * schema.set('strict', false); // Sets 'strict' to false + * schema.set('strict'); // 'false' + * + * @param {String} key option name + * @param {Object} [value] if not passed, the current option value is returned + * @see Schema ./ + * @api public + */ + +Schema.prototype.set = function(key, value, _tags) { + if (arguments.length === 1) { + return this.options[key]; + } + + switch (key) { + case 'read': + this.options[key] = readPref(value, _tags); + this._userProvidedOptions[key] = this.options[key]; + break; + case 'safe': + setSafe(this.options, value); + this._userProvidedOptions[key] = this.options[key]; + break; + case 'timestamps': + this.setupTimestamp(value); + this.options[key] = value; + this._userProvidedOptions[key] = this.options[key]; + break; + case '_id': + this.options[key] = value; + this._userProvidedOptions[key] = this.options[key]; + + if (value && !this.paths['_id']) { + addAutoId(this); + } else if (!value && this.paths['_id'] != null && this.paths['_id'].auto) { + this.remove('_id'); + } + break; + default: + this.options[key] = value; + this._userProvidedOptions[key] = this.options[key]; + break; + } + + return this; +}; + +/*! + * ignore + */ + +const safeDeprecationWarning = 'Mongoose: The `safe` option for schemas is ' + + 'deprecated. Use the `writeConcern` option instead: ' + + 'http://bit.ly/mongoose-write-concern'; + +const setSafe = util.deprecate(function setSafe(options, value) { + options.safe = value === false ? + { w: 0 } : + value; +}, safeDeprecationWarning); + +/** + * Gets a schema option. + * + * ####Example: + * + * schema.get('strict'); // true + * schema.set('strict', false); + * schema.get('strict'); // false + * + * @param {String} key option name + * @api public + * @return {Any} the option's value + */ + +Schema.prototype.get = function(key) { + return this.options[key]; +}; + +/** + * The allowed index types + * + * @receiver Schema + * @static indexTypes + * @api public + */ + +const indexTypes = '2d 2dsphere hashed text'.split(' '); + +Object.defineProperty(Schema, 'indexTypes', { + get: function() { + return indexTypes; + }, + set: function() { + throw new Error('Cannot overwrite Schema.indexTypes'); + } +}); + +/** + * Returns a list of indexes that this schema declares, via `schema.index()` + * or by `index: true` in a path's options. + * + * ####Example: + * + * const userSchema = new Schema({ + * email: { type: String, required: true, unique: true }, + * registeredAt: { type: Date, index: true } + * }); + * + * // [ [ { email: 1 }, { unique: true, background: true } ], + * // [ { registeredAt: 1 }, { background: true } ] ] + * userSchema.indexes(); + * + * @api public + * @return {Array} list of indexes defined in the schema + */ + +Schema.prototype.indexes = function() { + return getIndexes(this); +}; + +/** + * Creates a virtual type with the given name. + * + * @param {String} name + * @param {Object} [options] + * @param {String|Model} [options.ref] model name or model instance. Marks this as a [populate virtual](populate.html#populate-virtuals). + * @param {String|Function} [options.localField] Required for populate virtuals. See [populate virtual docs](populate.html#populate-virtuals) for more information. + * @param {String|Function} [options.foreignField] Required for populate virtuals. See [populate virtual docs](populate.html#populate-virtuals) for more information. + * @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. + * @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()`. + * @return {VirtualType} + */ + +Schema.prototype.virtual = function(name, options) { + if (name instanceof VirtualType || (name != null && name.constructor.name === 'VirtualType')) { + return this.virtual(name.path, name.options); + } + + options = new VirtualOptions(options); + + if (utils.hasUserDefinedProperty(options, ['ref', 'refPath'])) { + if (options.localField == null) { + throw new Error('Reference virtuals require `localField` option'); + } + + if (options.foreignField == null) { + throw new Error('Reference virtuals require `foreignField` option'); + } + + this.pre('init', function(obj) { + if (mpath.has(name, obj)) { + const _v = mpath.get(name, obj); + if (!this.$$populatedVirtuals) { + this.$$populatedVirtuals = {}; + } + + if (options.justOne || options.count) { + this.$$populatedVirtuals[name] = Array.isArray(_v) ? + _v[0] : + _v; + } else { + this.$$populatedVirtuals[name] = Array.isArray(_v) ? + _v : + _v == null ? [] : [_v]; + } + + mpath.unset(name, obj); + } + }); + + const virtual = this.virtual(name); + virtual.options = options; + return virtual. + get(function(_v) { + if (this.$$populatedVirtuals && + this.$$populatedVirtuals.hasOwnProperty(name)) { + return this.$$populatedVirtuals[name]; + } + if (_v == null) return undefined; + return _v; + }). + set(function(_v) { + if (!this.$$populatedVirtuals) { + this.$$populatedVirtuals = {}; + } + + if (options.justOne || options.count) { + this.$$populatedVirtuals[name] = Array.isArray(_v) ? + _v[0] : + _v; + + if (typeof this.$$populatedVirtuals[name] !== 'object') { + this.$$populatedVirtuals[name] = options.count ? _v : null; + } + } else { + this.$$populatedVirtuals[name] = Array.isArray(_v) ? + _v : + _v == null ? [] : [_v]; + + this.$$populatedVirtuals[name] = this.$$populatedVirtuals[name].filter(function(doc) { + return doc && typeof doc === 'object'; + }); + } + }); + } + + const virtuals = this.virtuals; + const parts = name.split('.'); + + if (this.pathType(name) === 'real') { + throw new Error('Virtual path "' + name + '"' + + ' conflicts with a real path in the schema'); + } + + virtuals[name] = parts.reduce(function(mem, part, i) { + mem[part] || (mem[part] = (i === parts.length - 1) + ? new VirtualType(options, name) + : {}); + return mem[part]; + }, this.tree); + + // Workaround for gh-8198: if virtual is under document array, make a fake + // virtual. See gh-8210 + let cur = parts[0]; + for (let i = 0; i < parts.length - 1; ++i) { + if (this.paths[cur] != null && this.paths[cur].$isMongooseDocumentArray) { + const remnant = parts.slice(i + 1).join('.'); + const v = this.paths[cur].schema.virtual(remnant); + v.get((v, virtual, doc) => { + const parent = doc.__parentArray[arrayParentSymbol]; + const path = cur + '.' + doc.__index + '.' + remnant; + return parent.get(path); + }); + break; + } + + cur += '.' + parts[i + 1]; + } + + return virtuals[name]; +}; + +/** + * Returns the virtual type with the given `name`. + * + * @param {String} name + * @return {VirtualType} + */ + +Schema.prototype.virtualpath = function(name) { + return this.virtuals.hasOwnProperty(name) ? this.virtuals[name] : null; +}; + +/** + * Removes the given `path` (or [`paths`]). + * + * ####Example: + * + * const schema = new Schema({ name: String, age: Number }); + * schema.remove('name'); + * schema.path('name'); // Undefined + * schema.path('age'); // SchemaNumber { ... } + * + * @param {String|Array} path + * @return {Schema} the Schema instance + * @api public + */ +Schema.prototype.remove = function(path) { + if (typeof path === 'string') { + path = [path]; + } + if (Array.isArray(path)) { + path.forEach(function(name) { + if (this.path(name) == null && !this.nested[name]) { + return; + } + if (this.nested[name]) { + const allKeys = Object.keys(this.paths). + concat(Object.keys(this.nested)); + for (const path of allKeys) { + if (path.startsWith(name + '.')) { + delete this.paths[path]; + delete this.nested[path]; + _deletePath(this, path); + } + } + + delete this.nested[name]; + _deletePath(this, name); + return; + } + + delete this.paths[name]; + _deletePath(this, name); + }, this); + } + return this; +}; + +/*! + * ignore + */ + +function _deletePath(schema, name) { + const pieces = name.split('.'); + const last = pieces.pop(); + + let branch = schema.tree; + + for (const piece of pieces) { + branch = branch[piece]; + } + + delete branch[last]; +} + +/** + * 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), + * and [instance methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Class_body_and_method_definitions) + * to schema [virtuals](http://mongoosejs.com/docs/guide.html#virtuals), + * [statics](http://mongoosejs.com/docs/guide.html#statics), and + * [methods](http://mongoosejs.com/docs/guide.html#methods). + * + * ####Example: + * + * ```javascript + * const md5 = require('md5'); + * const userSchema = new Schema({ email: String }); + * class UserClass { + * // `gravatarImage` becomes a virtual + * get gravatarImage() { + * const hash = md5(this.email.toLowerCase()); + * return `https://www.gravatar.com/avatar/${hash}`; + * } + * + * // `getProfileUrl()` becomes a document method + * getProfileUrl() { + * return `https://mysite.com/${this.email}`; + * } + * + * // `findByEmail()` becomes a static + * static findByEmail(email) { + * return this.findOne({ email }); + * } + * } + * + * // `schema` will now have a `gravatarImage` virtual, a `getProfileUrl()` method, + * // and a `findByEmail()` static + * userSchema.loadClass(UserClass); + * ``` + * + * @param {Function} model + * @param {Boolean} [virtualsOnly] if truthy, only pulls virtuals from the class, not methods or statics + */ +Schema.prototype.loadClass = function(model, virtualsOnly) { + if (model === Object.prototype || + model === Function.prototype || + model.prototype.hasOwnProperty('$isMongooseModelPrototype')) { + return this; + } + + this.loadClass(Object.getPrototypeOf(model)); + + // Add static methods + if (!virtualsOnly) { + Object.getOwnPropertyNames(model).forEach(function(name) { + if (name.match(/^(length|name|prototype)$/)) { + return; + } + const method = Object.getOwnPropertyDescriptor(model, name); + if (typeof method.value === 'function') { + this.static(name, method.value); + } + }, this); + } + + // Add methods and virtuals + Object.getOwnPropertyNames(model.prototype).forEach(function(name) { + if (name.match(/^(constructor)$/)) { + return; + } + const method = Object.getOwnPropertyDescriptor(model.prototype, name); + if (!virtualsOnly) { + if (typeof method.value === 'function') { + this.method(name, method.value); + } + } + if (typeof method.get === 'function') { + this.virtual(name).get(method.get); + } + if (typeof method.set === 'function') { + this.virtual(name).set(method.set); + } + }, this); + + return this; +}; + +/*! + * ignore + */ + +Schema.prototype._getSchema = function(path) { + const _this = this; + const pathschema = _this.path(path); + const resultPath = []; + + if (pathschema) { + pathschema.$fullPath = path; + return pathschema; + } + + function search(parts, schema) { + let p = parts.length + 1; + let foundschema; + let trypath; + + while (p--) { + trypath = parts.slice(0, p).join('.'); + foundschema = schema.path(trypath); + if (foundschema) { + resultPath.push(trypath); + + if (foundschema.caster) { + // array of Mixed? + if (foundschema.caster instanceof MongooseTypes.Mixed) { + foundschema.caster.$fullPath = resultPath.join('.'); + return foundschema.caster; + } + + // Now that we found the array, we need to check if there + // are remaining document paths to look up for casting. + // Also we need to handle array.$.path since schema.path + // doesn't work for that. + // If there is no foundschema.schema we are dealing with + // a path like array.$ + if (p !== parts.length) { + if (foundschema.schema) { + let ret; + if (parts[p] === '$' || isArrayFilter(parts[p])) { + if (p + 1 === parts.length) { + // comments.$ + return foundschema; + } + // comments.$.comments.$.title + ret = search(parts.slice(p + 1), foundschema.schema); + if (ret) { + ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || + !foundschema.schema.$isSingleNested; + } + return ret; + } + // this is the last path of the selector + ret = search(parts.slice(p), foundschema.schema); + if (ret) { + ret.$isUnderneathDocArray = ret.$isUnderneathDocArray || + !foundschema.schema.$isSingleNested; + } + return ret; + } + } + } else if (foundschema.$isSchemaMap) { + if (p + 1 >= parts.length) { + return foundschema.$__schemaType; + } + const ret = search(parts.slice(p + 1), foundschema.$__schemaType.schema); + return ret; + } + + foundschema.$fullPath = resultPath.join('.'); + + return foundschema; + } + } + } + + // look for arrays + const parts = path.split('.'); + for (let i = 0; i < parts.length; ++i) { + if (parts[i] === '$' || isArrayFilter(parts[i])) { + // Re: gh-5628, because `schema.path()` doesn't take $ into account. + parts[i] = '0'; + } + } + return search(parts, _this); +}; + +/*! + * ignore + */ + +Schema.prototype._getPathType = function(path) { + const _this = this; + const pathschema = _this.path(path); + + if (pathschema) { + return 'real'; + } + + function search(parts, schema) { + let p = parts.length + 1, + foundschema, + trypath; + + while (p--) { + trypath = parts.slice(0, p).join('.'); + foundschema = schema.path(trypath); + if (foundschema) { + if (foundschema.caster) { + // array of Mixed? + if (foundschema.caster instanceof MongooseTypes.Mixed) { + return { schema: foundschema, pathType: 'mixed' }; + } + + // Now that we found the array, we need to check if there + // are remaining document paths to look up for casting. + // Also we need to handle array.$.path since schema.path + // doesn't work for that. + // If there is no foundschema.schema we are dealing with + // a path like array.$ + if (p !== parts.length && foundschema.schema) { + if (parts[p] === '$' || isArrayFilter(parts[p])) { + if (p === parts.length - 1) { + return { schema: foundschema, pathType: 'nested' }; + } + // comments.$.comments.$.title + return search(parts.slice(p + 1), foundschema.schema); + } + // this is the last path of the selector + return search(parts.slice(p), foundschema.schema); + } + return { + schema: foundschema, + pathType: foundschema.$isSingleNested ? 'nested' : 'array' + }; + } + return { schema: foundschema, pathType: 'real' }; + } else if (p === parts.length && schema.nested[trypath]) { + return { schema: schema, pathType: 'nested' }; + } + } + return { schema: foundschema || schema, pathType: 'undefined' }; + } + + // look for arrays + return search(path.split('.'), _this); +}; + +/*! + * ignore + */ + +function isArrayFilter(piece) { + return piece.startsWith('$[') && piece.endsWith(']'); +} + +/*! + * Module exports. + */ + +module.exports = exports = Schema; + +// require down here because of reference issues + +/** + * The various built-in Mongoose Schema Types. + * + * ####Example: + * + * const mongoose = require('mongoose'); + * const ObjectId = mongoose.Schema.Types.ObjectId; + * + * ####Types: + * + * - [String](#schema-string-js) + * - [Number](#schema-number-js) + * - [Boolean](#schema-boolean-js) | Bool + * - [Array](#schema-array-js) + * - [Buffer](#schema-buffer-js) + * - [Date](#schema-date-js) + * - [ObjectId](#schema-objectid-js) | Oid + * - [Mixed](#schema-mixed-js) + * + * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema. + * + * const Mixed = mongoose.Schema.Types.Mixed; + * new mongoose.Schema({ _user: Mixed }) + * + * @api public + */ + +Schema.Types = MongooseTypes = require('./schema/index'); + +/*! + * ignore + */ + +exports.ObjectId = MongooseTypes.ObjectId; diff --git a/node_modules/mongoose/lib/schema/SingleNestedPath.js b/node_modules/mongoose/lib/schema/SingleNestedPath.js new file mode 100644 index 0000000..d0108ee --- /dev/null +++ b/node_modules/mongoose/lib/schema/SingleNestedPath.js @@ -0,0 +1,336 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const CastError = require('../error/cast'); +const EventEmitter = require('events').EventEmitter; +const ObjectExpectedError = require('../error/objectExpected'); +const SchemaSingleNestedOptions = require('../options/SchemaSingleNestedOptions'); +const SchemaType = require('../schematype'); +const $exists = require('./operators/exists'); +const castToNumber = require('./operators/helpers').castToNumber; +const discriminator = require('../helpers/model/discriminator'); +const geospatial = require('./operators/geospatial'); +const get = require('../helpers/get'); +const getConstructor = require('../helpers/discriminator/getConstructor'); +const handleIdOption = require('../helpers/schema/handleIdOption'); +const internalToObjectOptions = require('../options').internalToObjectOptions; + +let Subdocument; + +module.exports = SingleNestedPath; + +/** + * Single nested subdocument SchemaType constructor. + * + * @param {Schema} schema + * @param {String} key + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SingleNestedPath(schema, path, options) { + schema = handleIdOption(schema, options); + + this.caster = _createConstructor(schema); + this.caster.path = path; + this.caster.prototype.$basePath = path; + this.schema = schema; + this.$isSingleNested = true; + SchemaType.call(this, path, options, 'Embedded'); +} + +/*! + * ignore + */ + +SingleNestedPath.prototype = Object.create(SchemaType.prototype); +SingleNestedPath.prototype.constructor = SingleNestedPath; +SingleNestedPath.prototype.OptionsConstructor = SchemaSingleNestedOptions; + +/*! + * ignore + */ + +function _createConstructor(schema, baseClass) { + // lazy load + Subdocument || (Subdocument = require('../types/subdocument')); + + const _embedded = function SingleNested(value, path, parent) { + const _this = this; + + this.$parent = parent; + Subdocument.apply(this, arguments); + + this.$session(this.ownerDocument().$session()); + + if (parent) { + parent.on('save', function() { + _this.emit('save', _this); + _this.constructor.emit('save', _this); + }); + + parent.on('isNew', function(val) { + _this.isNew = val; + _this.emit('isNew', val); + _this.constructor.emit('isNew', val); + }); + } + }; + + const proto = baseClass != null ? baseClass.prototype : Subdocument.prototype; + _embedded.prototype = Object.create(proto); + _embedded.prototype.$__setSchema(schema); + _embedded.prototype.constructor = _embedded; + _embedded.schema = schema; + _embedded.$isSingleNested = true; + _embedded.events = new EventEmitter(); + _embedded.prototype.toBSON = function() { + return this.toObject(internalToObjectOptions); + }; + + // apply methods + for (const i in schema.methods) { + _embedded.prototype[i] = schema.methods[i]; + } + + // apply statics + for (const i in schema.statics) { + _embedded[i] = schema.statics[i]; + } + + for (const i in EventEmitter.prototype) { + _embedded[i] = EventEmitter.prototype[i]; + } + + return _embedded; +} + +/*! + * Special case for when users use a common location schema to represent + * locations for use with $geoWithin. + * https://docs.mongodb.org/manual/reference/operator/query/geoWithin/ + * + * @param {Object} val + * @api private + */ + +SingleNestedPath.prototype.$conditionalHandlers.$geoWithin = function handle$geoWithin(val) { + return { $geometry: this.castForQuery(val.$geometry) }; +}; + +/*! + * ignore + */ + +SingleNestedPath.prototype.$conditionalHandlers.$near = +SingleNestedPath.prototype.$conditionalHandlers.$nearSphere = geospatial.cast$near; + +SingleNestedPath.prototype.$conditionalHandlers.$within = +SingleNestedPath.prototype.$conditionalHandlers.$geoWithin = geospatial.cast$within; + +SingleNestedPath.prototype.$conditionalHandlers.$geoIntersects = + geospatial.cast$geoIntersects; + +SingleNestedPath.prototype.$conditionalHandlers.$minDistance = castToNumber; +SingleNestedPath.prototype.$conditionalHandlers.$maxDistance = castToNumber; + +SingleNestedPath.prototype.$conditionalHandlers.$exists = $exists; + +/** + * Casts contents + * + * @param {Object} value + * @api private + */ + +SingleNestedPath.prototype.cast = function(val, doc, init, priorVal) { + if (val && val.$isSingleNested && val.parent === doc) { + return val; + } + + if (val != null && (typeof val !== 'object' || Array.isArray(val))) { + throw new ObjectExpectedError(this.path, val); + } + + const Constructor = getConstructor(this.caster, val); + + let subdoc; + + // Only pull relevant selected paths and pull out the base path + const parentSelected = get(doc, '$__.selected', {}); + const path = this.path; + const selected = Object.keys(parentSelected).reduce((obj, key) => { + if (key.startsWith(path + '.')) { + obj[key.substr(path.length + 1)] = parentSelected[key]; + } + return obj; + }, {}); + + if (init) { + subdoc = new Constructor(void 0, selected, doc); + subdoc.init(val); + } else { + if (Object.keys(val).length === 0) { + return new Constructor({}, selected, doc, undefined, { priorDoc: priorVal }); + } + + return new Constructor(val, selected, doc, undefined, { priorDoc: priorVal }); + } + + return subdoc; +}; + +/** + * Casts contents for query + * + * @param {string} [$conditional] optional query operator (like `$eq` or `$in`) + * @param {any} value + * @api private + */ + +SingleNestedPath.prototype.castForQuery = function($conditional, val, options) { + let handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) { + throw new Error('Can\'t use ' + $conditional); + } + return handler.call(this, val); + } + val = $conditional; + if (val == null) { + return val; + } + + if (this.options.runSetters) { + val = this._applySetters(val); + } + + const Constructor = getConstructor(this.caster, val); + const overrideStrict = options != null && options.strict != null ? + options.strict : + void 0; + + try { + val = new Constructor(val, overrideStrict); + } catch (error) { + // Make sure we always wrap in a CastError (gh-6803) + if (!(error instanceof CastError)) { + throw new CastError('Embedded', val, this.path, error, this); + } + throw error; + } + return val; +}; + +/** + * Async validation on this single nested doc. + * + * @api private + */ + +SingleNestedPath.prototype.doValidate = function(value, fn, scope, options) { + const Constructor = getConstructor(this.caster, value); + + if (options && options.skipSchemaValidators) { + if (!(value instanceof Constructor)) { + value = new Constructor(value, null, scope); + } + return value.validate(fn); + } + + SchemaType.prototype.doValidate.call(this, value, function(error) { + if (error) { + return fn(error); + } + if (!value) { + return fn(null); + } + + value.validate(fn); + }, scope, options); +}; + +/** + * Synchronously validate this single nested doc + * + * @api private + */ + +SingleNestedPath.prototype.doValidateSync = function(value, scope, options) { + if (!options || !options.skipSchemaValidators) { + const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, value, scope); + if (schemaTypeError) { + return schemaTypeError; + } + } + if (!value) { + return; + } + return value.validateSync(); +}; + +/** + * Adds a discriminator to this single nested subdocument. + * + * ####Example: + * const shapeSchema = Schema({ name: String }, { discriminatorKey: 'kind' }); + * const schema = Schema({ shape: shapeSchema }); + * + * const singleNestedPath = parentSchema.path('shape'); + * singleNestedPath.discriminator('Circle', Schema({ radius: Number })); + * + * @param {String} name + * @param {Schema} schema fields to add to the schema for instances of this sub-class + * @param {String} [value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter. + * @return {Function} the constructor Mongoose will use for creating instances of this discriminator model + * @see discriminators /docs/discriminators.html + * @api public + */ + +SingleNestedPath.prototype.discriminator = function(name, schema, value) { + schema = discriminator(this.caster, name, schema, value); + + this.caster.discriminators[name] = _createConstructor(schema, this.caster); + + return this.caster.discriminators[name]; +}; + +/** + * Sets a default option for all SingleNestedPath instances. + * + * ####Example: + * + * // Make all numbers have option `min` equal to 0. + * mongoose.Schema.Embedded.set('required', true); + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +SingleNestedPath.defaultOptions = {}; + +SingleNestedPath.set = SchemaType.set; + +/*! + * ignore + */ + +SingleNestedPath.prototype.clone = function() { + const options = Object.assign({}, this.options); + const schematype = new this.constructor(this.schema, this.path, options); + schematype.validators = this.validators.slice(); + if (this.requiredValidator !== undefined) { + schematype.requiredValidator = this.requiredValidator; + } + schematype.caster.discriminators = Object.assign({}, this.caster.discriminators); + return schematype; +}; diff --git a/node_modules/mongoose/lib/schema/array.js b/node_modules/mongoose/lib/schema/array.js new file mode 100644 index 0000000..828eb1b --- /dev/null +++ b/node_modules/mongoose/lib/schema/array.js @@ -0,0 +1,589 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const $exists = require('./operators/exists'); +const $type = require('./operators/type'); +const MongooseError = require('../error/mongooseError'); +const SchemaArrayOptions = require('../options/SchemaArrayOptions'); +const SchemaType = require('../schematype'); +const CastError = SchemaType.CastError; +const Mixed = require('./mixed'); +const arrayDepth = require('../helpers/arrayDepth'); +const cast = require('../cast'); +const get = require('../helpers/get'); +const isOperator = require('../helpers/query/isOperator'); +const util = require('util'); +const utils = require('../utils'); +const castToNumber = require('./operators/helpers').castToNumber; +const geospatial = require('./operators/geospatial'); +const getDiscriminatorByValue = require('../helpers/discriminator/getDiscriminatorByValue'); + +let MongooseArray; +let EmbeddedDoc; + +const isNestedArraySymbol = Symbol('mongoose#isNestedArray'); + +/** + * Array SchemaType constructor + * + * @param {String} key + * @param {SchemaType} cast + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SchemaArray(key, cast, options, schemaOptions) { + // lazy load + EmbeddedDoc || (EmbeddedDoc = require('../types').Embedded); + + let typeKey = 'type'; + if (schemaOptions && schemaOptions.typeKey) { + typeKey = schemaOptions.typeKey; + } + this.schemaOptions = schemaOptions; + + if (cast) { + let castOptions = {}; + + if (utils.isPOJO(cast)) { + if (cast[typeKey]) { + // support { type: Woot } + castOptions = utils.clone(cast); // do not alter user arguments + delete castOptions[typeKey]; + cast = cast[typeKey]; + } else { + cast = Mixed; + } + } + + if (cast === Object) { + cast = Mixed; + } + + // support { type: 'String' } + const name = typeof cast === 'string' + ? cast + : utils.getFunctionName(cast); + + const Types = require('./index.js'); + const caster = Types.hasOwnProperty(name) ? Types[name] : cast; + + this.casterConstructor = caster; + + if (this.casterConstructor instanceof SchemaArray) { + this.casterConstructor[isNestedArraySymbol] = true; + } + + if (typeof caster === 'function' && + !caster.$isArraySubdocument && + !caster.$isSchemaMap) { + this.caster = new caster(null, castOptions); + } else { + this.caster = caster; + } + + this.$embeddedSchemaType = this.caster; + + if (!(this.caster instanceof EmbeddedDoc)) { + this.caster.path = key; + } + } + + this.$isMongooseArray = true; + + SchemaType.call(this, key, options, 'Array'); + + let defaultArr; + let fn; + + if (this.defaultValue != null) { + defaultArr = this.defaultValue; + fn = typeof defaultArr === 'function'; + } + + if (!('defaultValue' in this) || this.defaultValue !== void 0) { + const defaultFn = function() { + let arr = []; + if (fn) { + arr = defaultArr.call(this); + } else if (defaultArr != null) { + arr = arr.concat(defaultArr); + } + // Leave it up to `cast()` to convert the array + return arr; + }; + defaultFn.$runBeforeSetters = !fn; + this.default(defaultFn); + } +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +SchemaArray.schemaName = 'Array'; + + +/** + * Options for all arrays. + * + * - `castNonArrays`: `true` by default. If `false`, Mongoose will throw a CastError when a value isn't an array. If `true`, Mongoose will wrap the provided value in an array before casting. + * + * @static options + * @api public + */ + +SchemaArray.options = { castNonArrays: true }; + +SchemaArray.defaultOptions = {}; + +/** + * Sets a default option for all Array instances. + * + * ####Example: + * + * // Make all Array instances have `required` of true by default. + * mongoose.Schema.Array.set('required', true); + * + * const User = mongoose.model('User', new Schema({ test: Array })); + * new User({ }).validateSync().errors.test.message; // Path `test` is required. + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ +SchemaArray.set = SchemaType.set; + +/*! + * Inherits from SchemaType. + */ +SchemaArray.prototype = Object.create(SchemaType.prototype); +SchemaArray.prototype.constructor = SchemaArray; +SchemaArray.prototype.OptionsConstructor = SchemaArrayOptions; + +/*! + * ignore + */ + +SchemaArray._checkRequired = SchemaType.prototype.checkRequired; + +/** + * Override the function the required validator uses to check whether an array + * passes the `required` check. + * + * ####Example: + * + * // Require non-empty array to pass `required` check + * mongoose.Schema.Types.Array.checkRequired(v => Array.isArray(v) && v.length); + * + * const M = mongoose.model({ arr: { type: Array, required: true } }); + * new M({ arr: [] }).validateSync(); // `null`, validation fails! + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +SchemaArray.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies the `required` validator. + * + * @param {Any} value + * @param {Document} doc + * @return {Boolean} + * @api public + */ + +SchemaArray.prototype.checkRequired = function checkRequired(value, doc) { + if (SchemaType._isRef(this, value, doc, true)) { + return !!value; + } + + // `require('util').inherits()` does **not** copy static properties, and + // plugins like mongoose-float use `inherits()` for pre-ES6. + const _checkRequired = typeof this.constructor.checkRequired == 'function' ? + this.constructor.checkRequired() : + SchemaArray.checkRequired(); + + return _checkRequired(value); +}; + +/** + * Adds an enum validator if this is an array of strings or numbers. Equivalent to + * `SchemaString.prototype.enum()` or `SchemaNumber.prototype.enum()` + * + * @param {String|Object} [args...] enumeration values + * @return {SchemaArray} this + */ + +SchemaArray.prototype.enum = function() { + let arr = this; + while (true) { + const instance = get(arr, 'caster.instance'); + if (instance === 'Array') { + arr = arr.caster; + continue; + } + if (instance !== 'String' && instance !== 'Number') { + throw new Error('`enum` can only be set on an array of strings or numbers ' + + ', not ' + instance); + } + break; + } + arr.caster.enum.apply(arr.caster, arguments); + return this; +}; + +/** + * Overrides the getters application for the population special-case + * + * @param {Object} value + * @param {Object} scope + * @api private + */ + +SchemaArray.prototype.applyGetters = function(value, scope) { + if (this.caster.options && this.caster.options.ref) { + // means the object id was populated + return value; + } + + return SchemaType.prototype.applyGetters.call(this, value, scope); +}; + +SchemaArray.prototype._applySetters = function(value, scope, init, priorVal) { + if (this.casterConstructor instanceof SchemaArray && + SchemaArray.options.castNonArrays && + !this[isNestedArraySymbol]) { + // Check nesting levels and wrap in array if necessary + let depth = 0; + let arr = this; + while (arr != null && + arr instanceof SchemaArray && + !arr.$isMongooseDocumentArray) { + ++depth; + arr = arr.casterConstructor; + } + + // No need to wrap empty arrays + if (value != null && value.length > 0) { + const valueDepth = arrayDepth(value); + if (valueDepth.min === valueDepth.max && valueDepth.max < depth && valueDepth.containsNonArrayItem) { + for (let i = valueDepth.max; i < depth; ++i) { + value = [value]; + } + } + } + } + + return SchemaType.prototype._applySetters.call(this, value, scope, init, priorVal); +}; + +/** + * Casts values for set(). + * + * @param {Object} value + * @param {Document} doc document that triggers the casting + * @param {Boolean} init whether this is an initialization cast + * @api private + */ + +SchemaArray.prototype.cast = function(value, doc, init, prev, options) { + // lazy load + MongooseArray || (MongooseArray = require('../types').Array); + + let i; + let l; + + if (Array.isArray(value)) { + if (!value.length && doc) { + const indexes = doc.schema.indexedPaths(); + + const arrayPath = this.path; + for (i = 0, l = indexes.length; i < l; ++i) { + const pathIndex = indexes[i][0][arrayPath]; + if (pathIndex === '2dsphere' || pathIndex === '2d') { + return; + } + } + + // Special case: if this index is on the parent of what looks like + // GeoJSON, skip setting the default to empty array re: #1668, #3233 + const arrayGeojsonPath = this.path.endsWith('.coordinates') ? + this.path.substr(0, this.path.lastIndexOf('.')) : null; + if (arrayGeojsonPath != null) { + for (i = 0, l = indexes.length; i < l; ++i) { + const pathIndex = indexes[i][0][arrayGeojsonPath]; + if (pathIndex === '2dsphere') { + return; + } + } + } + } + + if (!(value && value.isMongooseArray)) { + value = new MongooseArray(value, this._arrayPath || this.path, doc); + } else if (value && value.isMongooseArray) { + // We need to create a new array, otherwise change tracking will + // update the old doc (gh-4449) + value = new MongooseArray(value, this._arrayPath || this.path, doc); + } + + const isPopulated = doc != null && doc.$__ != null && doc.populated(this.path); + if (isPopulated) { + return value; + } + + if (this.caster && this.casterConstructor !== Mixed) { + try { + for (i = 0, l = value.length; i < l; i++) { + // Special case: number arrays disallow undefined. + // Re: gh-840 + // See commit 1298fe92d2c790a90594bd08199e45a4a09162a6 + if (this.caster.instance === 'Number' && value[i] === void 0) { + throw new MongooseError('Mongoose number arrays disallow storing undefined'); + } + const opts = {}; + if (options != null && options.arrayPath != null) { + opts.arrayPath = options.arrayPath + '.' + i; + } else if (this.caster._arrayPath != null) { + opts.arrayPath = this.caster._arrayPath.slice(0, -2) + '.' + i; + } + value[i] = this.caster.cast(value[i], doc, init, void 0, opts); + } + } catch (e) { + // rethrow + throw new CastError('[' + e.kind + ']', util.inspect(value), this.path, e, this); + } + } + + return value; + } + + if (init || SchemaArray.options.castNonArrays) { + // gh-2442: if we're loading this from the db and its not an array, mark + // the whole array as modified. + if (!!doc && !!init) { + doc.markModified(this.path); + } + return this.cast([value], doc, init); + } + + throw new CastError('Array', util.inspect(value), this.path, null, this); +}; + +/*! + * Ignore + */ + +SchemaArray.prototype.discriminator = function(name, schema) { + let arr = this; // eslint-disable-line consistent-this + while (arr.$isMongooseArray && !arr.$isMongooseDocumentArray) { + arr = arr.casterConstructor; + if (arr == null || typeof arr === 'function') { + throw new MongooseError('You can only add an embedded discriminator on ' + + 'a document array, ' + this.path + ' is a plain array'); + } + } + return arr.discriminator(name, schema); +}; + +/*! + * ignore + */ + +SchemaArray.prototype.clone = function() { + const options = Object.assign({}, this.options); + const schematype = new this.constructor(this.path, this.caster, options, this.schemaOptions); + schematype.validators = this.validators.slice(); + if (this.requiredValidator !== undefined) { + schematype.requiredValidator = this.requiredValidator; + } + return schematype; +}; + +/** + * Casts values for queries. + * + * @param {String} $conditional + * @param {any} [value] + * @api private + */ + +SchemaArray.prototype.castForQuery = function($conditional, value) { + let handler; + let val; + + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + + if (!handler) { + throw new Error('Can\'t use ' + $conditional + ' with Array.'); + } + + val = handler.call(this, value); + } else { + val = $conditional; + let Constructor = this.casterConstructor; + + if (val && + Constructor.discriminators && + Constructor.schema && + Constructor.schema.options && + Constructor.schema.options.discriminatorKey) { + if (typeof val[Constructor.schema.options.discriminatorKey] === 'string' && + Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]) { + Constructor = Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]; + } else { + const constructorByValue = getDiscriminatorByValue(Constructor, val[Constructor.schema.options.discriminatorKey]); + if (constructorByValue) { + Constructor = constructorByValue; + } + } + } + + const proto = this.casterConstructor.prototype; + let method = proto && (proto.castForQuery || proto.cast); + if (!method && Constructor.castForQuery) { + method = Constructor.castForQuery; + } + const caster = this.caster; + + if (Array.isArray(val)) { + this.setters.reverse().forEach(setter => { + val = setter.call(this, val, this); + }); + val = val.map(function(v) { + if (utils.isObject(v) && v.$elemMatch) { + return v; + } + if (method) { + v = method.call(caster, v); + return v; + } + if (v != null) { + v = new Constructor(v); + return v; + } + return v; + }); + } else if (method) { + val = method.call(caster, val); + } else if (val != null) { + val = new Constructor(val); + } + } + + return val; +}; + +function cast$all(val) { + if (!Array.isArray(val)) { + val = [val]; + } + + val = val.map(function(v) { + if (utils.isObject(v)) { + const o = {}; + o[this.path] = v; + return cast(this.casterConstructor.schema, o)[this.path]; + } + return v; + }, this); + + return this.castForQuery(val); +} + +function cast$elemMatch(val) { + const keys = Object.keys(val); + const numKeys = keys.length; + for (let i = 0; i < numKeys; ++i) { + const key = keys[i]; + const value = val[key]; + if (isOperator(key) && value != null) { + val[key] = this.castForQuery(key, value); + } + } + + // Is this an embedded discriminator and is the discriminator key set? + // If so, use the discriminator schema. See gh-7449 + const discriminatorKey = get(this, + 'casterConstructor.schema.options.discriminatorKey'); + const discriminators = get(this, 'casterConstructor.schema.discriminators', {}); + if (discriminatorKey != null && + val[discriminatorKey] != null && + discriminators[val[discriminatorKey]] != null) { + return cast(discriminators[val[discriminatorKey]], val); + } + + return cast(this.casterConstructor.schema, val); +} + +const handle = SchemaArray.prototype.$conditionalHandlers = {}; + +handle.$all = cast$all; +handle.$options = String; +handle.$elemMatch = cast$elemMatch; +handle.$geoIntersects = geospatial.cast$geoIntersects; +handle.$or = createLogicalQueryOperatorHandler('$or'); +handle.$and = createLogicalQueryOperatorHandler('$and'); +handle.$nor = createLogicalQueryOperatorHandler('$nor'); + +function createLogicalQueryOperatorHandler(op) { + return function logicalQueryOperatorHandler(val) { + if (!Array.isArray(val)) { + throw new TypeError('conditional ' + op + ' requires an array'); + } + + const ret = []; + for (const obj of val) { + ret.push(cast(this.casterConstructor.schema, obj)); + } + + return ret; + }; +} + +handle.$near = +handle.$nearSphere = geospatial.cast$near; + +handle.$within = +handle.$geoWithin = geospatial.cast$within; + +handle.$size = +handle.$minDistance = +handle.$maxDistance = castToNumber; + +handle.$exists = $exists; +handle.$type = $type; + +handle.$eq = +handle.$gt = +handle.$gte = +handle.$lt = +handle.$lte = +handle.$ne = +handle.$regex = SchemaArray.prototype.castForQuery; + +// `$in` is special because you can also include an empty array in the query +// like `$in: [1, []]`, see gh-5913 +handle.$nin = SchemaType.prototype.$conditionalHandlers.$nin; +handle.$in = SchemaType.prototype.$conditionalHandlers.$in; + +/*! + * Module exports. + */ + +module.exports = SchemaArray; diff --git a/node_modules/mongoose/lib/schema/boolean.js b/node_modules/mongoose/lib/schema/boolean.js new file mode 100644 index 0000000..c264084 --- /dev/null +++ b/node_modules/mongoose/lib/schema/boolean.js @@ -0,0 +1,258 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const CastError = require('../error/cast'); +const SchemaType = require('../schematype'); +const castBoolean = require('../cast/boolean'); +const utils = require('../utils'); + +/** + * Boolean SchemaType constructor. + * + * @param {String} path + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SchemaBoolean(path, options) { + SchemaType.call(this, path, options, 'Boolean'); +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +SchemaBoolean.schemaName = 'Boolean'; + +SchemaBoolean.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +SchemaBoolean.prototype = Object.create(SchemaType.prototype); +SchemaBoolean.prototype.constructor = SchemaBoolean; + +/*! + * ignore + */ + +SchemaBoolean._cast = castBoolean; + +/** + * Sets a default option for all Boolean instances. + * + * ####Example: + * + * // Make all booleans have `default` of false. + * mongoose.Schema.Boolean.set('default', false); + * + * const Order = mongoose.model('Order', new Schema({ isPaid: Boolean })); + * new Order({ }).isPaid; // false + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +SchemaBoolean.set = SchemaType.set; + +/** + * Get/set the function used to cast arbitrary values to booleans. + * + * ####Example: + * + * // Make Mongoose cast empty string '' to false. + * const original = mongoose.Schema.Boolean.cast(); + * mongoose.Schema.Boolean.cast(v => { + * if (v === '') { + * return false; + * } + * return original(v); + * }); + * + * // Or disable casting entirely + * mongoose.Schema.Boolean.cast(false); + * + * @param {Function} caster + * @return {Function} + * @function get + * @static + * @api public + */ + +SchemaBoolean.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = v => { + if (v != null && typeof v !== 'boolean') { + throw new Error(); + } + return v; + }; + } + this._cast = caster; + + return this._cast; +}; + +/*! + * ignore + */ + +SchemaBoolean._checkRequired = v => v === true || v === false; + +/** + * Override the function the required validator uses to check whether a boolean + * passes the `required` check. + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +SchemaBoolean.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies a required validator. For a boolean + * to satisfy a required validator, it must be strictly equal to true or to + * false. + * + * @param {Any} value + * @return {Boolean} + * @api public + */ + +SchemaBoolean.prototype.checkRequired = function(value) { + return this.constructor._checkRequired(value); +}; + +/** + * Configure which values get casted to `true`. + * + * ####Example: + * + * const M = mongoose.model('Test', new Schema({ b: Boolean })); + * new M({ b: 'affirmative' }).b; // undefined + * mongoose.Schema.Boolean.convertToTrue.add('affirmative'); + * new M({ b: 'affirmative' }).b; // true + * + * @property convertToTrue + * @type Set + * @api public + */ + +Object.defineProperty(SchemaBoolean, 'convertToTrue', { + get: () => castBoolean.convertToTrue, + set: v => { castBoolean.convertToTrue = v; } +}); + +/** + * Configure which values get casted to `false`. + * + * ####Example: + * + * const M = mongoose.model('Test', new Schema({ b: Boolean })); + * new M({ b: 'nay' }).b; // undefined + * mongoose.Schema.Types.Boolean.convertToFalse.add('nay'); + * new M({ b: 'nay' }).b; // false + * + * @property convertToFalse + * @type Set + * @api public + */ + +Object.defineProperty(SchemaBoolean, 'convertToFalse', { + get: () => castBoolean.convertToFalse, + set: v => { castBoolean.convertToFalse = v; } +}); + +/** + * Casts to boolean + * + * @param {Object} value + * @param {Object} model - this value is optional + * @api private + */ + +SchemaBoolean.prototype.cast = function(value) { + const castBoolean = typeof this.constructor.cast === 'function' ? + this.constructor.cast() : + SchemaBoolean.cast(); + try { + return castBoolean(value); + } catch (error) { + throw new CastError('Boolean', value, this.path, error, this); + } +}; + +SchemaBoolean.$conditionalHandlers = + utils.options(SchemaType.prototype.$conditionalHandlers, {}); + +/** + * Casts contents for queries. + * + * @param {String} $conditional + * @param {any} val + * @api private + */ + +SchemaBoolean.prototype.castForQuery = function($conditional, val) { + let handler; + if (arguments.length === 2) { + handler = SchemaBoolean.$conditionalHandlers[$conditional]; + + if (handler) { + return handler.call(this, val); + } + + return this._castForQuery(val); + } + + return this._castForQuery($conditional); +}; + +/** + * + * @api private + */ + +SchemaBoolean.prototype._castNullish = function _castNullish(v) { + if (typeof v === 'undefined' && + this.$$context != null && + this.$$context._mongooseOptions != null && + this.$$context._mongooseOptions.omitUndefined) { + return v; + } + const castBoolean = typeof this.constructor.cast === 'function' ? + this.constructor.cast() : + SchemaBoolean.cast(); + if (castBoolean == null) { + return v; + } + if (castBoolean.convertToFalse instanceof Set && castBoolean.convertToFalse.has(v)) { + return false; + } + if (castBoolean.convertToTrue instanceof Set && castBoolean.convertToTrue.has(v)) { + return true; + } + return v; +}; + +/*! + * Module exports. + */ + +module.exports = SchemaBoolean; diff --git a/node_modules/mongoose/lib/schema/buffer.js b/node_modules/mongoose/lib/schema/buffer.js new file mode 100644 index 0000000..c5b8cb7 --- /dev/null +++ b/node_modules/mongoose/lib/schema/buffer.js @@ -0,0 +1,276 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseBuffer = require('../types/buffer'); +const SchemaBufferOptions = require('../options/SchemaBufferOptions'); +const SchemaType = require('../schematype'); +const handleBitwiseOperator = require('./operators/bitwise'); +const utils = require('../utils'); + +const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; + +const Binary = MongooseBuffer.Binary; +const CastError = SchemaType.CastError; +let Document; + +/** + * Buffer SchemaType constructor + * + * @param {String} key + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SchemaBuffer(key, options) { + SchemaType.call(this, key, options, 'Buffer'); +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +SchemaBuffer.schemaName = 'Buffer'; + +SchemaBuffer.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +SchemaBuffer.prototype = Object.create(SchemaType.prototype); +SchemaBuffer.prototype.constructor = SchemaBuffer; +SchemaBuffer.prototype.OptionsConstructor = SchemaBufferOptions; + +/*! + * ignore + */ + +SchemaBuffer._checkRequired = v => !!(v && v.length); + +/** + * Sets a default option for all Buffer instances. + * + * ####Example: + * + * // Make all buffers have `required` of true by default. + * mongoose.Schema.Buffer.set('required', true); + * + * const User = mongoose.model('User', new Schema({ test: Buffer })); + * new User({ }).validateSync().errors.test.message; // Path `test` is required. + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +SchemaBuffer.set = SchemaType.set; + +/** + * Override the function the required validator uses to check whether a string + * passes the `required` check. + * + * ####Example: + * + * // Allow empty strings to pass `required` check + * mongoose.Schema.Types.String.checkRequired(v => v != null); + * + * const M = mongoose.model({ buf: { type: Buffer, required: true } }); + * new M({ buf: Buffer.from('') }).validateSync(); // validation passes! + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +SchemaBuffer.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies a required validator. To satisfy a + * required validator, a buffer must not be null or undefined and have + * non-zero length. + * + * @param {Any} value + * @param {Document} doc + * @return {Boolean} + * @api public + */ + +SchemaBuffer.prototype.checkRequired = function(value, doc) { + if (SchemaType._isRef(this, value, doc, true)) { + return !!value; + } + return this.constructor._checkRequired(value); +}; + +/** + * Casts contents + * + * @param {Object} value + * @param {Document} doc document that triggers the casting + * @param {Boolean} init + * @api private + */ + +SchemaBuffer.prototype.cast = function(value, doc, init) { + let ret; + if (SchemaType._isRef(this, value, doc, init)) { + // wait! we may need to cast this to a document + + if (value === null || value === undefined) { + return value; + } + + // lazy load + Document || (Document = require('./../document')); + + if (value instanceof Document) { + value.$__.wasPopulated = true; + return value; + } + + // setting a populated path + if (Buffer.isBuffer(value)) { + return value; + } else if (!utils.isObject(value)) { + throw new CastError('Buffer', value, this.path, null, this); + } + + // Handle the case where user directly sets a populated + // path to a plain object; cast to the Model used in + // the population query. + const path = doc.$__fullPath(this.path); + const owner = doc.ownerDocument ? doc.ownerDocument() : doc; + const pop = owner.populated(path, true); + ret = new pop.options[populateModelSymbol](value); + ret.$__.wasPopulated = true; + return ret; + } + + // documents + if (value && value._id) { + value = value._id; + } + + if (value && value.isMongooseBuffer) { + return value; + } + + if (Buffer.isBuffer(value)) { + if (!value || !value.isMongooseBuffer) { + value = new MongooseBuffer(value, [this.path, doc]); + if (this.options.subtype != null) { + value._subtype = this.options.subtype; + } + } + return value; + } + + if (value instanceof Binary) { + ret = new MongooseBuffer(value.value(true), [this.path, doc]); + if (typeof value.sub_type !== 'number') { + throw new CastError('Buffer', value, this.path, null, this); + } + ret._subtype = value.sub_type; + return ret; + } + + if (value === null) { + return value; + } + + + const type = typeof value; + if ( + type === 'string' || type === 'number' || Array.isArray(value) || + (type === 'object' && value.type === 'Buffer' && Array.isArray(value.data)) // gh-6863 + ) { + if (type === 'number') { + value = [value]; + } + ret = new MongooseBuffer(value, [this.path, doc]); + if (this.options.subtype != null) { + ret._subtype = this.options.subtype; + } + return ret; + } + + throw new CastError('Buffer', value, this.path, null, this); +}; + +/** + * Sets the default [subtype](https://studio3t.com/whats-new/best-practices-uuid-mongodb/) + * for this buffer. You can find a [list of allowed subtypes here](http://api.mongodb.com/python/current/api/bson/binary.html). + * + * ####Example: + * + * const s = new Schema({ uuid: { type: Buffer, subtype: 4 }); + * const M = db.model('M', s); + * const m = new M({ uuid: 'test string' }); + * m.uuid._subtype; // 4 + * + * @param {Number} subtype the default subtype + * @return {SchemaType} this + * @api public + */ + +SchemaBuffer.prototype.subtype = function(subtype) { + this.options.subtype = subtype; + return this; +}; + +/*! + * ignore + */ +function handleSingle(val) { + return this.castForQuery(val); +} + +SchemaBuffer.prototype.$conditionalHandlers = + utils.options(SchemaType.prototype.$conditionalHandlers, { + $bitsAllClear: handleBitwiseOperator, + $bitsAnyClear: handleBitwiseOperator, + $bitsAllSet: handleBitwiseOperator, + $bitsAnySet: handleBitwiseOperator, + $gt: handleSingle, + $gte: handleSingle, + $lt: handleSingle, + $lte: handleSingle + }); + +/** + * Casts contents for queries. + * + * @param {String} $conditional + * @param {any} [value] + * @api private + */ + +SchemaBuffer.prototype.castForQuery = function($conditional, val) { + let handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) { + throw new Error('Can\'t use ' + $conditional + ' with Buffer.'); + } + return handler.call(this, val); + } + val = $conditional; + const casted = this._castForQuery(val); + return casted ? casted.toObject({ transform: false, virtuals: false }) : casted; +}; + +/*! + * Module exports. + */ + +module.exports = SchemaBuffer; diff --git a/node_modules/mongoose/lib/schema/date.js b/node_modules/mongoose/lib/schema/date.js new file mode 100644 index 0000000..4b9ad1c --- /dev/null +++ b/node_modules/mongoose/lib/schema/date.js @@ -0,0 +1,390 @@ +/*! + * Module requirements. + */ + +'use strict'; + +const MongooseError = require('../error/index'); +const SchemaDateOptions = require('../options/SchemaDateOptions'); +const SchemaType = require('../schematype'); +const castDate = require('../cast/date'); +const utils = require('../utils'); + +const CastError = SchemaType.CastError; + +/** + * Date SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SchemaDate(key, options) { + SchemaType.call(this, key, options, 'Date'); +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +SchemaDate.schemaName = 'Date'; + +SchemaDate.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +SchemaDate.prototype = Object.create(SchemaType.prototype); +SchemaDate.prototype.constructor = SchemaDate; +SchemaDate.prototype.OptionsConstructor = SchemaDateOptions; + +/*! + * ignore + */ + +SchemaDate._cast = castDate; + +/** + * Sets a default option for all Date instances. + * + * ####Example: + * + * // Make all dates have `required` of true by default. + * mongoose.Schema.Date.set('required', true); + * + * const User = mongoose.model('User', new Schema({ test: Date })); + * new User({ }).validateSync().errors.test.message; // Path `test` is required. + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +SchemaDate.set = SchemaType.set; + +/** + * Get/set the function used to cast arbitrary values to dates. + * + * ####Example: + * + * // Mongoose converts empty string '' into `null` for date types. You + * // can create a custom caster to disable it. + * const original = mongoose.Schema.Types.Date.cast(); + * mongoose.Schema.Types.Date.cast(v => { + * assert.ok(v !== ''); + * return original(v); + * }); + * + * // Or disable casting entirely + * mongoose.Schema.Types.Date.cast(false); + * + * @param {Function} caster + * @return {Function} + * @function get + * @static + * @api public + */ + +SchemaDate.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = v => { + if (v != null && !(v instanceof Date)) { + throw new Error(); + } + return v; + }; + } + this._cast = caster; + + return this._cast; +}; + +/** + * Declares a TTL index (rounded to the nearest second) for _Date_ types only. + * + * This sets the `expireAfterSeconds` index option available in MongoDB >= 2.1.2. + * This index type is only compatible with Date types. + * + * ####Example: + * + * // expire in 24 hours + * new Schema({ createdAt: { type: Date, expires: 60*60*24 }}); + * + * `expires` utilizes the `ms` module from [guille](https://github.com/guille/) allowing us to use a friendlier syntax: + * + * ####Example: + * + * // expire in 24 hours + * new Schema({ createdAt: { type: Date, expires: '24h' }}); + * + * // expire in 1.5 hours + * new Schema({ createdAt: { type: Date, expires: '1.5h' }}); + * + * // expire in 7 days + * const schema = new Schema({ createdAt: Date }); + * schema.path('createdAt').expires('7d'); + * + * @param {Number|String} when + * @added 3.0.0 + * @return {SchemaType} this + * @api public + */ + +SchemaDate.prototype.expires = function(when) { + if (!this._index || this._index.constructor.name !== 'Object') { + this._index = {}; + } + + this._index.expires = when; + utils.expires(this._index); + return this; +}; + +/*! + * ignore + */ + +SchemaDate._checkRequired = v => v instanceof Date; + +/** + * Override the function the required validator uses to check whether a string + * passes the `required` check. + * + * ####Example: + * + * // Allow empty strings to pass `required` check + * mongoose.Schema.Types.String.checkRequired(v => v != null); + * + * const M = mongoose.model({ str: { type: String, required: true } }); + * new M({ str: '' }).validateSync(); // `null`, validation passes! + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +SchemaDate.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies a required validator. To satisfy + * a required validator, the given value must be an instance of `Date`. + * + * @param {Any} value + * @param {Document} doc + * @return {Boolean} + * @api public + */ + +SchemaDate.prototype.checkRequired = function(value, doc) { + if (SchemaType._isRef(this, value, doc, true)) { + return !!value; + } + + // `require('util').inherits()` does **not** copy static properties, and + // plugins like mongoose-float use `inherits()` for pre-ES6. + const _checkRequired = typeof this.constructor.checkRequired == 'function' ? + this.constructor.checkRequired() : + SchemaDate.checkRequired(); + return _checkRequired(value); +}; + +/** + * Sets a minimum date validator. + * + * ####Example: + * + * const s = new Schema({ d: { type: Date, min: Date('1970-01-01') }) + * const M = db.model('M', s) + * const m = new M({ d: Date('1969-12-31') }) + * m.save(function (err) { + * console.error(err) // validator error + * m.d = Date('2014-12-08'); + * m.save() // success + * }) + * + * // custom error messages + * // We can also use the special {MIN} token which will be replaced with the invalid value + * const min = [Date('1970-01-01'), 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).']; + * const schema = new Schema({ d: { type: Date, min: min }) + * const M = mongoose.model('M', schema); + * const s= new M({ d: Date('1969-12-31') }); + * s.validate(function (err) { + * console.log(String(err)) // ValidationError: The value of path `d` (1969-12-31) is before the limit (1970-01-01). + * }) + * + * @param {Date} value minimum date + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaDate.prototype.min = function(value, message) { + if (this.minValidator) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.minValidator; + }, this); + } + + if (value) { + let msg = message || MongooseError.messages.Date.min; + if (typeof msg === 'string') { + msg = msg.replace(/{MIN}/, (value === Date.now ? 'Date.now()' : value.toString())); + } + const _this = this; + this.validators.push({ + validator: this.minValidator = function(val) { + let _value = value; + if (typeof value === 'function' && value !== Date.now) { + _value = _value.call(this); + } + const min = (_value === Date.now ? _value() : _this.cast(_value)); + return val === null || val.valueOf() >= min.valueOf(); + }, + message: msg, + type: 'min', + min: value + }); + } + + return this; +}; + +/** + * Sets a maximum date validator. + * + * ####Example: + * + * const s = new Schema({ d: { type: Date, max: Date('2014-01-01') }) + * const M = db.model('M', s) + * const m = new M({ d: Date('2014-12-08') }) + * m.save(function (err) { + * console.error(err) // validator error + * m.d = Date('2013-12-31'); + * m.save() // success + * }) + * + * // custom error messages + * // We can also use the special {MAX} token which will be replaced with the invalid value + * const max = [Date('2014-01-01'), 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).']; + * const schema = new Schema({ d: { type: Date, max: max }) + * const M = mongoose.model('M', schema); + * const s= new M({ d: Date('2014-12-08') }); + * s.validate(function (err) { + * console.log(String(err)) // ValidationError: The value of path `d` (2014-12-08) exceeds the limit (2014-01-01). + * }) + * + * @param {Date} maximum date + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaDate.prototype.max = function(value, message) { + if (this.maxValidator) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.maxValidator; + }, this); + } + + if (value) { + let msg = message || MongooseError.messages.Date.max; + if (typeof msg === 'string') { + msg = msg.replace(/{MAX}/, (value === Date.now ? 'Date.now()' : value.toString())); + } + const _this = this; + this.validators.push({ + validator: this.maxValidator = function(val) { + let _value = value; + if (typeof _value === 'function' && _value !== Date.now) { + _value = _value.call(this); + } + const max = (_value === Date.now ? _value() : _this.cast(_value)); + return val === null || val.valueOf() <= max.valueOf(); + }, + message: msg, + type: 'max', + max: value + }); + } + + return this; +}; + +/** + * Casts to date + * + * @param {Object} value to cast + * @api private + */ + +SchemaDate.prototype.cast = function(value) { + const castDate = typeof this.constructor.cast === 'function' ? + this.constructor.cast() : + SchemaDate.cast(); + try { + return castDate(value); + } catch (error) { + throw new CastError('date', value, this.path, error, this); + } +}; + +/*! + * Date Query casting. + * + * @api private + */ + +function handleSingle(val) { + return this.cast(val); +} + +SchemaDate.prototype.$conditionalHandlers = + utils.options(SchemaType.prototype.$conditionalHandlers, { + $gt: handleSingle, + $gte: handleSingle, + $lt: handleSingle, + $lte: handleSingle + }); + + +/** + * Casts contents for queries. + * + * @param {String} $conditional + * @param {any} [value] + * @api private + */ + +SchemaDate.prototype.castForQuery = function($conditional, val) { + if (arguments.length !== 2) { + return this._castForQuery($conditional); + } + + const handler = this.$conditionalHandlers[$conditional]; + + if (!handler) { + throw new Error('Can\'t use ' + $conditional + ' with Date.'); + } + + return handler.call(this, val); +}; + +/*! + * Module exports. + */ + +module.exports = SchemaDate; diff --git a/node_modules/mongoose/lib/schema/decimal128.js b/node_modules/mongoose/lib/schema/decimal128.js new file mode 100644 index 0000000..ca9bcb4 --- /dev/null +++ b/node_modules/mongoose/lib/schema/decimal128.js @@ -0,0 +1,235 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const SchemaType = require('../schematype'); +const CastError = SchemaType.CastError; +const Decimal128Type = require('../types/decimal128'); +const castDecimal128 = require('../cast/decimal128'); +const utils = require('../utils'); + +const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; + +let Document; + +/** + * Decimal128 SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function Decimal128(key, options) { + SchemaType.call(this, key, options, 'Decimal128'); +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +Decimal128.schemaName = 'Decimal128'; + +Decimal128.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +Decimal128.prototype = Object.create(SchemaType.prototype); +Decimal128.prototype.constructor = Decimal128; + +/*! + * ignore + */ + +Decimal128._cast = castDecimal128; + +/** + * Sets a default option for all Decimal128 instances. + * + * ####Example: + * + * // Make all decimal 128s have `required` of true by default. + * mongoose.Schema.Decimal128.set('required', true); + * + * const User = mongoose.model('User', new Schema({ test: mongoose.Decimal128 })); + * new User({ }).validateSync().errors.test.message; // Path `test` is required. + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +Decimal128.set = SchemaType.set; + +/** + * Get/set the function used to cast arbitrary values to decimals. + * + * ####Example: + * + * // Make Mongoose only refuse to cast numbers as decimal128 + * const original = mongoose.Schema.Types.Decimal128.cast(); + * mongoose.Decimal128.cast(v => { + * assert.ok(typeof v !== 'number'); + * return original(v); + * }); + * + * // Or disable casting entirely + * mongoose.Decimal128.cast(false); + * + * @param {Function} [caster] + * @return {Function} + * @function get + * @static + * @api public + */ + +Decimal128.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = v => { + if (v != null && !(v instanceof Decimal128Type)) { + throw new Error(); + } + return v; + }; + } + this._cast = caster; + + return this._cast; +}; + +/*! + * ignore + */ + +Decimal128._checkRequired = v => v instanceof Decimal128Type; + +/** + * Override the function the required validator uses to check whether a string + * passes the `required` check. + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +Decimal128.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies a required validator. + * + * @param {Any} value + * @param {Document} doc + * @return {Boolean} + * @api public + */ + +Decimal128.prototype.checkRequired = function checkRequired(value, doc) { + if (SchemaType._isRef(this, value, doc, true)) { + return !!value; + } + + // `require('util').inherits()` does **not** copy static properties, and + // plugins like mongoose-float use `inherits()` for pre-ES6. + const _checkRequired = typeof this.constructor.checkRequired == 'function' ? + this.constructor.checkRequired() : + Decimal128.checkRequired(); + + return _checkRequired(value); +}; + +/** + * Casts to Decimal128 + * + * @param {Object} value + * @param {Object} doc + * @param {Boolean} init whether this is an initialization cast + * @api private + */ + +Decimal128.prototype.cast = function(value, doc, init) { + if (SchemaType._isRef(this, value, doc, init)) { + // wait! we may need to cast this to a document + + if (value === null || value === undefined) { + return value; + } + + // lazy load + Document || (Document = require('./../document')); + + if (value instanceof Document) { + value.$__.wasPopulated = true; + return value; + } + + // setting a populated path + if (value instanceof Decimal128Type) { + return value; + } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { + throw new CastError('Decimal128', value, this.path, null, this); + } + + // Handle the case where user directly sets a populated + // path to a plain object; cast to the Model used in + // the population query. + const path = doc.$__fullPath(this.path); + const owner = doc.ownerDocument ? doc.ownerDocument() : doc; + const pop = owner.populated(path, true); + let ret = value; + if (!doc.$__.populated || + !doc.$__.populated[path] || + !doc.$__.populated[path].options || + !doc.$__.populated[path].options.options || + !doc.$__.populated[path].options.options.lean) { + ret = new pop.options[populateModelSymbol](value); + ret.$__.wasPopulated = true; + } + + return ret; + } + + const castDecimal128 = typeof this.constructor.cast === 'function' ? + this.constructor.cast() : + Decimal128.cast(); + try { + return castDecimal128(value); + } catch (error) { + throw new CastError('Decimal128', value, this.path, error, this); + } +}; + +/*! + * ignore + */ + +function handleSingle(val) { + return this.cast(val); +} + +Decimal128.prototype.$conditionalHandlers = + utils.options(SchemaType.prototype.$conditionalHandlers, { + $gt: handleSingle, + $gte: handleSingle, + $lt: handleSingle, + $lte: handleSingle + }); + +/*! + * Module exports. + */ + +module.exports = Decimal128; diff --git a/node_modules/mongoose/lib/schema/documentarray.js b/node_modules/mongoose/lib/schema/documentarray.js new file mode 100644 index 0000000..ac88560 --- /dev/null +++ b/node_modules/mongoose/lib/schema/documentarray.js @@ -0,0 +1,546 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const ArrayType = require('./array'); +const CastError = require('../error/cast'); +const EventEmitter = require('events').EventEmitter; +const SchemaDocumentArrayOptions = + require('../options/SchemaDocumentArrayOptions'); +const SchemaType = require('../schematype'); +const ValidationError = require('../error/validation'); +const discriminator = require('../helpers/model/discriminator'); +const get = require('../helpers/get'); +const handleIdOption = require('../helpers/schema/handleIdOption'); +const util = require('util'); +const utils = require('../utils'); +const getConstructor = require('../helpers/discriminator/getConstructor'); + +const arrayPathSymbol = require('../helpers/symbols').arrayPathSymbol; +const documentArrayParent = require('../helpers/symbols').documentArrayParent; + +let MongooseDocumentArray; +let Subdocument; + +/** + * SubdocsArray SchemaType constructor + * + * @param {String} key + * @param {Schema} schema + * @param {Object} options + * @inherits SchemaArray + * @api public + */ + +function DocumentArrayPath(key, schema, options, schemaOptions) { + if (schemaOptions != null && schemaOptions._id != null) { + schema = handleIdOption(schema, schemaOptions); + } else if (options != null && options._id != null) { + schema = handleIdOption(schema, options); + } + + const EmbeddedDocument = _createConstructor(schema, options); + EmbeddedDocument.prototype.$basePath = key; + + ArrayType.call(this, key, EmbeddedDocument, options); + + this.schema = schema; + this.schemaOptions = schemaOptions || {}; + this.$isMongooseDocumentArray = true; + this.Constructor = EmbeddedDocument; + + EmbeddedDocument.base = schema.base; + + const fn = this.defaultValue; + + if (!('defaultValue' in this) || fn !== void 0) { + this.default(function() { + let arr = fn.call(this); + if (!Array.isArray(arr)) { + arr = [arr]; + } + // Leave it up to `cast()` to convert this to a documentarray + return arr; + }); + } + + const parentSchemaType = this; + this.$embeddedSchemaType = new SchemaType(key + '.$', { + required: get(this, 'schemaOptions.required', false) + }); + this.$embeddedSchemaType.cast = function(value, doc, init) { + return parentSchemaType.cast(value, doc, init)[0]; + }; + this.$embeddedSchemaType.$isMongooseDocumentArrayElement = true; + this.$embeddedSchemaType.caster = this.Constructor; + this.$embeddedSchemaType.schema = this.schema; +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +DocumentArrayPath.schemaName = 'DocumentArray'; + +/** + * Options for all document arrays. + * + * - `castNonArrays`: `true` by default. If `false`, Mongoose will throw a CastError when a value isn't an array. If `true`, Mongoose will wrap the provided value in an array before casting. + * + * @api public + */ + +DocumentArrayPath.options = { castNonArrays: true }; + +/*! + * Inherits from ArrayType. + */ +DocumentArrayPath.prototype = Object.create(ArrayType.prototype); +DocumentArrayPath.prototype.constructor = DocumentArrayPath; +DocumentArrayPath.prototype.OptionsConstructor = SchemaDocumentArrayOptions; + +/*! + * Ignore + */ + +function _createConstructor(schema, options, baseClass) { + Subdocument || (Subdocument = require('../types/embedded')); + + // compile an embedded document for this schema + function EmbeddedDocument() { + Subdocument.apply(this, arguments); + + this.$session(this.ownerDocument().$session()); + } + + const proto = baseClass != null ? baseClass.prototype : Subdocument.prototype; + EmbeddedDocument.prototype = Object.create(proto); + EmbeddedDocument.prototype.$__setSchema(schema); + EmbeddedDocument.schema = schema; + EmbeddedDocument.prototype.constructor = EmbeddedDocument; + EmbeddedDocument.$isArraySubdocument = true; + EmbeddedDocument.events = new EventEmitter(); + + // apply methods + for (const i in schema.methods) { + EmbeddedDocument.prototype[i] = schema.methods[i]; + } + + // apply statics + for (const i in schema.statics) { + EmbeddedDocument[i] = schema.statics[i]; + } + + for (const i in EventEmitter.prototype) { + EmbeddedDocument[i] = EventEmitter.prototype[i]; + } + + EmbeddedDocument.options = options; + + return EmbeddedDocument; +} + +/** + * Adds a discriminator to this document array. + * + * ####Example: + * const shapeSchema = Schema({ name: String }, { discriminatorKey: 'kind' }); + * const schema = Schema({ shapes: [shapeSchema] }); + * + * const docArrayPath = parentSchema.path('shapes'); + * docArrayPath.discriminator('Circle', Schema({ radius: Number })); + * + * @param {String} name + * @param {Schema} schema fields to add to the schema for instances of this sub-class + * @param {String} [value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter. + * @see discriminators /docs/discriminators.html + * @return {Function} the constructor Mongoose will use for creating instances of this discriminator model + * @api public + */ + +DocumentArrayPath.prototype.discriminator = function(name, schema, tiedValue) { + if (typeof name === 'function') { + name = utils.getFunctionName(name); + } + + schema = discriminator(this.casterConstructor, name, schema, tiedValue); + + const EmbeddedDocument = _createConstructor(schema, null, this.casterConstructor); + EmbeddedDocument.baseCasterConstructor = this.casterConstructor; + + try { + Object.defineProperty(EmbeddedDocument, 'name', { + value: name + }); + } catch (error) { + // Ignore error, only happens on old versions of node + } + + this.casterConstructor.discriminators[name] = EmbeddedDocument; + + return this.casterConstructor.discriminators[name]; +}; + +/** + * Performs local validations first, then validations on each embedded doc + * + * @api private + */ + +DocumentArrayPath.prototype.doValidate = function(array, fn, scope, options) { + // lazy load + MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentarray')); + + const _this = this; + try { + SchemaType.prototype.doValidate.call(this, array, cb, scope); + } catch (err) { + err.$isArrayValidatorError = true; + return fn(err); + } + + function cb(err) { + if (err) { + err.$isArrayValidatorError = true; + return fn(err); + } + + let count = array && array.length; + let error; + + if (!count) { + return fn(); + } + if (options && options.updateValidator) { + return fn(); + } + if (!array.isMongooseDocumentArray) { + array = new MongooseDocumentArray(array, _this.path, scope); + } + + // handle sparse arrays, do not use array.forEach which does not + // iterate over sparse elements yet reports array.length including + // them :( + + function callback(err) { + if (err != null) { + error = err; + if (!(error instanceof ValidationError)) { + error.$isArrayValidatorError = true; + } + } + --count || fn(error); + } + + for (let i = 0, len = count; i < len; ++i) { + // sidestep sparse entries + let doc = array[i]; + if (doc == null) { + --count || fn(error); + continue; + } + + // If you set the array index directly, the doc might not yet be + // a full fledged mongoose subdoc, so make it into one. + if (!(doc instanceof Subdocument)) { + const Constructor = getConstructor(_this.casterConstructor, array[i]); + doc = array[i] = new Constructor(doc, array, undefined, undefined, i); + } + + doc.$__validate(callback); + } + } +}; + +/** + * Performs local validations first, then validations on each embedded doc. + * + * ####Note: + * + * This method ignores the asynchronous validators. + * + * @return {MongooseError|undefined} + * @api private + */ + +DocumentArrayPath.prototype.doValidateSync = function(array, scope) { + const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope); + if (schemaTypeError != null) { + schemaTypeError.$isArrayValidatorError = true; + return schemaTypeError; + } + + const count = array && array.length; + let resultError = null; + + if (!count) { + return; + } + + // handle sparse arrays, do not use array.forEach which does not + // iterate over sparse elements yet reports array.length including + // them :( + + for (let i = 0, len = count; i < len; ++i) { + // sidestep sparse entries + let doc = array[i]; + if (!doc) { + continue; + } + + // If you set the array index directly, the doc might not yet be + // a full fledged mongoose subdoc, so make it into one. + if (!(doc instanceof Subdocument)) { + const Constructor = getConstructor(this.casterConstructor, array[i]); + doc = array[i] = new Constructor(doc, array, undefined, undefined, i); + } + + const subdocValidateError = doc.validateSync(); + + if (subdocValidateError && resultError == null) { + resultError = subdocValidateError; + } + } + + return resultError; +}; + +/*! + * ignore + */ + +DocumentArrayPath.prototype.getDefault = function(scope) { + let ret = typeof this.defaultValue === 'function' + ? this.defaultValue.call(scope) + : this.defaultValue; + + if (ret == null) { + return ret; + } + + // lazy load + MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentarray')); + + if (!Array.isArray(ret)) { + ret = [ret]; + } + + ret = new MongooseDocumentArray(ret, this.path, scope); + + for (let i = 0; i < ret.length; ++i) { + const Constructor = getConstructor(this.casterConstructor, ret[i]); + const _subdoc = new Constructor({}, ret, undefined, + undefined, i); + _subdoc.init(ret[i]); + _subdoc.isNew = true; + + // Make sure all paths in the subdoc are set to `default` instead + // of `init` since we used `init`. + Object.assign(_subdoc.$__.activePaths.default, _subdoc.$__.activePaths.init); + _subdoc.$__.activePaths.init = {}; + + ret[i] = _subdoc; + } + + return ret; +}; + +/** + * Casts contents + * + * @param {Object} value + * @param {Document} document that triggers the casting + * @api private + */ + +DocumentArrayPath.prototype.cast = function(value, doc, init, prev, options) { + // lazy load + MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentarray')); + + // Skip casting if `value` is the same as the previous value, no need to cast. See gh-9266 + if (value != null && value[arrayPathSymbol] != null && value === prev) { + return value; + } + + let selected; + let subdoc; + const _opts = { transform: false, virtuals: false }; + options = options || {}; + + if (!Array.isArray(value)) { + if (!init && !DocumentArrayPath.options.castNonArrays) { + throw new CastError('DocumentArray', util.inspect(value), this.path, null, this); + } + // gh-2442 mark whole array as modified if we're initializing a doc from + // the db and the path isn't an array in the document + if (!!doc && init) { + doc.markModified(this.path); + } + return this.cast([value], doc, init, prev, options); + } + + if (!(value && value.isMongooseDocumentArray) && + !options.skipDocumentArrayCast) { + value = new MongooseDocumentArray(value, this.path, doc); + } else if (value && value.isMongooseDocumentArray) { + // We need to create a new array, otherwise change tracking will + // update the old doc (gh-4449) + value = new MongooseDocumentArray(value, this.path, doc); + } + + if (options.arrayPath != null) { + value[arrayPathSymbol] = options.arrayPath; + } + + const len = value.length; + const initDocumentOptions = { skipId: true, willInit: true }; + + for (let i = 0; i < len; ++i) { + if (!value[i]) { + continue; + } + + const Constructor = getConstructor(this.casterConstructor, value[i]); + + // Check if the document has a different schema (re gh-3701) + if ((value[i].$__) && + (!(value[i] instanceof Constructor) || value[i][documentArrayParent] !== doc)) { + value[i] = value[i].toObject({ + transform: false, + // Special case: if different model, but same schema, apply virtuals + // re: gh-7898 + virtuals: value[i].schema === Constructor.schema + }); + } + + if (value[i] instanceof Subdocument) { + // Might not have the correct index yet, so ensure it does. + if (value[i].__index == null) { + value[i].$setIndex(i); + } + } else if (value[i] != null) { + if (init) { + if (doc) { + selected || (selected = scopePaths(this, doc.$__.selected, init)); + } else { + selected = true; + } + + subdoc = new Constructor(null, value, initDocumentOptions, selected, i); + value[i] = subdoc.init(value[i]); + } else { + if (prev && typeof prev.id === 'function') { + subdoc = prev.id(value[i]._id); + } + + if (prev && subdoc && utils.deepEqual(subdoc.toObject(_opts), value[i])) { + // handle resetting doc with existing id and same data + subdoc.set(value[i]); + // if set() is hooked it will have no return value + // see gh-746 + value[i] = subdoc; + } else { + try { + subdoc = new Constructor(value[i], value, undefined, + undefined, i); + // if set() is hooked it will have no return value + // see gh-746 + value[i] = subdoc; + } catch (error) { + const valueInErrorMessage = util.inspect(value[i]); + throw new CastError('embedded', valueInErrorMessage, + value[arrayPathSymbol], error, this); + } + } + } + } + } + + return value; +}; + +/*! + * ignore + */ + +DocumentArrayPath.prototype.clone = function() { + const options = Object.assign({}, this.options); + const schematype = new this.constructor(this.path, this.schema, options, this.schemaOptions); + schematype.validators = this.validators.slice(); + if (this.requiredValidator !== undefined) { + schematype.requiredValidator = this.requiredValidator; + } + schematype.Constructor.discriminators = Object.assign({}, + this.Constructor.discriminators); + return schematype; +}; + +/*! + * Scopes paths selected in a query to this array. + * Necessary for proper default application of subdocument values. + * + * @param {DocumentArrayPath} array - the array to scope `fields` paths + * @param {Object|undefined} fields - the root fields selected in the query + * @param {Boolean|undefined} init - if we are being created part of a query result + */ + +function scopePaths(array, fields, init) { + if (!(init && fields)) { + return undefined; + } + + const path = array.path + '.'; + const keys = Object.keys(fields); + let i = keys.length; + const selected = {}; + let hasKeys; + let key; + let sub; + + while (i--) { + key = keys[i]; + if (key.startsWith(path)) { + sub = key.substring(path.length); + if (sub === '$') { + continue; + } + if (sub.startsWith('$.')) { + sub = sub.substr(2); + } + hasKeys || (hasKeys = true); + selected[sub] = fields[key]; + } + } + + return hasKeys && selected || undefined; +} + +/** + * Sets a default option for all DocumentArray instances. + * + * ####Example: + * + * // Make all numbers have option `min` equal to 0. + * mongoose.Schema.DocumentArray.set('_id', false); + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +DocumentArrayPath.defaultOptions = {}; + +DocumentArrayPath.set = SchemaType.set; + +/*! + * Module exports. + */ + +module.exports = DocumentArrayPath; diff --git a/node_modules/mongoose/lib/schema/index.js b/node_modules/mongoose/lib/schema/index.js new file mode 100644 index 0000000..f33b084 --- /dev/null +++ b/node_modules/mongoose/lib/schema/index.js @@ -0,0 +1,37 @@ + +/*! + * Module exports. + */ + +'use strict'; + +exports.String = require('./string'); + +exports.Number = require('./number'); + +exports.Boolean = require('./boolean'); + +exports.DocumentArray = require('./documentarray'); + +exports.Embedded = require('./SingleNestedPath'); + +exports.Array = require('./array'); + +exports.Buffer = require('./buffer'); + +exports.Date = require('./date'); + +exports.ObjectId = require('./objectid'); + +exports.Mixed = require('./mixed'); + +exports.Decimal128 = exports.Decimal = require('./decimal128'); + +exports.Map = require('./map'); + +// alias + +exports.Oid = exports.ObjectId; +exports.Object = exports.Mixed; +exports.Bool = exports.Boolean; +exports.ObjectID = exports.ObjectId; diff --git a/node_modules/mongoose/lib/schema/map.js b/node_modules/mongoose/lib/schema/map.js new file mode 100644 index 0000000..468faea --- /dev/null +++ b/node_modules/mongoose/lib/schema/map.js @@ -0,0 +1,62 @@ +'use strict'; + +/*! + * ignore + */ + +const MongooseMap = require('../types/map'); +const SchemaMapOptions = require('../options/SchemaMapOptions'); +const SchemaType = require('../schematype'); +/*! + * ignore + */ + +class Map extends SchemaType { + constructor(key, options) { + super(key, options, 'Map'); + this.$isSchemaMap = true; + } + + set(option, value) { + return SchemaType.set(option, value); + } + + cast(val, doc, init) { + if (val instanceof MongooseMap) { + return val; + } + + if (init) { + const map = new MongooseMap({}, this.path, doc, this.$__schemaType); + + if (val instanceof global.Map) { + for (const key of val.keys()) { + map.$init(key, map.$__schemaType.cast(val.get(key), doc, true)); + } + } else { + for (const key of Object.keys(val)) { + map.$init(key, map.$__schemaType.cast(val[key], doc, true)); + } + } + + return map; + } + + return new MongooseMap(val, this.path, doc, this.$__schemaType); + } + + clone() { + const schematype = super.clone(); + + if (this.$__schemaType != null) { + schematype.$__schemaType = this.$__schemaType.clone(); + } + return schematype; + } +} + +Map.prototype.OptionsConstructor = SchemaMapOptions; + +Map.defaultOptions = {}; + +module.exports = Map; diff --git a/node_modules/mongoose/lib/schema/mixed.js b/node_modules/mongoose/lib/schema/mixed.js new file mode 100644 index 0000000..88e4db6 --- /dev/null +++ b/node_modules/mongoose/lib/schema/mixed.js @@ -0,0 +1,128 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const SchemaType = require('../schematype'); +const symbols = require('./symbols'); +const isObject = require('../helpers/isObject'); + +/** + * Mixed SchemaType constructor. + * + * @param {String} path + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function Mixed(path, options) { + if (options && options.default) { + const def = options.default; + if (Array.isArray(def) && def.length === 0) { + // make sure empty array defaults are handled + options.default = Array; + } else if (!options.shared && isObject(def) && Object.keys(def).length === 0) { + // prevent odd "shared" objects between documents + options.default = function() { + return {}; + }; + } + } + + SchemaType.call(this, path, options, 'Mixed'); + + this[symbols.schemaMixedSymbol] = true; +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +Mixed.schemaName = 'Mixed'; + +Mixed.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +Mixed.prototype = Object.create(SchemaType.prototype); +Mixed.prototype.constructor = Mixed; + +/** + * Attaches a getter for all Mixed paths. + * + * ####Example: + * + * // Hide the 'hidden' path + * mongoose.Schema.Mixed.get(v => Object.assign({}, v, { hidden: null })); + * + * const Model = mongoose.model('Test', new Schema({ test: {} })); + * new Model({ test: { hidden: 'Secret!' } }).test.hidden; // null + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +Mixed.get = SchemaType.get; + +/** + * Sets a default option for all Mixed instances. + * + * ####Example: + * + * // Make all mixed instances have `required` of true by default. + * mongoose.Schema.Mixed.set('required', true); + * + * const User = mongoose.model('User', new Schema({ test: mongoose.Mixed })); + * new User({ }).validateSync().errors.test.message; // Path `test` is required. + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +Mixed.set = SchemaType.set; + +/** + * Casts `val` for Mixed. + * + * _this is a no-op_ + * + * @param {Object} value to cast + * @api private + */ + +Mixed.prototype.cast = function(val) { + return val; +}; + +/** + * Casts contents for queries. + * + * @param {String} $cond + * @param {any} [val] + * @api private + */ + +Mixed.prototype.castForQuery = function($cond, val) { + if (arguments.length === 2) { + return val; + } + return $cond; +}; + +/*! + * Module exports. + */ + +module.exports = Mixed; diff --git a/node_modules/mongoose/lib/schema/number.js b/node_modules/mongoose/lib/schema/number.js new file mode 100644 index 0000000..719a1aa --- /dev/null +++ b/node_modules/mongoose/lib/schema/number.js @@ -0,0 +1,444 @@ +'use strict'; + +/*! + * Module requirements. + */ + +const MongooseError = require('../error/index'); +const SchemaNumberOptions = require('../options/SchemaNumberOptions'); +const SchemaType = require('../schematype'); +const castNumber = require('../cast/number'); +const handleBitwiseOperator = require('./operators/bitwise'); +const utils = require('../utils'); + +const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; + +const CastError = SchemaType.CastError; +let Document; + +/** + * Number SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SchemaNumber(key, options) { + SchemaType.call(this, key, options, 'Number'); +} + +/** + * Attaches a getter for all Number instances. + * + * ####Example: + * + * // Make all numbers round down + * mongoose.Number.get(function(v) { return Math.floor(v); }); + * + * const Model = mongoose.model('Test', new Schema({ test: Number })); + * new Model({ test: 3.14 }).test; // 3 + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaNumber.get = SchemaType.get; + +/** + * Sets a default option for all Number instances. + * + * ####Example: + * + * // Make all numbers have option `min` equal to 0. + * mongoose.Schema.Number.set('min', 0); + * + * const Order = mongoose.model('Order', new Schema({ amount: Number })); + * new Order({ amount: -10 }).validateSync().errors.amount.message; // Path `amount` must be larger than 0. + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +SchemaNumber.set = SchemaType.set; + +/*! + * ignore + */ + +SchemaNumber._cast = castNumber; + +/** + * Get/set the function used to cast arbitrary values to numbers. + * + * ####Example: + * + * // Make Mongoose cast empty strings '' to 0 for paths declared as numbers + * const original = mongoose.Number.cast(); + * mongoose.Number.cast(v => { + * if (v === '') { return 0; } + * return original(v); + * }); + * + * // Or disable casting entirely + * mongoose.Number.cast(false); + * + * @param {Function} caster + * @return {Function} + * @function get + * @static + * @api public + */ + +SchemaNumber.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = v => { + if (typeof v !== 'number') { + throw new Error(); + } + return v; + }; + } + this._cast = caster; + + return this._cast; +}; + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +SchemaNumber.schemaName = 'Number'; + +SchemaNumber.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +SchemaNumber.prototype = Object.create(SchemaType.prototype); +SchemaNumber.prototype.constructor = SchemaNumber; +SchemaNumber.prototype.OptionsConstructor = SchemaNumberOptions; + +/*! + * ignore + */ + +SchemaNumber._checkRequired = v => typeof v === 'number' || v instanceof Number; + +/** + * Override the function the required validator uses to check whether a string + * passes the `required` check. + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +SchemaNumber.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies a required validator. + * + * @param {Any} value + * @param {Document} doc + * @return {Boolean} + * @api public + */ + +SchemaNumber.prototype.checkRequired = function checkRequired(value, doc) { + if (SchemaType._isRef(this, value, doc, true)) { + return !!value; + } + + // `require('util').inherits()` does **not** copy static properties, and + // plugins like mongoose-float use `inherits()` for pre-ES6. + const _checkRequired = typeof this.constructor.checkRequired == 'function' ? + this.constructor.checkRequired() : + SchemaNumber.checkRequired(); + + return _checkRequired(value); +}; + +/** + * Sets a minimum number validator. + * + * ####Example: + * + * const s = new Schema({ n: { type: Number, min: 10 }) + * const M = db.model('M', s) + * const m = new M({ n: 9 }) + * m.save(function (err) { + * console.error(err) // validator error + * m.n = 10; + * m.save() // success + * }) + * + * // custom error messages + * // We can also use the special {MIN} token which will be replaced with the invalid value + * const min = [10, 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).']; + * const schema = new Schema({ n: { type: Number, min: min }) + * const M = mongoose.model('Measurement', schema); + * const s= new M({ n: 4 }); + * s.validate(function (err) { + * console.log(String(err)) // ValidationError: The value of path `n` (4) is beneath the limit (10). + * }) + * + * @param {Number} value minimum number + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaNumber.prototype.min = function(value, message) { + if (this.minValidator) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.minValidator; + }, this); + } + + if (value !== null && value !== undefined) { + let msg = message || MongooseError.messages.Number.min; + msg = msg.replace(/{MIN}/, value); + this.validators.push({ + validator: this.minValidator = function(v) { + return v == null || v >= value; + }, + message: msg, + type: 'min', + min: value + }); + } + + return this; +}; + +/** + * Sets a maximum number validator. + * + * ####Example: + * + * const s = new Schema({ n: { type: Number, max: 10 }) + * const M = db.model('M', s) + * const m = new M({ n: 11 }) + * m.save(function (err) { + * console.error(err) // validator error + * m.n = 10; + * m.save() // success + * }) + * + * // custom error messages + * // We can also use the special {MAX} token which will be replaced with the invalid value + * const max = [10, 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).']; + * const schema = new Schema({ n: { type: Number, max: max }) + * const M = mongoose.model('Measurement', schema); + * const s= new M({ n: 4 }); + * s.validate(function (err) { + * console.log(String(err)) // ValidationError: The value of path `n` (4) exceeds the limit (10). + * }) + * + * @param {Number} maximum number + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaNumber.prototype.max = function(value, message) { + if (this.maxValidator) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.maxValidator; + }, this); + } + + if (value !== null && value !== undefined) { + let msg = message || MongooseError.messages.Number.max; + msg = msg.replace(/{MAX}/, value); + this.validators.push({ + validator: this.maxValidator = function(v) { + return v == null || v <= value; + }, + message: msg, + type: 'max', + max: value + }); + } + + return this; +}; + +/** + * Sets a enum validator + * + * ####Example: + * + * const s = new Schema({ n: { type: Number, enum: [1, 2, 3] }); + * const M = db.model('M', s); + * + * const m = new M({ n: 4 }); + * await m.save(); // throws validation error + * + * m.n = 3; + * await m.save(); // succeeds + * + * @param {Array} values allowed values + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaNumber.prototype.enum = function(values, message) { + if (this.enumValidator) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.enumValidator; + }, this); + } + + if (!Array.isArray(values)) { + values = Array.prototype.slice.call(arguments); + message = MongooseError.messages.Number.enum; + } + + message = message == null ? MongooseError.messages.Number.enum : message; + + this.enumValidator = v => v == null || values.indexOf(v) !== -1; + this.validators.push({ + validator: this.enumValidator, + message: message, + type: 'enum', + enumValues: values + }); + + return this; +}; + +/** + * Casts to number + * + * @param {Object} value value to cast + * @param {Document} doc document that triggers the casting + * @param {Boolean} init + * @api private + */ + +SchemaNumber.prototype.cast = function(value, doc, init) { + if (SchemaType._isRef(this, value, doc, init)) { + // wait! we may need to cast this to a document + + if (value === null || value === undefined) { + return value; + } + + // lazy load + Document || (Document = require('./../document')); + + if (value instanceof Document) { + value.$__.wasPopulated = true; + return value; + } + + // setting a populated path + if (typeof value === 'number') { + return value; + } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { + throw new CastError('Number', value, this.path, null, this); + } + + // Handle the case where user directly sets a populated + // path to a plain object; cast to the Model used in + // the population query. + const path = doc.$__fullPath(this.path); + const owner = doc.ownerDocument ? doc.ownerDocument() : doc; + const pop = owner.populated(path, true); + const ret = new pop.options[populateModelSymbol](value); + ret.$__.wasPopulated = true; + return ret; + } + + const val = value && typeof value._id !== 'undefined' ? + value._id : // documents + value; + + const castNumber = typeof this.constructor.cast === 'function' ? + this.constructor.cast() : + SchemaNumber.cast(); + try { + return castNumber(val); + } catch (err) { + throw new CastError('Number', val, this.path, err, this); + } +}; + +/*! + * ignore + */ + +function handleSingle(val) { + return this.cast(val); +} + +function handleArray(val) { + const _this = this; + if (!Array.isArray(val)) { + return [this.cast(val)]; + } + return val.map(function(m) { + return _this.cast(m); + }); +} + +SchemaNumber.prototype.$conditionalHandlers = + utils.options(SchemaType.prototype.$conditionalHandlers, { + $bitsAllClear: handleBitwiseOperator, + $bitsAnyClear: handleBitwiseOperator, + $bitsAllSet: handleBitwiseOperator, + $bitsAnySet: handleBitwiseOperator, + $gt: handleSingle, + $gte: handleSingle, + $lt: handleSingle, + $lte: handleSingle, + $mod: handleArray + }); + +/** + * Casts contents for queries. + * + * @param {String} $conditional + * @param {any} [value] + * @api private + */ + +SchemaNumber.prototype.castForQuery = function($conditional, val) { + let handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) { + throw new CastError('number', val, this.path, null, this); + } + return handler.call(this, val); + } + val = this._castForQuery($conditional); + return val; +}; + +/*! + * Module exports. + */ + +module.exports = SchemaNumber; diff --git a/node_modules/mongoose/lib/schema/objectid.js b/node_modules/mongoose/lib/schema/objectid.js new file mode 100644 index 0000000..ee27f2e --- /dev/null +++ b/node_modules/mongoose/lib/schema/objectid.js @@ -0,0 +1,319 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const SchemaObjectIdOptions = require('../options/SchemaObjectIdOptions'); +const SchemaType = require('../schematype'); +const castObjectId = require('../cast/objectid'); +const oid = require('../types/objectid'); +const utils = require('../utils'); + +const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; + +const CastError = SchemaType.CastError; +let Document; + +/** + * ObjectId SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function ObjectId(key, options) { + const isKeyHexStr = typeof key === 'string' && key.length === 24 && /^[a-f0-9]+$/i.test(key); + const suppressWarning = options && options.suppressWarning; + if ((isKeyHexStr || typeof key === 'undefined') && !suppressWarning) { + console.warn('mongoose: To create a new ObjectId please try ' + + '`Mongoose.Types.ObjectId` instead of using ' + + '`Mongoose.Schema.ObjectId`. Set the `suppressWarning` option if ' + + 'you\'re trying to create a hex char path in your schema.'); + console.trace(); + } + SchemaType.call(this, key, options, 'ObjectID'); +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +ObjectId.schemaName = 'ObjectId'; + +ObjectId.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +ObjectId.prototype = Object.create(SchemaType.prototype); +ObjectId.prototype.constructor = ObjectId; +ObjectId.prototype.OptionsConstructor = SchemaObjectIdOptions; + +/** + * Attaches a getter for all ObjectId instances + * + * ####Example: + * + * // Always convert to string when getting an ObjectId + * mongoose.ObjectId.get(v => v.toString()); + * + * const Model = mongoose.model('Test', new Schema({})); + * typeof (new Model({})._id); // 'string' + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +ObjectId.get = SchemaType.get; + +/** + * Sets a default option for all ObjectId instances. + * + * ####Example: + * + * // Make all object ids have option `required` equal to true. + * mongoose.Schema.ObjectId.set('required', true); + * + * const Order = mongoose.model('Order', new Schema({ userId: ObjectId })); + * new Order({ }).validateSync().errors.userId.message; // Path `userId` is required. + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +ObjectId.set = SchemaType.set; + +/** + * Adds an auto-generated ObjectId default if turnOn is true. + * @param {Boolean} turnOn auto generated ObjectId defaults + * @api public + * @return {SchemaType} this + */ + +ObjectId.prototype.auto = function(turnOn) { + if (turnOn) { + this.default(defaultId); + this.set(resetId); + } + + return this; +}; + +/*! + * ignore + */ + +ObjectId._checkRequired = v => v instanceof oid; + +/*! + * ignore + */ + +ObjectId._cast = castObjectId; + +/** + * Get/set the function used to cast arbitrary values to objectids. + * + * ####Example: + * + * // Make Mongoose only try to cast length 24 strings. By default, any 12 + * // char string is a valid ObjectId. + * const original = mongoose.ObjectId.cast(); + * mongoose.ObjectId.cast(v => { + * assert.ok(typeof v !== 'string' || v.length === 24); + * return original(v); + * }); + * + * // Or disable casting entirely + * mongoose.ObjectId.cast(false); + * + * @param {Function} caster + * @return {Function} + * @function get + * @static + * @api public + */ + +ObjectId.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = v => { + if (!(v instanceof oid)) { + throw new Error(v + ' is not an instance of ObjectId'); + } + return v; + }; + } + this._cast = caster; + + return this._cast; +}; + +/** + * Override the function the required validator uses to check whether a string + * passes the `required` check. + * + * ####Example: + * + * // Allow empty strings to pass `required` check + * mongoose.Schema.Types.String.checkRequired(v => v != null); + * + * const M = mongoose.model({ str: { type: String, required: true } }); + * new M({ str: '' }).validateSync(); // `null`, validation passes! + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +ObjectId.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies a required validator. + * + * @param {Any} value + * @param {Document} doc + * @return {Boolean} + * @api public + */ + +ObjectId.prototype.checkRequired = function checkRequired(value, doc) { + if (SchemaType._isRef(this, value, doc, true)) { + return !!value; + } + + // `require('util').inherits()` does **not** copy static properties, and + // plugins like mongoose-float use `inherits()` for pre-ES6. + const _checkRequired = typeof this.constructor.checkRequired == 'function' ? + this.constructor.checkRequired() : + ObjectId.checkRequired(); + + return _checkRequired(value); +}; + +/** + * Casts to ObjectId + * + * @param {Object} value + * @param {Object} doc + * @param {Boolean} init whether this is an initialization cast + * @api private + */ + +ObjectId.prototype.cast = function(value, doc, init) { + if (SchemaType._isRef(this, value, doc, init)) { + // wait! we may need to cast this to a document + + if (value === null || value === undefined) { + return value; + } + + // lazy load + Document || (Document = require('./../document')); + + if (value instanceof Document) { + value.$__.wasPopulated = true; + return value; + } + + // setting a populated path + if (value instanceof oid) { + return value; + } else if ((value.constructor.name || '').toLowerCase() === 'objectid') { + return new oid(value.toHexString()); + } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { + throw new CastError('ObjectId', value, this.path, null, this); + } + + // Handle the case where user directly sets a populated + // path to a plain object; cast to the Model used in + // the population query. + const path = doc.$__fullPath(this.path); + const owner = doc.ownerDocument ? doc.ownerDocument() : doc; + const pop = owner.populated(path, true); + let ret = value; + if (!doc.$__.populated || + !doc.$__.populated[path] || + !doc.$__.populated[path].options || + !doc.$__.populated[path].options.options || + !doc.$__.populated[path].options.options.lean) { + ret = new pop.options[populateModelSymbol](value); + ret.$__.wasPopulated = true; + } + + return ret; + } + + const castObjectId = typeof this.constructor.cast === 'function' ? + this.constructor.cast() : + ObjectId.cast(); + try { + return castObjectId(value); + } catch (error) { + throw new CastError('ObjectId', value, this.path, error, this); + } +}; + +/*! + * ignore + */ + +function handleSingle(val) { + return this.cast(val); +} + +ObjectId.prototype.$conditionalHandlers = + utils.options(SchemaType.prototype.$conditionalHandlers, { + $gt: handleSingle, + $gte: handleSingle, + $lt: handleSingle, + $lte: handleSingle + }); + +/*! + * ignore + */ + +function defaultId() { + return new oid(); +} + +defaultId.$runBeforeSetters = true; + +function resetId(v) { + Document || (Document = require('./../document')); + + if (this instanceof Document) { + if (v === void 0) { + const _v = new oid; + this.$__._id = _v; + return _v; + } + + this.$__._id = v; + } + + return v; +} + +/*! + * Module exports. + */ + +module.exports = ObjectId; diff --git a/node_modules/mongoose/lib/schema/operators/bitwise.js b/node_modules/mongoose/lib/schema/operators/bitwise.js new file mode 100644 index 0000000..07e18cd --- /dev/null +++ b/node_modules/mongoose/lib/schema/operators/bitwise.js @@ -0,0 +1,38 @@ +/*! + * Module requirements. + */ + +'use strict'; + +const CastError = require('../../error/cast'); + +/*! + * ignore + */ + +function handleBitwiseOperator(val) { + const _this = this; + if (Array.isArray(val)) { + return val.map(function(v) { + return _castNumber(_this.path, v); + }); + } else if (Buffer.isBuffer(val)) { + return val; + } + // Assume trying to cast to number + return _castNumber(_this.path, val); +} + +/*! + * ignore + */ + +function _castNumber(path, num) { + const v = Number(num); + if (isNaN(v)) { + throw new CastError('number', num, path); + } + return v; +} + +module.exports = handleBitwiseOperator; diff --git a/node_modules/mongoose/lib/schema/operators/exists.js b/node_modules/mongoose/lib/schema/operators/exists.js new file mode 100644 index 0000000..916b4cb --- /dev/null +++ b/node_modules/mongoose/lib/schema/operators/exists.js @@ -0,0 +1,12 @@ +'use strict'; + +const castBoolean = require('../../cast/boolean'); + +/*! + * ignore + */ + +module.exports = function(val) { + const path = this != null ? this.path : null; + return castBoolean(val, path); +}; diff --git a/node_modules/mongoose/lib/schema/operators/geospatial.js b/node_modules/mongoose/lib/schema/operators/geospatial.js new file mode 100644 index 0000000..80a6052 --- /dev/null +++ b/node_modules/mongoose/lib/schema/operators/geospatial.js @@ -0,0 +1,107 @@ +/*! + * Module requirements. + */ + +'use strict'; + +const castArraysOfNumbers = require('./helpers').castArraysOfNumbers; +const castToNumber = require('./helpers').castToNumber; + +/*! + * ignore + */ + +exports.cast$geoIntersects = cast$geoIntersects; +exports.cast$near = cast$near; +exports.cast$within = cast$within; + +function cast$near(val) { + const SchemaArray = require('../array'); + + if (Array.isArray(val)) { + castArraysOfNumbers(val, this); + return val; + } + + _castMinMaxDistance(this, val); + + if (val && val.$geometry) { + return cast$geometry(val, this); + } + + if (!Array.isArray(val)) { + throw new TypeError('$near must be either an array or an object ' + + 'with a $geometry property'); + } + + return SchemaArray.prototype.castForQuery.call(this, val); +} + +function cast$geometry(val, self) { + switch (val.$geometry.type) { + case 'Polygon': + case 'LineString': + case 'Point': + castArraysOfNumbers(val.$geometry.coordinates, self); + break; + default: + // ignore unknowns + break; + } + + _castMinMaxDistance(self, val); + + return val; +} + +function cast$within(val) { + _castMinMaxDistance(this, val); + + if (val.$box || val.$polygon) { + const type = val.$box ? '$box' : '$polygon'; + val[type].forEach(arr => { + if (!Array.isArray(arr)) { + const msg = 'Invalid $within $box argument. ' + + 'Expected an array, received ' + arr; + throw new TypeError(msg); + } + arr.forEach((v, i) => { + arr[i] = castToNumber.call(this, v); + }); + }); + } else if (val.$center || val.$centerSphere) { + const type = val.$center ? '$center' : '$centerSphere'; + val[type].forEach((item, i) => { + if (Array.isArray(item)) { + item.forEach((v, j) => { + item[j] = castToNumber.call(this, v); + }); + } else { + val[type][i] = castToNumber.call(this, item); + } + }); + } else if (val.$geometry) { + cast$geometry(val, this); + } + + return val; +} + +function cast$geoIntersects(val) { + const geo = val.$geometry; + if (!geo) { + return; + } + + cast$geometry(val, this); + return val; +} + +function _castMinMaxDistance(self, val) { + if (val.$maxDistance) { + val.$maxDistance = castToNumber.call(self, val.$maxDistance); + } + if (val.$minDistance) { + val.$minDistance = castToNumber.call(self, val.$minDistance); + } +} diff --git a/node_modules/mongoose/lib/schema/operators/helpers.js b/node_modules/mongoose/lib/schema/operators/helpers.js new file mode 100644 index 0000000..a17951c --- /dev/null +++ b/node_modules/mongoose/lib/schema/operators/helpers.js @@ -0,0 +1,32 @@ +'use strict'; + +/*! + * Module requirements. + */ + +const SchemaNumber = require('../number'); + +/*! + * @ignore + */ + +exports.castToNumber = castToNumber; +exports.castArraysOfNumbers = castArraysOfNumbers; + +/*! + * @ignore + */ + +function castToNumber(val) { + return SchemaNumber.cast()(val); +} + +function castArraysOfNumbers(arr, self) { + arr.forEach(function(v, i) { + if (Array.isArray(v)) { + castArraysOfNumbers(v, self); + } else { + arr[i] = castToNumber.call(self, v); + } + }); +} diff --git a/node_modules/mongoose/lib/schema/operators/text.js b/node_modules/mongoose/lib/schema/operators/text.js new file mode 100644 index 0000000..4b95916 --- /dev/null +++ b/node_modules/mongoose/lib/schema/operators/text.js @@ -0,0 +1,39 @@ +'use strict'; + +const CastError = require('../../error/cast'); +const castBoolean = require('../../cast/boolean'); +const castString = require('../../cast/string'); + +/*! + * Casts val to an object suitable for `$text`. Throws an error if the object + * can't be casted. + * + * @param {Any} val value to cast + * @param {String} [path] path to associate with any errors that occured + * @return {Object} casted object + * @see https://docs.mongodb.com/manual/reference/operator/query/text/ + * @api private + */ + +module.exports = function(val, path) { + if (val == null || typeof val !== 'object') { + throw new CastError('$text', val, path); + } + + if (val.$search != null) { + val.$search = castString(val.$search, path + '.$search'); + } + if (val.$language != null) { + val.$language = castString(val.$language, path + '.$language'); + } + if (val.$caseSensitive != null) { + val.$caseSensitive = castBoolean(val.$caseSensitive, + path + '.$castSensitive'); + } + if (val.$diacriticSensitive != null) { + val.$diacriticSensitive = castBoolean(val.$diacriticSensitive, + path + '.$diacriticSensitive'); + } + + return val; +}; diff --git a/node_modules/mongoose/lib/schema/operators/type.js b/node_modules/mongoose/lib/schema/operators/type.js new file mode 100644 index 0000000..c8e391a --- /dev/null +++ b/node_modules/mongoose/lib/schema/operators/type.js @@ -0,0 +1,13 @@ +'use strict'; + +/*! + * ignore + */ + +module.exports = function(val) { + if (typeof val !== 'number' && typeof val !== 'string') { + throw new Error('$type parameter must be number or string'); + } + + return val; +}; diff --git a/node_modules/mongoose/lib/schema/string.js b/node_modules/mongoose/lib/schema/string.js new file mode 100644 index 0000000..a3bfaf4 --- /dev/null +++ b/node_modules/mongoose/lib/schema/string.js @@ -0,0 +1,679 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const SchemaType = require('../schematype'); +const MongooseError = require('../error/index'); +const SchemaStringOptions = require('../options/SchemaStringOptions'); +const castString = require('../cast/string'); +const utils = require('../utils'); + +const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; + +const CastError = SchemaType.CastError; +let Document; + +/** + * String SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SchemaString(key, options) { + this.enumValues = []; + this.regExp = null; + SchemaType.call(this, key, options, 'String'); +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +SchemaString.schemaName = 'String'; + +SchemaString.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +SchemaString.prototype = Object.create(SchemaType.prototype); +SchemaString.prototype.constructor = SchemaString; +Object.defineProperty(SchemaString.prototype, 'OptionsConstructor', { + configurable: false, + enumerable: false, + writable: false, + value: SchemaStringOptions +}); + +/*! + * ignore + */ + +SchemaString._cast = castString; + +/** + * Get/set the function used to cast arbitrary values to strings. + * + * ####Example: + * + * // Throw an error if you pass in an object. Normally, Mongoose allows + * // objects with custom `toString()` functions. + * const original = mongoose.Schema.Types.String.cast(); + * mongoose.Schema.Types.String.cast(v => { + * assert.ok(v == null || typeof v !== 'object'); + * return original(v); + * }); + * + * // Or disable casting entirely + * mongoose.Schema.Types.String.cast(false); + * + * @param {Function} caster + * @return {Function} + * @function get + * @static + * @api public + */ + +SchemaString.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = v => { + if (v != null && typeof v !== 'string') { + throw new Error(); + } + return v; + }; + } + this._cast = caster; + + return this._cast; +}; + +/** + * Attaches a getter for all String instances. + * + * ####Example: + * + * // Make all numbers round down + * mongoose.Schema.String.get(v => v.toLowerCase()); + * + * const Model = mongoose.model('Test', new Schema({ test: String })); + * new Model({ test: 'FOO' }).test; // 'foo' + * + * @param {Function} getter + * @return {this} + * @function get + * @static + * @api public + */ + +SchemaString.get = SchemaType.get; + +/** + * Sets a default option for all String instances. + * + * ####Example: + * + * // Make all strings have option `trim` equal to true. + * mongoose.Schema.String.set('trim', true); + * + * const User = mongoose.model('User', new Schema({ name: String })); + * new User({ name: ' John Doe ' }).name; // 'John Doe' + * + * @param {String} option - The option you'd like to set the value for + * @param {*} value - value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +SchemaString.set = SchemaType.set; + +/*! + * ignore + */ + +SchemaString._checkRequired = v => (v instanceof String || typeof v === 'string') && v.length; + +/** + * Override the function the required validator uses to check whether a string + * passes the `required` check. + * + * ####Example: + * + * // Allow empty strings to pass `required` check + * mongoose.Schema.Types.String.checkRequired(v => v != null); + * + * const M = mongoose.model({ str: { type: String, required: true } }); + * new M({ str: '' }).validateSync(); // `null`, validation passes! + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +SchemaString.checkRequired = SchemaType.checkRequired; + +/** + * Adds an enum validator + * + * ####Example: + * + * const states = ['opening', 'open', 'closing', 'closed'] + * const s = new Schema({ state: { type: String, enum: states }}) + * const M = db.model('M', s) + * const m = new M({ state: 'invalid' }) + * m.save(function (err) { + * console.error(String(err)) // ValidationError: `invalid` is not a valid enum value for path `state`. + * m.state = 'open' + * m.save(callback) // success + * }) + * + * // or with custom error messages + * const enum = { + * values: ['opening', 'open', 'closing', 'closed'], + * message: 'enum validator failed for path `{PATH}` with value `{VALUE}`' + * } + * const s = new Schema({ state: { type: String, enum: enum }) + * const M = db.model('M', s) + * const m = new M({ state: 'invalid' }) + * m.save(function (err) { + * console.error(String(err)) // ValidationError: enum validator failed for path `state` with value `invalid` + * m.state = 'open' + * m.save(callback) // success + * }) + * + * @param {String|Object} [args...] enumeration values + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaString.prototype.enum = function() { + if (this.enumValidator) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.enumValidator; + }, this); + this.enumValidator = false; + } + + if (arguments[0] === void 0 || arguments[0] === false) { + return this; + } + + let values; + let errorMessage; + + if (utils.isObject(arguments[0])) { + values = arguments[0].values; + errorMessage = arguments[0].message; + } else { + values = arguments; + errorMessage = MongooseError.messages.String.enum; + } + + for (const value of values) { + if (value !== undefined) { + this.enumValues.push(this.cast(value)); + } + } + + const vals = this.enumValues; + this.enumValidator = function(v) { + return undefined === v || ~vals.indexOf(v); + }; + this.validators.push({ + validator: this.enumValidator, + message: errorMessage, + type: 'enum', + enumValues: vals + }); + + return this; +}; + +/** + * Adds a lowercase [setter](http://mongoosejs.com/docs/api.html#schematype_SchemaType-set). + * + * ####Example: + * + * const s = new Schema({ email: { type: String, lowercase: true }}) + * const M = db.model('M', s); + * const m = new M({ email: 'SomeEmail@example.COM' }); + * console.log(m.email) // someemail@example.com + * M.find({ email: 'SomeEmail@example.com' }); // Queries by 'someemail@example.com' + * + * Note that `lowercase` does **not** affect regular expression queries: + * + * ####Example: + * // Still queries for documents whose `email` matches the regular + * // expression /SomeEmail/. Mongoose does **not** convert the RegExp + * // to lowercase. + * M.find({ email: /SomeEmail/ }); + * + * @api public + * @return {SchemaType} this + */ + +SchemaString.prototype.lowercase = function(shouldApply) { + if (arguments.length > 0 && !shouldApply) { + return this; + } + return this.set(function(v, self) { + if (typeof v !== 'string') { + v = self.cast(v); + } + if (v) { + return v.toLowerCase(); + } + return v; + }); +}; + +/** + * Adds an uppercase [setter](http://mongoosejs.com/docs/api.html#schematype_SchemaType-set). + * + * ####Example: + * + * const s = new Schema({ caps: { type: String, uppercase: true }}) + * const M = db.model('M', s); + * const m = new M({ caps: 'an example' }); + * console.log(m.caps) // AN EXAMPLE + * M.find({ caps: 'an example' }) // Matches documents where caps = 'AN EXAMPLE' + * + * Note that `uppercase` does **not** affect regular expression queries: + * + * ####Example: + * // Mongoose does **not** convert the RegExp to uppercase. + * M.find({ email: /an example/ }); + * + * @api public + * @return {SchemaType} this + */ + +SchemaString.prototype.uppercase = function(shouldApply) { + if (arguments.length > 0 && !shouldApply) { + return this; + } + return this.set(function(v, self) { + if (typeof v !== 'string') { + v = self.cast(v); + } + if (v) { + return v.toUpperCase(); + } + return v; + }); +}; + +/** + * Adds a trim [setter](http://mongoosejs.com/docs/api.html#schematype_SchemaType-set). + * + * The string value will be trimmed when set. + * + * ####Example: + * + * const s = new Schema({ name: { type: String, trim: true }}); + * const M = db.model('M', s); + * const string = ' some name '; + * console.log(string.length); // 11 + * const m = new M({ name: string }); + * console.log(m.name.length); // 9 + * + * // Equivalent to `findOne({ name: string.trim() })` + * M.findOne({ name: string }); + * + * Note that `trim` does **not** affect regular expression queries: + * + * ####Example: + * // Mongoose does **not** trim whitespace from the RegExp. + * M.find({ name: / some name / }); + * + * @api public + * @return {SchemaType} this + */ + +SchemaString.prototype.trim = function(shouldTrim) { + if (arguments.length > 0 && !shouldTrim) { + return this; + } + return this.set(function(v, self) { + if (typeof v !== 'string') { + v = self.cast(v); + } + if (v) { + return v.trim(); + } + return v; + }); +}; + +/** + * Sets a minimum length validator. + * + * ####Example: + * + * const schema = new Schema({ postalCode: { type: String, minlength: 5 }) + * const Address = db.model('Address', schema) + * const address = new Address({ postalCode: '9512' }) + * address.save(function (err) { + * console.error(err) // validator error + * address.postalCode = '95125'; + * address.save() // success + * }) + * + * // custom error messages + * // We can also use the special {MINLENGTH} token which will be replaced with the minimum allowed length + * const minlength = [5, 'The value of path `{PATH}` (`{VALUE}`) is shorter than the minimum allowed length ({MINLENGTH}).']; + * const schema = new Schema({ postalCode: { type: String, minlength: minlength }) + * const Address = mongoose.model('Address', schema); + * const address = new Address({ postalCode: '9512' }); + * address.validate(function (err) { + * console.log(String(err)) // ValidationError: The value of path `postalCode` (`9512`) is shorter than the minimum length (5). + * }) + * + * @param {Number} value minimum string length + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaString.prototype.minlength = function(value, message) { + if (this.minlengthValidator) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.minlengthValidator; + }, this); + } + + if (value !== null && value !== undefined) { + let msg = message || MongooseError.messages.String.minlength; + msg = msg.replace(/{MINLENGTH}/, value); + this.validators.push({ + validator: this.minlengthValidator = function(v) { + return v === null || v.length >= value; + }, + message: msg, + type: 'minlength', + minlength: value + }); + } + + return this; +}; + +/** + * Sets a maximum length validator. + * + * ####Example: + * + * const schema = new Schema({ postalCode: { type: String, maxlength: 9 }) + * const Address = db.model('Address', schema) + * const address = new Address({ postalCode: '9512512345' }) + * address.save(function (err) { + * console.error(err) // validator error + * address.postalCode = '95125'; + * address.save() // success + * }) + * + * // custom error messages + * // We can also use the special {MAXLENGTH} token which will be replaced with the maximum allowed length + * const maxlength = [9, 'The value of path `{PATH}` (`{VALUE}`) exceeds the maximum allowed length ({MAXLENGTH}).']; + * const schema = new Schema({ postalCode: { type: String, maxlength: maxlength }) + * const Address = mongoose.model('Address', schema); + * const address = new Address({ postalCode: '9512512345' }); + * address.validate(function (err) { + * console.log(String(err)) // ValidationError: The value of path `postalCode` (`9512512345`) exceeds the maximum allowed length (9). + * }) + * + * @param {Number} value maximum string length + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaString.prototype.maxlength = function(value, message) { + if (this.maxlengthValidator) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.maxlengthValidator; + }, this); + } + + if (value !== null && value !== undefined) { + let msg = message || MongooseError.messages.String.maxlength; + msg = msg.replace(/{MAXLENGTH}/, value); + this.validators.push({ + validator: this.maxlengthValidator = function(v) { + return v === null || v.length <= value; + }, + message: msg, + type: 'maxlength', + maxlength: value + }); + } + + return this; +}; + +/** + * Sets a regexp validator. + * + * Any value that does not pass `regExp`.test(val) will fail validation. + * + * ####Example: + * + * const s = new Schema({ name: { type: String, match: /^a/ }}) + * const M = db.model('M', s) + * const m = new M({ name: 'I am invalid' }) + * m.validate(function (err) { + * console.error(String(err)) // "ValidationError: Path `name` is invalid (I am invalid)." + * m.name = 'apples' + * m.validate(function (err) { + * assert.ok(err) // success + * }) + * }) + * + * // using a custom error message + * const match = [ /\.html$/, "That file doesn't end in .html ({VALUE})" ]; + * const s = new Schema({ file: { type: String, match: match }}) + * const M = db.model('M', s); + * const m = new M({ file: 'invalid' }); + * m.validate(function (err) { + * console.log(String(err)) // "ValidationError: That file doesn't end in .html (invalid)" + * }) + * + * Empty strings, `undefined`, and `null` values always pass the match validator. If you require these values, enable the `required` validator also. + * + * const s = new Schema({ name: { type: String, match: /^a/, required: true }}) + * + * @param {RegExp} regExp regular expression to test against + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @api public + */ + +SchemaString.prototype.match = function match(regExp, message) { + // yes, we allow multiple match validators + + const msg = message || MongooseError.messages.String.match; + + const matchValidator = function(v) { + if (!regExp) { + return false; + } + + // In case RegExp happens to have `/g` flag set, we need to reset the + // `lastIndex`, otherwise `match` will intermittently fail. + regExp.lastIndex = 0; + + const ret = ((v != null && v !== '') + ? regExp.test(v) + : true); + return ret; + }; + + this.validators.push({ + validator: matchValidator, + message: msg, + type: 'regexp', + regexp: regExp + }); + return this; +}; + +/** + * Check if the given value satisfies the `required` validator. The value is + * considered valid if it is a string (that is, not `null` or `undefined`) and + * has positive length. The `required` validator **will** fail for empty + * strings. + * + * @param {Any} value + * @param {Document} doc + * @return {Boolean} + * @api public + */ + +SchemaString.prototype.checkRequired = function checkRequired(value, doc) { + if (SchemaType._isRef(this, value, doc, true)) { + return !!value; + } + + // `require('util').inherits()` does **not** copy static properties, and + // plugins like mongoose-float use `inherits()` for pre-ES6. + const _checkRequired = typeof this.constructor.checkRequired == 'function' ? + this.constructor.checkRequired() : + SchemaString.checkRequired(); + + return _checkRequired(value); +}; + +/** + * Casts to String + * + * @api private + */ + +SchemaString.prototype.cast = function(value, doc, init) { + if (SchemaType._isRef(this, value, doc, init)) { + // wait! we may need to cast this to a document + + if (value === null || value === undefined) { + return value; + } + + // lazy load + Document || (Document = require('./../document')); + + if (value instanceof Document) { + value.$__.wasPopulated = true; + return value; + } + + // setting a populated path + if (typeof value === 'string') { + return value; + } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { + throw new CastError('string', value, this.path, null, this); + } + + // Handle the case where user directly sets a populated + // path to a plain object; cast to the Model used in + // the population query. + const path = doc.$__fullPath(this.path); + const owner = doc.ownerDocument ? doc.ownerDocument() : doc; + const pop = owner.populated(path, true); + const ret = new pop.options[populateModelSymbol](value); + ret.$__.wasPopulated = true; + return ret; + } + + const castString = typeof this.constructor.cast === 'function' ? + this.constructor.cast() : + SchemaString.cast(); + try { + return castString(value); + } catch (error) { + throw new CastError('string', value, this.path, null, this); + } +}; + +/*! + * ignore + */ + +function handleSingle(val) { + return this.castForQuery(val); +} + +function handleArray(val) { + const _this = this; + if (!Array.isArray(val)) { + return [this.castForQuery(val)]; + } + return val.map(function(m) { + return _this.castForQuery(m); + }); +} + +const $conditionalHandlers = utils.options(SchemaType.prototype.$conditionalHandlers, { + $all: handleArray, + $gt: handleSingle, + $gte: handleSingle, + $lt: handleSingle, + $lte: handleSingle, + $options: String, + $regex: handleSingle, + $not: handleSingle +}); + +Object.defineProperty(SchemaString.prototype, '$conditionalHandlers', { + configurable: false, + enumerable: false, + writable: false, + value: Object.freeze($conditionalHandlers) +}); + +/** + * Casts contents for queries. + * + * @param {String} $conditional + * @param {any} [val] + * @api private + */ + +SchemaString.prototype.castForQuery = function($conditional, val) { + let handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) { + throw new Error('Can\'t use ' + $conditional + ' with String.'); + } + return handler.call(this, val); + } + val = $conditional; + if (Object.prototype.toString.call(val) === '[object RegExp]') { + return val; + } + + return this._castForQuery(val); +}; + +/*! + * Module exports. + */ + +module.exports = SchemaString; diff --git a/node_modules/mongoose/lib/schema/symbols.js b/node_modules/mongoose/lib/schema/symbols.js new file mode 100644 index 0000000..08d1d27 --- /dev/null +++ b/node_modules/mongoose/lib/schema/symbols.js @@ -0,0 +1,5 @@ +'use strict'; + +exports.schemaMixedSymbol = Symbol.for('mongoose:schema_mixed'); + +exports.builtInMiddleware = Symbol.for('mongoose:built-in-middleware');
\ No newline at end of file diff --git a/node_modules/mongoose/lib/schematype.js b/node_modules/mongoose/lib/schematype.js new file mode 100644 index 0000000..af81375 --- /dev/null +++ b/node_modules/mongoose/lib/schematype.js @@ -0,0 +1,1581 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const MongooseError = require('./error/index'); +const SchemaTypeOptions = require('./options/SchemaTypeOptions'); +const $exists = require('./schema/operators/exists'); +const $type = require('./schema/operators/type'); +const get = require('./helpers/get'); +const handleImmutable = require('./helpers/schematype/handleImmutable'); +const immediate = require('./helpers/immediate'); +const schemaTypeSymbol = require('./helpers/symbols').schemaTypeSymbol; +const util = require('util'); +const utils = require('./utils'); +const validatorErrorSymbol = require('./helpers/symbols').validatorErrorSymbol; + +const documentIsSelected = require('./helpers/symbols').documentIsSelected; +const documentIsModified = require('./helpers/symbols').documentIsModified; + +const CastError = MongooseError.CastError; +const ValidatorError = MongooseError.ValidatorError; + +/** + * SchemaType constructor. Do **not** instantiate `SchemaType` directly. + * Mongoose converts your schema paths into SchemaTypes automatically. + * + * ####Example: + * + * const schema = new Schema({ name: String }); + * schema.path('name') instanceof SchemaType; // true + * + * @param {String} path + * @param {SchemaTypeOptions} [options] See [SchemaTypeOptions docs](/docs/api/schematypeoptions.html) + * @param {String} [instance] + * @api public + */ + +function SchemaType(path, options, instance) { + this[schemaTypeSymbol] = true; + this.path = path; + this.instance = instance; + this.validators = []; + this.getters = this.constructor.hasOwnProperty('getters') ? + this.constructor.getters.slice() : + []; + this.setters = []; + + options = options || {}; + const defaultOptions = this.constructor.defaultOptions || {}; + const defaultOptionsKeys = Object.keys(defaultOptions); + + for (const option of defaultOptionsKeys) { + if (defaultOptions.hasOwnProperty(option) && !options.hasOwnProperty(option)) { + options[option] = defaultOptions[option]; + } + } + + if (options.select == null) { + delete options.select; + } + + const Options = this.OptionsConstructor || SchemaTypeOptions; + this.options = new Options(options); + this._index = null; + + + if (utils.hasUserDefinedProperty(this.options, 'immutable')) { + this.$immutable = this.options.immutable; + + handleImmutable(this); + } + + const keys = Object.keys(this.options); + for (const prop of keys) { + if (prop === 'cast') { + continue; + } + if (utils.hasUserDefinedProperty(this.options, prop) && typeof this[prop] === 'function') { + // { unique: true, index: true } + if (prop === 'index' && this._index) { + if (options.index === false) { + const index = this._index; + if (typeof index === 'object' && index != null) { + if (index.unique) { + throw new Error('Path "' + this.path + '" may not have `index` ' + + 'set to false and `unique` set to true'); + } + if (index.sparse) { + throw new Error('Path "' + this.path + '" may not have `index` ' + + 'set to false and `sparse` set to true'); + } + } + + this._index = false; + } + continue; + } + + const val = options[prop]; + // Special case so we don't screw up array defaults, see gh-5780 + if (prop === 'default') { + this.default(val); + continue; + } + + const opts = Array.isArray(val) ? val : [val]; + + this[prop].apply(this, opts); + } + } + + Object.defineProperty(this, '$$context', { + enumerable: false, + configurable: false, + writable: true, + value: null + }); +} + +/*! + * ignore + */ + +SchemaType.prototype.OptionsConstructor = SchemaTypeOptions; + +/** + * Get/set the function used to cast arbitrary values to this type. + * + * ####Example: + * + * // Disallow `null` for numbers, and don't try to cast any values to + * // numbers, so even strings like '123' will cause a CastError. + * mongoose.Number.cast(function(v) { + * assert.ok(v === undefined || typeof v === 'number'); + * return v; + * }); + * + * @param {Function|false} caster Function that casts arbitrary values to this type, or throws an error if casting failed + * @return {Function} + * @static + * @receiver SchemaType + * @function cast + * @api public + */ + +SchemaType.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = v => v; + } + this._cast = caster; + + return this._cast; +}; + +/** + * Sets a default option for this schema type. + * + * ####Example: + * + * // Make all strings be trimmed by default + * mongoose.SchemaTypes.String.set('trim', true); + * + * @param {String} option The name of the option you'd like to set (e.g. trim, lowercase, etc...) + * @param {*} value The value of the option you'd like to set. + * @return {void} + * @static + * @receiver SchemaType + * @function set + * @api public + */ + +SchemaType.set = function set(option, value) { + if (!this.hasOwnProperty('defaultOptions')) { + this.defaultOptions = Object.assign({}, this.defaultOptions); + } + this.defaultOptions[option] = value; +}; + +/** + * Attaches a getter for all instances of this schema type. + * + * ####Example: + * + * // Make all numbers round down + * mongoose.Number.get(function(v) { return Math.floor(v); }); + * + * @param {Function} getter + * @return {this} + * @static + * @receiver SchemaType + * @function get + * @api public + */ + +SchemaType.get = function(getter) { + this.getters = this.hasOwnProperty('getters') ? this.getters : []; + this.getters.push(getter); +}; + +/** + * Sets a default value for this SchemaType. + * + * ####Example: + * + * const schema = new Schema({ n: { type: Number, default: 10 }) + * const M = db.model('M', schema) + * const m = new M; + * console.log(m.n) // 10 + * + * Defaults can be either `functions` which return the value to use as the default or the literal value itself. Either way, the value will be cast based on its schema type before being set during document creation. + * + * ####Example: + * + * // values are cast: + * const schema = new Schema({ aNumber: { type: Number, default: 4.815162342 }}) + * const M = db.model('M', schema) + * const m = new M; + * console.log(m.aNumber) // 4.815162342 + * + * // default unique objects for Mixed types: + * const schema = new Schema({ mixed: Schema.Types.Mixed }); + * schema.path('mixed').default(function () { + * return {}; + * }); + * + * // if we don't use a function to return object literals for Mixed defaults, + * // each document will receive a reference to the same object literal creating + * // a "shared" object instance: + * const schema = new Schema({ mixed: Schema.Types.Mixed }); + * schema.path('mixed').default({}); + * const M = db.model('M', schema); + * const m1 = new M; + * m1.mixed.added = 1; + * console.log(m1.mixed); // { added: 1 } + * const m2 = new M; + * console.log(m2.mixed); // { added: 1 } + * + * @param {Function|any} val the default value + * @return {defaultValue} + * @api public + */ + +SchemaType.prototype.default = function(val) { + if (arguments.length === 1) { + if (val === void 0) { + this.defaultValue = void 0; + return void 0; + } + + if (val != null && val.instanceOfSchema) { + throw new MongooseError('Cannot set default value of path `' + this.path + + '` to a mongoose Schema instance.'); + } + + this.defaultValue = val; + return this.defaultValue; + } else if (arguments.length > 1) { + this.defaultValue = utils.args(arguments); + } + return this.defaultValue; +}; + +/** + * Declares the index options for this schematype. + * + * ####Example: + * + * const s = new Schema({ name: { type: String, index: true }) + * const s = new Schema({ loc: { type: [Number], index: 'hashed' }) + * const s = new Schema({ loc: { type: [Number], index: '2d', sparse: true }) + * const s = new Schema({ loc: { type: [Number], index: { type: '2dsphere', sparse: true }}) + * const s = new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }}) + * s.path('my.path').index(true); + * s.path('my.date').index({ expires: 60 }); + * s.path('my.path').index({ unique: true, sparse: true }); + * + * ####NOTE: + * + * _Indexes are created [in the background](https://docs.mongodb.com/manual/core/index-creation/#index-creation-background) + * by default. If `background` is set to `false`, MongoDB will not execute any + * read/write operations you send until the index build. + * Specify `background: false` to override Mongoose's default._ + * + * @param {Object|Boolean|String} options + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.index = function(options) { + this._index = options; + utils.expires(this._index); + return this; +}; + +/** + * Declares an unique index. + * + * ####Example: + * + * const s = new Schema({ name: { type: String, unique: true }}); + * s.path('name').index({ unique: true }); + * + * _NOTE: violating the constraint returns an `E11000` error from MongoDB when saving, not a Mongoose validation error._ + * + * @param {Boolean} bool + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.unique = function(bool) { + if (this._index === false) { + if (!bool) { + return; + } + throw new Error('Path "' + this.path + '" may not have `index` set to ' + + 'false and `unique` set to true'); + } + if (this._index == null || this._index === true) { + this._index = {}; + } else if (typeof this._index === 'string') { + this._index = { type: this._index }; + } + + this._index.unique = bool; + return this; +}; + +/** + * Declares a full text index. + * + * ###Example: + * + * const s = new Schema({name : {type: String, text : true }) + * s.path('name').index({text : true}); + * @param {Boolean} bool + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.text = function(bool) { + if (this._index === false) { + if (!bool) { + return; + } + throw new Error('Path "' + this.path + '" may not have `index` set to ' + + 'false and `text` set to true'); + } + + if (this._index === null || this._index === undefined || + typeof this._index === 'boolean') { + this._index = {}; + } else if (typeof this._index === 'string') { + this._index = { type: this._index }; + } + + this._index.text = bool; + return this; +}; + +/** + * Declares a sparse index. + * + * ####Example: + * + * const s = new Schema({ name: { type: String, sparse: true } }); + * s.path('name').index({ sparse: true }); + * + * @param {Boolean} bool + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.sparse = function(bool) { + if (this._index === false) { + if (!bool) { + return; + } + throw new Error('Path "' + this.path + '" may not have `index` set to ' + + 'false and `sparse` set to true'); + } + + if (this._index == null || typeof this._index === 'boolean') { + this._index = {}; + } else if (typeof this._index === 'string') { + this._index = { type: this._index }; + } + + this._index.sparse = bool; + return this; +}; + +/** + * Defines this path as immutable. Mongoose prevents you from changing + * immutable paths unless the parent document has [`isNew: true`](/docs/api.html#document_Document-isNew). + * + * ####Example: + * + * const schema = new Schema({ + * name: { type: String, immutable: true }, + * age: Number + * }); + * const Model = mongoose.model('Test', schema); + * + * await Model.create({ name: 'test' }); + * const doc = await Model.findOne(); + * + * doc.isNew; // false + * doc.name = 'new name'; + * doc.name; // 'test', because `name` is immutable + * + * Mongoose also prevents changing immutable properties using `updateOne()` + * and `updateMany()` based on [strict mode](/docs/guide.html#strict). + * + * ####Example: + * + * // Mongoose will strip out the `name` update, because `name` is immutable + * Model.updateOne({}, { $set: { name: 'test2' }, $inc: { age: 1 } }); + * + * // If `strict` is set to 'throw', Mongoose will throw an error if you + * // update `name` + * const err = await Model.updateOne({}, { name: 'test2' }, { strict: 'throw' }). + * then(() => null, err => err); + * err.name; // StrictModeError + * + * // If `strict` is `false`, Mongoose allows updating `name` even though + * // the property is immutable. + * Model.updateOne({}, { name: 'test2' }, { strict: false }); + * + * @param {Boolean} bool + * @return {SchemaType} this + * @see isNew /docs/api.html#document_Document-isNew + * @api public + */ + +SchemaType.prototype.immutable = function(bool) { + this.$immutable = bool; + handleImmutable(this); + + return this; +}; + +/** + * Defines a custom function for transforming this path when converting a document to JSON. + * + * Mongoose calls this function with one parameter: the current `value` of the path. Mongoose + * then uses the return value in the JSON output. + * + * ####Example: + * + * const schema = new Schema({ + * date: { type: Date, transform: v => v.getFullYear() } + * }); + * const Model = mongoose.model('Test', schema); + * + * await Model.create({ date: new Date('2016-06-01') }); + * const doc = await Model.findOne(); + * + * doc.date instanceof Date; // true + * + * doc.toJSON().date; // 2016 as a number + * JSON.stringify(doc); // '{"_id":...,"date":2016}' + * + * @param {Function} fn + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.transform = function(fn) { + this.options.transform = fn; + + return this; +}; + +/** + * Adds a setter to this schematype. + * + * ####Example: + * + * function capitalize (val) { + * if (typeof val !== 'string') val = ''; + * return val.charAt(0).toUpperCase() + val.substring(1); + * } + * + * // defining within the schema + * const s = new Schema({ name: { type: String, set: capitalize }}); + * + * // or with the SchemaType + * const s = new Schema({ name: String }) + * s.path('name').set(capitalize); + * + * Setters allow you to transform the data before it gets to the raw mongodb + * document or query. + * + * Suppose you are implementing user registration for a website. Users provide + * an email and password, which gets saved to mongodb. The email is a string + * that you will want to normalize to lower case, in order to avoid one email + * having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM. + * + * You can set up email lower case normalization easily via a Mongoose setter. + * + * function toLower(v) { + * return v.toLowerCase(); + * } + * + * const UserSchema = new Schema({ + * email: { type: String, set: toLower } + * }); + * + * const User = db.model('User', UserSchema); + * + * const user = new User({email: 'AVENUE@Q.COM'}); + * console.log(user.email); // 'avenue@q.com' + * + * // or + * const user = new User(); + * user.email = 'Avenue@Q.com'; + * console.log(user.email); // 'avenue@q.com' + * User.updateOne({ _id: _id }, { $set: { email: 'AVENUE@Q.COM' } }); // update to 'avenue@q.com' + * + * As you can see above, setters allow you to transform the data before it + * stored in MongoDB, or before executing a query. + * + * _NOTE: we could have also just used the built-in `lowercase: true` SchemaType option instead of defining our own function._ + * + * new Schema({ email: { type: String, lowercase: true }}) + * + * Setters are also passed a second argument, the schematype on which the setter was defined. This allows for tailored behavior based on options passed in the schema. + * + * function inspector (val, schematype) { + * if (schematype.options.required) { + * return schematype.path + ' is required'; + * } else { + * return val; + * } + * } + * + * const VirusSchema = new Schema({ + * name: { type: String, required: true, set: inspector }, + * taxonomy: { type: String, set: inspector } + * }) + * + * const Virus = db.model('Virus', VirusSchema); + * const v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' }); + * + * console.log(v.name); // name is required + * console.log(v.taxonomy); // Parvovirinae + * + * You can also use setters to modify other properties on the document. If + * you're setting a property `name` on a document, the setter will run with + * `this` as the document. Be careful, in mongoose 5 setters will also run + * when querying by `name` with `this` as the query. + * + * ```javascript + * const nameSchema = new Schema({ name: String, keywords: [String] }); + * nameSchema.path('name').set(function(v) { + * // Need to check if `this` is a document, because in mongoose 5 + * // setters will also run on queries, in which case `this` will be a + * // mongoose query object. + * if (this instanceof Document && v != null) { + * this.keywords = v.split(' '); + * } + * return v; + * }); + * ``` + * + * @param {Function} fn + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.set = function(fn) { + if (typeof fn !== 'function') { + throw new TypeError('A setter must be a function.'); + } + this.setters.push(fn); + return this; +}; + +/** + * Adds a getter to this schematype. + * + * ####Example: + * + * function dob (val) { + * if (!val) return val; + * return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear(); + * } + * + * // defining within the schema + * const s = new Schema({ born: { type: Date, get: dob }) + * + * // or by retreiving its SchemaType + * const s = new Schema({ born: Date }) + * s.path('born').get(dob) + * + * Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see. + * + * Suppose you are storing credit card numbers and you want to hide everything except the last 4 digits to the mongoose user. You can do so by defining a getter in the following way: + * + * function obfuscate (cc) { + * return '****-****-****-' + cc.slice(cc.length-4, cc.length); + * } + * + * const AccountSchema = new Schema({ + * creditCardNumber: { type: String, get: obfuscate } + * }); + * + * const Account = db.model('Account', AccountSchema); + * + * Account.findById(id, function (err, found) { + * console.log(found.creditCardNumber); // '****-****-****-1234' + * }); + * + * Getters are also passed a second argument, the schematype on which the getter was defined. This allows for tailored behavior based on options passed in the schema. + * + * function inspector (val, schematype) { + * if (schematype.options.required) { + * return schematype.path + ' is required'; + * } else { + * return schematype.path + ' is not'; + * } + * } + * + * const VirusSchema = new Schema({ + * name: { type: String, required: true, get: inspector }, + * taxonomy: { type: String, get: inspector } + * }) + * + * const Virus = db.model('Virus', VirusSchema); + * + * Virus.findById(id, function (err, virus) { + * console.log(virus.name); // name is required + * console.log(virus.taxonomy); // taxonomy is not + * }) + * + * @param {Function} fn + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.get = function(fn) { + if (typeof fn !== 'function') { + throw new TypeError('A getter must be a function.'); + } + this.getters.push(fn); + return this; +}; + +/** + * Adds validator(s) for this document path. + * + * Validators always receive the value to validate as their first argument and + * must return `Boolean`. Returning `false` or throwing an error means + * validation failed. + * + * The error message argument is optional. If not passed, the [default generic error message template](#error_messages_MongooseError-messages) will be used. + * + * ####Examples: + * + * // make sure every value is equal to "something" + * function validator (val) { + * return val == 'something'; + * } + * new Schema({ name: { type: String, validate: validator }}); + * + * // with a custom error message + * + * const custom = [validator, 'Uh oh, {PATH} does not equal "something".'] + * new Schema({ name: { type: String, validate: custom }}); + * + * // adding many validators at a time + * + * const many = [ + * { validator: validator, msg: 'uh oh' } + * , { validator: anotherValidator, msg: 'failed' } + * ] + * new Schema({ name: { type: String, validate: many }}); + * + * // or utilizing SchemaType methods directly: + * + * const schema = new Schema({ name: 'string' }); + * schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`'); + * + * ####Error message templates: + * + * From the examples above, you may have noticed that error messages support + * basic templating. There are a few other template keywords besides `{PATH}` + * and `{VALUE}` too. To find out more, details are available + * [here](#error_messages_MongooseError.messages). + * + * If Mongoose's built-in error message templating isn't enough, Mongoose + * supports setting the `message` property to a function. + * + * schema.path('name').validate({ + * validator: function() { return v.length > 5; }, + * // `errors['name']` will be "name must have length 5, got 'foo'" + * message: function(props) { + * return `${props.path} must have length 5, got '${props.value}'`; + * } + * }); + * + * To bypass Mongoose's error messages and just copy the error message that + * the validator throws, do this: + * + * schema.path('name').validate({ + * validator: function() { throw new Error('Oops!'); }, + * // `errors['name']` will be "Oops!" + * message: function(props) { return props.reason.message; } + * }); + * + * ####Asynchronous validation: + * + * Mongoose supports validators that return a promise. A validator that returns + * a promise is called an _async validator_. Async validators run in + * parallel, and `validate()` will wait until all async validators have settled. + * + * schema.path('name').validate({ + * validator: function (value) { + * return new Promise(function (resolve, reject) { + * resolve(false); // validation failed + * }); + * } + * }); + * + * You might use asynchronous validators to retreive other documents from the database to validate against or to meet other I/O bound validation needs. + * + * Validation occurs `pre('save')` or whenever you manually execute [document#validate](#document_Document-validate). + * + * If validation fails during `pre('save')` and no callback was passed to receive the error, an `error` event will be emitted on your Models associated db [connection](#connection_Connection), passing the validation error object along. + * + * const conn = mongoose.createConnection(..); + * conn.on('error', handleError); + * + * const Product = conn.model('Product', yourSchema); + * const dvd = new Product(..); + * dvd.save(); // emits error on the `conn` above + * + * If you want to handle these errors at the Model level, add an `error` + * listener to your Model as shown below. + * + * // registering an error listener on the Model lets us handle errors more locally + * Product.on('error', handleError); + * + * @param {RegExp|Function|Object} obj validator function, or hash describing options + * @param {Function} [obj.validator] validator function. If the validator function returns `undefined` or a truthy value, validation succeeds. If it returns [falsy](https://masteringjs.io/tutorials/fundamentals/falsy) (except `undefined`) or throws an error, validation fails. + * @param {String|Function} [obj.message] optional error message. If function, should return the error message as a string + * @param {Boolean} [obj.propsParameter=false] If true, Mongoose will pass the validator properties object (with the `validator` function, `message`, etc.) as the 2nd arg to the validator function. This is disabled by default because many validators [rely on positional args](https://github.com/chriso/validator.js#validators), so turning this on may cause unpredictable behavior in external validators. + * @param {String|Function} [errorMsg] optional error message. If function, should return the error message as a string + * @param {String} [type] optional validator type + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.validate = function(obj, message, type) { + if (typeof obj === 'function' || obj && utils.getFunctionName(obj.constructor) === 'RegExp') { + let properties; + if (typeof message === 'function') { + properties = { validator: obj, message: message }; + properties.type = type || 'user defined'; + } else if (message instanceof Object && !type) { + properties = utils.clone(message); + if (!properties.message) { + properties.message = properties.msg; + } + properties.validator = obj; + properties.type = properties.type || 'user defined'; + } else { + if (message == null) { + message = MongooseError.messages.general.default; + } + if (!type) { + type = 'user defined'; + } + properties = { message: message, type: type, validator: obj }; + } + + if (properties.isAsync) { + handleIsAsync(); + } + + this.validators.push(properties); + return this; + } + + let i; + let length; + let arg; + + for (i = 0, length = arguments.length; i < length; i++) { + arg = arguments[i]; + if (!utils.isPOJO(arg)) { + const msg = 'Invalid validator. Received (' + typeof arg + ') ' + + arg + + '. See http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate'; + + throw new Error(msg); + } + this.validate(arg.validator, arg); + } + + return this; +}; + +/*! + * ignore + */ + +const handleIsAsync = util.deprecate(function handleIsAsync() {}, + 'Mongoose: the `isAsync` option for custom validators is deprecated. Make ' + + 'your async validators return a promise instead: ' + + 'https://mongoosejs.com/docs/validation.html#async-custom-validators'); + +/** + * Adds a required validator to this SchemaType. The validator gets added + * to the front of this SchemaType's validators array using `unshift()`. + * + * ####Example: + * + * const s = new Schema({ born: { type: Date, required: true }) + * + * // or with custom error message + * + * const s = new Schema({ born: { type: Date, required: '{PATH} is required!' }) + * + * // or with a function + * + * const s = new Schema({ + * userId: ObjectId, + * username: { + * type: String, + * required: function() { return this.userId != null; } + * } + * }) + * + * // or with a function and a custom message + * const s = new Schema({ + * userId: ObjectId, + * username: { + * type: String, + * required: [ + * function() { return this.userId != null; }, + * 'username is required if id is specified' + * ] + * } + * }) + * + * // or through the path API + * + * s.path('name').required(true); + * + * // with custom error messaging + * + * s.path('name').required(true, 'grrr :( '); + * + * // or make a path conditionally required based on a function + * const isOver18 = function() { return this.age >= 18; }; + * s.path('voterRegistrationId').required(isOver18); + * + * The required validator uses the SchemaType's `checkRequired` function to + * determine whether a given value satisfies the required validator. By default, + * a value satisfies the required validator if `val != null` (that is, if + * the value is not null nor undefined). However, most built-in mongoose schema + * types override the default `checkRequired` function: + * + * @param {Boolean|Function|Object} required enable/disable the validator, or function that returns required boolean, or options object + * @param {Boolean|Function} [options.isRequired] enable/disable the validator, or function that returns required boolean + * @param {Function} [options.ErrorConstructor] custom error constructor. The constructor receives 1 parameter, an object containing the validator properties. + * @param {String} [message] optional custom error message + * @return {SchemaType} this + * @see Customized Error Messages #error_messages_MongooseError-messages + * @see SchemaArray#checkRequired #schema_array_SchemaArray.checkRequired + * @see SchemaBoolean#checkRequired #schema_boolean_SchemaBoolean-checkRequired + * @see SchemaBuffer#checkRequired #schema_buffer_SchemaBuffer.schemaName + * @see SchemaNumber#checkRequired #schema_number_SchemaNumber-min + * @see SchemaObjectId#checkRequired #schema_objectid_ObjectId-auto + * @see SchemaString#checkRequired #schema_string_SchemaString-checkRequired + * @api public + */ + +SchemaType.prototype.required = function(required, message) { + let customOptions = {}; + + if (arguments.length > 0 && required == null) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.requiredValidator; + }, this); + + this.isRequired = false; + delete this.originalRequiredValue; + return this; + } + + if (typeof required === 'object') { + customOptions = required; + message = customOptions.message || message; + required = required.isRequired; + } + + if (required === false) { + this.validators = this.validators.filter(function(v) { + return v.validator !== this.requiredValidator; + }, this); + + this.isRequired = false; + delete this.originalRequiredValue; + return this; + } + + const _this = this; + this.isRequired = true; + + this.requiredValidator = function(v) { + const cachedRequired = get(this, '$__.cachedRequired'); + + // no validation when this path wasn't selected in the query. + if (cachedRequired != null && !this[documentIsSelected](_this.path) && !this[documentIsModified](_this.path)) { + return true; + } + + // `$cachedRequired` gets set in `_evaluateRequiredFunctions()` so we + // don't call required functions multiple times in one validate call + // See gh-6801 + if (cachedRequired != null && _this.path in cachedRequired) { + const res = cachedRequired[_this.path] ? + _this.checkRequired(v, this) : + true; + delete cachedRequired[_this.path]; + return res; + } else if (typeof required === 'function') { + return required.apply(this) ? _this.checkRequired(v, this) : true; + } + + return _this.checkRequired(v, this); + }; + this.originalRequiredValue = required; + + if (typeof required === 'string') { + message = required; + required = undefined; + } + + const msg = message || MongooseError.messages.general.required; + this.validators.unshift(Object.assign({}, customOptions, { + validator: this.requiredValidator, + message: msg, + type: 'required' + })); + + return this; +}; + +/** + * Set the model that this path refers to. This is the option that [populate](https://mongoosejs.com/docs/populate.html) + * looks at to determine the foreign collection it should query. + * + * ####Example: + * const userSchema = new Schema({ name: String }); + * const User = mongoose.model('User', userSchema); + * + * const postSchema = new Schema({ user: mongoose.ObjectId }); + * postSchema.path('user').ref('User'); // Can set ref to a model name + * postSchema.path('user').ref(User); // Or a model class + * postSchema.path('user').ref(() => 'User'); // Or a function that returns the model name + * postSchema.path('user').ref(() => User); // Or a function that returns the model class + * + * // Or you can just declare the `ref` inline in your schema + * const postSchema2 = new Schema({ + * user: { type: mongoose.ObjectId, ref: User } + * }); + * + * @param {String|Model|Function} ref either a model name, a [Model](https://mongoosejs.com/docs/models.html), or a function that returns a model name or model. + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.ref = function(ref) { + this.options.ref = ref; + return this; +}; + +/** + * Gets the default value + * + * @param {Object} scope the scope which callback are executed + * @param {Boolean} init + * @api private + */ + +SchemaType.prototype.getDefault = function(scope, init) { + let ret = typeof this.defaultValue === 'function' + ? this.defaultValue.call(scope) + : this.defaultValue; + + if (ret !== null && ret !== undefined) { + if (typeof ret === 'object' && (!this.options || !this.options.shared)) { + ret = utils.clone(ret); + } + + const casted = this.applySetters(ret, scope, init); + if (casted && casted.$isSingleNested) { + casted.$parent = scope; + } + return casted; + } + return ret; +}; + +/*! + * Applies setters without casting + * + * @api private + */ + +SchemaType.prototype._applySetters = function(value, scope, init, priorVal) { + let v = value; + const setters = this.setters; + const caster = this.caster; + + for (const setter of utils.clone(setters).reverse()) { + v = setter.call(scope, v, this); + } + + if (Array.isArray(v) && caster && caster.setters) { + const newVal = []; + + for (let i = 0; i < v.length; ++i) { + const value = v[i]; + try { + newVal.push(caster.applySetters(value, scope, init, priorVal)); + } catch (err) { + if (err instanceof MongooseError.CastError) { + err.$originalErrorPath = err.path; + err.path = err.path + '.' + i; + } + throw err; + } + } + v = newVal; + } + + + return v; +}; + +/*! + * ignore + */ + +SchemaType.prototype._castNullish = function _castNullish(v) { + return v; +}; + +/** + * Applies setters + * + * @param {Object} value + * @param {Object} scope + * @param {Boolean} init + * @api private + */ + +SchemaType.prototype.applySetters = function(value, scope, init, priorVal, options) { + let v = this._applySetters(value, scope, init, priorVal, options); + + if (v == null) { + return this._castNullish(v); + } + + // do not cast until all setters are applied #665 + v = this.cast(v, scope, init, priorVal, options); + + return v; +}; + +/** + * Applies getters to a value + * + * @param {Object} value + * @param {Object} scope + * @api private + */ + +SchemaType.prototype.applyGetters = function(value, scope) { + let v = value; + const getters = this.getters; + const len = getters.length; + + if (len === 0) { + return v; + } + + for (let i = 0; i < len; ++i) { + v = getters[i].call(scope, v, this); + } + + return v; +}; + +/** + * Sets default `select()` behavior for this path. + * + * Set to `true` if this path should always be included in the results, `false` if it should be excluded by default. This setting can be overridden at the query level. + * + * ####Example: + * + * T = db.model('T', new Schema({ x: { type: String, select: true }})); + * T.find(..); // field x will always be selected .. + * // .. unless overridden; + * T.find().select('-x').exec(callback); + * + * @param {Boolean} val + * @return {SchemaType} this + * @api public + */ + +SchemaType.prototype.select = function select(val) { + this.selected = !!val; + return this; +}; + +/** + * Performs a validation of `value` using the validators declared for this SchemaType. + * + * @param {any} value + * @param {Function} callback + * @param {Object} scope + * @api private + */ + +SchemaType.prototype.doValidate = function(value, fn, scope, options) { + let err = false; + const path = this.path; + + // Avoid non-object `validators` + const validators = this.validators. + filter(v => v != null && typeof v === 'object'); + + let count = validators.length; + + if (!count) { + return fn(null); + } + + const _this = this; + validators.forEach(function(v) { + if (err) { + return; + } + + const validator = v.validator; + let ok; + + const validatorProperties = utils.clone(v); + validatorProperties.path = options && options.path ? options.path : path; + validatorProperties.value = value; + + if (validator instanceof RegExp) { + validate(validator.test(value), validatorProperties); + return; + } + + if (typeof validator !== 'function') { + return; + } + + if (value === undefined && validator !== _this.requiredValidator) { + validate(true, validatorProperties); + return; + } + + if (validatorProperties.isAsync) { + asyncValidate(validator, scope, value, validatorProperties, validate); + return; + } + + try { + if (validatorProperties.propsParameter) { + ok = validator.call(scope, value, validatorProperties); + } else { + ok = validator.call(scope, value); + } + } catch (error) { + ok = false; + validatorProperties.reason = error; + if (error.message) { + validatorProperties.message = error.message; + } + } + + if (ok != null && typeof ok.then === 'function') { + ok.then( + function(ok) { validate(ok, validatorProperties); }, + function(error) { + validatorProperties.reason = error; + validatorProperties.message = error.message; + ok = false; + validate(ok, validatorProperties); + }); + } else { + validate(ok, validatorProperties); + } + + }); + + function validate(ok, validatorProperties) { + if (err) { + return; + } + if (ok === undefined || ok) { + if (--count <= 0) { + immediate(function() { + fn(null); + }); + } + } else { + const ErrorConstructor = validatorProperties.ErrorConstructor || ValidatorError; + err = new ErrorConstructor(validatorProperties); + err[validatorErrorSymbol] = true; + immediate(function() { + fn(err); + }); + } + } +}; + +/*! + * Handle async validators + */ + +function asyncValidate(validator, scope, value, props, cb) { + let called = false; + const returnVal = validator.call(scope, value, function(ok, customMsg) { + if (called) { + return; + } + called = true; + if (customMsg) { + props.message = customMsg; + } + cb(ok, props); + }); + if (typeof returnVal === 'boolean') { + called = true; + cb(returnVal, props); + } else if (returnVal && typeof returnVal.then === 'function') { + // Promise + returnVal.then( + function(ok) { + if (called) { + return; + } + called = true; + cb(ok, props); + }, + function(error) { + if (called) { + return; + } + called = true; + + props.reason = error; + props.message = error.message; + cb(false, props); + }); + } +} + +/** + * Performs a validation of `value` using the validators declared for this SchemaType. + * + * ####Note: + * + * This method ignores the asynchronous validators. + * + * @param {any} value + * @param {Object} scope + * @return {MongooseError|undefined} + * @api private + */ + +SchemaType.prototype.doValidateSync = function(value, scope, options) { + const path = this.path; + const count = this.validators.length; + + if (!count) { + return null; + } + + let validators = this.validators; + if (value === void 0) { + if (this.validators.length > 0 && this.validators[0].type === 'required') { + validators = [this.validators[0]]; + } else { + return null; + } + } + + let err = null; + validators.forEach(function(v) { + if (err) { + return; + } + + if (v == null || typeof v !== 'object') { + return; + } + + const validator = v.validator; + const validatorProperties = utils.clone(v); + validatorProperties.path = options && options.path ? options.path : path; + validatorProperties.value = value; + let ok; + + // Skip any explicit async validators. Validators that return a promise + // will still run, but won't trigger any errors. + if (validator.isAsync) { + return; + } + + if (validator instanceof RegExp) { + validate(validator.test(value), validatorProperties); + return; + } + + if (typeof validator !== 'function') { + return; + } + + try { + if (validatorProperties.propsParameter) { + ok = validator.call(scope, value, validatorProperties); + } else { + ok = validator.call(scope, value); + } + } catch (error) { + ok = false; + validatorProperties.reason = error; + } + + // Skip any validators that return a promise, we can't handle those + // synchronously + if (ok != null && typeof ok.then === 'function') { + return; + } + validate(ok, validatorProperties); + }); + + return err; + + function validate(ok, validatorProperties) { + if (err) { + return; + } + if (ok !== undefined && !ok) { + const ErrorConstructor = validatorProperties.ErrorConstructor || ValidatorError; + err = new ErrorConstructor(validatorProperties); + err[validatorErrorSymbol] = true; + } + } +}; + +/** + * Determines if value is a valid Reference. + * + * @param {SchemaType} self + * @param {Object} value + * @param {Document} doc + * @param {Boolean} init + * @return {Boolean} + * @api private + */ + +SchemaType._isRef = function(self, value, doc, init) { + // fast path + let ref = init && self.options && (self.options.ref || self.options.refPath); + + if (!ref && doc && doc.$__ != null) { + // checks for + // - this populated with adhoc model and no ref was set in schema OR + // - setting / pushing values after population + const path = doc.$__fullPath(self.path); + const owner = doc.ownerDocument ? doc.ownerDocument() : doc; + ref = owner.populated(path) || doc.populated(self.path); + } + + if (ref) { + if (value == null) { + return true; + } + if (!Buffer.isBuffer(value) && // buffers are objects too + value._bsontype !== 'Binary' // raw binary value from the db + && utils.isObject(value) // might have deselected _id in population query + ) { + return true; + } + } + + return false; +}; + +/*! + * ignore + */ + +function handleSingle(val) { + return this.castForQuery(val); +} + +/*! + * ignore + */ + +function handleArray(val) { + const _this = this; + if (!Array.isArray(val)) { + return [this.castForQuery(val)]; + } + return val.map(function(m) { + return _this.castForQuery(m); + }); +} + +/*! + * Just like handleArray, except also allows `[]` because surprisingly + * `$in: [1, []]` works fine + */ + +function handle$in(val) { + const _this = this; + if (!Array.isArray(val)) { + return [this.castForQuery(val)]; + } + return val.map(function(m) { + if (Array.isArray(m) && m.length === 0) { + return m; + } + return _this.castForQuery(m); + }); +} + +/*! + * ignore + */ + +SchemaType.prototype.$conditionalHandlers = { + $all: handleArray, + $eq: handleSingle, + $in: handle$in, + $ne: handleSingle, + $nin: handle$in, + $exists: $exists, + $type: $type +}; + +/*! + * Wraps `castForQuery` to handle context + */ + +SchemaType.prototype.castForQueryWrapper = function(params) { + this.$$context = params.context; + if ('$conditional' in params) { + const ret = this.castForQuery(params.$conditional, params.val); + this.$$context = null; + return ret; + } + if (params.$skipQueryCastForUpdate || params.$applySetters) { + const ret = this._castForQuery(params.val); + this.$$context = null; + return ret; + } + + const ret = this.castForQuery(params.val); + this.$$context = null; + return ret; +}; + +/** + * Cast the given value with the given optional query operator. + * + * @param {String} [$conditional] query operator, like `$eq` or `$in` + * @param {any} val + * @api private + */ + +SchemaType.prototype.castForQuery = function($conditional, val) { + let handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) { + throw new Error('Can\'t use ' + $conditional); + } + return handler.call(this, val); + } + val = $conditional; + return this._castForQuery(val); +}; + +/*! + * Internal switch for runSetters + * + * @api private + */ + +SchemaType.prototype._castForQuery = function(val) { + return this.applySetters(val, this.$$context); +}; + +/** + * Override the function the required validator uses to check whether a value + * passes the `required` check. Override this on the individual SchemaType. + * + * ####Example: + * + * // Use this to allow empty strings to pass the `required` validator + * mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string'); + * + * @param {Function} fn + * @return {Function} + * @static + * @receiver SchemaType + * @function checkRequired + * @api public + */ + +SchemaType.checkRequired = function(fn) { + if (arguments.length > 0) { + this._checkRequired = fn; + } + + return this._checkRequired; +}; + +/** + * Default check for if this path satisfies the `required` validator. + * + * @param {any} val + * @api private + */ + +SchemaType.prototype.checkRequired = function(val) { + return val != null; +}; + +/*! + * ignore + */ + +SchemaType.prototype.clone = function() { + const options = Object.assign({}, this.options); + const schematype = new this.constructor(this.path, options, this.instance); + schematype.validators = this.validators.slice(); + if (this.requiredValidator !== undefined) schematype.requiredValidator = this.requiredValidator; + if (this.defaultValue !== undefined) schematype.defaultValue = this.defaultValue; + if (this.$immutable !== undefined && this.options.immutable === undefined) { + schematype.$immutable = this.$immutable; + + handleImmutable(schematype); + } + if (this._index !== undefined) schematype._index = this._index; + if (this.selected !== undefined) schematype.selected = this.selected; + if (this.isRequired !== undefined) schematype.isRequired = this.isRequired; + if (this.originalRequiredValue !== undefined) schematype.originalRequiredValue = this.originalRequiredValue; + schematype.getters = this.getters.slice(); + schematype.setters = this.setters.slice(); + return schematype; +}; + +/*! + * Module exports. + */ + +module.exports = exports = SchemaType; + +exports.CastError = CastError; + +exports.ValidatorError = ValidatorError; diff --git a/node_modules/mongoose/lib/statemachine.js b/node_modules/mongoose/lib/statemachine.js new file mode 100644 index 0000000..7e36dc1 --- /dev/null +++ b/node_modules/mongoose/lib/statemachine.js @@ -0,0 +1,180 @@ + +/*! + * Module dependencies. + */ + +'use strict'; + +const utils = require('./utils'); + +/*! + * StateMachine represents a minimal `interface` for the + * constructors it builds via StateMachine.ctor(...). + * + * @api private + */ + +const StateMachine = module.exports = exports = function StateMachine() { +}; + +/*! + * StateMachine.ctor('state1', 'state2', ...) + * A factory method for subclassing StateMachine. + * The arguments are a list of states. For each state, + * the constructor's prototype gets state transition + * methods named after each state. These transition methods + * place their path argument into the given state. + * + * @param {String} state + * @param {String} [state] + * @return {Function} subclass constructor + * @private + */ + +StateMachine.ctor = function() { + const states = utils.args(arguments); + + const ctor = function() { + StateMachine.apply(this, arguments); + this.paths = {}; + this.states = {}; + this.stateNames = states; + + let i = states.length, + state; + + while (i--) { + state = states[i]; + this.states[state] = {}; + } + }; + + ctor.prototype = new StateMachine(); + + states.forEach(function(state) { + // Changes the `path`'s state to `state`. + ctor.prototype[state] = function(path) { + this._changeState(path, state); + }; + }); + + return ctor; +}; + +/*! + * This function is wrapped by the state change functions: + * + * - `require(path)` + * - `modify(path)` + * - `init(path)` + * + * @api private + */ + +StateMachine.prototype._changeState = function _changeState(path, nextState) { + const prevBucket = this.states[this.paths[path]]; + if (prevBucket) delete prevBucket[path]; + + this.paths[path] = nextState; + this.states[nextState][path] = true; +}; + +/*! + * ignore + */ + +StateMachine.prototype.clear = function clear(state) { + const keys = Object.keys(this.states[state]); + let i = keys.length; + let path; + + while (i--) { + path = keys[i]; + delete this.states[state][path]; + delete this.paths[path]; + } +}; + +/*! + * Checks to see if at least one path is in the states passed in via `arguments` + * e.g., this.some('required', 'inited') + * + * @param {String} state that we want to check for. + * @private + */ + +StateMachine.prototype.some = function some() { + const _this = this; + const what = arguments.length ? arguments : this.stateNames; + return Array.prototype.some.call(what, function(state) { + return Object.keys(_this.states[state]).length; + }); +}; + +/*! + * This function builds the functions that get assigned to `forEach` and `map`, + * since both of those methods share a lot of the same logic. + * + * @param {String} iterMethod is either 'forEach' or 'map' + * @return {Function} + * @api private + */ + +StateMachine.prototype._iter = function _iter(iterMethod) { + return function() { + const numArgs = arguments.length; + let states = utils.args(arguments, 0, numArgs - 1); + const callback = arguments[numArgs - 1]; + + if (!states.length) states = this.stateNames; + + const _this = this; + + const paths = states.reduce(function(paths, state) { + return paths.concat(Object.keys(_this.states[state])); + }, []); + + return paths[iterMethod](function(path, i, paths) { + return callback(path, i, paths); + }); + }; +}; + +/*! + * Iterates over the paths that belong to one of the parameter states. + * + * The function profile can look like: + * this.forEach(state1, fn); // iterates over all paths in state1 + * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2 + * this.forEach(fn); // iterates over all paths in all states + * + * @param {String} [state] + * @param {String} [state] + * @param {Function} callback + * @private + */ + +StateMachine.prototype.forEach = function forEach() { + this.forEach = this._iter('forEach'); + return this.forEach.apply(this, arguments); +}; + +/*! + * Maps over the paths that belong to one of the parameter states. + * + * The function profile can look like: + * this.forEach(state1, fn); // iterates over all paths in state1 + * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2 + * this.forEach(fn); // iterates over all paths in all states + * + * @param {String} [state] + * @param {String} [state] + * @param {Function} callback + * @return {Array} + * @private + */ + +StateMachine.prototype.map = function map() { + this.map = this._iter('map'); + return this.map.apply(this, arguments); +}; diff --git a/node_modules/mongoose/lib/types/array.js b/node_modules/mongoose/lib/types/array.js new file mode 100644 index 0000000..41c51e5 --- /dev/null +++ b/node_modules/mongoose/lib/types/array.js @@ -0,0 +1,66 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const CoreMongooseArray = require('./core_array'); +const Document = require('../document'); + +const arrayAtomicsSymbol = require('../helpers/symbols').arrayAtomicsSymbol; +const arrayParentSymbol = require('../helpers/symbols').arrayParentSymbol; +const arrayPathSymbol = require('../helpers/symbols').arrayPathSymbol; +const arraySchemaSymbol = require('../helpers/symbols').arraySchemaSymbol; + +const _basePush = Array.prototype.push; + +/** + * Mongoose Array constructor. + * + * ####NOTE: + * + * _Values always have to be passed to the constructor to initialize, otherwise `MongooseArray#push` will mark the array as modified._ + * + * @param {Array} values + * @param {String} path + * @param {Document} doc parent document + * @api private + * @inherits Array + * @see http://bit.ly/f6CnZU + */ + +function MongooseArray(values, path, doc) { + // TODO: replace this with `new CoreMongooseArray().concat()` when we remove + // support for node 4.x and 5.x, see https://i.imgur.com/UAAHk4S.png + const arr = new CoreMongooseArray(); + arr[arrayAtomicsSymbol] = {}; + + if (Array.isArray(values)) { + const len = values.length; + for (let i = 0; i < len; ++i) { + _basePush.call(arr, values[i]); + } + + arr[arrayAtomicsSymbol] = values[arrayAtomicsSymbol] || {}; + } + + arr[arrayPathSymbol] = path; + arr[arraySchemaSymbol] = void 0; + + // Because doc comes from the context of another function, doc === global + // can happen if there was a null somewhere up the chain (see #3020) + // RB Jun 17, 2015 updated to check for presence of expected paths instead + // to make more proof against unusual node environments + if (doc && doc instanceof Document) { + arr[arrayParentSymbol] = doc; + arr[arraySchemaSymbol] = doc.schema.path(path); + } + + return arr; +} + +/*! + * Module exports. + */ + +module.exports = exports = MongooseArray; diff --git a/node_modules/mongoose/lib/types/buffer.js b/node_modules/mongoose/lib/types/buffer.js new file mode 100644 index 0000000..9bac2e8 --- /dev/null +++ b/node_modules/mongoose/lib/types/buffer.js @@ -0,0 +1,276 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const Binary = require('../driver').get().Binary; +const utils = require('../utils'); +const Buffer = require('safe-buffer').Buffer; + +/** + * Mongoose Buffer constructor. + * + * Values always have to be passed to the constructor to initialize. + * + * @param {Buffer} value + * @param {String} encode + * @param {Number} offset + * @api private + * @inherits Buffer + * @see http://bit.ly/f6CnZU + */ + +function MongooseBuffer(value, encode, offset) { + const length = arguments.length; + let val; + + if (length === 0 || arguments[0] === null || arguments[0] === undefined) { + val = 0; + } else { + val = value; + } + + let encoding; + let path; + let doc; + + if (Array.isArray(encode)) { + // internal casting + path = encode[0]; + doc = encode[1]; + } else { + encoding = encode; + } + + let buf; + if (typeof val === 'number' || val instanceof Number) { + buf = Buffer.alloc(val); + } else { // string, array or object { type: 'Buffer', data: [...] } + buf = Buffer.from(val, encoding, offset); + } + utils.decorate(buf, MongooseBuffer.mixin); + buf.isMongooseBuffer = true; + + // make sure these internal props don't show up in Object.keys() + buf[MongooseBuffer.pathSymbol] = path; + buf[parentSymbol] = doc; + + buf._subtype = 0; + return buf; +} + +const pathSymbol = Symbol.for('mongoose#Buffer#_path'); +const parentSymbol = Symbol.for('mongoose#Buffer#_parent'); +MongooseBuffer.pathSymbol = pathSymbol; + +/*! + * Inherit from Buffer. + */ + +MongooseBuffer.mixin = { + + /** + * Default subtype for the Binary representing this Buffer + * + * @api private + * @property _subtype + * @receiver MongooseBuffer + */ + + _subtype: undefined, + + /** + * Marks this buffer as modified. + * + * @api private + * @method _markModified + * @receiver MongooseBuffer + */ + + _markModified: function() { + const parent = this[parentSymbol]; + + if (parent) { + parent.markModified(this[MongooseBuffer.pathSymbol]); + } + return this; + }, + + /** + * Writes the buffer. + * + * @api public + * @method write + * @receiver MongooseBuffer + */ + + write: function() { + const written = Buffer.prototype.write.apply(this, arguments); + + if (written > 0) { + this._markModified(); + } + + return written; + }, + + /** + * Copies the buffer. + * + * ####Note: + * + * `Buffer#copy` does not mark `target` as modified so you must copy from a `MongooseBuffer` for it to work as expected. This is a work around since `copy` modifies the target, not this. + * + * @return {Number} The number of bytes copied. + * @param {Buffer} target + * @method copy + * @receiver MongooseBuffer + */ + + copy: function(target) { + const ret = Buffer.prototype.copy.apply(this, arguments); + + if (target && target.isMongooseBuffer) { + target._markModified(); + } + + return ret; + } +}; + +/*! + * Compile other Buffer methods marking this buffer as modified. + */ + +( +// node < 0.5 + ('writeUInt8 writeUInt16 writeUInt32 writeInt8 writeInt16 writeInt32 ' + + 'writeFloat writeDouble fill ' + + 'utf8Write binaryWrite asciiWrite set ' + + + // node >= 0.5 + 'writeUInt16LE writeUInt16BE writeUInt32LE writeUInt32BE ' + + 'writeInt16LE writeInt16BE writeInt32LE writeInt32BE ' + 'writeFloatLE writeFloatBE writeDoubleLE writeDoubleBE') +).split(' ').forEach(function(method) { + if (!Buffer.prototype[method]) { + return; + } + MongooseBuffer.mixin[method] = function() { + const ret = Buffer.prototype[method].apply(this, arguments); + this._markModified(); + return ret; + }; +}); + +/** + * Converts this buffer to its Binary type representation. + * + * ####SubTypes: + * + * const bson = require('bson') + * bson.BSON_BINARY_SUBTYPE_DEFAULT + * bson.BSON_BINARY_SUBTYPE_FUNCTION + * bson.BSON_BINARY_SUBTYPE_BYTE_ARRAY + * bson.BSON_BINARY_SUBTYPE_UUID + * bson.BSON_BINARY_SUBTYPE_MD5 + * bson.BSON_BINARY_SUBTYPE_USER_DEFINED + * + * doc.buffer.toObject(bson.BSON_BINARY_SUBTYPE_USER_DEFINED); + * + * @see http://bsonspec.org/#/specification + * @param {Hex} [subtype] + * @return {Binary} + * @api public + * @method toObject + * @receiver MongooseBuffer + */ + +MongooseBuffer.mixin.toObject = function(options) { + const subtype = typeof options === 'number' + ? options + : (this._subtype || 0); + return new Binary(Buffer.from(this), subtype); +}; + +/** + * Converts this buffer for storage in MongoDB, including subtype + * + * @return {Binary} + * @api public + * @method toBSON + * @receiver MongooseBuffer + */ + +MongooseBuffer.mixin.toBSON = function() { + return new Binary(this, this._subtype || 0); +}; + +/** + * Determines if this buffer is equals to `other` buffer + * + * @param {Buffer} other + * @return {Boolean} + * @method equals + * @receiver MongooseBuffer + */ + +MongooseBuffer.mixin.equals = function(other) { + if (!Buffer.isBuffer(other)) { + return false; + } + + if (this.length !== other.length) { + return false; + } + + for (let i = 0; i < this.length; ++i) { + if (this[i] !== other[i]) { + return false; + } + } + + return true; +}; + +/** + * Sets the subtype option and marks the buffer modified. + * + * ####SubTypes: + * + * const bson = require('bson') + * bson.BSON_BINARY_SUBTYPE_DEFAULT + * bson.BSON_BINARY_SUBTYPE_FUNCTION + * bson.BSON_BINARY_SUBTYPE_BYTE_ARRAY + * bson.BSON_BINARY_SUBTYPE_UUID + * bson.BSON_BINARY_SUBTYPE_MD5 + * bson.BSON_BINARY_SUBTYPE_USER_DEFINED + * + * doc.buffer.subtype(bson.BSON_BINARY_SUBTYPE_UUID); + * + * @see http://bsonspec.org/#/specification + * @param {Hex} subtype + * @api public + * @method subtype + * @receiver MongooseBuffer + */ + +MongooseBuffer.mixin.subtype = function(subtype) { + if (typeof subtype !== 'number') { + throw new TypeError('Invalid subtype. Expected a number'); + } + + if (this._subtype !== subtype) { + this._markModified(); + } + + this._subtype = subtype; +}; + +/*! + * Module exports. + */ + +MongooseBuffer.Binary = Binary; + +module.exports = MongooseBuffer; diff --git a/node_modules/mongoose/lib/types/core_array.js b/node_modules/mongoose/lib/types/core_array.js new file mode 100644 index 0000000..954df52 --- /dev/null +++ b/node_modules/mongoose/lib/types/core_array.js @@ -0,0 +1,958 @@ +'use strict'; + +const Document = require('../document'); +const EmbeddedDocument = require('./embedded'); +const MongooseError = require('../error/mongooseError'); +const ObjectId = require('./objectid'); +const cleanModifiedSubpaths = require('../helpers/document/cleanModifiedSubpaths'); +const get = require('../helpers/get'); +const internalToObjectOptions = require('../options').internalToObjectOptions; +const utils = require('../utils'); +const util = require('util'); + +const arrayAtomicsSymbol = require('../helpers/symbols').arrayAtomicsSymbol; +const arrayParentSymbol = require('../helpers/symbols').arrayParentSymbol; +const arrayPathSymbol = require('../helpers/symbols').arrayPathSymbol; +const arraySchemaSymbol = require('../helpers/symbols').arraySchemaSymbol; +const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; +const slicedSymbol = Symbol('mongoose#Array#sliced'); + +const _basePush = Array.prototype.push; + +const validatorsSymbol = Symbol('mongoose#MongooseCoreArray#validators'); + +/*! + * ignore + */ + +class CoreMongooseArray extends Array { + get isMongooseArray() { + return true; + } + + get validators() { + return this[validatorsSymbol]; + } + + set validators(v) { + this[validatorsSymbol] = v; + } + + /** + * Depopulates stored atomic operation values as necessary for direct insertion to MongoDB. + * + * If no atomics exist, we return all array values after conversion. + * + * @return {Array} + * @method $__getAtomics + * @memberOf MongooseArray + * @instance + * @api private + */ + + $__getAtomics() { + const ret = []; + const keys = Object.keys(this[arrayAtomicsSymbol]); + let i = keys.length; + + const opts = Object.assign({}, internalToObjectOptions, { _isNested: true }); + + if (i === 0) { + ret[0] = ['$set', this.toObject(opts)]; + return ret; + } + + while (i--) { + const op = keys[i]; + let val = this[arrayAtomicsSymbol][op]; + + // the atomic values which are arrays are not MongooseArrays. we + // need to convert their elements as if they were MongooseArrays + // to handle populated arrays versus DocumentArrays properly. + if (utils.isMongooseObject(val)) { + val = val.toObject(opts); + } else if (Array.isArray(val)) { + val = this.toObject.call(val, opts); + } else if (val != null && Array.isArray(val.$each)) { + val.$each = this.toObject.call(val.$each, opts); + } else if (val != null && typeof val.valueOf === 'function') { + val = val.valueOf(); + } + + if (op === '$addToSet') { + val = { $each: val }; + } + + ret.push([op, val]); + } + + return ret; + } + + /*! + * ignore + */ + + $atomics() { + return this[arrayAtomicsSymbol]; + } + + /*! + * ignore + */ + + $parent() { + return this[arrayParentSymbol]; + } + + /*! + * ignore + */ + + $path() { + return this[arrayPathSymbol]; + } + + /** + * Atomically shifts the array at most one time per document `save()`. + * + * ####NOTE: + * + * _Calling this multiple times on an array before saving sends the same command as calling it once._ + * _This update is implemented using the MongoDB [$pop](http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._ + * + * doc.array = [1,2,3]; + * + * const shifted = doc.array.$shift(); + * console.log(shifted); // 1 + * console.log(doc.array); // [2,3] + * + * // no affect + * shifted = doc.array.$shift(); + * console.log(doc.array); // [2,3] + * + * doc.save(function (err) { + * if (err) return handleError(err); + * + * // we saved, now $shift works again + * shifted = doc.array.$shift(); + * console.log(shifted ); // 2 + * console.log(doc.array); // [3] + * }) + * + * @api public + * @memberOf MongooseArray + * @instance + * @method $shift + * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop + */ + + $shift() { + this._registerAtomic('$pop', -1); + this._markModified(); + + // only allow shifting once + if (this._shifted) { + return; + } + this._shifted = true; + + return [].shift.call(this); + } + + /** + * Pops the array atomically at most one time per document `save()`. + * + * #### NOTE: + * + * _Calling this mulitple times on an array before saving sends the same command as calling it once._ + * _This update is implemented using the MongoDB [$pop](http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._ + * + * doc.array = [1,2,3]; + * + * const popped = doc.array.$pop(); + * console.log(popped); // 3 + * console.log(doc.array); // [1,2] + * + * // no affect + * popped = doc.array.$pop(); + * console.log(doc.array); // [1,2] + * + * doc.save(function (err) { + * if (err) return handleError(err); + * + * // we saved, now $pop works again + * popped = doc.array.$pop(); + * console.log(popped); // 2 + * console.log(doc.array); // [1] + * }) + * + * @api public + * @method $pop + * @memberOf MongooseArray + * @instance + * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop + * @method $pop + * @memberOf MongooseArray + */ + + $pop() { + this._registerAtomic('$pop', 1); + this._markModified(); + + // only allow popping once + if (this._popped) { + return; + } + this._popped = true; + + return [].pop.call(this); + } + + /*! + * ignore + */ + + $schema() { + return this[arraySchemaSymbol]; + } + + /** + * Casts a member based on this arrays schema. + * + * @param {any} value + * @return value the casted value + * @method _cast + * @api private + * @memberOf MongooseArray + */ + + _cast(value) { + let populated = false; + let Model; + + if (this[arrayParentSymbol]) { + populated = this[arrayParentSymbol].populated(this[arrayPathSymbol], true); + } + + if (populated && value !== null && value !== undefined) { + // cast to the populated Models schema + Model = populated.options[populateModelSymbol]; + + // only objects are permitted so we can safely assume that + // non-objects are to be interpreted as _id + if (Buffer.isBuffer(value) || + value instanceof ObjectId || !utils.isObject(value)) { + value = { _id: value }; + } + + // gh-2399 + // we should cast model only when it's not a discriminator + const isDisc = value.schema && value.schema.discriminatorMapping && + value.schema.discriminatorMapping.key !== undefined; + if (!isDisc) { + value = new Model(value); + } + return this[arraySchemaSymbol].caster.applySetters(value, this[arrayParentSymbol], true); + } + + return this[arraySchemaSymbol].caster.applySetters(value, this[arrayParentSymbol], false); + } + + /** + * Internal helper for .map() + * + * @api private + * @return {Number} + * @method _mapCast + * @memberOf MongooseArray + */ + + _mapCast(val, index) { + return this._cast(val, this.length + index); + } + + /** + * Marks this array as modified. + * + * If it bubbles up from an embedded document change, then it takes the following arguments (otherwise, takes 0 arguments) + * + * @param {EmbeddedDocument} embeddedDoc the embedded doc that invoked this method on the Array + * @param {String} embeddedPath the path which changed in the embeddedDoc + * @method _markModified + * @api private + * @memberOf MongooseArray + */ + + _markModified(elem, embeddedPath) { + const parent = this[arrayParentSymbol]; + let dirtyPath; + + if (parent) { + dirtyPath = this[arrayPathSymbol]; + + if (arguments.length) { + if (embeddedPath != null) { + // an embedded doc bubbled up the change + dirtyPath = dirtyPath + '.' + this.indexOf(elem) + '.' + embeddedPath; + } else { + // directly set an index + dirtyPath = dirtyPath + '.' + elem; + } + } + + if (dirtyPath != null && dirtyPath.endsWith('.$')) { + return this; + } + + parent.markModified(dirtyPath, arguments.length > 0 ? elem : parent); + } + + return this; + } + + /** + * Register an atomic operation with the parent. + * + * @param {Array} op operation + * @param {any} val + * @method _registerAtomic + * @api private + * @memberOf MongooseArray + */ + + _registerAtomic(op, val) { + if (this[slicedSymbol]) { + return; + } + if (op === '$set') { + // $set takes precedence over all other ops. + // mark entire array modified. + this[arrayAtomicsSymbol] = { $set: val }; + cleanModifiedSubpaths(this[arrayParentSymbol], this[arrayPathSymbol]); + this._markModified(); + return this; + } + + const atomics = this[arrayAtomicsSymbol]; + + // reset pop/shift after save + if (op === '$pop' && !('$pop' in atomics)) { + const _this = this; + this[arrayParentSymbol].once('save', function() { + _this._popped = _this._shifted = null; + }); + } + + // check for impossible $atomic combos (Mongo denies more than one + // $atomic op on a single path + if (this[arrayAtomicsSymbol].$set || Object.keys(atomics).length && !(op in atomics)) { + // a different op was previously registered. + // save the entire thing. + this[arrayAtomicsSymbol] = { $set: this }; + return this; + } + + let selector; + + if (op === '$pullAll' || op === '$addToSet') { + atomics[op] || (atomics[op] = []); + atomics[op] = atomics[op].concat(val); + } else if (op === '$pullDocs') { + const pullOp = atomics['$pull'] || (atomics['$pull'] = {}); + if (val[0] instanceof EmbeddedDocument) { + selector = pullOp['$or'] || (pullOp['$or'] = []); + Array.prototype.push.apply(selector, val.map(function(v) { + return v.toObject({ transform: false, virtuals: false }); + })); + } else { + selector = pullOp['_id'] || (pullOp['_id'] = { $in: [] }); + selector['$in'] = selector['$in'].concat(val); + } + } else if (op === '$push') { + atomics.$push = atomics.$push || { $each: [] }; + if (val != null && utils.hasUserDefinedProperty(val, '$each')) { + atomics.$push = val; + } else { + atomics.$push.$each = atomics.$push.$each.concat(val); + } + } else { + atomics[op] = val; + } + + return this; + } + + /** + * Adds values to the array if not already present. + * + * ####Example: + * + * console.log(doc.array) // [2,3,4] + * const added = doc.array.addToSet(4,5); + * console.log(doc.array) // [2,3,4,5] + * console.log(added) // [5] + * + * @param {any} [args...] + * @return {Array} the values that were added + * @memberOf MongooseArray + * @api public + * @method addToSet + */ + + addToSet() { + _checkManualPopulation(this, arguments); + + let values = [].map.call(arguments, this._mapCast, this); + values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol]); + const added = []; + let type = ''; + if (values[0] instanceof EmbeddedDocument) { + type = 'doc'; + } else if (values[0] instanceof Date) { + type = 'date'; + } + + values.forEach(function(v) { + let found; + const val = +v; + switch (type) { + case 'doc': + found = this.some(function(doc) { + return doc.equals(v); + }); + break; + case 'date': + found = this.some(function(d) { + return +d === val; + }); + break; + default: + found = ~this.indexOf(v); + } + + if (!found) { + [].push.call(this, v); + this._registerAtomic('$addToSet', v); + this._markModified(); + [].push.call(added, v); + } + }, this); + + return added; + } + + /** + * Returns the number of pending atomic operations to send to the db for this array. + * + * @api private + * @return {Number} + * @method hasAtomics + * @memberOf MongooseArray + */ + + hasAtomics() { + if (!utils.isPOJO(this[arrayAtomicsSymbol])) { + return 0; + } + + return Object.keys(this[arrayAtomicsSymbol]).length; + } + + /** + * Return whether or not the `obj` is included in the array. + * + * @param {Object} obj the item to check + * @return {Boolean} + * @api public + * @method includes + * @memberOf MongooseArray + */ + + includes(obj, fromIndex) { + const ret = this.indexOf(obj, fromIndex); + return ret !== -1; + } + + /** + * Return the index of `obj` or `-1` if not found. + * + * @param {Object} obj the item to look for + * @return {Number} + * @api public + * @method indexOf + * @memberOf MongooseArray + */ + + indexOf(obj, fromIndex) { + if (obj instanceof ObjectId) { + obj = obj.toString(); + } + + fromIndex = fromIndex == null ? 0 : fromIndex; + const len = this.length; + for (let i = fromIndex; i < len; ++i) { + if (obj == this[i]) { + return i; + } + } + return -1; + } + + /** + * Helper for console.log + * + * @api public + * @method inspect + * @memberOf MongooseArray + */ + + inspect() { + return JSON.stringify(this); + } + + /** + * Pushes items to the array non-atomically. + * + * ####NOTE: + * + * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ + * + * @param {any} [args...] + * @api public + * @method nonAtomicPush + * @memberOf MongooseArray + */ + + nonAtomicPush() { + const values = [].map.call(arguments, this._mapCast, this); + const ret = [].push.apply(this, values); + this._registerAtomic('$set', this); + this._markModified(); + return ret; + } + + /** + * Wraps [`Array#pop`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/pop) with proper change tracking. + * + * ####Note: + * + * _marks the entire array as modified which will pass the entire thing to $set potentially overwritting any changes that happen between when you retrieved the object and when you save it._ + * + * @see MongooseArray#$pop #types_array_MongooseArray-%24pop + * @api public + * @method pop + * @memberOf MongooseArray + */ + + pop() { + const ret = [].pop.call(this); + this._registerAtomic('$set', this); + this._markModified(); + return ret; + } + + /** + * Pulls items from the array atomically. Equality is determined by casting + * the provided value to an embedded document and comparing using + * [the `Document.equals()` function.](./api.html#document_Document-equals) + * + * ####Examples: + * + * doc.array.pull(ObjectId) + * doc.array.pull({ _id: 'someId' }) + * doc.array.pull(36) + * doc.array.pull('tag 1', 'tag 2') + * + * To remove a document from a subdocument array we may pass an object with a matching `_id`. + * + * doc.subdocs.push({ _id: 4815162342 }) + * doc.subdocs.pull({ _id: 4815162342 }) // removed + * + * Or we may passing the _id directly and let mongoose take care of it. + * + * doc.subdocs.push({ _id: 4815162342 }) + * doc.subdocs.pull(4815162342); // works + * + * The first pull call will result in a atomic operation on the database, if pull is called repeatedly without saving the document, a $set operation is used on the complete array instead, overwriting possible changes that happened on the database in the meantime. + * + * @param {any} [args...] + * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull + * @api public + * @method pull + * @memberOf MongooseArray + */ + + pull() { + const values = [].map.call(arguments, this._cast, this); + const cur = this[arrayParentSymbol].get(this[arrayPathSymbol]); + let i = cur.length; + let mem; + + while (i--) { + mem = cur[i]; + if (mem instanceof Document) { + const some = values.some(function(v) { + return mem.equals(v); + }); + if (some) { + [].splice.call(cur, i, 1); + } + } else if (~cur.indexOf.call(values, mem)) { + [].splice.call(cur, i, 1); + } + } + + if (values[0] instanceof EmbeddedDocument) { + this._registerAtomic('$pullDocs', values.map(function(v) { + return v.$__getValue('_id') || v; + })); + } else { + this._registerAtomic('$pullAll', values); + } + + this._markModified(); + + // Might have modified child paths and then pulled, like + // `doc.children[1].name = 'test';` followed by + // `doc.children.remove(doc.children[0]);`. In this case we fall back + // to a `$set` on the whole array. See #3511 + if (cleanModifiedSubpaths(this[arrayParentSymbol], this[arrayPathSymbol]) > 0) { + this._registerAtomic('$set', this); + } + + return this; + } + + /** + * Wraps [`Array#push`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push) with proper change tracking. + * + * ####Example: + * + * const schema = Schema({ nums: [Number] }); + * const Model = mongoose.model('Test', schema); + * + * const doc = await Model.create({ nums: [3, 4] }); + * doc.nums.push(5); // Add 5 to the end of the array + * await doc.save(); + * + * // You can also pass an object with `$each` as the + * // first parameter to use MongoDB's `$position` + * doc.nums.push({ + * $each: [1, 2], + * $position: 0 + * }); + * doc.nums; // [1, 2, 3, 4, 5] + * + * @param {Object} [args...] + * @api public + * @method push + * @memberOf MongooseArray + */ + + push() { + let values = arguments; + let atomic = values; + const isOverwrite = values[0] != null && + utils.hasUserDefinedProperty(values[0], '$each'); + if (isOverwrite) { + atomic = values[0]; + values = values[0].$each; + } + + if (this[arraySchemaSymbol] == null) { + return _basePush.apply(this, values); + } + + _checkManualPopulation(this, values); + + const parent = this[arrayParentSymbol]; + values = [].map.call(values, this._mapCast, this); + values = this[arraySchemaSymbol].applySetters(values, parent, undefined, + undefined, { skipDocumentArrayCast: true }); + let ret; + const atomics = this[arrayAtomicsSymbol]; + + if (isOverwrite) { + atomic.$each = values; + + if (get(atomics, '$push.$each.length', 0) > 0 && + atomics.$push.$position != atomics.$position) { + throw new MongooseError('Cannot call `Array#push()` multiple times ' + + 'with different `$position`'); + } + + if (atomic.$position != null) { + [].splice.apply(this, [atomic.$position, 0].concat(values)); + ret = this.length; + } else { + ret = [].push.apply(this, values); + } + } else { + if (get(atomics, '$push.$each.length', 0) > 0 && + atomics.$push.$position != null) { + throw new MongooseError('Cannot call `Array#push()` multiple times ' + + 'with different `$position`'); + } + atomic = values; + ret = [].push.apply(this, values); + } + this._registerAtomic('$push', atomic); + this._markModified(); + return ret; + } + + /** + * Alias of [pull](#mongoosearray_MongooseArray-pull) + * + * @see MongooseArray#pull #types_array_MongooseArray-pull + * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull + * @api public + * @memberOf MongooseArray + * @instance + * @method remove + */ + + remove() { + return this.pull.apply(this, arguments); + } + + /** + * Sets the casted `val` at index `i` and marks the array modified. + * + * ####Example: + * + * // given documents based on the following + * const Doc = mongoose.model('Doc', new Schema({ array: [Number] })); + * + * const doc = new Doc({ array: [2,3,4] }) + * + * console.log(doc.array) // [2,3,4] + * + * doc.array.set(1,"5"); + * console.log(doc.array); // [2,5,4] // properly cast to number + * doc.save() // the change is saved + * + * // VS not using array#set + * doc.array[1] = "5"; + * console.log(doc.array); // [2,"5",4] // no casting + * doc.save() // change is not saved + * + * @return {Array} this + * @api public + * @method set + * @memberOf MongooseArray + */ + + set(i, val) { + const value = this._cast(val, i); + this[i] = value; + this._markModified(i); + return this; + } + + /** + * Wraps [`Array#shift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking. + * + * ####Example: + * + * doc.array = [2,3]; + * const res = doc.array.shift(); + * console.log(res) // 2 + * console.log(doc.array) // [3] + * + * ####Note: + * + * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ + * + * @api public + * @method shift + * @memberOf MongooseArray + */ + + shift() { + const ret = [].shift.call(this); + this._registerAtomic('$set', this); + this._markModified(); + return ret; + } + + /** + * Wraps [`Array#sort`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort) with proper change tracking. + * + * ####NOTE: + * + * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ + * + * @api public + * @method sort + * @memberOf MongooseArray + * @see https://masteringjs.io/tutorials/fundamentals/array-sort + */ + + sort() { + const ret = [].sort.apply(this, arguments); + this._registerAtomic('$set', this); + return ret; + } + + /** + * Wraps [`Array#splice`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice) with proper change tracking and casting. + * + * ####Note: + * + * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ + * + * @api public + * @method splice + * @memberOf MongooseArray + * @see https://masteringjs.io/tutorials/fundamentals/array-splice + */ + + splice() { + let ret; + + _checkManualPopulation(this, Array.prototype.slice.call(arguments, 2)); + + if (arguments.length) { + let vals; + if (this[arraySchemaSymbol] == null) { + vals = arguments; + } else { + vals = []; + for (let i = 0; i < arguments.length; ++i) { + vals[i] = i < 2 ? + arguments[i] : + this._cast(arguments[i], arguments[0] + (i - 2)); + } + } + + ret = [].splice.apply(this, vals); + this._registerAtomic('$set', this); + } + + return ret; + } + + /*! + * ignore + */ + + slice() { + const ret = super.slice.apply(this, arguments); + ret[arrayParentSymbol] = this[arrayParentSymbol]; + ret[arraySchemaSymbol] = this[arraySchemaSymbol]; + ret[arrayAtomicsSymbol] = this[arrayAtomicsSymbol]; + ret[slicedSymbol] = true; + return ret; + } + + /*! + * ignore + */ + + toBSON() { + return this.toObject(internalToObjectOptions); + } + + /** + * Returns a native js Array. + * + * @param {Object} options + * @return {Array} + * @api public + * @method toObject + * @memberOf MongooseArray + */ + + toObject(options) { + if (options && options.depopulate) { + options = utils.clone(options); + options._isNested = true; + return this.map(function(doc) { + return doc instanceof Document + ? doc.toObject(options) + : doc; + }); + } + + return this.slice(); + } + + /** + * Wraps [`Array#unshift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking. + * + * ####Note: + * + * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwriting any changes that happen between when you retrieved the object and when you save it._ + * + * @api public + * @method unshift + * @memberOf MongooseArray + */ + + unshift() { + _checkManualPopulation(this, arguments); + + let values; + if (this[arraySchemaSymbol] == null) { + values = arguments; + } else { + values = [].map.call(arguments, this._cast, this); + values = this[arraySchemaSymbol].applySetters(values, this[arrayParentSymbol]); + } + + [].unshift.apply(this, values); + this._registerAtomic('$set', this); + this._markModified(); + return this.length; + } +} + +if (util.inspect.custom) { + CoreMongooseArray.prototype[util.inspect.custom] = + CoreMongooseArray.prototype.inspect; +} + +/*! + * ignore + */ + +function _isAllSubdocs(docs, ref) { + if (!ref) { + return false; + } + + for (const arg of docs) { + if (arg == null) { + return false; + } + const model = arg.constructor; + if (!(arg instanceof Document) || + (model.modelName !== ref && model.baseModelName !== ref)) { + return false; + } + } + + return true; +} + +/*! + * ignore + */ + +function _checkManualPopulation(arr, docs) { + const ref = arr == null ? + null : + get(arr[arraySchemaSymbol], 'caster.options.ref', null); + if (arr.length === 0 && + docs.length > 0) { + if (_isAllSubdocs(docs, ref)) { + arr[arrayParentSymbol].populated(arr[arrayPathSymbol], [], { + [populateModelSymbol]: docs[0].constructor + }); + } + } +} + +module.exports = CoreMongooseArray; diff --git a/node_modules/mongoose/lib/types/decimal128.js b/node_modules/mongoose/lib/types/decimal128.js new file mode 100644 index 0000000..cb08861 --- /dev/null +++ b/node_modules/mongoose/lib/types/decimal128.js @@ -0,0 +1,13 @@ +/** + * ObjectId type constructor + * + * ####Example + * + * const id = new mongoose.Types.ObjectId; + * + * @constructor ObjectId + */ + +'use strict'; + +module.exports = require('../driver').get().Decimal128; diff --git a/node_modules/mongoose/lib/types/documentarray.js b/node_modules/mongoose/lib/types/documentarray.js new file mode 100644 index 0000000..2855ae4 --- /dev/null +++ b/node_modules/mongoose/lib/types/documentarray.js @@ -0,0 +1,421 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const CoreMongooseArray = require('./core_array'); +const Document = require('../document'); +const ObjectId = require('./objectid'); +const castObjectId = require('../cast/objectid'); +const getDiscriminatorByValue = require('../helpers/discriminator/getDiscriminatorByValue'); +const internalToObjectOptions = require('../options').internalToObjectOptions; +const util = require('util'); +const utils = require('../utils'); + +const arrayAtomicsSymbol = require('../helpers/symbols').arrayAtomicsSymbol; +const arrayParentSymbol = require('../helpers/symbols').arrayParentSymbol; +const arrayPathSymbol = require('../helpers/symbols').arrayPathSymbol; +const arraySchemaSymbol = require('../helpers/symbols').arraySchemaSymbol; +const documentArrayParent = require('../helpers/symbols').documentArrayParent; + +const _basePush = Array.prototype.push; + +class CoreDocumentArray extends CoreMongooseArray { + get isMongooseDocumentArray() { + return true; + } + + /*! + * ignore + */ + + toBSON() { + return this.toObject(internalToObjectOptions); + } + + /*! + * ignore + */ + + map() { + const ret = super.map.apply(this, arguments); + ret[arraySchemaSymbol] = null; + ret[arrayPathSymbol] = null; + ret[arrayParentSymbol] = null; + + return ret; + } + + /** + * Overrides MongooseArray#cast + * + * @method _cast + * @api private + * @receiver MongooseDocumentArray + */ + + _cast(value, index) { + if (this[arraySchemaSymbol] == null) { + return value; + } + let Constructor = this[arraySchemaSymbol].casterConstructor; + const isInstance = Constructor.$isMongooseDocumentArray ? + value && value.isMongooseDocumentArray : + value instanceof Constructor; + if (isInstance || + // Hack re: #5001, see #5005 + (value && value.constructor && value.constructor.baseCasterConstructor === Constructor)) { + if (!(value[documentArrayParent] && value.__parentArray)) { + // value may have been created using array.create() + value[documentArrayParent] = this[arrayParentSymbol]; + value.__parentArray = this; + } + value.$setIndex(index); + return value; + } + + if (value === undefined || value === null) { + return null; + } + + // handle cast('string') or cast(ObjectId) etc. + // only objects are permitted so we can safely assume that + // non-objects are to be interpreted as _id + if (Buffer.isBuffer(value) || + value instanceof ObjectId || !utils.isObject(value)) { + value = { _id: value }; + } + + if (value && + Constructor.discriminators && + Constructor.schema && + Constructor.schema.options && + Constructor.schema.options.discriminatorKey) { + if (typeof value[Constructor.schema.options.discriminatorKey] === 'string' && + Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]]) { + Constructor = Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]]; + } else { + const constructorByValue = getDiscriminatorByValue(Constructor, value[Constructor.schema.options.discriminatorKey]); + if (constructorByValue) { + Constructor = constructorByValue; + } + } + } + + if (Constructor.$isMongooseDocumentArray) { + return Constructor.cast(value, this, undefined, undefined, index); + } + return new Constructor(value, this, undefined, undefined, index); + } + + /** + * Searches array items for the first document with a matching _id. + * + * ####Example: + * + * const embeddedDoc = m.array.id(some_id); + * + * @return {EmbeddedDocument|null} the subdocument or null if not found. + * @param {ObjectId|String|Number|Buffer} id + * @TODO cast to the _id based on schema for proper comparison + * @method id + * @api public + * @receiver MongooseDocumentArray + */ + + id(id) { + let casted; + let sid; + let _id; + + try { + casted = castObjectId(id).toString(); + } catch (e) { + casted = null; + } + + for (const val of this) { + if (!val) { + continue; + } + + _id = val.get('_id'); + + if (_id === null || typeof _id === 'undefined') { + continue; + } else if (_id instanceof Document) { + sid || (sid = String(id)); + if (sid == _id._id) { + return val; + } + } else if (!(id instanceof ObjectId) && !(_id instanceof ObjectId)) { + if (id == _id || utils.deepEqual(id, _id)) { + return val; + } + } else if (casted == _id) { + return val; + } + } + + return null; + } + + /** + * Returns a native js Array of plain js objects + * + * ####NOTE: + * + * _Each sub-document is converted to a plain object by calling its `#toObject` method._ + * + * @param {Object} [options] optional options to pass to each documents `toObject` method call during conversion + * @return {Array} + * @method toObject + * @api public + * @receiver MongooseDocumentArray + */ + + toObject(options) { + // `[].concat` coerces the return value into a vanilla JS array, rather + // than a Mongoose array. + return [].concat(this.map(function(doc) { + if (doc == null) { + return null; + } + if (typeof doc.toObject !== 'function') { + return doc; + } + return doc.toObject(options); + })); + } + + slice() { + const arr = super.slice.apply(this, arguments); + arr[arrayParentSymbol] = this[arrayParentSymbol]; + arr[arrayPathSymbol] = this[arrayPathSymbol]; + + return arr; + } + + /** + * Wraps [`Array#push`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push) with proper change tracking. + * + * @param {Object} [args...] + * @api public + * @method push + * @memberOf MongooseDocumentArray + */ + + push() { + const ret = super.push.apply(this, arguments); + + _updateParentPopulated(this); + + return ret; + } + + /** + * Pulls items from the array atomically. + * + * @param {Object} [args...] + * @api public + * @method pull + * @memberOf MongooseDocumentArray + */ + + pull() { + const ret = super.pull.apply(this, arguments); + + _updateParentPopulated(this); + + return ret; + } + + /** + * Wraps [`Array#shift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking. + */ + + shift() { + const ret = super.shift.apply(this, arguments); + + _updateParentPopulated(this); + + return ret; + } + + /** + * Wraps [`Array#splice`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice) with proper change tracking and casting. + */ + + splice() { + const ret = super.splice.apply(this, arguments); + + _updateParentPopulated(this); + + return ret; + } + + /** + * Helper for console.log + * + * @method inspect + * @api public + * @receiver MongooseDocumentArray + */ + + inspect() { + return this.toObject(); + } + + /** + * Creates a subdocument casted to this schema. + * + * This is the same subdocument constructor used for casting. + * + * @param {Object} obj the value to cast to this arrays SubDocument schema + * @method create + * @api public + * @receiver MongooseDocumentArray + */ + + create(obj) { + let Constructor = this[arraySchemaSymbol].casterConstructor; + if (obj && + Constructor.discriminators && + Constructor.schema && + Constructor.schema.options && + Constructor.schema.options.discriminatorKey) { + if (typeof obj[Constructor.schema.options.discriminatorKey] === 'string' && + Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]]) { + Constructor = Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]]; + } else { + const constructorByValue = getDiscriminatorByValue(Constructor, obj[Constructor.schema.options.discriminatorKey]); + if (constructorByValue) { + Constructor = constructorByValue; + } + } + } + + return new Constructor(obj, this); + } + + /*! + * ignore + */ + + notify(event) { + const _this = this; + return function notify(val, _arr) { + _arr = _arr || _this; + let i = _arr.length; + while (i--) { + if (_arr[i] == null) { + continue; + } + switch (event) { + // only swap for save event for now, we may change this to all event types later + case 'save': + val = _this[i]; + break; + default: + // NO-OP + break; + } + + if (_arr[i].isMongooseArray) { + notify(val, _arr[i]); + } else if (_arr[i]) { + _arr[i].emit(event, val); + } + } + }; + } +} + +if (util.inspect.custom) { + CoreDocumentArray.prototype[util.inspect.custom] = + CoreDocumentArray.prototype.inspect; +} + +/*! + * If this is a document array, each element may contain single + * populated paths, so we need to modify the top-level document's + * populated cache. See gh-8247, gh-8265. + */ + +function _updateParentPopulated(arr) { + const parent = arr[arrayParentSymbol]; + if (!parent || parent.$__.populated == null) return; + + const populatedPaths = Object.keys(parent.$__.populated). + filter(p => p.startsWith(arr[arrayPathSymbol] + '.')); + + for (const path of populatedPaths) { + const remnant = path.slice((arr[arrayPathSymbol] + '.').length); + if (!Array.isArray(parent.$__.populated[path].value)) { + continue; + } + + parent.$__.populated[path].value = arr.map(val => val.populated(remnant)); + } +} + +/** + * DocumentArray constructor + * + * @param {Array} values + * @param {String} path the path to this array + * @param {Document} doc parent document + * @api private + * @return {MongooseDocumentArray} + * @inherits MongooseArray + * @see http://bit.ly/f6CnZU + */ + +function MongooseDocumentArray(values, path, doc) { + // TODO: replace this with `new CoreDocumentArray().concat()` when we remove + // support for node 4.x and 5.x, see https://i.imgur.com/UAAHk4S.png + const arr = new CoreDocumentArray(); + + arr[arrayAtomicsSymbol] = {}; + arr[arraySchemaSymbol] = void 0; + if (Array.isArray(values)) { + if (values instanceof CoreDocumentArray && + values[arrayPathSymbol] === path && + values[arrayParentSymbol] === doc) { + arr[arrayAtomicsSymbol] = Object.assign({}, values[arrayAtomicsSymbol]); + } + values.forEach(v => { + _basePush.call(arr, v); + }); + } + arr[arrayPathSymbol] = path; + + // Because doc comes from the context of another function, doc === global + // can happen if there was a null somewhere up the chain (see #3020 && #3034) + // RB Jun 17, 2015 updated to check for presence of expected paths instead + // to make more proof against unusual node environments + if (doc && doc instanceof Document) { + arr[arrayParentSymbol] = doc; + arr[arraySchemaSymbol] = doc.schema.path(path); + + // `schema.path()` doesn't drill into nested arrays properly yet, see + // gh-6398, gh-6602. This is a workaround because nested arrays are + // always plain non-document arrays, so once you get to a document array + // nesting is done. Matryoshka code. + while (arr != null && + arr[arraySchemaSymbol] != null && + arr[arraySchemaSymbol].$isMongooseArray && + !arr[arraySchemaSymbol].$isMongooseDocumentArray) { + arr[arraySchemaSymbol] = arr[arraySchemaSymbol].casterConstructor; + } + } + + return arr; +} + +/*! + * Module exports. + */ + +module.exports = MongooseDocumentArray; diff --git a/node_modules/mongoose/lib/types/embedded.js b/node_modules/mongoose/lib/types/embedded.js new file mode 100644 index 0000000..18a9778 --- /dev/null +++ b/node_modules/mongoose/lib/types/embedded.js @@ -0,0 +1,446 @@ +/* eslint no-func-assign: 1 */ + +/*! + * Module dependencies. + */ + +'use strict'; + +const Document = require('../document_provider')(); +const EventEmitter = require('events').EventEmitter; +const ValidationError = require('../error/validation'); +const immediate = require('../helpers/immediate'); +const internalToObjectOptions = require('../options').internalToObjectOptions; +const get = require('../helpers/get'); +const promiseOrCallback = require('../helpers/promiseOrCallback'); +const util = require('util'); + +const documentArrayParent = require('../helpers/symbols').documentArrayParent; +const validatorErrorSymbol = require('../helpers/symbols').validatorErrorSymbol; + +/** + * EmbeddedDocument constructor. + * + * @param {Object} obj js object returned from the db + * @param {MongooseDocumentArray} parentArr the parent array of this document + * @param {Boolean} skipId + * @inherits Document + * @api private + */ + +function EmbeddedDocument(obj, parentArr, skipId, fields, index) { + if (parentArr != null && parentArr.isMongooseDocumentArray) { + this.__parentArray = parentArr; + this[documentArrayParent] = parentArr.$parent(); + } else { + this.__parentArray = undefined; + this[documentArrayParent] = undefined; + } + this.$setIndex(index); + this.$isDocumentArrayElement = true; + + Document.call(this, obj, fields, skipId); + + const _this = this; + this.on('isNew', function(val) { + _this.isNew = val; + }); + + _this.on('save', function() { + _this.constructor.emit('save', _this); + }); +} + +/*! + * Inherit from Document + */ +EmbeddedDocument.prototype = Object.create(Document.prototype); +EmbeddedDocument.prototype.constructor = EmbeddedDocument; + +for (const i in EventEmitter.prototype) { + EmbeddedDocument[i] = EventEmitter.prototype[i]; +} + +EmbeddedDocument.prototype.toBSON = function() { + return this.toObject(internalToObjectOptions); +}; + +/*! + * ignore + */ + +EmbeddedDocument.prototype.$setIndex = function(index) { + this.__index = index; + + if (get(this, '$__.validationError', null) != null) { + const keys = Object.keys(this.$__.validationError.errors); + for (const key of keys) { + this.invalidate(key, this.$__.validationError.errors[key]); + } + } +}; + +/** + * Marks the embedded doc modified. + * + * ####Example: + * + * const doc = blogpost.comments.id(hexstring); + * doc.mixed.type = 'changed'; + * doc.markModified('mixed.type'); + * + * @param {String} path the path which changed + * @api public + * @receiver EmbeddedDocument + */ + +EmbeddedDocument.prototype.markModified = function(path) { + this.$__.activePaths.modify(path); + if (!this.__parentArray) { + return; + } + + const pathToCheck = this.__parentArray.$path() + '.0.' + path; + if (this.isNew && this.ownerDocument().isSelected(pathToCheck)) { + // Mark the WHOLE parent array as modified + // if this is a new document (i.e., we are initializing + // a document), + this.__parentArray._markModified(); + } else { + this.__parentArray._markModified(this, path); + } +}; + +/*! + * ignore + */ + +EmbeddedDocument.prototype.populate = function() { + throw new Error('Mongoose does not support calling populate() on nested ' + + 'docs. Instead of `doc.arr[0].populate("path")`, use ' + + '`doc.populate("arr.0.path")`'); +}; + +/** + * Used as a stub for [hooks.js](https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3) + * + * ####NOTE: + * + * _This is a no-op. Does not actually save the doc to the db._ + * + * @param {Function} [fn] + * @return {Promise} resolved Promise + * @api private + */ + +EmbeddedDocument.prototype.save = function(options, fn) { + if (typeof options === 'function') { + fn = options; + options = {}; + } + options = options || {}; + + if (!options.suppressWarning) { + console.warn('mongoose: calling `save()` on a subdoc does **not** save ' + + 'the document to MongoDB, it only runs save middleware. ' + + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + + 'if you\'re sure this behavior is right for your app.'); + } + + return promiseOrCallback(fn, cb => { + this.$__save(cb); + }); +}; + +/** + * Used as a stub for middleware + * + * ####NOTE: + * + * _This is a no-op. Does not actually save the doc to the db._ + * + * @param {Function} [fn] + * @method $__save + * @api private + */ + +EmbeddedDocument.prototype.$__save = function(fn) { + return immediate(() => fn(null, this)); +}; + +/*! + * Registers remove event listeners for triggering + * on subdocuments. + * + * @param {EmbeddedDocument} sub + * @api private + */ + +function registerRemoveListener(sub) { + let owner = sub.ownerDocument(); + + function emitRemove() { + owner.removeListener('save', emitRemove); + owner.removeListener('remove', emitRemove); + sub.emit('remove', sub); + sub.constructor.emit('remove', sub); + owner = sub = null; + } + + owner.on('save', emitRemove); + owner.on('remove', emitRemove); +} + +/*! + * no-op for hooks + */ + +EmbeddedDocument.prototype.$__remove = function(cb) { + return cb(null, this); +}; + +/** + * Removes the subdocument from its parent array. + * + * @param {Object} [options] + * @param {Function} [fn] + * @api public + */ + +EmbeddedDocument.prototype.remove = function(options, fn) { + if (typeof options === 'function' && !fn) { + fn = options; + options = undefined; + } + if (!this.__parentArray || (options && options.noop)) { + fn && fn(null); + return this; + } + + let _id; + if (!this.willRemove) { + _id = this._doc._id; + if (!_id) { + throw new Error('For your own good, Mongoose does not know ' + + 'how to remove an EmbeddedDocument that has no _id'); + } + this.__parentArray.pull({ _id: _id }); + this.willRemove = true; + registerRemoveListener(this); + } + + if (fn) { + fn(null); + } + + return this; +}; + +/** + * Override #update method of parent documents. + * @api private + */ + +EmbeddedDocument.prototype.update = function() { + throw new Error('The #update method is not available on EmbeddedDocuments'); +}; + +/** + * Helper for console.log + * + * @api public + */ + +EmbeddedDocument.prototype.inspect = function() { + return this.toObject({ + transform: false, + virtuals: false, + flattenDecimals: false + }); +}; + +if (util.inspect.custom) { + /*! + * Avoid Node deprecation warning DEP0079 + */ + + EmbeddedDocument.prototype[util.inspect.custom] = EmbeddedDocument.prototype.inspect; +} + +/** + * Marks a path as invalid, causing validation to fail. + * + * @param {String} path the field to invalidate + * @param {String|Error} err error which states the reason `path` was invalid + * @return {Boolean} + * @api public + */ + +EmbeddedDocument.prototype.invalidate = function(path, err, val) { + Document.prototype.invalidate.call(this, path, err, val); + + if (!this[documentArrayParent] || this.__index == null) { + if (err[validatorErrorSymbol] || err instanceof ValidationError) { + return this.ownerDocument().$__.validationError; + } + throw err; + } + + const index = this.__index; + const parentPath = this.__parentArray.$path(); + const fullPath = [parentPath, index, path].join('.'); + this[documentArrayParent].invalidate(fullPath, err, val); + + return this.ownerDocument().$__.validationError; +}; + +/** + * Marks a path as valid, removing existing validation errors. + * + * @param {String} path the field to mark as valid + * @api private + * @method $markValid + * @receiver EmbeddedDocument + */ + +EmbeddedDocument.prototype.$markValid = function(path) { + if (!this[documentArrayParent]) { + return; + } + + const index = this.__index; + if (typeof index !== 'undefined') { + const parentPath = this.__parentArray.$path(); + const fullPath = [parentPath, index, path].join('.'); + this[documentArrayParent].$markValid(fullPath); + } +}; + +/*! + * ignore + */ + +EmbeddedDocument.prototype.$ignore = function(path) { + Document.prototype.$ignore.call(this, path); + + if (!this[documentArrayParent]) { + return; + } + + const index = this.__index; + if (typeof index !== 'undefined') { + const parentPath = this.__parentArray.$path(); + const fullPath = [parentPath, index, path].join('.'); + this[documentArrayParent].$ignore(fullPath); + } +}; + +/** + * Checks if a path is invalid + * + * @param {String} path the field to check + * @api private + * @method $isValid + * @receiver EmbeddedDocument + */ + +EmbeddedDocument.prototype.$isValid = function(path) { + const index = this.__index; + if (typeof index !== 'undefined' && this[documentArrayParent]) { + return !this[documentArrayParent].$__.validationError || + !this[documentArrayParent].$__.validationError.errors[this.$__fullPath(path)]; + } + + return true; +}; + +/** + * Returns the top level document of this sub-document. + * + * @return {Document} + */ + +EmbeddedDocument.prototype.ownerDocument = function() { + if (this.$__.ownerDocument) { + return this.$__.ownerDocument; + } + + let parent = this[documentArrayParent]; + if (!parent) { + return this; + } + + while (parent[documentArrayParent] || parent.$parent) { + parent = parent[documentArrayParent] || parent.$parent; + } + + this.$__.ownerDocument = parent; + return this.$__.ownerDocument; +}; + +/** + * Returns the full path to this document. If optional `path` is passed, it is appended to the full path. + * + * @param {String} [path] + * @return {String} + * @api private + * @method $__fullPath + * @memberOf EmbeddedDocument + * @instance + */ + +EmbeddedDocument.prototype.$__fullPath = function(path) { + if (!this.$__.fullPath) { + let parent = this; // eslint-disable-line consistent-this + if (!parent[documentArrayParent]) { + return path; + } + + const paths = []; + while (parent[documentArrayParent] || parent.$parent) { + if (parent[documentArrayParent]) { + paths.unshift(parent.__parentArray.$path()); + } else { + paths.unshift(parent.$basePath); + } + parent = parent[documentArrayParent] || parent.$parent; + } + + this.$__.fullPath = paths.join('.'); + + if (!this.$__.ownerDocument) { + // optimization + this.$__.ownerDocument = parent; + } + } + + return path + ? this.$__.fullPath + '.' + path + : this.$__.fullPath; +}; + +/** + * Returns this sub-documents parent document. + * + * @api public + */ + +EmbeddedDocument.prototype.parent = function() { + return this[documentArrayParent]; +}; + +/** + * Returns this sub-documents parent array. + * + * @api public + */ + +EmbeddedDocument.prototype.parentArray = function() { + return this.__parentArray; +}; + +/*! + * Module exports. + */ + +module.exports = EmbeddedDocument; diff --git a/node_modules/mongoose/lib/types/index.js b/node_modules/mongoose/lib/types/index.js new file mode 100644 index 0000000..a1945a0 --- /dev/null +++ b/node_modules/mongoose/lib/types/index.js @@ -0,0 +1,20 @@ + +/*! + * Module exports. + */ + +'use strict'; + +exports.Array = require('./array'); +exports.Buffer = require('./buffer'); + +exports.Document = // @deprecate +exports.Embedded = require('./embedded'); + +exports.DocumentArray = require('./documentarray'); +exports.Decimal128 = require('./decimal128'); +exports.ObjectId = require('./objectid'); + +exports.Map = require('./map'); + +exports.Subdocument = require('./subdocument'); diff --git a/node_modules/mongoose/lib/types/map.js b/node_modules/mongoose/lib/types/map.js new file mode 100644 index 0000000..f50402e --- /dev/null +++ b/node_modules/mongoose/lib/types/map.js @@ -0,0 +1,226 @@ +'use strict'; + +const Mixed = require('../schema/mixed'); +const deepEqual = require('../utils').deepEqual; +const get = require('../helpers/get'); +const handleSpreadDoc = require('../helpers/document/handleSpreadDoc'); +const util = require('util'); +const specialProperties = require('../helpers/specialProperties'); + +const populateModelSymbol = require('../helpers/symbols').populateModelSymbol; + +/*! + * ignore + */ + +class MongooseMap extends Map { + constructor(v, path, doc, schemaType) { + if (v != null && v.constructor.name === 'Object') { + v = Object.keys(v).reduce((arr, key) => arr.concat([[key, v[key]]]), []); + } + super(v); + + this.$__parent = doc != null && doc.$__ != null ? doc : null; + this.$__path = path; + this.$__schemaType = schemaType == null ? new Mixed(path) : schemaType; + + this.$__runDeferred(); + } + + $init(key, value) { + checkValidKey(key); + + super.set(key, value); + + if (value != null && value.$isSingleNested) { + value.$basePath = this.$__path + '.' + key; + } + } + + $__set(key, value) { + super.set(key, value); + } + + get(key, options) { + options = options || {}; + if (options.getters === false) { + return super.get(key); + } + return this.$__schemaType.applyGetters(super.get(key), this.$__parent); + } + + set(key, value) { + checkValidKey(key); + value = handleSpreadDoc(value); + + // Weird, but because you can't assign to `this` before calling `super()` + // you can't get access to `$__schemaType` to cast in the initial call to + // `set()` from the `super()` constructor. + + if (this.$__schemaType == null) { + this.$__deferred = this.$__deferred || []; + this.$__deferred.push({ key: key, value: value }); + return; + } + + const fullPath = this.$__path + '.' + key; + const populated = this.$__parent != null && this.$__parent.$__ ? + this.$__parent.populated(fullPath) || this.$__parent.populated(this.$__path) : + null; + const priorVal = this.get(key); + + if (populated != null) { + if (value.$__ == null) { + value = new populated.options[populateModelSymbol](value); + } + value.$__.wasPopulated = true; + } else { + try { + value = this.$__schemaType. + applySetters(value, this.$__parent, false, this.get(key)); + } catch (error) { + if (this.$__parent != null && this.$__parent.$__ != null) { + this.$__parent.invalidate(fullPath, error); + return; + } + throw error; + } + } + + super.set(key, value); + + if (value != null && value.$isSingleNested) { + value.$basePath = this.$__path + '.' + key; + } + + const parent = this.$__parent; + if (parent != null && parent.$__ != null && !deepEqual(value, priorVal)) { + parent.markModified(this.$__path + '.' + key); + } + } + + clear() { + super.clear(); + const parent = this.$__parent; + if (parent != null) { + parent.markModified(this.$__path); + } + } + + delete(key) { + this.set(key, undefined); + super.delete(key); + } + + toBSON() { + return new Map(this); + } + + toObject(options) { + if (get(options, 'flattenMaps')) { + const ret = {}; + const keys = this.keys(); + for (const key of keys) { + ret[key] = this.get(key); + } + return ret; + } + + return new Map(this); + } + + toJSON() { + const ret = {}; + const keys = this.keys(); + for (const key of keys) { + ret[key] = this.get(key); + } + return ret; + } + + inspect() { + return new Map(this); + } + + $__runDeferred() { + if (!this.$__deferred) { + return; + } + + for (const keyValueObject of this.$__deferred) { + this.set(keyValueObject.key, keyValueObject.value); + } + + this.$__deferred = null; + } +} + +if (util.inspect.custom) { + Object.defineProperty(MongooseMap.prototype, util.inspect.custom, { + enumerable: false, + writable: false, + configurable: false, + value: MongooseMap.prototype.inspect + }); +} + +Object.defineProperty(MongooseMap.prototype, '$__set', { + enumerable: false, + writable: true, + configurable: false +}); + +Object.defineProperty(MongooseMap.prototype, '$__parent', { + enumerable: false, + writable: true, + configurable: false +}); + +Object.defineProperty(MongooseMap.prototype, '$__path', { + enumerable: false, + writable: true, + configurable: false +}); + +Object.defineProperty(MongooseMap.prototype, '$__schemaType', { + enumerable: false, + writable: true, + configurable: false +}); + +Object.defineProperty(MongooseMap.prototype, '$isMongooseMap', { + enumerable: false, + writable: false, + configurable: false, + value: true +}); + +Object.defineProperty(MongooseMap.prototype, '$__deferredCalls', { + enumerable: false, + writable: false, + configurable: false, + value: true +}); + +/*! + * Since maps are stored as objects under the hood, keys must be strings + * and can't contain any invalid characters + */ + +function checkValidKey(key) { + const keyType = typeof key; + if (keyType !== 'string') { + throw new TypeError(`Mongoose maps only support string keys, got ${keyType}`); + } + if (key.startsWith('$')) { + throw new Error(`Mongoose maps do not support keys that start with "$", got "${key}"`); + } + if (key.includes('.')) { + throw new Error(`Mongoose maps do not support keys that contain ".", got "${key}"`); + } + if (specialProperties.has(key)) { + throw new Error(`Mongoose maps do not support reserved key name "${key}"`); + } +} + +module.exports = MongooseMap; diff --git a/node_modules/mongoose/lib/types/objectid.js b/node_modules/mongoose/lib/types/objectid.js new file mode 100644 index 0000000..19e3d90 --- /dev/null +++ b/node_modules/mongoose/lib/types/objectid.js @@ -0,0 +1,30 @@ +/** + * ObjectId type constructor + * + * ####Example + * + * const id = new mongoose.Types.ObjectId; + * + * @constructor ObjectId + */ + +'use strict'; + +const ObjectId = require('../driver').get().ObjectId; +const objectIdSymbol = require('../helpers/symbols').objectIdSymbol; + +/*! + * Getter for convenience with populate, see gh-6115 + */ + +Object.defineProperty(ObjectId.prototype, '_id', { + enumerable: false, + configurable: true, + get: function() { + return this; + } +}); + +ObjectId.prototype[objectIdSymbol] = true; + +module.exports = ObjectId; diff --git a/node_modules/mongoose/lib/types/subdocument.js b/node_modules/mongoose/lib/types/subdocument.js new file mode 100644 index 0000000..d6494ff --- /dev/null +++ b/node_modules/mongoose/lib/types/subdocument.js @@ -0,0 +1,292 @@ +'use strict'; + +const Document = require('../document'); +const immediate = require('../helpers/immediate'); +const internalToObjectOptions = require('../options').internalToObjectOptions; +const promiseOrCallback = require('../helpers/promiseOrCallback'); + +const documentArrayParent = require('../helpers/symbols').documentArrayParent; + +module.exports = Subdocument; + +/** + * Subdocument constructor. + * + * @inherits Document + * @api private + */ + +function Subdocument(value, fields, parent, skipId, options) { + this.$isSingleNested = true; + + const hasPriorDoc = options != null && options.priorDoc; + let initedPaths = null; + if (hasPriorDoc) { + this._doc = Object.assign({}, options.priorDoc._doc); + delete this._doc[this.schema.options.discriminatorKey]; + initedPaths = Object.keys(options.priorDoc._doc || {}). + filter(key => key !== this.schema.options.discriminatorKey); + } + if (parent != null) { + // If setting a nested path, should copy isNew from parent re: gh-7048 + options = Object.assign({}, options, { + isNew: parent.isNew, + defaults: parent.$__.$options.defaults + }); + } + Document.call(this, value, fields, skipId, options); + + if (hasPriorDoc) { + for (const key of initedPaths) { + if (!this.$__.activePaths.states.modify[key] && + !this.$__.activePaths.states.default[key] && + !this.$__.$setCalled.has(key)) { + const schematype = this.schema.path(key); + const def = schematype == null ? void 0 : schematype.getDefault(this); + if (def === void 0) { + delete this._doc[key]; + } else { + this._doc[key] = def; + this.$__.activePaths.default(key); + } + } + } + + delete options.priorDoc; + delete this.$__.$options.priorDoc; + } +} + +Subdocument.prototype = Object.create(Document.prototype); + +Subdocument.prototype.toBSON = function() { + return this.toObject(internalToObjectOptions); +}; + +/** + * Used as a stub for middleware + * + * ####NOTE: + * + * _This is a no-op. Does not actually save the doc to the db._ + * + * @param {Function} [fn] + * @return {Promise} resolved Promise + * @api private + */ + +Subdocument.prototype.save = function(options, fn) { + if (typeof options === 'function') { + fn = options; + options = {}; + } + options = options || {}; + + if (!options.suppressWarning) { + console.warn('mongoose: calling `save()` on a subdoc does **not** save ' + + 'the document to MongoDB, it only runs save middleware. ' + + 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' + + 'if you\'re sure this behavior is right for your app.'); + } + + return promiseOrCallback(fn, cb => { + this.$__save(cb); + }); +}; + +/** + * Used as a stub for middleware + * + * ####NOTE: + * + * _This is a no-op. Does not actually save the doc to the db._ + * + * @param {Function} [fn] + * @method $__save + * @api private + */ + +Subdocument.prototype.$__save = function(fn) { + return immediate(() => fn(null, this)); +}; + +Subdocument.prototype.$isValid = function(path) { + if (this.$parent && this.$basePath) { + return this.$parent.$isValid([this.$basePath, path].join('.')); + } + return Document.prototype.$isValid.call(this, path); +}; + +Subdocument.prototype.markModified = function(path) { + Document.prototype.markModified.call(this, path); + + if (this.$parent && this.$basePath) { + if (this.$parent.isDirectModified(this.$basePath)) { + return; + } + this.$parent.markModified([this.$basePath, path].join('.'), this); + } +}; + +Subdocument.prototype.isModified = function(paths, modifiedPaths) { + if (this.$parent && this.$basePath) { + if (Array.isArray(paths) || typeof paths === 'string') { + paths = (Array.isArray(paths) ? paths : paths.split(' ')); + paths = paths.map(p => [this.$basePath, p].join('.')); + + return this.$parent.isModified(paths, modifiedPaths); + } + + return this.$parent.isModified(this.$basePath); + } + + return Document.prototype.isModified.call(this, paths, modifiedPaths); +}; + +/** + * Marks a path as valid, removing existing validation errors. + * + * @param {String} path the field to mark as valid + * @api private + * @method $markValid + * @receiver Subdocument + */ + +Subdocument.prototype.$markValid = function(path) { + Document.prototype.$markValid.call(this, path); + if (this.$parent && this.$basePath) { + this.$parent.$markValid([this.$basePath, path].join('.')); + } +}; + +/*! + * ignore + */ + +Subdocument.prototype.invalidate = function(path, err, val) { + // Hack: array subdocuments' validationError is equal to the owner doc's, + // so validating an array subdoc gives the top-level doc back. Temporary + // workaround for #5208 so we don't have circular errors. + if (err !== this.ownerDocument().$__.validationError) { + Document.prototype.invalidate.call(this, path, err, val); + } + + if (this.$parent && this.$basePath) { + this.$parent.invalidate([this.$basePath, path].join('.'), err, val); + } else if (err.kind === 'cast' || err.name === 'CastError') { + throw err; + } + + return this.ownerDocument().$__.validationError; +}; + +/*! + * ignore + */ + +Subdocument.prototype.$ignore = function(path) { + Document.prototype.$ignore.call(this, path); + if (this.$parent && this.$basePath) { + this.$parent.$ignore([this.$basePath, path].join('.')); + } +}; + +/** + * Returns the top level document of this sub-document. + * + * @return {Document} + */ + +Subdocument.prototype.ownerDocument = function() { + if (this.$__.ownerDocument) { + return this.$__.ownerDocument; + } + + let parent = this.$parent; + if (!parent) { + return this; + } + + while (parent.$parent || parent[documentArrayParent]) { + parent = parent.$parent || parent[documentArrayParent]; + } + + this.$__.ownerDocument = parent; + return this.$__.ownerDocument; +}; + +/** + * Returns this sub-documents parent document. + * + * @api public + */ + +Subdocument.prototype.parent = function() { + return this.$parent; +}; + +/*! + * no-op for hooks + */ + +Subdocument.prototype.$__remove = function(cb) { + return cb(null, this); +}; + +/** + * Null-out this subdoc + * + * @param {Object} [options] + * @param {Function} [callback] optional callback for compatibility with Document.prototype.remove + */ + +Subdocument.prototype.remove = function(options, callback) { + if (typeof options === 'function') { + callback = options; + options = null; + } + + registerRemoveListener(this); + + // If removing entire doc, no need to remove subdoc + if (!options || !options.noop) { + this.$parent.set(this.$basePath, null); + } + + if (typeof callback === 'function') { + callback(null); + } +}; + +/*! + * ignore + */ + +Subdocument.prototype.populate = function() { + throw new Error('Mongoose does not support calling populate() on nested ' + + 'docs. Instead of `doc.nested.populate("path")`, use ' + + '`doc.populate("nested.path")`'); +}; + +/*! + * Registers remove event listeners for triggering + * on subdocuments. + * + * @param {Subdocument} sub + * @api private + */ + +function registerRemoveListener(sub) { + let owner = sub.ownerDocument(); + + function emitRemove() { + owner.removeListener('save', emitRemove); + owner.removeListener('remove', emitRemove); + sub.emit('remove', sub); + sub.constructor.emit('remove', sub); + owner = sub = null; + } + + owner.on('save', emitRemove); + owner.on('remove', emitRemove); +} diff --git a/node_modules/mongoose/lib/utils.js b/node_modules/mongoose/lib/utils.js new file mode 100644 index 0000000..48b29aa --- /dev/null +++ b/node_modules/mongoose/lib/utils.js @@ -0,0 +1,908 @@ +'use strict'; + +/*! + * Module dependencies. + */ + +const ms = require('ms'); +const mpath = require('mpath'); +const sliced = require('sliced'); +const Buffer = require('safe-buffer').Buffer; +const Decimal = require('./types/decimal128'); +const ObjectId = require('./types/objectid'); +const PopulateOptions = require('./options/PopulateOptions'); +const clone = require('./helpers/clone'); +const isObject = require('./helpers/isObject'); +const isBsonType = require('./helpers/isBsonType'); +const getFunctionName = require('./helpers/getFunctionName'); +const isMongooseObject = require('./helpers/isMongooseObject'); +const promiseOrCallback = require('./helpers/promiseOrCallback'); +const specialProperties = require('./helpers/specialProperties'); + +let Document; + +exports.specialProperties = specialProperties; + +/*! + * Produces a collection name from model `name`. By default, just returns + * the model name + * + * @param {String} name a model name + * @param {Function} pluralize function that pluralizes the collection name + * @return {String} a collection name + * @api private + */ + +exports.toCollectionName = function(name, pluralize) { + if (name === 'system.profile') { + return name; + } + if (name === 'system.indexes') { + return name; + } + if (typeof pluralize === 'function') { + return pluralize(name); + } + return name; +}; + +/*! + * Determines if `a` and `b` are deep equal. + * + * Modified from node/lib/assert.js + * + * @param {any} a a value to compare to `b` + * @param {any} b a value to compare to `a` + * @return {Boolean} + * @api private + */ + +exports.deepEqual = function deepEqual(a, b) { + if (a === b) { + return true; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime(); + } + + if ((isBsonType(a, 'ObjectID') && isBsonType(b, 'ObjectID')) || + (isBsonType(a, 'Decimal128') && isBsonType(b, 'Decimal128'))) { + return a.toString() === b.toString(); + } + + if (a instanceof RegExp && b instanceof RegExp) { + return a.source === b.source && + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline && + a.global === b.global; + } + + if (typeof a !== 'object' && typeof b !== 'object') { + return a === b; + } + + if (a === null || b === null || a === undefined || b === undefined) { + return false; + } + + if (a.prototype !== b.prototype) { + return false; + } + + // Handle MongooseNumbers + if (a instanceof Number && b instanceof Number) { + return a.valueOf() === b.valueOf(); + } + + if (Buffer.isBuffer(a)) { + return exports.buffer.areEqual(a, b); + } + + if (isMongooseObject(a)) { + a = a.toObject(); + } + if (isMongooseObject(b)) { + b = b.toObject(); + } + + let ka; + let kb; + let key; + let i; + try { + ka = Object.keys(a); + kb = Object.keys(b); + } catch (e) { + // happens when one is a string literal and the other isn't + return false; + } + + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length !== kb.length) { + return false; + } + + // the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + + // ~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] !== kb[i]) { + return false; + } + } + + // equivalent values for every corresponding key, and + // ~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!deepEqual(a[key], b[key])) { + return false; + } + } + + return true; +}; + +/*! + * Get the last element of an array + */ + +exports.last = function(arr) { + if (arr.length > 0) { + return arr[arr.length - 1]; + } + return void 0; +}; + +exports.clone = clone; + +/*! + * ignore + */ + +exports.promiseOrCallback = promiseOrCallback; + +/*! + * ignore + */ + +exports.omit = function omit(obj, keys) { + if (keys == null) { + return Object.assign({}, obj); + } + if (!Array.isArray(keys)) { + keys = [keys]; + } + + const ret = Object.assign({}, obj); + for (const key of keys) { + delete ret[key]; + } + return ret; +}; + + +/*! + * Shallow copies defaults into options. + * + * @param {Object} defaults + * @param {Object} options + * @return {Object} the merged object + * @api private + */ + +exports.options = function(defaults, options) { + const keys = Object.keys(defaults); + let i = keys.length; + let k; + + options = options || {}; + + while (i--) { + k = keys[i]; + if (!(k in options)) { + options[k] = defaults[k]; + } + } + + return options; +}; + +/*! + * Generates a random string + * + * @api private + */ + +exports.random = function() { + return Math.random().toString().substr(3); +}; + +/*! + * Merges `from` into `to` without overwriting existing properties. + * + * @param {Object} to + * @param {Object} from + * @api private + */ + +exports.merge = function merge(to, from, options, path) { + options = options || {}; + + const keys = Object.keys(from); + let i = 0; + const len = keys.length; + let key; + + path = path || ''; + const omitNested = options.omitNested || {}; + + while (i < len) { + key = keys[i++]; + if (options.omit && options.omit[key]) { + continue; + } + if (omitNested[path]) { + continue; + } + if (specialProperties.has(key)) { + continue; + } + if (to[key] == null) { + to[key] = from[key]; + } else if (exports.isObject(from[key])) { + if (!exports.isObject(to[key])) { + to[key] = {}; + } + if (from[key] != null) { + // Skip merging schemas if we're creating a discriminator schema and + // base schema has a given path as a single nested but discriminator schema + // has the path as a document array, or vice versa (gh-9534) + if (options.isDiscriminatorSchemaMerge && + (from[key].$isSingleNested && to[key].$isMongooseDocumentArray) || + (from[key].$isMongooseDocumentArray && to[key].$isSingleNested)) { + continue; + } else if (from[key].instanceOfSchema) { + if (to[key].instanceOfSchema) { + to[key].add(from[key].clone()); + } else { + to[key] = from[key].clone(); + } + continue; + } else if (from[key] instanceof ObjectId) { + to[key] = new ObjectId(from[key]); + continue; + } + } + merge(to[key], from[key], options, path ? path + '.' + key : key); + } else if (options.overwrite) { + to[key] = from[key]; + } + } +}; + +/*! + * Applies toObject recursively. + * + * @param {Document|Array|Object} obj + * @return {Object} + * @api private + */ + +exports.toObject = function toObject(obj) { + Document || (Document = require('./document')); + let ret; + + if (obj == null) { + return obj; + } + + if (obj instanceof Document) { + return obj.toObject(); + } + + if (Array.isArray(obj)) { + ret = []; + + for (const doc of obj) { + ret.push(toObject(doc)); + } + + return ret; + } + + if (exports.isPOJO(obj)) { + ret = {}; + + for (const k in obj) { + if (specialProperties.has(k)) { + continue; + } + ret[k] = toObject(obj[k]); + } + + return ret; + } + + return obj; +}; + +exports.isObject = isObject; + +/*! + * Determines if `arg` is a plain old JavaScript object (POJO). Specifically, + * `arg` must be an object but not an instance of any special class, like String, + * ObjectId, etc. + * + * `Object.getPrototypeOf()` is part of ES5: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf + * + * @param {Object|Array|String|Function|RegExp|any} arg + * @api private + * @return {Boolean} + */ + +exports.isPOJO = function isPOJO(arg) { + if (arg == null || typeof arg !== 'object') { + return false; + } + const proto = Object.getPrototypeOf(arg); + // Prototype may be null if you used `Object.create(null)` + // Checking `proto`'s constructor is safe because `getPrototypeOf()` + // explicitly crosses the boundary from object data to object metadata + return !proto || proto.constructor.name === 'Object'; +}; + +/*! + * Determines if `obj` is a built-in object like an array, date, boolean, + * etc. + */ + +exports.isNativeObject = function(arg) { + return Array.isArray(arg) || + arg instanceof Date || + arg instanceof Boolean || + arg instanceof Number || + arg instanceof String; +}; + +/*! + * Determines if `val` is an object that has no own keys + */ + +exports.isEmptyObject = function(val) { + return val != null && + typeof val === 'object' && + Object.keys(val).length === 0; +}; + +/*! + * Search if `obj` or any POJOs nested underneath `obj` has a property named + * `key` + */ + +exports.hasKey = function hasKey(obj, key) { + const props = Object.keys(obj); + for (const prop of props) { + if (prop === key) { + return true; + } + if (exports.isPOJO(obj[prop]) && exports.hasKey(obj[prop], key)) { + return true; + } + } + return false; +}; + +/*! + * A faster Array.prototype.slice.call(arguments) alternative + * @api private + */ + +exports.args = sliced; + +/*! + * process.nextTick helper. + * + * Wraps `callback` in a try/catch + nextTick. + * + * node-mongodb-native has a habit of state corruption when an error is immediately thrown from within a collection callback. + * + * @param {Function} callback + * @api private + */ + +exports.tick = function tick(callback) { + if (typeof callback !== 'function') { + return; + } + return function() { + try { + callback.apply(this, arguments); + } catch (err) { + // only nextTick on err to get out of + // the event loop and avoid state corruption. + process.nextTick(function() { + throw err; + }); + } + }; +}; + +/*! + * Returns true if `v` is an object that can be serialized as a primitive in + * MongoDB + */ + +exports.isMongooseType = function(v) { + return v instanceof ObjectId || v instanceof Decimal || v instanceof Buffer; +}; + +exports.isMongooseObject = isMongooseObject; + +/*! + * Converts `expires` options of index objects to `expiresAfterSeconds` options for MongoDB. + * + * @param {Object} object + * @api private + */ + +exports.expires = function expires(object) { + if (!(object && object.constructor.name === 'Object')) { + return; + } + if (!('expires' in object)) { + return; + } + + let when; + if (typeof object.expires !== 'string') { + when = object.expires; + } else { + when = Math.round(ms(object.expires) / 1000); + } + object.expireAfterSeconds = when; + delete object.expires; +}; + +/*! + * populate helper + */ + +exports.populate = function populate(path, select, model, match, options, subPopulate, justOne, count) { + // might have passed an object specifying all arguments + let obj = null; + if (arguments.length === 1) { + if (path instanceof PopulateOptions) { + return [path]; + } + + if (Array.isArray(path)) { + const singles = makeSingles(path); + return singles.map(o => exports.populate(o)[0]); + } + + if (exports.isObject(path)) { + obj = Object.assign({}, path); + } else { + obj = { path: path }; + } + } else if (typeof model === 'object') { + obj = { + path: path, + select: select, + match: model, + options: match + }; + } else { + obj = { + path: path, + select: select, + model: model, + match: match, + options: options, + populate: subPopulate, + justOne: justOne, + count: count + }; + } + + if (typeof obj.path !== 'string') { + throw new TypeError('utils.populate: invalid path. Expected string. Got typeof `' + typeof path + '`'); + } + + return _populateObj(obj); + + // The order of select/conditions args is opposite Model.find but + // necessary to keep backward compatibility (select could be + // an array, string, or object literal). + function makeSingles(arr) { + const ret = []; + arr.forEach(function(obj) { + if (/[\s]/.test(obj.path)) { + const paths = obj.path.split(' '); + paths.forEach(function(p) { + const copy = Object.assign({}, obj); + copy.path = p; + ret.push(copy); + }); + } else { + ret.push(obj); + } + }); + + return ret; + } +}; + +function _populateObj(obj) { + if (Array.isArray(obj.populate)) { + const ret = []; + obj.populate.forEach(function(obj) { + if (/[\s]/.test(obj.path)) { + const copy = Object.assign({}, obj); + const paths = copy.path.split(' '); + paths.forEach(function(p) { + copy.path = p; + ret.push(exports.populate(copy)[0]); + }); + } else { + ret.push(exports.populate(obj)[0]); + } + }); + obj.populate = exports.populate(ret); + } else if (obj.populate != null && typeof obj.populate === 'object') { + obj.populate = exports.populate(obj.populate); + } + + const ret = []; + const paths = obj.path.split(' '); + if (obj.options != null) { + obj.options = exports.clone(obj.options); + } + + for (const path of paths) { + ret.push(new PopulateOptions(Object.assign({}, obj, { path: path }))); + } + + return ret; +} + +/*! + * Return the value of `obj` at the given `path`. + * + * @param {String} path + * @param {Object} obj + */ + +exports.getValue = function(path, obj, map) { + return mpath.get(path, obj, '_doc', map); +}; + +/*! + * Sets the value of `obj` at the given `path`. + * + * @param {String} path + * @param {Anything} val + * @param {Object} obj + */ + +exports.setValue = function(path, val, obj, map, _copying) { + mpath.set(path, val, obj, '_doc', map, _copying); +}; + +/*! + * Returns an array of values from object `o`. + * + * @param {Object} o + * @return {Array} + * @private + */ + +exports.object = {}; +exports.object.vals = function vals(o) { + const keys = Object.keys(o); + let i = keys.length; + const ret = []; + + while (i--) { + ret.push(o[keys[i]]); + } + + return ret; +}; + +/*! + * @see exports.options + */ + +exports.object.shallowCopy = exports.options; + +/*! + * Safer helper for hasOwnProperty checks + * + * @param {Object} obj + * @param {String} prop + */ + +const hop = Object.prototype.hasOwnProperty; +exports.object.hasOwnProperty = function(obj, prop) { + return hop.call(obj, prop); +}; + +/*! + * Determine if `val` is null or undefined + * + * @return {Boolean} + */ + +exports.isNullOrUndefined = function(val) { + return val === null || val === undefined; +}; + +/*! + * ignore + */ + +exports.array = {}; + +/*! + * Flattens an array. + * + * [ 1, [ 2, 3, [4] ]] -> [1,2,3,4] + * + * @param {Array} arr + * @param {Function} [filter] If passed, will be invoked with each item in the array. If `filter` returns a falsy value, the item will not be included in the results. + * @return {Array} + * @private + */ + +exports.array.flatten = function flatten(arr, filter, ret) { + ret || (ret = []); + + arr.forEach(function(item) { + if (Array.isArray(item)) { + flatten(item, filter, ret); + } else { + if (!filter || filter(item)) { + ret.push(item); + } + } + }); + + return ret; +}; + +/*! + * ignore + */ + +const _hasOwnProperty = Object.prototype.hasOwnProperty; + +exports.hasUserDefinedProperty = function(obj, key) { + if (obj == null) { + return false; + } + + if (Array.isArray(key)) { + for (const k of key) { + if (exports.hasUserDefinedProperty(obj, k)) { + return true; + } + } + return false; + } + + if (_hasOwnProperty.call(obj, key)) { + return true; + } + if (typeof obj === 'object' && key in obj) { + const v = obj[key]; + return v !== Object.prototype[key] && v !== Array.prototype[key]; + } + + return false; +}; + +/*! + * ignore + */ + +const MAX_ARRAY_INDEX = Math.pow(2, 32) - 1; + +exports.isArrayIndex = function(val) { + if (typeof val === 'number') { + return val >= 0 && val <= MAX_ARRAY_INDEX; + } + if (typeof val === 'string') { + if (!/^\d+$/.test(val)) { + return false; + } + val = +val; + return val >= 0 && val <= MAX_ARRAY_INDEX; + } + + return false; +}; + +/*! + * Removes duplicate values from an array + * + * [1, 2, 3, 3, 5] => [1, 2, 3, 5] + * [ ObjectId("550988ba0c19d57f697dc45e"), ObjectId("550988ba0c19d57f697dc45e") ] + * => [ObjectId("550988ba0c19d57f697dc45e")] + * + * @param {Array} arr + * @return {Array} + * @private + */ + +exports.array.unique = function(arr) { + const primitives = {}; + const ids = {}; + const ret = []; + + for (const item of arr) { + if (typeof item === 'number' || typeof item === 'string' || item == null) { + if (primitives[item]) { + continue; + } + ret.push(item); + primitives[item] = true; + } else if (item instanceof ObjectId) { + if (ids[item.toString()]) { + continue; + } + ret.push(item); + ids[item.toString()] = true; + } else { + ret.push(item); + } + } + + return ret; +}; + +/*! + * Determines if two buffers are equal. + * + * @param {Buffer} a + * @param {Object} b + */ + +exports.buffer = {}; +exports.buffer.areEqual = function(a, b) { + if (!Buffer.isBuffer(a)) { + return false; + } + if (!Buffer.isBuffer(b)) { + return false; + } + if (a.length !== b.length) { + return false; + } + for (let i = 0, len = a.length; i < len; ++i) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +}; + +exports.getFunctionName = getFunctionName; +/*! + * Decorate buffers + */ + +exports.decorate = function(destination, source) { + for (const key in source) { + if (specialProperties.has(key)) { + continue; + } + destination[key] = source[key]; + } +}; + +/** + * merges to with a copy of from + * + * @param {Object} to + * @param {Object} fromObj + * @api private + */ + +exports.mergeClone = function(to, fromObj) { + if (isMongooseObject(fromObj)) { + fromObj = fromObj.toObject({ + transform: false, + virtuals: false, + depopulate: true, + getters: false, + flattenDecimals: false + }); + } + const keys = Object.keys(fromObj); + const len = keys.length; + let i = 0; + let key; + + while (i < len) { + key = keys[i++]; + if (specialProperties.has(key)) { + continue; + } + if (typeof to[key] === 'undefined') { + to[key] = exports.clone(fromObj[key], { + transform: false, + virtuals: false, + depopulate: true, + getters: false, + flattenDecimals: false + }); + } else { + let val = fromObj[key]; + if (val != null && val.valueOf && !(val instanceof Date)) { + val = val.valueOf(); + } + if (exports.isObject(val)) { + let obj = val; + if (isMongooseObject(val) && !val.isMongooseBuffer) { + obj = obj.toObject({ + transform: false, + virtuals: false, + depopulate: true, + getters: false, + flattenDecimals: false + }); + } + if (val.isMongooseBuffer) { + obj = Buffer.from(obj); + } + exports.mergeClone(to[key], obj); + } else { + to[key] = exports.clone(val, { + flattenDecimals: false + }); + } + } + } +}; + +/** + * Executes a function on each element of an array (like _.each) + * + * @param {Array} arr + * @param {Function} fn + * @api private + */ + +exports.each = function(arr, fn) { + for (const item of arr) { + fn(item); + } +}; + +/*! + * ignore + */ + +exports.getOption = function(name) { + const sources = Array.prototype.slice.call(arguments, 1); + + for (const source of sources) { + if (source[name] != null) { + return source[name]; + } + } + + return null; +}; + +/*! + * ignore + */ + +exports.noop = function() {}; diff --git a/node_modules/mongoose/lib/validoptions.js b/node_modules/mongoose/lib/validoptions.js new file mode 100644 index 0000000..510d7e2 --- /dev/null +++ b/node_modules/mongoose/lib/validoptions.js @@ -0,0 +1,34 @@ + +/*! + * Valid mongoose options + */ + +'use strict'; + +const VALID_OPTIONS = Object.freeze([ + 'applyPluginsToChildSchemas', + 'applyPluginsToDiscriminators', + 'autoCreate', + 'autoIndex', + 'bufferCommands', + 'cloneSchemas', + 'debug', + 'maxTimeMS', + 'objectIdGetter', + 'returnOriginal', + 'runValidators', + 'selectPopulatedPaths', + 'setDefaultsOnInsert', + 'strict', + 'strictQuery', + 'toJSON', + 'toObject', + 'typePojoToMixed', + 'useCreateIndex', + 'useFindAndModify', + 'useNewUrlParser', + 'usePushEach', + 'useUnifiedTopology' +]); + +module.exports = VALID_OPTIONS; diff --git a/node_modules/mongoose/lib/virtualtype.js b/node_modules/mongoose/lib/virtualtype.js new file mode 100644 index 0000000..35f26b4 --- /dev/null +++ b/node_modules/mongoose/lib/virtualtype.js @@ -0,0 +1,168 @@ +'use strict'; + +/** + * VirtualType constructor + * + * This is what mongoose uses to define virtual attributes via `Schema.prototype.virtual`. + * + * ####Example: + * + * const fullname = schema.virtual('fullname'); + * fullname instanceof mongoose.VirtualType // true + * + * @param {Object} options + * @param {string|function} [options.ref] if `ref` is not nullish, this becomes a [populated virtual](/docs/populate.html#populate-virtuals) + * @param {string|function} [options.localField] the local field to populate on if this is a populated virtual. + * @param {string|function} [options.foreignField] the foreign field to populate on if this is a populated virtual. + * @param {boolean} [options.justOne=false] by default, a populated virtual is an array. If you set `justOne`, the populated virtual will be a single doc or `null`. + * @param {boolean} [options.getters=false] if you set this to `true`, Mongoose will call any custom getters you defined on this virtual + * @param {boolean} [options.count=false] if you set this to `true`, `populate()` will set this virtual to the number of populated documents, as opposed to the documents themselves, using [`Query#countDocuments()`](./api.html#query_Query-countDocuments) + * @param {Object|Function} [options.match=null] add an extra match condition to `populate()` + * @param {Number} [options.limit=null] add a default `limit` to the `populate()` query + * @param {Number} [options.skip=null] add a default `skip` to the `populate()` query + * @param {Number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents. + * @param {Object} [options.options=null] Additional options like `limit` and `lean`. + * @api public + */ + +function VirtualType(options, name) { + this.path = name; + this.getters = []; + this.setters = []; + this.options = Object.assign({}, options); +} + +/** + * If no getters/getters, add a default + * + * @param {Function} fn + * @return {VirtualType} this + * @api private + */ + +VirtualType.prototype._applyDefaultGetters = function() { + if (this.getters.length > 0 || this.setters.length > 0) { + return; + } + + const path = this.path; + const internalProperty = '$' + path; + this.getters.push(function() { + return this[internalProperty]; + }); + this.setters.push(function(v) { + this[internalProperty] = v; + }); +}; + +/*! + * ignore + */ + +VirtualType.prototype.clone = function() { + const clone = new VirtualType(this.options, this.path); + clone.getters = [].concat(this.getters); + clone.setters = [].concat(this.setters); + return clone; +}; + +/** + * Adds a custom getter to this virtual. + * + * Mongoose calls the getter function with 3 parameters: + * + * - `value`: the value returned by the previous getter. If there is only one getter, `value` will be `undefined`. + * - `virtual`: the virtual object you called `.get()` on + * - `doc`: the document this virtual is attached to. Equivalent to `this`. + * + * ####Example: + * + * const virtual = schema.virtual('fullname'); + * virtual.get(function(value, virtual, doc) { + * return this.name.first + ' ' + this.name.last; + * }); + * + * @param {Function(Any, VirtualType, Document)} fn + * @return {VirtualType} this + * @api public + */ + +VirtualType.prototype.get = function(fn) { + this.getters.push(fn); + return this; +}; + +/** + * Adds a custom setter to this virtual. + * + * Mongoose calls the setter function with 3 parameters: + * + * - `value`: the value being set + * - `virtual`: the virtual object you're calling `.set()` on + * - `doc`: the document this virtual is attached to. Equivalent to `this`. + * + * ####Example: + * + * const virtual = schema.virtual('fullname'); + * virtual.set(function(value, virtual, doc) { + * const parts = value.split(' '); + * this.name.first = parts[0]; + * this.name.last = parts[1]; + * }); + * + * const Model = mongoose.model('Test', schema); + * const doc = new Model(); + * // Calls the setter with `value = 'Jean-Luc Picard'` + * doc.fullname = 'Jean-Luc Picard'; + * doc.name.first; // 'Jean-Luc' + * doc.name.last; // 'Picard' + * + * @param {Function(Any, VirtualType, Document)} fn + * @return {VirtualType} this + * @api public + */ + +VirtualType.prototype.set = function(fn) { + this.setters.push(fn); + return this; +}; + +/** + * Applies getters to `value`. + * + * @param {Object} value + * @param {Document} doc The document this virtual is attached to + * @return {any} the value after applying all getters + * @api public + */ + +VirtualType.prototype.applyGetters = function(value, doc) { + let v = value; + for (let l = this.getters.length - 1; l >= 0; l--) { + v = this.getters[l].call(doc, v, this, doc); + } + return v; +}; + +/** + * Applies setters to `value`. + * + * @param {Object} value + * @param {Document} doc + * @return {any} the value after applying all setters + * @api public + */ + +VirtualType.prototype.applySetters = function(value, doc) { + let v = value; + for (let l = this.setters.length - 1; l >= 0; l--) { + v = this.setters[l].call(doc, v, this, doc); + } + return v; +}; + +/*! + * exports + */ + +module.exports = VirtualType; |