initial
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* ES6 Promise wrapper constructor.
|
||||
*
|
||||
* Promises are returned from executed queries. Example:
|
||||
*
|
||||
* var query = Candy.find({ bar: true });
|
||||
* var promise = query.exec();
|
||||
*
|
||||
* DEPRECATED. Mongoose 5.0 will use native promises by default (or bluebird,
|
||||
* if native promises are not present) but still
|
||||
* support plugging in your own ES6-compatible promises library. Mongoose 5.0
|
||||
* will **not** support mpromise.
|
||||
*
|
||||
* @param {Function} fn a function which will be called when the promise is resolved that accepts `fn(err, ...){}` as signature
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function ES6Promise() {
|
||||
throw new Error('Can\'t use ES6 promise with mpromise style constructor');
|
||||
}
|
||||
|
||||
ES6Promise.use = function(Promise) {
|
||||
ES6Promise.ES6 = Promise;
|
||||
};
|
||||
|
||||
module.exports = ES6Promise;
|
||||
+879
@@ -0,0 +1,879 @@
|
||||
/*!
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
var AggregationCursor = require('./cursor/AggregationCursor');
|
||||
var PromiseProvider = require('./promise_provider');
|
||||
var Query = require('./query');
|
||||
var eachAsync = require('./services/cursor/eachAsync');
|
||||
var util = require('util');
|
||||
var utils = require('./utils');
|
||||
var read = Query.prototype.read;
|
||||
|
||||
/**
|
||||
* Aggregate constructor used for building aggregation pipelines.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* new Aggregate();
|
||||
* new Aggregate({ $project: { a: 1, b: 1 } });
|
||||
* new Aggregate({ $project: { a: 1, b: 1 } }, { $skip: 5 });
|
||||
* new Aggregate([{ $project: { a: 1, b: 1 } }, { $skip: 5 }]);
|
||||
*
|
||||
* Returned when calling Model.aggregate().
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* 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).
|
||||
* - Requires MongoDB >= 2.1
|
||||
* - Mongoose does **not** cast pipeline stages. `new Aggregate({ $match: { _id: '00000000000000000000000a' } });` will not work unless `_id` is a string in the database. Use `new Aggregate({ $match: { _id: mongoose.Types.ObjectId('00000000000000000000000a') } });` instead.
|
||||
*
|
||||
* @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/
|
||||
* @see driver http://mongodb.github.com/node-mongodb-native/api-generated/collection.html#aggregate
|
||||
* @param {Object|Array} [ops] aggregation operator(s) or operator array
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Aggregate() {
|
||||
this._pipeline = [];
|
||||
this._model = undefined;
|
||||
this.options = {};
|
||||
|
||||
if (arguments.length === 1 && util.isArray(arguments[0])) {
|
||||
this.append.apply(this, arguments[0]);
|
||||
} else {
|
||||
this.append.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds this aggregate to a model.
|
||||
*
|
||||
* @param {Model} model the model to which the aggregate is to be bound
|
||||
* @return {Aggregate}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Aggregate.prototype.model = function(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
|
||||
* var pipeline = [{ $match: { daw: 'Logic Audio X' }} ];
|
||||
* aggregate.append(pipeline);
|
||||
*
|
||||
* @param {Object} ops operator(s) to append
|
||||
* @return {Aggregate}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Aggregate.prototype.append = function() {
|
||||
var 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) {
|
||||
var 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) {
|
||||
var 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;
|
||||
}
|
||||
var 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
|
||||
* @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
|
||||
* @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
|
||||
* @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
|
||||
* @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
|
||||
* @param {Object} parameters
|
||||
* @return {Aggregate}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Aggregate.prototype.near = function(arg) {
|
||||
var op = {};
|
||||
op.$geoNear = arg;
|
||||
return this.append(op);
|
||||
};
|
||||
|
||||
/*!
|
||||
* define methods
|
||||
*/
|
||||
|
||||
'group match skip limit out'.split(' ').forEach(function($operator) {
|
||||
Aggregate.prototype[$operator] = function(arg) {
|
||||
var 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");
|
||||
*
|
||||
* @see $unwind http://docs.mongodb.org/manual/reference/aggregation/unwind/
|
||||
* @param {String} fields the field(s) to unwind
|
||||
* @return {Aggregate}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Aggregate.prototype.unwind = function() {
|
||||
var args = utils.args(arguments);
|
||||
|
||||
var res = [];
|
||||
for (var i = 0; i < args.length; ++i) {
|
||||
var arg = args[i];
|
||||
if (arg && typeof arg === 'object') {
|
||||
res.push({ $unwind: arg });
|
||||
} else if (typeof arg === 'string') {
|
||||
res.push({
|
||||
$unwind: (arg && arg.charAt(0) === '$') ? arg : '$' + arg
|
||||
});
|
||||
} else {
|
||||
throw new Error('Invalid arg "' + arg + '" to unwind(), ' +
|
||||
'must be string or object');
|
||||
}
|
||||
}
|
||||
|
||||
return this.append.apply(this, res);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var cloneOptions = {};
|
||||
if (options) {
|
||||
if (!utils.isObject(options)) {
|
||||
throw new TypeError('Invalid graphLookup() argument. Must be an object.');
|
||||
}
|
||||
|
||||
utils.mergeClone(cloneOptions, options);
|
||||
var startWith = cloneOptions.startWith;
|
||||
|
||||
if (startWith && typeof startWith === 'string') {
|
||||
cloneOptions.startWith = cloneOptions.startWith.charAt(0) === '$' ?
|
||||
cloneOptions.startWith :
|
||||
'$' + cloneOptions.startWith;
|
||||
}
|
||||
|
||||
}
|
||||
return this.append({ $graphLookup: cloneOptions });
|
||||
};
|
||||
|
||||
/**
|
||||
* Appepnds 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
|
||||
|
||||
var sort = {};
|
||||
|
||||
if (arg.constructor.name === 'Object') {
|
||||
var 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;
|
||||
}
|
||||
var 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
|
||||
* @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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute the aggregation with explain
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* Model.aggregate(..).explain(callback)
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
Aggregate.prototype.explain = function(callback) {
|
||||
var _this = this;
|
||||
var Promise = PromiseProvider.get();
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
if (!_this._pipeline.length) {
|
||||
var err = new Error('Aggregate has empty pipeline');
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
prepareDiscriminatorPipeline(_this);
|
||||
|
||||
_this._model
|
||||
.collection
|
||||
.aggregate(_this._pipeline, _this.options || {})
|
||||
.explain(function(error, result) {
|
||||
if (error) {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(null, result);
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the allowDiskUse option for the aggregation query (ignored for < 2.6.0)
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* Model.aggregate(..).allowDiskUse(true).exec(callback)
|
||||
*
|
||||
* @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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lets you set arbitrary options, for middleware or plugins.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var 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)
|
||||
* @see mongodb http://docs.mongodb.org/manual/reference/command/aggregate/
|
||||
* @return {Aggregate} this
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Aggregate.prototype.option = function(value) {
|
||||
for (var 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:
|
||||
*
|
||||
* var cursor = Model.aggregate(..).cursor({ batchSize: 1000, useMongooseAggCursor: true }).exec();
|
||||
* cursor.each(function(error, doc) {
|
||||
* // 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)
|
||||
* @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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a [cursor flag](http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag)
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* Model.aggregate(..).addCursorFlag('noCursorTimeout', true).exec();
|
||||
*
|
||||
* @param {String} flag
|
||||
* @param {Boolean} value
|
||||
* @see mongodb http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag
|
||||
*/
|
||||
|
||||
Aggregate.prototype.addCursorFlag = function(flag, value) {
|
||||
if (!this.options) {
|
||||
this.options = {};
|
||||
}
|
||||
this.options[flag] = value;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a collation
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* Model.aggregate(..).collation({ locale: 'en_US', strength: 1 }).exec();
|
||||
*
|
||||
* @param {Object} collation options
|
||||
* @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});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* var 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');
|
||||
}
|
||||
var _this = this;
|
||||
var model = this._model;
|
||||
var Promise = PromiseProvider.get();
|
||||
var options = utils.clone(this.options || {});
|
||||
var pipeline = this._pipeline;
|
||||
var collection = this._model.collection;
|
||||
|
||||
if (options && options.cursor) {
|
||||
if (options.cursor.async) {
|
||||
delete options.cursor.async;
|
||||
return new Promise.ES6(function(resolve) {
|
||||
if (!collection.buffer) {
|
||||
process.nextTick(function() {
|
||||
var cursor = collection.aggregate(pipeline, options);
|
||||
decorateCursor(cursor);
|
||||
resolve(cursor);
|
||||
callback && callback(null, cursor);
|
||||
});
|
||||
return;
|
||||
}
|
||||
collection.emitter.once('queue', function() {
|
||||
var cursor = collection.aggregate(pipeline, options);
|
||||
decorateCursor(cursor);
|
||||
resolve(cursor);
|
||||
callback && callback(null, cursor);
|
||||
});
|
||||
});
|
||||
} else if (options.cursor.useMongooseAggCursor) {
|
||||
delete options.cursor.useMongooseAggCursor;
|
||||
return new AggregationCursor(this);
|
||||
}
|
||||
var cursor = collection.aggregate(pipeline, options);
|
||||
decorateCursor(cursor);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
if (!pipeline.length) {
|
||||
var err = new Error('Aggregate has empty pipeline');
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
prepareDiscriminatorPipeline(_this);
|
||||
|
||||
model.hooks.execPre('aggregate', _this, function(error) {
|
||||
if (error) {
|
||||
var _opts = { error: error };
|
||||
return model.hooks.execPost('aggregate', _this, [null], _opts, function(error) {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
collection.aggregate(pipeline, options, function(error, result) {
|
||||
var _opts = { error: error };
|
||||
model.hooks.execPost('aggregate', _this, [result], _opts, function(error, result) {
|
||||
if (error) {
|
||||
if (callback) {
|
||||
callback(error);
|
||||
}
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(null, result);
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*!
|
||||
* Add `eachAsync()` to aggregation cursors
|
||||
*/
|
||||
|
||||
function decorateCursor(cursor) {
|
||||
cursor.eachAsync = function(fn, opts, callback) {
|
||||
if (typeof opts === 'function') {
|
||||
callback = opts;
|
||||
opts = {};
|
||||
}
|
||||
opts = opts || {};
|
||||
|
||||
return eachAsync(function(cb) { return cursor.next(cb); }, fn, opts, callback);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks whether an object is likely a pipeline operator
|
||||
*
|
||||
* @param {Object} obj object to check
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function isOperator(obj) {
|
||||
var k;
|
||||
|
||||
if (typeof obj !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
k = Object.keys(obj);
|
||||
|
||||
return k.length === 1 && k
|
||||
.some(function(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) {
|
||||
var schema = aggregate._model.schema,
|
||||
discriminatorMapping = schema && schema.discriminatorMapping;
|
||||
|
||||
if (discriminatorMapping && !discriminatorMapping.isRoot) {
|
||||
var originalPipeline = aggregate._pipeline,
|
||||
discriminatorKey = discriminatorMapping.key,
|
||||
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 {
|
||||
var match = {};
|
||||
match[discriminatorKey] = discriminatorValue;
|
||||
aggregate._pipeline.unshift({ $match: match });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = Aggregate;
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
var DocumentProvider = require('./document_provider.js');
|
||||
var 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');
|
||||
|
||||
/**
|
||||
* The Mongoose [Schema](#schema_Schema) constructor
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
* var Schema = mongoose.Schema;
|
||||
* var CatSchema = new Schema(..);
|
||||
*
|
||||
* @method Schema
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.Schema = require('./schema');
|
||||
|
||||
/**
|
||||
* The various Mongoose Types.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
* var 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.
|
||||
*
|
||||
* var ObjectId = mongoose.Types.ObjectId;
|
||||
* var 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](#document-js) constructor.
|
||||
*
|
||||
* @method Document
|
||||
* @api public
|
||||
*/
|
||||
exports.Document = DocumentProvider();
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.mongoose = module.exports;
|
||||
window.Buffer = Buffer;
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var NodeJSDocument = require('./document');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var MongooseError = require('./error');
|
||||
var Schema = require('./schema');
|
||||
var ObjectId = require('./types/objectid');
|
||||
var utils = require('./utils');
|
||||
var ValidationError = MongooseError.ValidationError;
|
||||
var InternalCache = require('./internal');
|
||||
var PromiseProvider = require('./promise_provider');
|
||||
var VersionError = require('./error').VersionError;
|
||||
|
||||
var Embedded;
|
||||
|
||||
/**
|
||||
* 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 (utils.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);
|
||||
|
||||
this.$__ = new InternalCache;
|
||||
this.$__.emitter = new EventEmitter();
|
||||
this.isNew = true;
|
||||
this.errors = undefined;
|
||||
|
||||
if (typeof fields === 'boolean') {
|
||||
this.$__.strictMode = fields;
|
||||
fields = undefined;
|
||||
} else {
|
||||
this.$__.strictMode = this.schema.options && this.schema.options.strict;
|
||||
this.$__.selected = fields;
|
||||
}
|
||||
|
||||
var required = this.schema.requiredPaths();
|
||||
for (var i = 0; i < required.length; ++i) {
|
||||
this.$__.activePaths.require(required[i]);
|
||||
}
|
||||
|
||||
this.$__.emitter.setMaxListeners(0);
|
||||
this._doc = this.$__buildDoc(obj, fields, skipId);
|
||||
|
||||
if (!skipInit && obj) {
|
||||
this.init(obj);
|
||||
}
|
||||
|
||||
this.$__registerHooksFromSchema();
|
||||
|
||||
// apply methods
|
||||
for (var m in schema.methods) {
|
||||
this[m] = schema.methods[m];
|
||||
}
|
||||
// apply statics
|
||||
for (var s in schema.statics) {
|
||||
this[s] = schema.statics[s];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherit from the NodeJS document
|
||||
*/
|
||||
|
||||
Document.prototype = Object.create(NodeJSDocument.prototype);
|
||||
Document.prototype.constructor = Document;
|
||||
|
||||
/*!
|
||||
* Browser doc exposes the event emitter API
|
||||
*/
|
||||
|
||||
Document.$emitter = new EventEmitter();
|
||||
|
||||
utils.each(
|
||||
['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners',
|
||||
'removeAllListeners', 'addListener'],
|
||||
function(emitterFn) {
|
||||
Document[emitterFn] = function() {
|
||||
return Document.$emitter[emitterFn].apply(Document.$emitter, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
/*!
|
||||
* Executes methods queued from the Schema definition
|
||||
*
|
||||
* @api private
|
||||
* @method $__registerHooksFromSchema
|
||||
* @deprecated
|
||||
* @memberOf Document
|
||||
*/
|
||||
|
||||
Document.prototype.$__registerHooksFromSchema = function() {
|
||||
Embedded = Embedded || require('./types/embedded');
|
||||
var Promise = PromiseProvider.get();
|
||||
|
||||
var _this = this;
|
||||
var q = _this.schema && _this.schema.callQueue;
|
||||
var toWrapEl;
|
||||
var len;
|
||||
var i;
|
||||
var j;
|
||||
var pointCut;
|
||||
var keys;
|
||||
if (!q.length) {
|
||||
return _this;
|
||||
}
|
||||
|
||||
// we are only interested in 'pre' hooks, and group by point-cut
|
||||
var toWrap = { post: [] };
|
||||
var pair;
|
||||
|
||||
for (i = 0; i < q.length; ++i) {
|
||||
pair = q[i];
|
||||
if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') {
|
||||
_this[pair[0]].apply(_this, pair[1]);
|
||||
continue;
|
||||
}
|
||||
var args = [].slice.call(pair[1]);
|
||||
pointCut = pair[0] === 'on' ? 'post' : args[0];
|
||||
if (!(pointCut in toWrap)) {
|
||||
toWrap[pointCut] = {post: [], pre: []};
|
||||
}
|
||||
if (pair[0] === 'post') {
|
||||
toWrap[pointCut].post.push(args);
|
||||
} else if (pair[0] === 'on') {
|
||||
toWrap[pointCut].push(args);
|
||||
} else {
|
||||
toWrap[pointCut].pre.push(args);
|
||||
}
|
||||
}
|
||||
|
||||
// 'post' hooks are simpler
|
||||
len = toWrap.post.length;
|
||||
toWrap.post.forEach(function(args) {
|
||||
_this.on.apply(_this, args);
|
||||
});
|
||||
delete toWrap.post;
|
||||
|
||||
// 'init' should be synchronous on subdocuments
|
||||
if (toWrap.init && _this instanceof Embedded) {
|
||||
if (toWrap.init.pre) {
|
||||
toWrap.init.pre.forEach(function(args) {
|
||||
_this.$pre.apply(_this, args);
|
||||
});
|
||||
}
|
||||
if (toWrap.init.post) {
|
||||
toWrap.init.post.forEach(function(args) {
|
||||
_this.$post.apply(_this, args);
|
||||
});
|
||||
}
|
||||
delete toWrap.init;
|
||||
} else if (toWrap.set) {
|
||||
// Set hooks also need to be sync re: gh-3479
|
||||
if (toWrap.set.pre) {
|
||||
toWrap.set.pre.forEach(function(args) {
|
||||
_this.$pre.apply(_this, args);
|
||||
});
|
||||
}
|
||||
if (toWrap.set.post) {
|
||||
toWrap.set.post.forEach(function(args) {
|
||||
_this.$post.apply(_this, args);
|
||||
});
|
||||
}
|
||||
delete toWrap.set;
|
||||
}
|
||||
|
||||
keys = Object.keys(toWrap);
|
||||
len = keys.length;
|
||||
for (i = 0; i < len; ++i) {
|
||||
pointCut = keys[i];
|
||||
// this is so we can wrap everything into a promise;
|
||||
var newName = ('$__original_' + pointCut);
|
||||
if (!_this[pointCut]) {
|
||||
continue;
|
||||
}
|
||||
if (_this[pointCut].$isWrapped) {
|
||||
continue;
|
||||
}
|
||||
_this[newName] = _this[pointCut];
|
||||
_this[pointCut] = (function(_newName) {
|
||||
return function wrappedPointCut() {
|
||||
var args = [].slice.call(arguments);
|
||||
var lastArg = args.pop();
|
||||
var fn;
|
||||
var originalError = new Error();
|
||||
var $results;
|
||||
if (lastArg && typeof lastArg !== 'function') {
|
||||
args.push(lastArg);
|
||||
} else {
|
||||
fn = lastArg;
|
||||
}
|
||||
|
||||
var promise = new Promise.ES6(function(resolve, reject) {
|
||||
args.push(function(error) {
|
||||
if (error) {
|
||||
// gh-2633: since VersionError is very generic, take the
|
||||
// stack trace of the original save() function call rather
|
||||
// than the async trace
|
||||
if (error instanceof VersionError) {
|
||||
error.stack = originalError.stack;
|
||||
}
|
||||
_this.$__handleReject(error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// There may be multiple results and promise libs other than
|
||||
// mpromise don't support passing multiple values to `resolve()`
|
||||
$results = Array.prototype.slice.call(arguments, 1);
|
||||
resolve.apply(promise, $results);
|
||||
});
|
||||
|
||||
_this[_newName].apply(_this, args);
|
||||
});
|
||||
if (fn) {
|
||||
if (_this.constructor.$wrapCallback) {
|
||||
fn = _this.constructor.$wrapCallback(fn);
|
||||
}
|
||||
return promise.then(
|
||||
function() {
|
||||
process.nextTick(function() {
|
||||
fn.apply(null, [null].concat($results));
|
||||
});
|
||||
},
|
||||
function(error) {
|
||||
process.nextTick(function() {
|
||||
fn(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
})(newName);
|
||||
_this[pointCut].$isWrapped = true;
|
||||
|
||||
toWrapEl = toWrap[pointCut];
|
||||
var _len = toWrapEl.pre.length;
|
||||
args;
|
||||
for (j = 0; j < _len; ++j) {
|
||||
args = toWrapEl.pre[j];
|
||||
args[0] = newName;
|
||||
_this.$pre.apply(_this, args);
|
||||
}
|
||||
|
||||
_len = toWrapEl.post.length;
|
||||
for (j = 0; j < _len; ++j) {
|
||||
args = toWrapEl.post[j];
|
||||
args[0] = newName;
|
||||
_this.$post.apply(_this, args);
|
||||
}
|
||||
}
|
||||
return _this;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
Document.ValidationError = ValidationError;
|
||||
module.exports = exports = Document;
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var StrictModeError = require('./error/strict');
|
||||
var Types = require('./schema/index');
|
||||
var util = require('util');
|
||||
var utils = require('./utils');
|
||||
|
||||
var 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));
|
||||
}
|
||||
|
||||
var paths = Object.keys(obj);
|
||||
var i = paths.length;
|
||||
var _keys;
|
||||
var any$conditionals;
|
||||
var schematype;
|
||||
var nested;
|
||||
var path;
|
||||
var type;
|
||||
var val;
|
||||
|
||||
while (i--) {
|
||||
path = paths[i];
|
||||
val = obj[path];
|
||||
|
||||
if (path === '$or' || path === '$nor' || path === '$and') {
|
||||
var k = val.length;
|
||||
|
||||
while (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 (!schema) {
|
||||
// no casting for Mixed types
|
||||
continue;
|
||||
}
|
||||
|
||||
schematype = schema.path(path);
|
||||
|
||||
if (!schematype) {
|
||||
// Handle potential embedded array queries
|
||||
var split = path.split('.'),
|
||||
j = split.length,
|
||||
pathFirstHalf,
|
||||
pathLastHalf,
|
||||
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 (utils.isObject(val)) {
|
||||
// handle geo schemas that use object notation
|
||||
// { loc: { long: Number, lat: Number }
|
||||
|
||||
var 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) {
|
||||
var numbertype = new Types.Number('__QueryCasting__');
|
||||
var 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') {
|
||||
var withinType = value.$center
|
||||
|| value.$centerSphere
|
||||
|| value.$box
|
||||
|| value.$polygon;
|
||||
|
||||
if (!withinType) {
|
||||
throw new Error('Bad $within paramater: ' + 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 (utils.isMongooseObject(value.$geometry)) {
|
||||
value.$geometry = value.$geometry.toObject({
|
||||
transform: false,
|
||||
virtuals: false
|
||||
});
|
||||
}
|
||||
value = value.$geometry.coordinates;
|
||||
} else if (geo === '$geoWithin') {
|
||||
if (value.$geometry) {
|
||||
if (utils.isMongooseObject(value.$geometry)) {
|
||||
value.$geometry = value.$geometry.toObject({ virtuals: false });
|
||||
}
|
||||
var 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 (utils.isMongooseObject(value)) {
|
||||
value = value.toObject({ virtuals: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cast(value, numbertype, context);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (options && options.upsert && options.strict && !schema.nested[path]) {
|
||||
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 && options.strictQuery === 'throw') {
|
||||
throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
|
||||
'schema and strictQuery is true.');
|
||||
}
|
||||
} else if (val === null || val === undefined) {
|
||||
obj[path] = null;
|
||||
continue;
|
||||
} else if (val.constructor.name === 'Object') {
|
||||
any$conditionals = Object.keys(val).some(function(k) {
|
||||
return k.charAt(0) === '$' && k !== '$id' && k !== '$ref';
|
||||
});
|
||||
|
||||
if (!any$conditionals) {
|
||||
obj[path] = schematype.castForQueryWrapper({
|
||||
val: val,
|
||||
context: context
|
||||
});
|
||||
} else {
|
||||
var ks = Object.keys(val),
|
||||
$cond;
|
||||
|
||||
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 && _keys[0].charAt(0) === '$') {
|
||||
for (var 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) {
|
||||
var casted = [];
|
||||
for (var valIndex = 0; valIndex < val.length; valIndex++) {
|
||||
casted.push(schematype.castForQueryWrapper({
|
||||
val: val[valIndex],
|
||||
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) || utils.isObject(item)) {
|
||||
return _cast(item, numbertype, context);
|
||||
}
|
||||
val[i] = numbertype.castForQueryWrapper({ val: item, context: context });
|
||||
});
|
||||
} else {
|
||||
var nearKeys = Object.keys(val);
|
||||
var nearLen = nearKeys.length;
|
||||
while (nearLen--) {
|
||||
var nkey = nearKeys[nearLen];
|
||||
var item = val[nkey];
|
||||
if (Array.isArray(item) || utils.isObject(item)) {
|
||||
_cast(item, numbertype, context);
|
||||
val[nkey] = item;
|
||||
} else {
|
||||
val[nkey] = numbertype.castForQuery({ val: item, context: context });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var STATES = require('./connectionstate');
|
||||
|
||||
/**
|
||||
* 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 = {};
|
||||
}
|
||||
|
||||
opts.bufferCommands = undefined === opts.bufferCommands
|
||||
? true
|
||||
: opts.bufferCommands;
|
||||
|
||||
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 = this.opts.bufferCommands;
|
||||
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;
|
||||
var _this = this;
|
||||
setImmediate(function() {
|
||||
_this.doQueue();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the database disconnects
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Collection.prototype.onClose = function(force) {
|
||||
if (this.opts.bufferCommands && !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 (var i = 0, l = this.queue.length; i < l; i++) {
|
||||
if (typeof this.queue[i][0] === 'function') {
|
||||
this.queue[i][0].apply(this, this.queue[i][1]);
|
||||
} else {
|
||||
this[this.queue[i][0]].apply(this, this.queue[i][1]);
|
||||
}
|
||||
}
|
||||
this.queue = [];
|
||||
var _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#ensureIndex 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.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.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');
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Collection;
|
||||
+1155
File diff suppressed because it is too large
Load Diff
+27
@@ -0,0 +1,27 @@
|
||||
|
||||
/*!
|
||||
* Connection states
|
||||
*/
|
||||
|
||||
var STATES = module.exports = exports = Object.create(null);
|
||||
|
||||
var disconnected = 'disconnected';
|
||||
var connected = 'connected';
|
||||
var connecting = 'connecting';
|
||||
var disconnecting = 'disconnecting';
|
||||
var unauthorized = 'unauthorized';
|
||||
var uninitialized = 'uninitialized';
|
||||
|
||||
STATES[0] = disconnected;
|
||||
STATES[1] = connected;
|
||||
STATES[2] = connecting;
|
||||
STATES[3] = disconnecting;
|
||||
STATES[4] = unauthorized;
|
||||
STATES[99] = uninitialized;
|
||||
|
||||
STATES[disconnected] = 0;
|
||||
STATES[connected] = 1;
|
||||
STATES[connecting] = 2;
|
||||
STATES[disconnecting] = 3;
|
||||
STATES[unauthorized] = 4;
|
||||
STATES[uninitialized] = 99;
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var PromiseProvider = require('../promise_provider');
|
||||
var Readable = require('stream').Readable;
|
||||
var 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](https://strongloop.com/strongblog/whats-new-io-js-beta-streams3/),
|
||||
* 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 = [];
|
||||
var model = agg._model;
|
||||
delete agg.options.cursor.useMongooseAggCursor;
|
||||
|
||||
_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() {
|
||||
var _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() {
|
||||
_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()`
|
||||
* var 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
|
||||
*/
|
||||
|
||||
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) {
|
||||
var Promise = PromiseProvider.get();
|
||||
var _this = this;
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
_this.cursor.close(function(error) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
reject(error);
|
||||
return _this.listeners('error').length > 0 &&
|
||||
_this.emit('error', error);
|
||||
}
|
||||
_this.emit('close');
|
||||
resolve();
|
||||
callback && callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var Promise = PromiseProvider.get();
|
||||
var _this = this;
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
_next(_this, function(error, doc) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
return reject(error);
|
||||
}
|
||||
callback && callback(null, doc);
|
||||
resolve(doc);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 {Function} [callback] executed when all docs have been processed
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
* @method eachAsync
|
||||
*/
|
||||
|
||||
AggregationCursor.prototype.eachAsync = function(fn, callback) {
|
||||
var Promise = PromiseProvider.get();
|
||||
var _this = this;
|
||||
|
||||
var handleNextResult = function(doc, callback) {
|
||||
var promise = fn(doc);
|
||||
if (promise && typeof promise.then === 'function') {
|
||||
promise.then(
|
||||
function() { callback(null); },
|
||||
function(error) { callback(error); });
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
};
|
||||
|
||||
var iterate = function(callback) {
|
||||
return _next(_this, function(error, doc) {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
if (!doc) {
|
||||
return callback(null);
|
||||
}
|
||||
handleNextResult(doc, function(error) {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
// Make sure to clear the stack re: gh-4697
|
||||
setTimeout(function() {
|
||||
iterate(callback);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
iterate(function(error) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
return reject(error);
|
||||
}
|
||||
callback && callback(null);
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var _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) {
|
||||
var callback = cb;
|
||||
if (ctx._transforms.length) {
|
||||
callback = function(err, doc) {
|
||||
if (err || doc === null) {
|
||||
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;
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var PromiseProvider = require('../promise_provider');
|
||||
var Readable = require('stream').Readable;
|
||||
var eachAsync = require('../services/cursor/eachAsync');
|
||||
var helpers = require('../queryhelpers');
|
||||
var 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](https://strongloop.com/strongblog/whats-new-io-js-beta-streams3/),
|
||||
* 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;
|
||||
this._transforms = options.transform ? [options.transform] : [];
|
||||
var _this = this;
|
||||
var model = query.model;
|
||||
model.hooks.execPre('find', query, function() {
|
||||
model.collection.find(query._conditions, 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() {
|
||||
var _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() {
|
||||
_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()`
|
||||
* var 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) {
|
||||
var Promise = PromiseProvider.get();
|
||||
var _this = this;
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
_this.cursor.close(function(error) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
reject(error);
|
||||
return _this.listeners('error').length > 0 &&
|
||||
_this.emit('error', error);
|
||||
}
|
||||
_this.emit('close');
|
||||
resolve();
|
||||
callback && callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var Promise = PromiseProvider.get();
|
||||
var _this = this;
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
_next(_this, function(error, doc) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
return reject(error);
|
||||
}
|
||||
callback && callback(null, doc);
|
||||
resolve(doc);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
QueryCursor.prototype.eachAsync = function(fn, opts, callback) {
|
||||
var _this = this;
|
||||
if (typeof opts === 'function') {
|
||||
callback = opts;
|
||||
opts = {};
|
||||
}
|
||||
opts = opts || {};
|
||||
|
||||
return eachAsync(function(cb) { return _next(_this, cb); }, fn, opts, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var _this = this;
|
||||
_waitForCursor(this, function() {
|
||||
_this.cursor.addCursorFlag(flag, value);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the next doc from the underlying cursor and mongooseify it
|
||||
* (populate, etc.)
|
||||
*/
|
||||
|
||||
function _next(ctx, cb) {
|
||||
var callback = cb;
|
||||
if (ctx._transforms.length) {
|
||||
callback = function(err, doc) {
|
||||
if (err || doc === null) {
|
||||
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);
|
||||
}
|
||||
|
||||
var opts = ctx.query._mongooseOptions;
|
||||
if (!opts.populate) {
|
||||
return opts.lean === true ?
|
||||
callback(null, doc) :
|
||||
_create(ctx, doc, null, callback);
|
||||
}
|
||||
|
||||
var 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 === true ?
|
||||
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) {
|
||||
var instance = helpers.createModel(ctx.query.model, doc, ctx.query._fields);
|
||||
var opts = populatedIds ?
|
||||
{ populated: populatedIds } :
|
||||
undefined;
|
||||
|
||||
instance.init(doc, opts, function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
cb(null, instance);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = QueryCursor;
|
||||
+2663
File diff suppressed because it is too large
Load Diff
+30
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
var Document = require('./document.js');
|
||||
var BrowserDocument = require('./browserDocument.js');
|
||||
|
||||
var 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;
|
||||
};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
var BrowserDocument = require('./browserDocument.js');
|
||||
|
||||
/**
|
||||
* Returns the Document constructor for the current context
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
module.exports = function() {
|
||||
return BrowserDocument;
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
|
||||
# Driver Spec
|
||||
|
||||
TODO
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function() {};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Binary = require('bson').Binary;
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = Binary;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = require('bson').Decimal128;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
exports.Binary = require('./binary');
|
||||
exports.Decimal128 = require('./decimal128');
|
||||
exports.ObjectId = require('./objectid');
|
||||
exports.ReadPreference = require('./ReadPreference');
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
|
||||
/*!
|
||||
* [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) ObjectId
|
||||
* @constructor NodeMongoDbObjectId
|
||||
* @see ObjectId
|
||||
*/
|
||||
|
||||
var ObjectId = require('bson').ObjectID;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = exports = ObjectId;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
var driver;
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
driver = require('./node-mongodb-native');
|
||||
if (global.MONGOOSE_DRIVER_PATH) {
|
||||
driver = require(global.MONGOOSE_DRIVER_PATH);
|
||||
}
|
||||
} else {
|
||||
driver = require('./browser');
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = driver;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = require('./browser');
|
||||
Generated
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var mongodb = require('mongodb');
|
||||
var 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);
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Binary = require('mongodb').Binary;
|
||||
|
||||
module.exports = exports = Binary;
|
||||
Generated
Vendored
+271
@@ -0,0 +1,271 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseCollection = require('../../collection');
|
||||
var Collection = require('mongodb').Collection;
|
||||
var utils = require('../../utils');
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
this.collection = null;
|
||||
MongooseCollection.apply(this, arguments);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherit from abstract Collection.
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.__proto__ = MongooseCollection.prototype;
|
||||
|
||||
/**
|
||||
* Called when the connection opens.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.onOpen = function() {
|
||||
var _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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
var doc = docs[0];
|
||||
var exists = !!doc;
|
||||
|
||||
if (exists) {
|
||||
if (doc.options && doc.options.capped) {
|
||||
callback(null, c);
|
||||
} else {
|
||||
var 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
|
||||
var opts = utils.clone(_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);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Copy the collection methods and make them subject to queues
|
||||
*/
|
||||
|
||||
function iter(i) {
|
||||
NativeCollection.prototype[i] = function() {
|
||||
// If user force closed, queueing will hang forever. See #5664
|
||||
if (this.opts.$wasForceClosed) {
|
||||
return this.conn.db.collection(this.name)[i].apply(collection, args);
|
||||
}
|
||||
if (this.buffer) {
|
||||
this.addQueue(i, arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
var collection = this.collection;
|
||||
var args = arguments;
|
||||
var _this = this;
|
||||
var debug = _this.conn.base.options.debug;
|
||||
|
||||
if (debug) {
|
||||
if (typeof debug === 'function') {
|
||||
debug.apply(_this,
|
||||
[_this.name, i].concat(utils.args(args, 0, args.length - 1)));
|
||||
} else {
|
||||
this.$print(_this.name, i, args);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
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 (var i in Collection.prototype) {
|
||||
// Janky hack to work around gh-3005 until we can get rid of the mongoose
|
||||
// collection abstraction
|
||||
try {
|
||||
if (typeof Collection.prototype[i] !== 'function') {
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
iter(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug print helper
|
||||
*
|
||||
* @api public
|
||||
* @method $print
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.$print = function(name, i, args) {
|
||||
var moduleName = '\x1B[0;36mMongoose:\x1B[0m ';
|
||||
var functionCall = [name, i].join('.');
|
||||
var _args = [];
|
||||
for (var j = args.length - 1; j >= 0; --j) {
|
||||
if (this.$format(args[j]) || _args.length) {
|
||||
_args.unshift(this.$format(args[j]));
|
||||
}
|
||||
}
|
||||
var params = '(' + _args.join(', ') + ')';
|
||||
|
||||
console.error(moduleName + functionCall + params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatter for debug print args
|
||||
*
|
||||
* @api public
|
||||
* @method $format
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.$format = function(arg) {
|
||||
var type = typeof arg;
|
||||
if (type === 'function' || type === 'undefined') return '';
|
||||
return format(arg);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Debug print helper
|
||||
*/
|
||||
|
||||
function map(o) {
|
||||
return format(o, true);
|
||||
}
|
||||
function formatObjectId(x, key) {
|
||||
var representation = 'ObjectId("' + x[key].toHexString() + '")';
|
||||
x[key] = {inspect: function() { return representation; }};
|
||||
}
|
||||
function formatDate(x, key) {
|
||||
var representation = 'new Date("' + x[key].toUTCString() + '")';
|
||||
x[key] = {inspect: function() { return representation; }};
|
||||
}
|
||||
function format(obj, sub) {
|
||||
if (obj && typeof obj.toBSON === 'function') {
|
||||
obj = obj.toBSON();
|
||||
}
|
||||
var x = utils.clone(obj, {retainKeyOrder: 1, transform: false});
|
||||
var representation;
|
||||
|
||||
if (x != null) {
|
||||
if (x.constructor.name === 'Binary') {
|
||||
x = 'BinData(' + x.sub_type + ', "' + x.toString('base64') + '")';
|
||||
} else if (x.constructor.name === 'ObjectID') {
|
||||
representation = 'ObjectId("' + x.toHexString() + '")';
|
||||
x = {inspect: function() { return representation; }};
|
||||
} else if (x.constructor.name === 'Date') {
|
||||
representation = 'new Date("' + x.toUTCString() + '")';
|
||||
x = {inspect: function() { return representation; }};
|
||||
} else if (x.constructor.name === 'Object') {
|
||||
var keys = Object.keys(x);
|
||||
var numKeys = keys.length;
|
||||
var key;
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
key = keys[i];
|
||||
if (x[key]) {
|
||||
if (typeof x[key].toBSON === 'function') {
|
||||
x[key] = x[key].toBSON();
|
||||
}
|
||||
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 (Array.isArray(x[key])) {
|
||||
x[key] = x[key].map(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sub) return x;
|
||||
}
|
||||
|
||||
return require('util')
|
||||
.inspect(x, false, 10, true)
|
||||
.replace(/\n/g, '')
|
||||
.replace(/\s{2,}/g, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives information about this collections indexes.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @method getIndexes
|
||||
* @api public
|
||||
*/
|
||||
|
||||
NativeCollection.prototype.getIndexes = NativeCollection.prototype.indexInformation;
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = NativeCollection;
|
||||
Generated
Vendored
+403
@@ -0,0 +1,403 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseConnection = require('../../connection');
|
||||
var mongo = require('mongodb');
|
||||
var Db = mongo.Db;
|
||||
var Server = mongo.Server;
|
||||
var Mongos = mongo.Mongos;
|
||||
var STATES = require('../../connectionstate');
|
||||
var ReplSetServers = mongo.ReplSet;
|
||||
var DisconnectedError = require('../../error/disconnected');
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Opens the connection to MongoDB.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Connection} this
|
||||
* @api private
|
||||
*/
|
||||
|
||||
NativeConnection.prototype.doOpen = function(fn) {
|
||||
var _this = this;
|
||||
var server = new Server(this.host, this.port, this.options.server);
|
||||
|
||||
if (this.options && this.options.mongos) {
|
||||
var mongos = new Mongos([server], this.options.mongos);
|
||||
this.db = new Db(this.name, mongos, this.options.db);
|
||||
} else {
|
||||
this.db = new Db(this.name, server, this.options.db);
|
||||
}
|
||||
|
||||
this.db.open(function(err) {
|
||||
listen(_this);
|
||||
|
||||
if (!mongos) {
|
||||
server.s.server.on('error', function(error) {
|
||||
if (/after \d+ attempts/.test(error.message)) {
|
||||
_this.emit('error', new DisconnectedError(server.s.server.name));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (err) return fn(err);
|
||||
|
||||
fn();
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Switches to a different database using the same connection pool.
|
||||
*
|
||||
* Returns a new connection object, with the new db.
|
||||
*
|
||||
* @param {String} name The database name
|
||||
* @return {Connection} New Connection Object
|
||||
* @api public
|
||||
*/
|
||||
|
||||
NativeConnection.prototype.useDb = function(name) {
|
||||
// we have to manually copy all of the attributes...
|
||||
var newConn = new this.constructor();
|
||||
newConn.name = name;
|
||||
newConn.base = this.base;
|
||||
newConn.collections = {};
|
||||
newConn.models = {};
|
||||
newConn.replica = this.replica;
|
||||
newConn.hosts = this.hosts;
|
||||
newConn.host = this.host;
|
||||
newConn.port = this.port;
|
||||
newConn.user = this.user;
|
||||
newConn.pass = this.pass;
|
||||
newConn.options = this.options;
|
||||
newConn._readyState = this._readyState;
|
||||
newConn._closeCalled = this._closeCalled;
|
||||
newConn._hasOpened = this._hasOpened;
|
||||
newConn._listening = false;
|
||||
|
||||
// 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
|
||||
|
||||
var _this = this;
|
||||
|
||||
if (this.db && this._readyState === STATES.connected) {
|
||||
wireup();
|
||||
} else {
|
||||
this.once('connected', wireup);
|
||||
}
|
||||
|
||||
function wireup() {
|
||||
newConn.db = _this.db.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);
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a connection to a MongoDB ReplicaSet.
|
||||
*
|
||||
* See description of [doOpen](#NativeConnection-doOpen) for server options. In this case `options.replset` is also passed to ReplSetServers.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api private
|
||||
* @return {Connection} this
|
||||
*/
|
||||
|
||||
NativeConnection.prototype.doOpenSet = function(fn) {
|
||||
var servers = [],
|
||||
_this = this;
|
||||
|
||||
this.hosts.forEach(function(server) {
|
||||
var host = server.host || server.ipc;
|
||||
var port = server.port || 27017;
|
||||
servers.push(new Server(host, port, _this.options.server));
|
||||
});
|
||||
|
||||
var server = this.options.mongos
|
||||
? new Mongos(servers, this.options.mongos)
|
||||
: new ReplSetServers(servers, this.options.replset || this.options.replSet);
|
||||
this.db = new Db(this.name, server, this.options.db);
|
||||
|
||||
this.db.s.topology.on('left', function(data) {
|
||||
_this.emit('left', data);
|
||||
});
|
||||
|
||||
this.db.s.topology.on('joined', function(data) {
|
||||
_this.emit('joined', data);
|
||||
});
|
||||
|
||||
this.db.on('fullsetup', function() {
|
||||
_this.emit('fullsetup');
|
||||
});
|
||||
|
||||
this.db.on('all', function() {
|
||||
_this.emit('all');
|
||||
});
|
||||
|
||||
this.db.open(function(err) {
|
||||
if (err) return fn(err);
|
||||
fn();
|
||||
listen(_this);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection
|
||||
*
|
||||
* @param {Boolean} [force]
|
||||
* @param {Function} [fn]
|
||||
* @return {Connection} this
|
||||
* @api private
|
||||
*/
|
||||
|
||||
NativeConnection.prototype.doClose = function(force, fn) {
|
||||
this.db.close(force, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares default connection options for the node-mongodb-native driver.
|
||||
*
|
||||
* _NOTE: `passed` options take precedence over connection string options._
|
||||
*
|
||||
* @param {Object} passed options that were passed directly during connection
|
||||
* @param {Object} [connStrOptions] options that were passed in the connection string
|
||||
* @api private
|
||||
*/
|
||||
|
||||
NativeConnection.prototype.parseOptions = function(passed, connStrOpts) {
|
||||
var o = passed ? require('../../utils').clone(passed) : {};
|
||||
|
||||
o.db || (o.db = {});
|
||||
o.auth || (o.auth = {});
|
||||
o.server || (o.server = {});
|
||||
o.replset || (o.replset = o.replSet) || (o.replset = {});
|
||||
o.server.socketOptions || (o.server.socketOptions = {});
|
||||
o.replset.socketOptions || (o.replset.socketOptions = {});
|
||||
o.mongos || (o.mongos = (connStrOpts && connStrOpts.mongos));
|
||||
(o.mongos === true) && (o.mongos = {});
|
||||
|
||||
var opts = connStrOpts || {};
|
||||
Object.keys(opts).forEach(function(name) {
|
||||
switch (name) {
|
||||
case 'ssl':
|
||||
o.server.ssl = opts.ssl;
|
||||
o.replset.ssl = opts.ssl;
|
||||
o.mongos && (o.mongos.ssl = opts.ssl);
|
||||
break;
|
||||
case 'poolSize':
|
||||
if (typeof o.server[name] === 'undefined') {
|
||||
o.server[name] = o.replset[name] = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'slaveOk':
|
||||
if (typeof o.server.slave_ok === 'undefined') {
|
||||
o.server.slave_ok = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'autoReconnect':
|
||||
if (typeof o.server.auto_reconnect === 'undefined') {
|
||||
o.server.auto_reconnect = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'socketTimeoutMS':
|
||||
case 'connectTimeoutMS':
|
||||
if (typeof o.server.socketOptions[name] === 'undefined') {
|
||||
o.server.socketOptions[name] = o.replset.socketOptions[name] = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'authdb':
|
||||
if (typeof o.auth.authdb === 'undefined') {
|
||||
o.auth.authdb = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'authSource':
|
||||
if (typeof o.auth.authSource === 'undefined') {
|
||||
o.auth.authSource = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'authMechanism':
|
||||
if (typeof o.auth.authMechanism === 'undefined') {
|
||||
o.auth.authMechanism = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'retries':
|
||||
case 'reconnectWait':
|
||||
case 'rs_name':
|
||||
if (typeof o.replset[name] === 'undefined') {
|
||||
o.replset[name] = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'replicaSet':
|
||||
if (typeof o.replset.rs_name === 'undefined') {
|
||||
o.replset.rs_name = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'readSecondary':
|
||||
if (typeof o.replset.read_secondary === 'undefined') {
|
||||
o.replset.read_secondary = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'nativeParser':
|
||||
if (typeof o.db.native_parser === 'undefined') {
|
||||
o.db.native_parser = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'w':
|
||||
case 'safe':
|
||||
case 'fsync':
|
||||
case 'journal':
|
||||
case 'wtimeoutMS':
|
||||
if (typeof o.db[name] === 'undefined') {
|
||||
o.db[name] = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'readPreference':
|
||||
if (typeof o.db.readPreference === 'undefined') {
|
||||
o.db.readPreference = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'readPreferenceTags':
|
||||
if (typeof o.db.read_preference_tags === 'undefined') {
|
||||
o.db.read_preference_tags = opts[name];
|
||||
}
|
||||
break;
|
||||
case 'sslValidate':
|
||||
o.server.sslValidate = opts.sslValidate;
|
||||
o.replset.sslValidate = opts.sslValidate;
|
||||
o.mongos && (o.mongos.sslValidate = opts.sslValidate);
|
||||
}
|
||||
});
|
||||
|
||||
if (!('auto_reconnect' in o.server)) {
|
||||
o.server.auto_reconnect = true;
|
||||
}
|
||||
|
||||
// mongoose creates its own ObjectIds
|
||||
o.db.forceServerObjectId = false;
|
||||
|
||||
// default safe using new nomenclature
|
||||
if (!('journal' in o.db || 'j' in o.db ||
|
||||
'fsync' in o.db || 'safe' in o.db || 'w' in o.db)) {
|
||||
o.db.w = 1;
|
||||
}
|
||||
|
||||
if (o.promiseLibrary) {
|
||||
o.db.promiseLibrary = o.promiseLibrary;
|
||||
}
|
||||
|
||||
validate(o);
|
||||
return o;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Validates the driver db options.
|
||||
*
|
||||
* @param {Object} o
|
||||
*/
|
||||
|
||||
function validate(o) {
|
||||
if (o.db.w === -1 || o.db.w === 0) {
|
||||
if (o.db.journal || o.db.fsync || o.db.safe) {
|
||||
throw new Error(
|
||||
'Invalid writeConcern: '
|
||||
+ 'w set to -1 or 0 cannot be combined with safe|fsync|journal');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = NativeConnection;
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = require('mongodb').Decimal128;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
exports.Binary = require('./binary');
|
||||
exports.Decimal128 = require('./decimal128');
|
||||
exports.ObjectId = require('./objectid');
|
||||
exports.ReadPreference = require('./ReadPreference');
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
|
||||
/*!
|
||||
* [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) ObjectId
|
||||
* @constructor NodeMongoDbObjectId
|
||||
* @see ObjectId
|
||||
*/
|
||||
|
||||
var ObjectId = require('mongodb').ObjectId;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = exports = ObjectId;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/*!
|
||||
* MissingSchema Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function MissingSchemaError() {
|
||||
var msg = 'Schema hasn\'t been registered for document.\n'
|
||||
+ 'Use mongoose.Document(name, schema)';
|
||||
MongooseError.call(this, msg);
|
||||
this.name = 'MissingSchemaError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
MissingSchemaError.prototype = Object.create(MongooseError.prototype);
|
||||
MissingSchemaError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = MissingSchemaError;
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
var util = require('util');
|
||||
|
||||
/**
|
||||
* Casting Error constructor.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} value
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function CastError(type, value, path, reason) {
|
||||
var stringValue = util.inspect(value);
|
||||
stringValue = stringValue.replace(/^'/, '"').replace(/'$/, '"');
|
||||
if (stringValue.charAt(0) !== '"') {
|
||||
stringValue = '"' + stringValue + '"';
|
||||
}
|
||||
MongooseError.call(this, 'Cast to ' + type + ' failed for value ' +
|
||||
stringValue + ' at path "' + path + '"');
|
||||
this.name = 'CastError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.stringValue = stringValue;
|
||||
this.kind = type;
|
||||
this.value = value;
|
||||
this.path = path;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
CastError.prototype = Object.create(MongooseError.prototype);
|
||||
CastError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
CastError.prototype.setModel = function(model) {
|
||||
this.model = model;
|
||||
this.message = 'Cast to ' + this.kind + ' failed for value ' +
|
||||
this.stringValue + ' at path "' + this.path + '"' + ' for model "' +
|
||||
model.modelName + '"';
|
||||
};
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = CastError;
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Casting Error constructor.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} value
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function DisconnectedError(connectionString) {
|
||||
MongooseError.call(this, 'Ran out of retries trying to reconnect to "' +
|
||||
connectionString + '". Try setting `server.reconnectTries` and ' +
|
||||
'`server.reconnectInterval` to something higher.');
|
||||
this.name = 'DisconnectedError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
DisconnectedError.prototype = Object.create(MongooseError.prototype);
|
||||
DisconnectedError.prototype.constructor = MongooseError;
|
||||
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = DisconnectedError;
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/*!
|
||||
* DivergentArrayError constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function DivergentArrayError(paths) {
|
||||
var 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
|
||||
|
||||
MongooseError.call(this, msg);
|
||||
this.name = 'DivergentArrayError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
DivergentArrayError.prototype = Object.create(MongooseError.prototype);
|
||||
DivergentArrayError.prototype.constructor = MongooseError;
|
||||
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = DivergentArrayError;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
|
||||
/**
|
||||
* MongooseError constructor
|
||||
*
|
||||
* @param {String} msg Error message
|
||||
* @inherits Error https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error
|
||||
*/
|
||||
|
||||
function MongooseError(msg) {
|
||||
Error.call(this);
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.message = msg;
|
||||
this.name = 'MongooseError';
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from Error.
|
||||
*/
|
||||
|
||||
MongooseError.prototype = Object.create(Error.prototype);
|
||||
MongooseError.prototype.constructor = Error;
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = MongooseError;
|
||||
|
||||
/**
|
||||
* The default built-in validator error messages.
|
||||
*
|
||||
* @see Error.messages #error_messages_MongooseError-messages
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MongooseError.messages = require('./messages');
|
||||
|
||||
// backward compat
|
||||
MongooseError.Messages = MongooseError.messages;
|
||||
|
||||
/**
|
||||
* This error will be called 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
|
||||
*/
|
||||
|
||||
MongooseError.DocumentNotFoundError = require('./notFound');
|
||||
|
||||
/*!
|
||||
* Expose subclasses
|
||||
*/
|
||||
|
||||
MongooseError.CastError = require('./cast');
|
||||
MongooseError.ValidationError = require('./validation');
|
||||
MongooseError.ValidatorError = require('./validator');
|
||||
MongooseError.VersionError = require('./version');
|
||||
MongooseError.OverwriteModelError = require('./overwriteModel');
|
||||
MongooseError.MissingSchemaError = require('./missingSchema');
|
||||
MongooseError.DivergentArrayError = require('./divergentArray');
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
|
||||
/**
|
||||
* The default built-in validator error messages. These may be customized.
|
||||
*
|
||||
* // customize within each schema or globally like so
|
||||
* var 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
|
||||
*/
|
||||
|
||||
var 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.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}).';
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/*!
|
||||
* MissingSchema Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function MissingSchemaError(name) {
|
||||
var msg = 'Schema hasn\'t been registered for model "' + name + '".\n'
|
||||
+ 'Use mongoose.model(name, schema)';
|
||||
MongooseError.call(this, msg);
|
||||
this.name = 'MissingSchemaError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
MissingSchemaError.prototype = Object.create(MongooseError.prototype);
|
||||
MissingSchemaError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = MissingSchemaError;
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
var util = require('util');
|
||||
|
||||
/*!
|
||||
* OverwriteModel Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function DocumentNotFoundError(query) {
|
||||
var msg;
|
||||
var messages = MongooseError.messages;
|
||||
if (messages.DocumentNotFoundError != null) {
|
||||
msg = typeof messages.DocumentNotFoundError === 'function' ?
|
||||
messages.DocumentNotFoundError(query) :
|
||||
messages.DocumentNotFoundError;
|
||||
} else {
|
||||
msg = 'No document found for query "' + util.inspect(query) + '"';
|
||||
}
|
||||
|
||||
MongooseError.call(this, msg);
|
||||
|
||||
this.name = 'DocumentNotFoundError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
DocumentNotFoundError.prototype = Object.create(MongooseError.prototype);
|
||||
DocumentNotFoundError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = DocumentNotFoundError;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Strict mode error constructor
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} value
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ObjectExpectedError(path, val) {
|
||||
MongooseError.call(this, 'Tried to set nested object field `' + path +
|
||||
'` to primitive value `' + val + '` and strict mode is set to throw.');
|
||||
this.name = 'ObjectExpectedError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
ObjectExpectedError.prototype = Object.create(MongooseError.prototype);
|
||||
ObjectExpectedError.prototype.constructor = MongooseError;
|
||||
|
||||
module.exports = ObjectExpectedError;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ObjectParameterError(value, paramName, fnName) {
|
||||
MongooseError.call(this, 'Parameter "' + paramName + '" to ' + fnName +
|
||||
'() must be an object, got ' + value.toString());
|
||||
this.name = 'ObjectParameterError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
ObjectParameterError.prototype = Object.create(MongooseError.prototype);
|
||||
ObjectParameterError.prototype.constructor = MongooseError;
|
||||
|
||||
module.exports = ObjectParameterError;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/*!
|
||||
* OverwriteModel Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function OverwriteModelError(name) {
|
||||
MongooseError.call(this, 'Cannot overwrite `' + name + '` model once compiled.');
|
||||
this.name = 'OverwriteModelError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
OverwriteModelError.prototype = Object.create(MongooseError.prototype);
|
||||
OverwriteModelError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = OverwriteModelError;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Strict mode error constructor
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} value
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function StrictModeError(path, msg) {
|
||||
msg = msg || 'Field `' + path + '` is not in schema and strict ' +
|
||||
'mode is set to throw.';
|
||||
MongooseError.call(this, msg);
|
||||
this.name = 'StrictModeError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
StrictModeError.prototype = Object.create(MongooseError.prototype);
|
||||
StrictModeError.prototype.constructor = MongooseError;
|
||||
|
||||
module.exports = StrictModeError;
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*!
|
||||
* Module requirements
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
var utils = require('../utils');
|
||||
|
||||
/**
|
||||
* Document Validation Error
|
||||
*
|
||||
* @api private
|
||||
* @param {Document} instance
|
||||
* @inherits MongooseError
|
||||
*/
|
||||
|
||||
function ValidationError(instance) {
|
||||
this.errors = {};
|
||||
this._message = '';
|
||||
if (instance && instance.constructor.name === 'model') {
|
||||
this._message = instance.constructor.modelName + ' validation failed';
|
||||
MongooseError.call(this, this._message);
|
||||
} else {
|
||||
this._message = 'Validation failed';
|
||||
MongooseError.call(this, this._message);
|
||||
}
|
||||
this.name = 'ValidationError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
if (instance) {
|
||||
instance.errors = this.errors;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
ValidationError.prototype = Object.create(MongooseError.prototype);
|
||||
ValidationError.prototype.constructor = MongooseError;
|
||||
|
||||
/**
|
||||
* Console.log helper
|
||||
*/
|
||||
|
||||
ValidationError.prototype.toString = function() {
|
||||
return this.name + ': ' + _generateMessage(this);
|
||||
};
|
||||
|
||||
/*!
|
||||
* inspect helper
|
||||
*/
|
||||
|
||||
ValidationError.prototype.inspect = function() {
|
||||
return utils.assign(new Error(this.message), this);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Helper for JSON.stringify
|
||||
*/
|
||||
|
||||
ValidationError.prototype.toJSON = function() {
|
||||
return utils.assign({}, this, { message: this.message });
|
||||
};
|
||||
|
||||
/*!
|
||||
* add message
|
||||
*/
|
||||
|
||||
ValidationError.prototype.addError = function(path, error) {
|
||||
this.errors[path] = error;
|
||||
this.message = this._message + ': ' + _generateMessage(this);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _generateMessage(err) {
|
||||
var keys = Object.keys(err.errors || {});
|
||||
var len = keys.length;
|
||||
var msgs = [];
|
||||
var key;
|
||||
|
||||
for (var 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 = exports = ValidationError;
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Schema validator error
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ValidatorError(properties) {
|
||||
var msg = properties.message;
|
||||
if (!msg) {
|
||||
msg = MongooseError.messages.general.default;
|
||||
}
|
||||
|
||||
var message = this.formatMessage(msg, properties);
|
||||
MongooseError.call(this, message);
|
||||
this.name = 'ValidatorError';
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this);
|
||||
} else {
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
this.properties = properties;
|
||||
this.kind = properties.type;
|
||||
this.path = properties.path;
|
||||
this.value = properties.value;
|
||||
this.reason = properties.reason;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError
|
||||
*/
|
||||
|
||||
ValidatorError.prototype = Object.create(MongooseError.prototype);
|
||||
ValidatorError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* 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
|
||||
});
|
||||
|
||||
/*!
|
||||
* Formats error messages
|
||||
*/
|
||||
|
||||
ValidatorError.prototype.formatMessage = function(msg, properties) {
|
||||
var propertyNames = Object.keys(properties);
|
||||
for (var i = 0; i < propertyNames.length; ++i) {
|
||||
var propertyName = propertyNames[i];
|
||||
if (propertyName === 'message') {
|
||||
continue;
|
||||
}
|
||||
msg = msg.replace('{' + propertyName.toUpperCase() + '}', properties[propertyName]);
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
/*!
|
||||
* toString helper
|
||||
*/
|
||||
|
||||
ValidatorError.prototype.toString = function() {
|
||||
return this.message;
|
||||
};
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = ValidatorError;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseError = require('./');
|
||||
|
||||
/**
|
||||
* Version Error constructor.
|
||||
*
|
||||
* @inherits MongooseError
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function VersionError(doc, currentVersion, modifiedPaths) {
|
||||
var modifiedPathsStr = modifiedPaths.join(', ');
|
||||
MongooseError.call(this, 'No matching document found for id "' + doc._id +
|
||||
'" version ' + currentVersion + ' modifiedPaths "' + modifiedPathsStr + '"');
|
||||
this.name = 'VersionError';
|
||||
this.version = currentVersion;
|
||||
this.modifiedPaths = modifiedPaths;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseError.
|
||||
*/
|
||||
|
||||
VersionError.prototype = Object.create(MongooseError.prototype);
|
||||
VersionError.prototype.constructor = MongooseError;
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
module.exports = VersionError;
|
||||
+863
@@ -0,0 +1,863 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Schema = require('./schema');
|
||||
var SchemaType = require('./schematype');
|
||||
var VirtualType = require('./virtualtype');
|
||||
var STATES = require('./connectionstate');
|
||||
var Types = require('./types');
|
||||
var Query = require('./query');
|
||||
var Model = require('./model');
|
||||
var Document = require('./document');
|
||||
var utils = require('./utils');
|
||||
var format = utils.toCollectionName;
|
||||
var pkg = require('../package.json');
|
||||
|
||||
var querystring = require('querystring');
|
||||
var saveSubdocs = require('./plugins/saveSubdocs');
|
||||
var validateBeforeSave = require('./plugins/validateBeforeSave');
|
||||
|
||||
var Aggregate = require('./aggregate');
|
||||
var PromiseProvider = require('./promise_provider');
|
||||
var shardingPlugin = require('./plugins/sharding');
|
||||
|
||||
/**
|
||||
* Mongoose constructor.
|
||||
*
|
||||
* The exports object of the `mongoose` module is an instance of this class.
|
||||
* Most apps will only use this one instance.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Mongoose() {
|
||||
this.connections = [];
|
||||
this.models = {};
|
||||
this.modelSchemas = {};
|
||||
// default global options
|
||||
this.options = {
|
||||
pluralization: true
|
||||
};
|
||||
var conn = this.createConnection(); // default connection
|
||||
conn.models = this.models;
|
||||
|
||||
Object.defineProperty(this, 'plugins', {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
value: [
|
||||
[saveSubdocs, { deduplicate: true }],
|
||||
[validateBeforeSave, { deduplicate: true }],
|
||||
[shardingPlugin, { deduplicate: true }]
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose connection states for user-land
|
||||
*
|
||||
*/
|
||||
Mongoose.prototype.STATES = STATES;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* mongoose.set('debug', function(collectionName, methodName, arg1, arg2...) {}); // use custom function to log collection methods + arguments
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {String|Function|Boolean} value
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.set = function(key, value) {
|
||||
if (arguments.length === 1) {
|
||||
return this.options[key];
|
||||
}
|
||||
|
||||
this.options[key] = value;
|
||||
return this;
|
||||
};
|
||||
Mongoose.prototype.set.$hasSideEffects = true;
|
||||
|
||||
/**
|
||||
* Gets mongoose options
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* mongoose.get('test') // returns the 'test' value
|
||||
*
|
||||
* @param {String} key
|
||||
* @method get
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.get = Mongoose.prototype.set;
|
||||
|
||||
/*!
|
||||
* ReplSet connection string check.
|
||||
*/
|
||||
|
||||
var rgxReplSet = /^.+,.+$/;
|
||||
|
||||
/**
|
||||
* Checks if ?replicaSet query parameter is specified in URI
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* checkReplicaSetInUri('localhost:27000?replicaSet=rs0'); // true
|
||||
*
|
||||
* @param {String} uri
|
||||
* @return {boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var checkReplicaSetInUri = function(uri) {
|
||||
if (!uri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var queryStringStart = uri.indexOf('?');
|
||||
var isReplicaSet = false;
|
||||
if (queryStringStart !== -1) {
|
||||
try {
|
||||
var obj = querystring.parse(uri.substr(queryStringStart + 1));
|
||||
if (obj && obj.replicaSet) {
|
||||
isReplicaSet = true;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return isReplicaSet;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a Connection instance.
|
||||
*
|
||||
* Each `connection` instance maps to a single database. This method is helpful when mangaging multiple db connections.
|
||||
*
|
||||
* If arguments are passed, they are proxied to either [Connection#open](#connection_Connection-open) or [Connection#openSet](#connection_Connection-openSet) appropriately. This means we can pass `db`, `server`, and `replset` options to the driver. _Note that the `safe` option specified in your schema will overwrite the `safe` db option specified here unless you set your schemas `safe` option to `undefined`. See [this](/docs/guide.html#safe) for more information._
|
||||
*
|
||||
* _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
|
||||
* var 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
|
||||
* var opts = { replset: { strategy: 'ping', rs_name: 'testSet' }}
|
||||
* db = mongoose.createConnection('mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/database', opts);
|
||||
*
|
||||
* // with [host, database_name[, port] signature
|
||||
* db = mongoose.createConnection('localhost', 'database', port)
|
||||
*
|
||||
* // and options
|
||||
* var opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' }
|
||||
* db = mongoose.createConnection('localhost', 'database', port, opts)
|
||||
*
|
||||
* // initialize now, connect later
|
||||
* db = mongoose.createConnection();
|
||||
* db.open('localhost', 'database', port, [opts]);
|
||||
*
|
||||
* @param {String} [uri] a mongodb:// URI
|
||||
* @param {Object} [options] options to pass to the driver
|
||||
* @param {Object} [options.config] mongoose-specific options
|
||||
* @param {Boolean} [options.config.autoIndex] set to false to disable automatic index creation for all models associated with this connection.
|
||||
* @param {Boolean} [options.useMongoClient] false by default, set to true to use new mongoose connection logic
|
||||
* @see Connection#open #connection_Connection-open
|
||||
* @see Connection#openSet #connection_Connection-openSet
|
||||
* @return {Connection|Promise} the created Connection object, or promise that resolves to the connection if `useMongoClient` option specified.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.createConnection = function(uri, options, callback) {
|
||||
var conn = new Connection(this);
|
||||
this.connections.push(conn);
|
||||
|
||||
var rsOption = options && (options.replset || options.replSet);
|
||||
|
||||
if (options && options.useMongoClient) {
|
||||
return conn.openUri(uri, options, callback);
|
||||
}
|
||||
|
||||
if (arguments.length) {
|
||||
if (rgxReplSet.test(arguments[0]) || checkReplicaSetInUri(arguments[0])) {
|
||||
conn._openSetWithoutPromise.apply(conn, arguments);
|
||||
} else if (rsOption &&
|
||||
(rsOption.replicaSet || rsOption.rs_name)) {
|
||||
conn._openSetWithoutPromise.apply(conn, arguments);
|
||||
} else {
|
||||
conn._openWithoutPromise.apply(conn, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
return conn;
|
||||
};
|
||||
Mongoose.prototype.createConnection.$hasSideEffects = true;
|
||||
|
||||
/**
|
||||
* Opens the default mongoose connection.
|
||||
*
|
||||
* If arguments are passed, they are proxied to either
|
||||
* [Connection#open](#connection_Connection-open) or
|
||||
* [Connection#openSet](#connection_Connection-openSet) appropriately.
|
||||
*
|
||||
* _Options passed take precedence over options included in connection strings._
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* mongoose.connect('mongodb://user:pass@localhost:port/database');
|
||||
*
|
||||
* // replica sets
|
||||
* var uri = 'mongodb://user:pass@localhost:port,anotherhost:port,yetanother:port/mydatabase';
|
||||
* mongoose.connect(uri);
|
||||
*
|
||||
* // with options
|
||||
* mongoose.connect(uri, options);
|
||||
*
|
||||
* // connecting to multiple mongos
|
||||
* var uri = 'mongodb://hostA:27501,hostB:27501';
|
||||
* var opts = { mongos: true };
|
||||
* mongoose.connect(uri, opts);
|
||||
*
|
||||
* // optional callback that gets fired when initial connection completed
|
||||
* var 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]
|
||||
* @param {Boolean} [options.useMongoClient] false by default, set to true to use new mongoose connection logic
|
||||
* @param {Function} [callback]
|
||||
* @see Mongoose#createConnection #index_Mongoose-createConnection
|
||||
* @api public
|
||||
* @return {MongooseThenable} pseudo-promise wrapper around this
|
||||
*/
|
||||
|
||||
Mongoose.prototype.connect = function() {
|
||||
var conn = this.connection;
|
||||
if ((arguments.length === 2 || arguments.length === 3) &&
|
||||
typeof arguments[0] === 'string' &&
|
||||
typeof arguments[1] === 'object' &&
|
||||
arguments[1].useMongoClient === true) {
|
||||
return conn.openUri(arguments[0], arguments[1], arguments[2]);
|
||||
}
|
||||
if (rgxReplSet.test(arguments[0]) || checkReplicaSetInUri(arguments[0])) {
|
||||
return new MongooseThenable(this, conn.openSet.apply(conn, arguments));
|
||||
}
|
||||
|
||||
return new MongooseThenable(this, conn.open.apply(conn, arguments));
|
||||
};
|
||||
Mongoose.prototype.connect.$hasSideEffects = true;
|
||||
|
||||
/**
|
||||
* Disconnects all connections.
|
||||
*
|
||||
* @param {Function} [fn] called after all connection close.
|
||||
* @return {MongooseThenable} pseudo-promise wrapper around this
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.disconnect = function(fn) {
|
||||
var _this = this;
|
||||
|
||||
var Promise = PromiseProvider.get();
|
||||
return new MongooseThenable(this, new Promise.ES6(function(resolve, reject) {
|
||||
var remaining = _this.connections.length;
|
||||
if (remaining <= 0) {
|
||||
fn && fn();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
_this.connections.forEach(function(conn) {
|
||||
conn.close(function(error) {
|
||||
if (error) {
|
||||
fn && fn(error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
if (!--remaining) {
|
||||
fn && fn();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
};
|
||||
Mongoose.prototype.disconnect.$hasSideEffects = true;
|
||||
|
||||
/**
|
||||
* Defines a model or retrieves it.
|
||||
*
|
||||
* Models defined on the `mongoose` instance are available to all connection created by the same `mongoose` instance.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
*
|
||||
* // define an Actor model with this mongoose instance
|
||||
* mongoose.model('Actor', new Schema({ name: String }));
|
||||
*
|
||||
* // create a new connection
|
||||
* var conn = mongoose.createConnection(..);
|
||||
*
|
||||
* // retrieve the Actor model
|
||||
* var Actor = conn.model('Actor');
|
||||
*
|
||||
* _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:
|
||||
*
|
||||
* var schema = new Schema({ name: String }, { collection: 'actor' });
|
||||
*
|
||||
* // or
|
||||
*
|
||||
* schema.set('collection', 'actor');
|
||||
*
|
||||
* // or
|
||||
*
|
||||
* var collectionName = 'actor'
|
||||
* var M = mongoose.model('Actor', schema, collectionName)
|
||||
*
|
||||
* @param {String|Function} name model name or class extending Model
|
||||
* @param {Schema} [schema]
|
||||
* @param {String} [collection] name (optional, inferred from model name)
|
||||
* @param {Boolean} [skipInit] whether to skip initialization (defaults to false)
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.model = function(name, schema, collection, skipInit) {
|
||||
var 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.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 (typeof collection === 'boolean') {
|
||||
skipInit = collection;
|
||||
collection = null;
|
||||
}
|
||||
|
||||
// handle internal options from connection.model()
|
||||
var options;
|
||||
if (skipInit && utils.isObject(skipInit)) {
|
||||
options = skipInit;
|
||||
skipInit = true;
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// look up schema for the collection.
|
||||
if (!this.modelSchemas[name]) {
|
||||
if (schema) {
|
||||
// cache it so we only apply plugins once
|
||||
this.modelSchemas[name] = schema;
|
||||
} else {
|
||||
throw new mongoose.Error.MissingSchemaError(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (schema) {
|
||||
this._applyPlugins(schema);
|
||||
}
|
||||
|
||||
var sub;
|
||||
|
||||
// connection.model() may be passing a different schema for
|
||||
// an existing model name. in this case don't read from cache.
|
||||
if (this.models[name] && options.cache !== false) {
|
||||
if (schema && schema.instanceOfSchema && schema !== this.models[name].schema) {
|
||||
throw new mongoose.Error.OverwriteModelError(name);
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
// subclass current model with alternate collection
|
||||
model = this.models[name];
|
||||
schema = model.prototype.schema;
|
||||
sub = model.__subclass(this.connection, schema, collection);
|
||||
// do not cache the sub model
|
||||
return sub;
|
||||
}
|
||||
|
||||
return this.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 = this.options.pluralization;
|
||||
|
||||
|
||||
if (!collection) {
|
||||
collection = schema.get('collection') || format(name, schema.options);
|
||||
}
|
||||
|
||||
var connection = options.connection || this.connection;
|
||||
model = this.Model.compile(model || name, schema, collection, connection, this);
|
||||
|
||||
if (!skipInit) {
|
||||
model.init();
|
||||
}
|
||||
|
||||
if (options.cache === false) {
|
||||
return model;
|
||||
}
|
||||
|
||||
this.models[name] = model;
|
||||
return this.models[name];
|
||||
};
|
||||
Mongoose.prototype.model.$hasSideEffects = true;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
var names = Object.keys(this.models);
|
||||
return names;
|
||||
};
|
||||
Mongoose.prototype.modelNames.$hasSideEffects = true;
|
||||
|
||||
/**
|
||||
* Applies global plugins to `schema`.
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Mongoose.prototype._applyPlugins = function(schema) {
|
||||
if (schema.$globalPluginsApplied) {
|
||||
return;
|
||||
}
|
||||
var i;
|
||||
var len;
|
||||
for (i = 0, len = this.plugins.length; i < len; ++i) {
|
||||
schema.plugin(this.plugins[i][0], this.plugins[i][1]);
|
||||
}
|
||||
schema.$globalPluginsApplied = true;
|
||||
for (i = 0, len = schema.childSchemas.length; i < len; ++i) {
|
||||
this._applyPlugins(schema.childSchemas[i].schema);
|
||||
}
|
||||
};
|
||||
Mongoose.prototype._applyPlugins.$hasSideEffects = true;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.plugins.push([fn, opts]);
|
||||
return this;
|
||||
};
|
||||
Mongoose.prototype.plugin.$hasSideEffects = true;
|
||||
|
||||
/**
|
||||
* The default connection of the mongoose module.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var 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).
|
||||
*
|
||||
* @property connection
|
||||
* @return {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;
|
||||
}
|
||||
});
|
||||
|
||||
/*!
|
||||
* Driver depentend APIs
|
||||
*/
|
||||
|
||||
var driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native';
|
||||
|
||||
/*!
|
||||
* Connection
|
||||
*/
|
||||
|
||||
var Connection = require(driver + '/connection');
|
||||
|
||||
/*!
|
||||
* Collection
|
||||
*/
|
||||
|
||||
var 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
|
||||
*
|
||||
* @method Connection
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.Connection = Connection;
|
||||
|
||||
/**
|
||||
* The Mongoose version
|
||||
*
|
||||
* @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:
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
* var mongoose2 = new mongoose.Mongoose();
|
||||
*
|
||||
* @method Mongoose
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.Mongoose = Mongoose;
|
||||
|
||||
/**
|
||||
* The Mongoose [Schema](#schema_Schema) constructor
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
* var Schema = mongoose.Schema;
|
||||
* var 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:
|
||||
*
|
||||
* var mongoose = require('mongoose');
|
||||
* var 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.
|
||||
*
|
||||
* var ObjectId = mongoose.Types.ObjectId;
|
||||
* var 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.
|
||||
*
|
||||
* @method Promise
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Object.defineProperty(Mongoose.prototype, 'Promise', {
|
||||
get: function() {
|
||||
return PromiseProvider.get();
|
||||
},
|
||||
set: function(lib) {
|
||||
PromiseProvider.set(lib);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the current ES6-style promise constructor. In Mongoose 4.x,
|
||||
* equivalent to `mongoose.Promise.ES6`, but will change once we get rid
|
||||
* of the `.ES6` bit.
|
||||
*
|
||||
* @method Promise
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.getPromiseConstructor = function() {
|
||||
return PromiseProvider.get().ES6;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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](#document-js) constructor.
|
||||
*
|
||||
* @method Document
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.Document = Document;
|
||||
|
||||
/**
|
||||
* The Mongoose DocumentProvider constructor.
|
||||
*
|
||||
* @method DocumentProvider
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.DocumentProvider = require('./document_provider');
|
||||
|
||||
/**
|
||||
* The [MongooseError](#error_MongooseError) constructor.
|
||||
*
|
||||
* @method Error
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Mongoose.prototype.Error = require('./error');
|
||||
|
||||
/**
|
||||
* 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 [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');
|
||||
|
||||
/**
|
||||
* Wraps the given Mongoose instance into a thenable (pseudo-promise). This
|
||||
* is so `connect()` and `disconnect()` can return a thenable while maintaining
|
||||
* backwards compatibility.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function MongooseThenable(mongoose, promise) {
|
||||
var _this = this;
|
||||
for (var key in mongoose) {
|
||||
if (typeof mongoose[key] === 'function' && mongoose[key].$hasSideEffects) {
|
||||
(function(key) {
|
||||
_this[key] = function() {
|
||||
return mongoose[key].apply(mongoose, arguments);
|
||||
};
|
||||
})(key);
|
||||
} else if (['connection', 'connections'].indexOf(key) !== -1) {
|
||||
_this[key] = mongoose[key];
|
||||
}
|
||||
}
|
||||
this.$opPromise = promise;
|
||||
}
|
||||
|
||||
MongooseThenable.prototype = new Mongoose;
|
||||
|
||||
/**
|
||||
* Ability to use mongoose object as a pseudo-promise so `.connect().then()`
|
||||
* and `.disconnect().then()` are viable.
|
||||
*
|
||||
* @param {Function} onFulfilled
|
||||
* @param {Function} onRejected
|
||||
* @return {Promise}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
MongooseThenable.prototype.then = function(onFulfilled, onRejected) {
|
||||
var Promise = PromiseProvider.get();
|
||||
if (!this.$opPromise) {
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
reject(new Error('Can only call `.then()` if connect() or disconnect() ' +
|
||||
'has been called'));
|
||||
}).then(onFulfilled, onRejected);
|
||||
}
|
||||
this.$opPromise.$hasHandler = true;
|
||||
return this.$opPromise.then(onFulfilled, onRejected);
|
||||
};
|
||||
|
||||
/**
|
||||
* Ability to use mongoose object as a pseudo-promise so `.connect().then()`
|
||||
* and `.disconnect().then()` are viable.
|
||||
*
|
||||
* @param {Function} onFulfilled
|
||||
* @param {Function} onRejected
|
||||
* @return {Promise}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
MongooseThenable.prototype.catch = function(onRejected) {
|
||||
return this.then(null, onRejected);
|
||||
};
|
||||
|
||||
/*!
|
||||
* The exports object is an instance of Mongoose.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var mongoose = module.exports = exports = new Mongoose;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*!
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var StateMachine = require('./statemachine');
|
||||
var 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.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 = {};
|
||||
|
||||
// embedded docs
|
||||
this.ownerDocument = undefined;
|
||||
this.fullPath = undefined;
|
||||
}
|
||||
+4104
File diff suppressed because it is too large
Load Diff
+26
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function(schema) {
|
||||
// ensure the documents receive an id getter unless disabled
|
||||
var autoIdGetter = !schema.paths['id'] &&
|
||||
(!schema.options.noVirtualId && schema.options.id);
|
||||
if (autoIdGetter) {
|
||||
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;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
var each = require('async/each');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function(schema) {
|
||||
schema.callQueue.unshift(['pre', ['save', function(next) {
|
||||
if (this.ownerDocument) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
var _this = this;
|
||||
var subdocs = this.$__getAllSubdocs();
|
||||
|
||||
if (!subdocs.length) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
each(subdocs, function(subdoc, cb) {
|
||||
subdoc.save(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();
|
||||
});
|
||||
}]]);
|
||||
};
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
var 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.post('save', function() {
|
||||
storeShard.call(this);
|
||||
});
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function applyWhere() {
|
||||
var paths;
|
||||
var len;
|
||||
|
||||
if (this.$__.shardval) {
|
||||
paths = Object.keys(this.$__.shardval);
|
||||
len = paths.length;
|
||||
|
||||
this.$where = this.$where || {};
|
||||
for (var i = 0; i < len; ++i) {
|
||||
this.$where[paths[i]] = this.$__.shardval[paths[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports.storeShard = storeShard;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function storeShard() {
|
||||
// backwards compat
|
||||
var key = this.schema.options.shardKey || this.schema.options.shardkey;
|
||||
if (!(key && utils.getFunctionName(key.constructor) === 'Object')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var orig = this.$__.shardval = {},
|
||||
paths = Object.keys(key),
|
||||
len = paths.length,
|
||||
val;
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
val = this.getValue(paths[i]);
|
||||
if (utils.isMongooseObject(val)) {
|
||||
orig[paths[i]] = val.toObject({depopulate: true, _isNested: true});
|
||||
} else if (val !== null && val !== undefined && val.valueOf &&
|
||||
// Explicitly don't take value of dates
|
||||
(!val.constructor || utils.getFunctionName(val.constructor) !== 'Date')) {
|
||||
orig[paths[i]] = val.valueOf();
|
||||
} else {
|
||||
orig[paths[i]] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function(schema) {
|
||||
schema.callQueue.unshift(['pre', ['save', function(next, options) {
|
||||
var _this = this;
|
||||
// Nested docs have their own presave
|
||||
if (this.ownerDocument) {
|
||||
return next();
|
||||
}
|
||||
|
||||
var hasValidateBeforeSaveOption = options &&
|
||||
(typeof options === 'object') &&
|
||||
('validateBeforeSave' in options);
|
||||
|
||||
var shouldValidate;
|
||||
if (hasValidateBeforeSaveOption) {
|
||||
shouldValidate = !!options.validateBeforeSave;
|
||||
} else {
|
||||
shouldValidate = this.schema.options.validateBeforeSave;
|
||||
}
|
||||
|
||||
// Validate
|
||||
if (shouldValidate) {
|
||||
// HACK: use $__original_validate to avoid promises so bluebird doesn't
|
||||
// complain
|
||||
if (this.$__original_validate) {
|
||||
this.$__original_validate({__noPromise: true}, function(error) {
|
||||
return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.validate({__noPromise: true}, function(error) {
|
||||
return _this.schema.s.hooks.execPost('save:error', _this, [ _this], { error: error }, function(error) {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}]]);
|
||||
};
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
/*!
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
var MPromise = require('mpromise');
|
||||
var util = require('util');
|
||||
|
||||
/**
|
||||
* Promise constructor.
|
||||
*
|
||||
* Promises are returned from executed queries. Example:
|
||||
*
|
||||
* var query = Candy.find({ bar: true });
|
||||
* var promise = query.exec();
|
||||
*
|
||||
* DEPRECATED. Mongoose 5.0 will use native promises by default (or bluebird,
|
||||
* if native promises are not present) but still
|
||||
* support plugging in your own ES6-compatible promises library. Mongoose 5.0
|
||||
* will **not** support mpromise.
|
||||
*
|
||||
* @param {Function} fn a function which will be called when the promise is resolved that accepts `fn(err, ...){}` as signature
|
||||
* @inherits mpromise https://github.com/aheckmann/mpromise
|
||||
* @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
|
||||
* @event `err`: Emits when the promise is rejected
|
||||
* @event `complete`: Emits when the promise is fulfilled
|
||||
* @api public
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
function Promise(fn) {
|
||||
MPromise.call(this, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* ES6-style promise constructor wrapper around mpromise.
|
||||
*
|
||||
* @param {Function} resolver
|
||||
* @return {Promise} new promise
|
||||
* @api public
|
||||
*/
|
||||
Promise.ES6 = function(resolver) {
|
||||
var promise = new Promise();
|
||||
|
||||
// No try/catch for backwards compatibility
|
||||
resolver(
|
||||
function() {
|
||||
promise.complete.apply(promise, arguments);
|
||||
},
|
||||
function(e) {
|
||||
promise.error(e);
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Inherit from mpromise
|
||||
*/
|
||||
|
||||
Promise.prototype = Object.create(MPromise.prototype, {
|
||||
constructor: {
|
||||
value: Promise,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
Promise.prototype.then = util.deprecate(Promise.prototype.then,
|
||||
'Mongoose: mpromise (mongoose\'s default promise library) is deprecated, ' +
|
||||
'plug in your own promise library instead: ' +
|
||||
'http://mongoosejs.com/docs/promises.html');
|
||||
|
||||
/**
|
||||
* ES6-style `.catch()` shorthand
|
||||
*
|
||||
* @method catch
|
||||
* @memberOf Promise
|
||||
* @param {Function} onReject
|
||||
* @return {Promise}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Promise.prototype.catch = function(onReject) {
|
||||
return this.then(null, onReject);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Override event names for backward compatibility.
|
||||
*/
|
||||
|
||||
Promise.SUCCESS = 'complete';
|
||||
Promise.FAILURE = 'err';
|
||||
|
||||
/**
|
||||
* Adds `listener` to the `event`.
|
||||
*
|
||||
* If `event` is either the success or failure event and the event has already been emitted, the`listener` is called immediately and passed the results of the original emitted event.
|
||||
*
|
||||
* @see mpromise#on https://github.com/aheckmann/mpromise#on
|
||||
* @method on
|
||||
* @memberOf Promise
|
||||
* @param {String} event
|
||||
* @param {Function} listener
|
||||
* @return {Promise} this
|
||||
* @api public
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rejects this promise with `reason`.
|
||||
*
|
||||
* If the promise has already been fulfilled or rejected, not action is taken.
|
||||
*
|
||||
* @see mpromise#reject https://github.com/aheckmann/mpromise#reject
|
||||
* @method reject
|
||||
* @memberOf Promise
|
||||
* @param {Object|String|Error} reason
|
||||
* @return {Promise} this
|
||||
* @api public
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rejects this promise with `err`.
|
||||
*
|
||||
* If the promise has already been fulfilled or rejected, not action is taken.
|
||||
*
|
||||
* Differs from [#reject](#promise_Promise-reject) by first casting `err` to an `Error` if it is not `instanceof Error`.
|
||||
*
|
||||
* @api public
|
||||
* @param {Error|String} err
|
||||
* @return {Promise} this
|
||||
*/
|
||||
|
||||
Promise.prototype.error = function(err) {
|
||||
if (!(err instanceof Error)) {
|
||||
if (err instanceof Object) {
|
||||
err = util.inspect(err);
|
||||
}
|
||||
err = new Error(err);
|
||||
}
|
||||
return this.reject(err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves this promise to a rejected state if `err` is passed or a fulfilled state if no `err` is passed.
|
||||
*
|
||||
* If the promise has already been fulfilled or rejected, not action is taken.
|
||||
*
|
||||
* `err` will be cast to an Error if not already instanceof Error.
|
||||
*
|
||||
* _NOTE: overrides [mpromise#resolve](https://github.com/aheckmann/mpromise#resolve) to provide error casting._
|
||||
*
|
||||
* @param {Error} [err] error or null
|
||||
* @param {Object} [val] value to fulfill the promise with
|
||||
* @api public
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
Promise.prototype.resolve = function(err) {
|
||||
if (err) return this.error(err);
|
||||
return this.fulfill.apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a single function as a listener to both err and complete.
|
||||
*
|
||||
* It will be executed with traditional node.js argument position when the promise is resolved.
|
||||
*
|
||||
* promise.addBack(function (err, args...) {
|
||||
* if (err) return handleError(err);
|
||||
* console.log('success');
|
||||
* })
|
||||
*
|
||||
* Alias of [mpromise#onResolve](https://github.com/aheckmann/mpromise#onresolve).
|
||||
*
|
||||
* _Deprecated. Use `onResolve` instead._
|
||||
*
|
||||
* @method addBack
|
||||
* @param {Function} listener
|
||||
* @return {Promise} this
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
Promise.prototype.addBack = Promise.prototype.onResolve;
|
||||
|
||||
/**
|
||||
* Fulfills this promise with passed arguments.
|
||||
*
|
||||
* @method fulfill
|
||||
* @receiver Promise
|
||||
* @see https://github.com/aheckmann/mpromise#fulfill
|
||||
* @param {any} args
|
||||
* @api public
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fulfills this promise with passed arguments.
|
||||
*
|
||||
* Alias of [mpromise#fulfill](https://github.com/aheckmann/mpromise#fulfill).
|
||||
*
|
||||
* _Deprecated. Use `fulfill` instead._
|
||||
*
|
||||
* @method complete
|
||||
* @receiver Promise
|
||||
* @param {any} args
|
||||
* @api public
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
Promise.prototype.complete = MPromise.prototype.fulfill;
|
||||
|
||||
/**
|
||||
* Adds a listener to the `complete` (success) event.
|
||||
*
|
||||
* Alias of [mpromise#onFulfill](https://github.com/aheckmann/mpromise#onfulfill).
|
||||
*
|
||||
* _Deprecated. Use `onFulfill` instead._
|
||||
*
|
||||
* @method addCallback
|
||||
* @param {Function} listener
|
||||
* @return {Promise} this
|
||||
* @api public
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
Promise.prototype.addCallback = Promise.prototype.onFulfill;
|
||||
|
||||
/**
|
||||
* Adds a listener to the `err` (rejected) event.
|
||||
*
|
||||
* Alias of [mpromise#onReject](https://github.com/aheckmann/mpromise#onreject).
|
||||
*
|
||||
* _Deprecated. Use `onReject` instead._
|
||||
*
|
||||
* @method addErrback
|
||||
* @param {Function} listener
|
||||
* @return {Promise} this
|
||||
* @api public
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
Promise.prototype.addErrback = Promise.prototype.onReject;
|
||||
|
||||
/**
|
||||
* Creates a new promise and returns it. If `onFulfill` or `onReject` are passed, they are added as SUCCESS/ERROR callbacks to this promise after the nextTick.
|
||||
*
|
||||
* Conforms to [promises/A+](https://github.com/promises-aplus/promises-spec) specification.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var promise = Meetups.find({ tags: 'javascript' }).select('_id').exec();
|
||||
* promise.then(function (meetups) {
|
||||
* var ids = meetups.map(function (m) {
|
||||
* return m._id;
|
||||
* });
|
||||
* return People.find({ meetups: { $in: ids } }).exec();
|
||||
* }).then(function (people) {
|
||||
* if (people.length < 10000) {
|
||||
* throw new Error('Too few people!!!');
|
||||
* } else {
|
||||
* throw new Error('Still need more people!!!');
|
||||
* }
|
||||
* }).then(null, function (err) {
|
||||
* assert.ok(err instanceof Error);
|
||||
* });
|
||||
*
|
||||
* @see promises-A+ https://github.com/promises-aplus/promises-spec
|
||||
* @see mpromise#then https://github.com/aheckmann/mpromise#then
|
||||
* @method then
|
||||
* @memberOf Promise
|
||||
* @param {Function} onFulFill
|
||||
* @param {Function} onReject
|
||||
* @return {Promise} newPromise
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signifies that this promise was the last in a chain of `then()s`: if a handler passed to the call to `then` which produced this promise throws, the exception will go uncaught.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var p = new Promise;
|
||||
* p.then(function(){ throw new Error('shucks') });
|
||||
* setTimeout(function () {
|
||||
* p.fulfill();
|
||||
* // error was caught and swallowed by the promise returned from
|
||||
* // p.then(). we either have to always register handlers on
|
||||
* // the returned promises or we can do the following...
|
||||
* }, 10);
|
||||
*
|
||||
* // this time we use .end() which prevents catching thrown errors
|
||||
* var p = new Promise;
|
||||
* var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <--
|
||||
* setTimeout(function () {
|
||||
* p.fulfill(); // throws "shucks"
|
||||
* }, 10);
|
||||
*
|
||||
* @api public
|
||||
* @see mpromise#end https://github.com/aheckmann/mpromise#end
|
||||
* @method end
|
||||
* @memberOf Promise
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
/*!
|
||||
* expose
|
||||
*/
|
||||
|
||||
module.exports = Promise;
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MPromise = require('./promise');
|
||||
|
||||
/**
|
||||
* Helper for multiplexing promise implementations
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var Promise = {
|
||||
_promise: MPromise
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current promise constructor
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Promise.get = function() {
|
||||
return Promise._promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the current promise constructor
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Promise.set = function(lib) {
|
||||
if (lib === MPromise) {
|
||||
return Promise.reset();
|
||||
}
|
||||
Promise._promise = require('./ES6Promise');
|
||||
Promise._promise.use(lib);
|
||||
require('mquery').Promise = Promise._promise.ES6;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets to using mpromise
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Promise.reset = function() {
|
||||
Promise._promise = MPromise;
|
||||
};
|
||||
|
||||
module.exports = Promise;
|
||||
+3928
File diff suppressed because it is too large
Load Diff
+219
@@ -0,0 +1,219 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
var get = require('lodash.get');
|
||||
var isDefiningProjection = require('./services/projection/isDefiningProjection');
|
||||
var utils = require('./utils');
|
||||
|
||||
/*!
|
||||
* Prepare a set of path options for query population.
|
||||
*
|
||||
* @param {Query} query
|
||||
* @param {Object} options
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
exports.preparePopulationOptions = function preparePopulationOptions(query, options) {
|
||||
var pop = utils.object.vals(query.options.populate);
|
||||
|
||||
// lean options should trickle through all queries
|
||||
if (options.lean) {
|
||||
pop.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) {
|
||||
var pop = utils.object.vals(query._mongooseOptions.populate);
|
||||
|
||||
// lean options should trickle through all queries
|
||||
if (options.lean) {
|
||||
pop.forEach(makeLean(options.lean));
|
||||
}
|
||||
|
||||
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 {Model}
|
||||
*/
|
||||
exports.createModel = function createModel(model, doc, fields, userProvidedFields) {
|
||||
var discriminatorMapping = model.schema
|
||||
? model.schema.discriminatorMapping
|
||||
: null;
|
||||
|
||||
var key = discriminatorMapping && discriminatorMapping.isRoot
|
||||
? discriminatorMapping.key
|
||||
: null;
|
||||
|
||||
if (key && doc[key] && model.discriminators && model.discriminators[doc[key]]) {
|
||||
var discriminator = model.discriminators[doc[key]];
|
||||
var _fields = utils.clone(userProvidedFields);
|
||||
exports.applyPaths(_fields, discriminator.schema);
|
||||
return new model.discriminators[doc[key]](undefined, _fields, true);
|
||||
}
|
||||
|
||||
return new model(undefined, fields, true);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
exports.applyPaths = function applyPaths(fields, schema) {
|
||||
// determine if query is selecting or excluding fields
|
||||
var exclude;
|
||||
var keys;
|
||||
var ki;
|
||||
var field;
|
||||
|
||||
if (fields) {
|
||||
keys = Object.keys(fields);
|
||||
ki = keys.length;
|
||||
|
||||
while (ki--) {
|
||||
if (keys[ki][0] === '+') {
|
||||
continue;
|
||||
}
|
||||
field = fields[keys[ki]];
|
||||
// Skip `$meta` and `$slice`
|
||||
if (!isDefiningProjection(field)) {
|
||||
continue;
|
||||
}
|
||||
exclude = field === 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if selecting, apply default schematype select:true fields
|
||||
// if excluding, apply schematype select:false fields
|
||||
|
||||
var selected = [];
|
||||
var excluded = [];
|
||||
var stack = [];
|
||||
|
||||
var analyzePath = function(path, type) {
|
||||
if (typeof type.selected !== 'boolean') return;
|
||||
|
||||
var plusPath = '+' + path;
|
||||
if (fields && plusPath in fields) {
|
||||
// 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
|
||||
var pieces = path.split('.');
|
||||
var root = pieces[0];
|
||||
if (~excluded.indexOf(root)) {
|
||||
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)) {
|
||||
var cur = '';
|
||||
for (var i = 0; i < pieces.length; ++i) {
|
||||
cur += (cur.length === 0 ? '' : '.') + pieces[i];
|
||||
var projection = get(fields, cur, false);
|
||||
if (projection && typeof projection !== 'object') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(type.selected ? selected : excluded).push(path);
|
||||
};
|
||||
|
||||
var analyzeSchema = function(schema, prefix) {
|
||||
prefix || (prefix = '');
|
||||
|
||||
// avoid recursion
|
||||
if (stack.indexOf(schema) !== -1) {
|
||||
return;
|
||||
}
|
||||
stack.push(schema);
|
||||
|
||||
schema.eachPath(function(path, type) {
|
||||
if (prefix) path = prefix + '.' + path;
|
||||
|
||||
analyzePath(path, type);
|
||||
|
||||
// array of subdocs?
|
||||
if (type.schema) {
|
||||
analyzeSchema(type.schema, path);
|
||||
}
|
||||
});
|
||||
|
||||
stack.pop();
|
||||
};
|
||||
|
||||
analyzeSchema(schema);
|
||||
|
||||
var i;
|
||||
switch (exclude) {
|
||||
case true:
|
||||
for (i = 0; i < excluded.length; ++i) {
|
||||
fields[excluded[i]] = 0;
|
||||
}
|
||||
break;
|
||||
case false:
|
||||
if (schema &&
|
||||
schema.paths['_id'] &&
|
||||
schema.paths['_id'].options &&
|
||||
schema.paths['_id'].options.select === false) {
|
||||
fields._id = 0;
|
||||
}
|
||||
for (i = 0; i < selected.length; ++i) {
|
||||
fields[selected[i]] = 1;
|
||||
}
|
||||
break;
|
||||
case undefined:
|
||||
// user didn't specify fields, implies returning all fields.
|
||||
// only need to apply excluded fields
|
||||
for (i = 0; i < excluded.length; ++i) {
|
||||
fields[excluded[i]] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Set each path query option to lean
|
||||
*
|
||||
* @param {Object} option
|
||||
*/
|
||||
|
||||
function makeLean(val) {
|
||||
return function(option) {
|
||||
option.options || (option.options = {});
|
||||
option.options.lean = val;
|
||||
};
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
/* eslint no-empty: 1 */
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Stream = require('stream').Stream;
|
||||
var utils = require('./utils');
|
||||
var helpers = require('./queryhelpers');
|
||||
var K = function(k) {
|
||||
return k;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides a Node.js 0.8 style [ReadStream](http://nodejs.org/docs/v0.8.21/api/stream.html#stream_readable_stream) interface for Queries.
|
||||
*
|
||||
* var stream = Model.find().stream();
|
||||
*
|
||||
* stream.on('data', function (doc) {
|
||||
* // do something with the mongoose document
|
||||
* }).on('error', function (err) {
|
||||
* // handle the error
|
||||
* }).on('close', function () {
|
||||
* // the stream is closed
|
||||
* });
|
||||
*
|
||||
*
|
||||
* The stream interface allows us to simply "plug-in" to other _Node.js 0.8_ style write streams.
|
||||
*
|
||||
* Model.where('created').gte(twoWeeksAgo).stream().pipe(writeStream);
|
||||
*
|
||||
* ####Valid options
|
||||
*
|
||||
* - `transform`: optional function which accepts a mongoose document. The return value of the function will be emitted on `data`.
|
||||
*
|
||||
* ####Example
|
||||
*
|
||||
* // JSON.stringify all documents before emitting
|
||||
* var stream = Thing.find().stream({ transform: JSON.stringify });
|
||||
* stream.pipe(writeStream);
|
||||
*
|
||||
* _NOTE: plugging into an HTTP response will *not* work out of the box. Those streams expect only strings or buffers to be emitted, so first formatting our documents as strings/buffers is necessary._
|
||||
*
|
||||
* _NOTE: these streams are Node.js 0.8 style read streams which differ from Node.js 0.10 style. Node.js 0.10 streams are not well tested yet and are not guaranteed to work._
|
||||
*
|
||||
* @param {Query} query
|
||||
* @param {Object} [options]
|
||||
* @inherits NodeJS Stream http://nodejs.org/docs/v0.8.21/api/stream.html#stream_readable_stream
|
||||
* @event `data`: emits a single Mongoose document
|
||||
* @event `error`: emits when an error occurs during streaming. This will emit _before_ the `close` event.
|
||||
* @event `close`: emits when the stream reaches the end of the cursor or an error occurs, or the stream is manually `destroy`ed. After this event, no more events are emitted.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function QueryStream(query, options) {
|
||||
Stream.call(this);
|
||||
|
||||
this.query = query;
|
||||
this.readable = true;
|
||||
this.paused = false;
|
||||
this._cursor = null;
|
||||
this._destroyed = null;
|
||||
this._fields = null;
|
||||
this._buffer = null;
|
||||
this._inline = T_INIT;
|
||||
this._running = false;
|
||||
this._transform = options && typeof options.transform === 'function'
|
||||
? options.transform
|
||||
: K;
|
||||
|
||||
// give time to hook up events
|
||||
var _this = this;
|
||||
process.nextTick(function() {
|
||||
_this._init();
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherit from Stream
|
||||
*/
|
||||
|
||||
QueryStream.prototype.__proto__ = Stream.prototype;
|
||||
|
||||
/**
|
||||
* Flag stating whether or not this stream is readable.
|
||||
*
|
||||
* @property readable
|
||||
* @api public
|
||||
*/
|
||||
|
||||
QueryStream.prototype.readable;
|
||||
|
||||
/**
|
||||
* Flag stating whether or not this stream is paused.
|
||||
*
|
||||
* @property paused
|
||||
* @api public
|
||||
*/
|
||||
|
||||
QueryStream.prototype.paused;
|
||||
|
||||
// trampoline flags
|
||||
var T_INIT = 0;
|
||||
var T_IDLE = 1;
|
||||
var T_CONT = 2;
|
||||
|
||||
/**
|
||||
* Initializes the query.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
QueryStream.prototype._init = function() {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var query = this.query,
|
||||
model = query.model,
|
||||
options = query._optionsForExec(model),
|
||||
_this = this;
|
||||
|
||||
try {
|
||||
query.cast(model);
|
||||
} catch (err) {
|
||||
return _this.destroy(err);
|
||||
}
|
||||
|
||||
_this._fields = utils.clone(query._fields);
|
||||
options.fields = query._castFields(_this._fields);
|
||||
|
||||
model.collection.find(query._conditions, options, function(err, cursor) {
|
||||
if (err) {
|
||||
return _this.destroy(err);
|
||||
}
|
||||
_this._cursor = cursor;
|
||||
_this._next();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Trampoline for pulling the next doc from cursor.
|
||||
*
|
||||
* @see QueryStream#__next #querystream_QueryStream-__next
|
||||
* @api private
|
||||
*/
|
||||
|
||||
QueryStream.prototype._next = function _next() {
|
||||
if (this.paused || this._destroyed) {
|
||||
this._running = false;
|
||||
return this._running;
|
||||
}
|
||||
|
||||
this._running = true;
|
||||
|
||||
if (this._buffer && this._buffer.length) {
|
||||
var arg;
|
||||
while (!this.paused && !this._destroyed && (arg = this._buffer.shift())) { // eslint-disable-line no-cond-assign
|
||||
this._onNextObject.apply(this, arg);
|
||||
}
|
||||
}
|
||||
|
||||
// avoid stack overflows with large result sets.
|
||||
// trampoline instead of recursion.
|
||||
while (this.__next()) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pulls the next doc from the cursor.
|
||||
*
|
||||
* @see QueryStream#_next #querystream_QueryStream-_next
|
||||
* @api private
|
||||
*/
|
||||
|
||||
QueryStream.prototype.__next = function() {
|
||||
if (this.paused || this._destroyed) {
|
||||
this._running = false;
|
||||
return this._running;
|
||||
}
|
||||
|
||||
var _this = this;
|
||||
_this._inline = T_INIT;
|
||||
|
||||
_this._cursor.nextObject(function cursorcb(err, doc) {
|
||||
_this._onNextObject(err, doc);
|
||||
});
|
||||
|
||||
// if onNextObject() was already called in this tick
|
||||
// return ourselves to the trampoline.
|
||||
if (T_CONT === this._inline) {
|
||||
return true;
|
||||
}
|
||||
// onNextObject() hasn't fired yet. tell onNextObject
|
||||
// that its ok to call _next b/c we are not within
|
||||
// the trampoline anymore.
|
||||
this._inline = T_IDLE;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms raw `doc`s returned from the cursor into a model instance.
|
||||
*
|
||||
* @param {Error|null} err
|
||||
* @param {Object} doc
|
||||
* @api private
|
||||
*/
|
||||
|
||||
QueryStream.prototype._onNextObject = function _onNextObject(err, doc) {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.paused) {
|
||||
this._buffer || (this._buffer = []);
|
||||
this._buffer.push([err, doc]);
|
||||
this._running = false;
|
||||
return this._running;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return this.destroy(err);
|
||||
}
|
||||
|
||||
// when doc is null we hit the end of the cursor
|
||||
if (!doc) {
|
||||
this.emit('end');
|
||||
return this.destroy();
|
||||
}
|
||||
|
||||
var opts = this.query._mongooseOptions;
|
||||
|
||||
if (!opts.populate) {
|
||||
return opts.lean === true ?
|
||||
emit(this, doc) :
|
||||
createAndEmit(this, null, doc);
|
||||
}
|
||||
|
||||
var _this = this;
|
||||
var pop = helpers.preparePopulationOptionsMQ(_this.query, _this.query._mongooseOptions);
|
||||
|
||||
// Hack to work around gh-3108
|
||||
pop.forEach(function(option) {
|
||||
delete option.model;
|
||||
});
|
||||
|
||||
pop.__noPromise = true;
|
||||
_this.query.model.populate(doc, pop, function(err, doc) {
|
||||
if (err) {
|
||||
return _this.destroy(err);
|
||||
}
|
||||
return opts.lean === true ?
|
||||
emit(_this, doc) :
|
||||
createAndEmit(_this, pop, doc);
|
||||
});
|
||||
};
|
||||
|
||||
function createAndEmit(self, populatedIds, doc) {
|
||||
var instance = helpers.createModel(self.query.model, doc, self._fields);
|
||||
var opts = populatedIds ?
|
||||
{populated: populatedIds} :
|
||||
undefined;
|
||||
|
||||
instance.init(doc, opts, function(err) {
|
||||
if (err) {
|
||||
return self.destroy(err);
|
||||
}
|
||||
emit(self, instance);
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Emit a data event and manage the trampoline state
|
||||
*/
|
||||
|
||||
function emit(self, doc) {
|
||||
self.emit('data', self._transform(doc));
|
||||
|
||||
// trampoline management
|
||||
if (T_IDLE === self._inline) {
|
||||
// no longer in trampoline. restart it.
|
||||
self._next();
|
||||
} else {
|
||||
// in a trampoline. tell __next that its
|
||||
// ok to continue jumping.
|
||||
self._inline = T_CONT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses this stream.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
QueryStream.prototype.pause = function() {
|
||||
this.paused = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resumes this stream.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
QueryStream.prototype.resume = function() {
|
||||
this.paused = false;
|
||||
|
||||
if (!this._cursor) {
|
||||
// cannot start if not initialized
|
||||
return;
|
||||
}
|
||||
|
||||
// are we within the trampoline?
|
||||
if (T_INIT === this._inline) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._running) {
|
||||
// outside QueryStream control, need manual restart
|
||||
return this._next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys the stream, closing the underlying cursor, which emits the close event. No more events will be emitted after the close event.
|
||||
*
|
||||
* @param {Error} [err]
|
||||
* @api public
|
||||
*/
|
||||
|
||||
QueryStream.prototype.destroy = function(err) {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
this._destroyed = true;
|
||||
this._running = false;
|
||||
this.readable = false;
|
||||
|
||||
if (this._cursor) {
|
||||
this._cursor.close();
|
||||
}
|
||||
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
|
||||
this.emit('close');
|
||||
};
|
||||
|
||||
/**
|
||||
* Pipes this query stream into another stream. This method is inherited from NodeJS Streams.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* query.stream().pipe(writeStream [, options])
|
||||
*
|
||||
* @method pipe
|
||||
* @memberOf QueryStream
|
||||
* @see NodeJS http://nodejs.org/api/stream.html
|
||||
* @api public
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Module exports
|
||||
*/
|
||||
|
||||
module.exports = exports = QueryStream;
|
||||
+1924
File diff suppressed because it is too large
Load Diff
+349
@@ -0,0 +1,349 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var $exists = require('./operators/exists');
|
||||
var $type = require('./operators/type');
|
||||
var SchemaType = require('../schematype');
|
||||
var CastError = SchemaType.CastError;
|
||||
var Types = {
|
||||
Array: SchemaArray,
|
||||
Boolean: require('./boolean'),
|
||||
Date: require('./date'),
|
||||
Number: require('./number'),
|
||||
String: require('./string'),
|
||||
ObjectId: require('./objectid'),
|
||||
Buffer: require('./buffer')
|
||||
};
|
||||
var Mixed = require('./mixed');
|
||||
var cast = require('../cast');
|
||||
var util = require('util');
|
||||
var utils = require('../utils');
|
||||
var castToNumber = require('./operators/helpers').castToNumber;
|
||||
var geospatial = require('./operators/geospatial');
|
||||
|
||||
var MongooseArray;
|
||||
var EmbeddedDoc;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
var typeKey = 'type';
|
||||
if (schemaOptions && schemaOptions.typeKey) {
|
||||
typeKey = schemaOptions.typeKey;
|
||||
}
|
||||
|
||||
if (cast) {
|
||||
var castOptions = {};
|
||||
|
||||
if (utils.getFunctionName(cast.constructor) === 'Object') {
|
||||
if (cast[typeKey]) {
|
||||
// support { type: Woot }
|
||||
castOptions = utils.clone(cast); // do not alter user arguments
|
||||
delete castOptions[typeKey];
|
||||
cast = cast[typeKey];
|
||||
} else {
|
||||
cast = Mixed;
|
||||
}
|
||||
}
|
||||
|
||||
// support { type: 'String' }
|
||||
var name = typeof cast === 'string'
|
||||
? cast
|
||||
: utils.getFunctionName(cast);
|
||||
|
||||
var caster = name in Types
|
||||
? Types[name]
|
||||
: cast;
|
||||
|
||||
this.casterConstructor = caster;
|
||||
if (typeof caster === 'function' && !caster.$isArraySubdocument) {
|
||||
this.caster = new caster(null, castOptions);
|
||||
} else {
|
||||
this.caster = caster;
|
||||
}
|
||||
|
||||
if (!(this.caster instanceof EmbeddedDoc)) {
|
||||
this.caster.path = key;
|
||||
}
|
||||
}
|
||||
|
||||
this.$isMongooseArray = true;
|
||||
|
||||
SchemaType.call(this, key, options, 'Array');
|
||||
|
||||
var defaultArr;
|
||||
var fn;
|
||||
|
||||
if (this.defaultValue != null) {
|
||||
defaultArr = this.defaultValue;
|
||||
fn = typeof defaultArr === 'function';
|
||||
}
|
||||
|
||||
if (!('defaultValue' in this) || this.defaultValue !== void 0) {
|
||||
this.default(function() {
|
||||
var arr = [];
|
||||
if (fn) {
|
||||
arr = defaultArr();
|
||||
} else if (defaultArr != null) {
|
||||
arr = arr.concat(defaultArr);
|
||||
}
|
||||
// Leave it up to `cast()` to convert the array
|
||||
return arr;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This schema type's name, to defend against minifiers that mangle
|
||||
* function names.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
SchemaArray.schemaName = 'Array';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
SchemaArray.prototype = Object.create(SchemaType.prototype);
|
||||
SchemaArray.prototype.constructor = SchemaArray;
|
||||
|
||||
/**
|
||||
* Check if the given value satisfies a required validator. The given value
|
||||
* must be not null nor undefined, and have a positive length.
|
||||
*
|
||||
* @param {Any} value
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
SchemaArray.prototype.checkRequired = function(value) {
|
||||
return !!(value && value.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// lazy load
|
||||
MongooseArray || (MongooseArray = require('../types').Array);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (!value.length && doc) {
|
||||
var indexes = doc.schema.indexedPaths();
|
||||
|
||||
for (var i = 0, l = indexes.length; i < l; ++i) {
|
||||
var pathIndex = indexes[i][0][this.path];
|
||||
if (pathIndex === '2dsphere' || pathIndex === '2d') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(value && value.isMongooseArray)) {
|
||||
value = new MongooseArray(value, 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.path, doc);
|
||||
}
|
||||
|
||||
if (this.caster) {
|
||||
try {
|
||||
for (i = 0, l = value.length; i < l; i++) {
|
||||
value[i] = this.caster.cast(value[i], doc, init);
|
||||
}
|
||||
} catch (e) {
|
||||
// rethrow
|
||||
throw new CastError('[' + e.kind + ']', util.inspect(value), this.path, e);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Casts values for queries.
|
||||
*
|
||||
* @param {String} $conditional
|
||||
* @param {any} [value]
|
||||
* @api private
|
||||
*/
|
||||
|
||||
SchemaArray.prototype.castForQuery = function($conditional, value) {
|
||||
var handler,
|
||||
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;
|
||||
var Constructor = this.casterConstructor;
|
||||
|
||||
if (val &&
|
||||
Constructor.discriminators &&
|
||||
Constructor.schema.options.discriminatorKey &&
|
||||
typeof val[Constructor.schema.options.discriminatorKey] === 'string' &&
|
||||
Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]]) {
|
||||
Constructor = Constructor.discriminators[val[Constructor.schema.options.discriminatorKey]];
|
||||
}
|
||||
|
||||
var proto = this.casterConstructor.prototype;
|
||||
var method = proto && (proto.castForQuery || proto.cast);
|
||||
if (!method && Constructor.castForQuery) {
|
||||
method = Constructor.castForQuery;
|
||||
}
|
||||
var caster = this.caster;
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
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)) {
|
||||
var o = {};
|
||||
o[this.path] = v;
|
||||
return cast(this.casterConstructor.schema, o)[this.path];
|
||||
}
|
||||
return v;
|
||||
}, this);
|
||||
|
||||
return this.castForQuery(val);
|
||||
}
|
||||
|
||||
function cast$elemMatch(val) {
|
||||
var keys = Object.keys(val);
|
||||
var numKeys = keys.length;
|
||||
var key;
|
||||
var value;
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
key = keys[i];
|
||||
value = val[key];
|
||||
if (key.indexOf('$') === 0 && value) {
|
||||
val[key] = this.castForQuery(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return cast(this.casterConstructor.schema, val);
|
||||
}
|
||||
|
||||
var handle = SchemaArray.prototype.$conditionalHandlers = {};
|
||||
|
||||
handle.$all = cast$all;
|
||||
handle.$options = String;
|
||||
handle.$elemMatch = cast$elemMatch;
|
||||
handle.$geoIntersects = geospatial.cast$geoIntersects;
|
||||
handle.$or = handle.$and = function(val) {
|
||||
if (!Array.isArray(val)) {
|
||||
throw new TypeError('conditional $or/$and require array');
|
||||
}
|
||||
|
||||
var ret = [];
|
||||
for (var i = 0; i < val.length; ++i) {
|
||||
ret.push(cast(this.casterConstructor.schema, val[i]));
|
||||
}
|
||||
|
||||
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.$in =
|
||||
handle.$lt =
|
||||
handle.$lte =
|
||||
handle.$ne =
|
||||
handle.$nin =
|
||||
handle.$regex = SchemaArray.prototype.castForQuery;
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = SchemaArray;
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils');
|
||||
|
||||
var SchemaType = require('../schematype');
|
||||
var CastError = SchemaType.CastError;
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
SchemaBoolean.prototype = Object.create(SchemaType.prototype);
|
||||
SchemaBoolean.prototype.constructor = SchemaBoolean;
|
||||
|
||||
/**
|
||||
* 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 value === true || value === false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Casts to boolean
|
||||
*
|
||||
* @param {Object} value
|
||||
* @param {Object} model - this value is optional
|
||||
* @api private
|
||||
*/
|
||||
|
||||
SchemaBoolean.prototype.cast = function(value, model) {
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (this.options.strictBool || (model && model.schema.options.strictBool && this.options.strictBool !== false)) {
|
||||
// strict mode (throws if value is not a boolean, instead of converting)
|
||||
if (value === true || value === 'true' || value === 1 || value === '1') {
|
||||
return true;
|
||||
}
|
||||
if (value === false || value === 'false' || value === 0 || value === '0') {
|
||||
return false;
|
||||
}
|
||||
throw new CastError('boolean', value, this.path);
|
||||
} else {
|
||||
// legacy mode
|
||||
if (value === '0') {
|
||||
return false;
|
||||
}
|
||||
if (value === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (value === 'false') {
|
||||
return false;
|
||||
}
|
||||
return !!value;
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
var handler;
|
||||
if (arguments.length === 2) {
|
||||
handler = SchemaBoolean.$conditionalHandlers[$conditional];
|
||||
|
||||
if (handler) {
|
||||
return handler.call(this, val);
|
||||
}
|
||||
|
||||
return this._castForQuery(val);
|
||||
}
|
||||
|
||||
return this._castForQuery($conditional);
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = SchemaBoolean;
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var handleBitwiseOperator = require('./operators/bitwise');
|
||||
var utils = require('../utils');
|
||||
|
||||
var MongooseBuffer = require('../types/buffer');
|
||||
var SchemaType = require('../schematype');
|
||||
|
||||
var Binary = MongooseBuffer.Binary;
|
||||
var CastError = SchemaType.CastError;
|
||||
var 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';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
SchemaBuffer.prototype = Object.create(SchemaType.prototype);
|
||||
SchemaBuffer.prototype.constructor = SchemaBuffer;
|
||||
|
||||
/**
|
||||
* 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 !!(value && value.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var 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);
|
||||
}
|
||||
|
||||
// Handle the case where user directly sets a populated
|
||||
// path to a plain object; cast to the Model used in
|
||||
// the population query.
|
||||
var path = doc.$__fullPath(this.path);
|
||||
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
|
||||
var pop = owner.populated(path, true);
|
||||
ret = new pop.options.model(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;
|
||||
} else 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);
|
||||
}
|
||||
ret._subtype = value.sub_type;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
var type = typeof value;
|
||||
if (type === 'string' || type === 'number' || Array.isArray(value)) {
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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:
|
||||
*
|
||||
* var s = new Schema({ uuid: { type: Buffer, subtype: 4 });
|
||||
* var M = db.model('M', s);
|
||||
* var 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) {
|
||||
var 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;
|
||||
var casted = this._castForQuery(val);
|
||||
return casted ? casted.toObject({ transform: false, virtuals: false }) : casted;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = SchemaBuffer;
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
/*!
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var MongooseError = require('../error');
|
||||
var utils = require('../utils');
|
||||
|
||||
var SchemaType = require('../schematype');
|
||||
|
||||
var 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';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
SchemaDate.prototype = Object.create(SchemaType.prototype);
|
||||
SchemaDate.prototype.constructor = SchemaDate;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* var 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return value instanceof Date;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a minimum date validator.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var s = new Schema({ d: { type: Date, min: Date('1970-01-01') })
|
||||
* var M = db.model('M', s)
|
||||
* var 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
|
||||
* var min = [Date('1970-01-01'), 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).'];
|
||||
* var schema = new Schema({ d: { type: Date, min: min })
|
||||
* var M = mongoose.model('M', schema);
|
||||
* var 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) {
|
||||
var msg = message || MongooseError.messages.Date.min;
|
||||
msg = msg.replace(/{MIN}/, (value === Date.now ? 'Date.now()' : this.cast(value).toString()));
|
||||
var _this = this;
|
||||
this.validators.push({
|
||||
validator: this.minValidator = function(val) {
|
||||
var 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:
|
||||
*
|
||||
* var s = new Schema({ d: { type: Date, max: Date('2014-01-01') })
|
||||
* var M = db.model('M', s)
|
||||
* var 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
|
||||
* var max = [Date('2014-01-01'), 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).'];
|
||||
* var schema = new Schema({ d: { type: Date, max: max })
|
||||
* var M = mongoose.model('M', schema);
|
||||
* var 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) {
|
||||
var msg = message || MongooseError.messages.Date.max;
|
||||
msg = msg.replace(/{MAX}/, (value === Date.now ? 'Date.now()' : this.cast(value).toString()));
|
||||
var _this = this;
|
||||
this.validators.push({
|
||||
validator: this.maxValidator = function(val) {
|
||||
var 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) {
|
||||
// If null or undefined
|
||||
if (value === null || value === void 0 || value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
if (isNaN(value.valueOf())) {
|
||||
throw new CastError('date', value, this.path);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
var date;
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
throw new CastError('date', value, this.path);
|
||||
}
|
||||
|
||||
if (value instanceof Number || typeof value === 'number'
|
||||
|| String(value) == Number(value)) {
|
||||
// support for timestamps
|
||||
date = new Date(Number(value));
|
||||
} else if (value.valueOf) {
|
||||
// support for moment.js
|
||||
date = new Date(value.valueOf());
|
||||
}
|
||||
|
||||
if (!isNaN(date.valueOf())) {
|
||||
return date;
|
||||
}
|
||||
|
||||
throw new CastError('date', value, this.path);
|
||||
};
|
||||
|
||||
/*!
|
||||
* 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) {
|
||||
var handler;
|
||||
|
||||
if (arguments.length !== 2) {
|
||||
return this._castForQuery($conditional);
|
||||
}
|
||||
|
||||
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;
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/* eslint no-empty: 1 */
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var SchemaType = require('../schematype');
|
||||
var CastError = SchemaType.CastError;
|
||||
var Decimal128Type = require('../types/decimal128');
|
||||
var utils = require('../utils');
|
||||
var 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';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
Decimal128.prototype = Object.create(SchemaType.prototype);
|
||||
Decimal128.prototype.constructor = Decimal128;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
return value instanceof Decimal128Type;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// Handle the case where user directly sets a populated
|
||||
// path to a plain object; cast to the Model used in
|
||||
// the population query.
|
||||
var path = doc.$__fullPath(this.path);
|
||||
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
|
||||
var pop = owner.populated(path, true);
|
||||
var 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.model(value);
|
||||
ret.$__.wasPopulated = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
throw new CastError('Decimal128', value, this.path);
|
||||
};
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
/* eslint no-empty: 1 */
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var ArrayType = require('./array');
|
||||
var CastError = require('../error/cast');
|
||||
var Document = require('../document');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var SchemaType = require('../schematype');
|
||||
var discriminator = require('../services/model/discriminator');
|
||||
var util = require('util');
|
||||
var utils = require('../utils');
|
||||
|
||||
var MongooseDocumentArray;
|
||||
var Subdocument;
|
||||
|
||||
/**
|
||||
* SubdocsArray SchemaType constructor
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Schema} schema
|
||||
* @param {Object} options
|
||||
* @inherits SchemaArray
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function DocumentArray(key, schema, options) {
|
||||
var EmbeddedDocument = _createConstructor(schema, options);
|
||||
EmbeddedDocument.prototype.$basePath = key;
|
||||
|
||||
ArrayType.call(this, key, EmbeddedDocument, options);
|
||||
|
||||
this.schema = schema;
|
||||
this.$isMongooseDocumentArray = true;
|
||||
var fn = this.defaultValue;
|
||||
|
||||
if (!('defaultValue' in this) || fn !== void 0) {
|
||||
this.default(function() {
|
||||
var arr = fn.call(this);
|
||||
if (!Array.isArray(arr)) {
|
||||
arr = [arr];
|
||||
}
|
||||
// Leave it up to `cast()` to convert this to a documentarray
|
||||
return arr;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This schema type's name, to defend against minifiers that mangle
|
||||
* function names.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
DocumentArray.schemaName = 'DocumentArray';
|
||||
|
||||
/*!
|
||||
* Inherits from ArrayType.
|
||||
*/
|
||||
DocumentArray.prototype = Object.create(ArrayType.prototype);
|
||||
DocumentArray.prototype.constructor = DocumentArray;
|
||||
|
||||
/*!
|
||||
* Ignore
|
||||
*/
|
||||
|
||||
function _createConstructor(schema, options) {
|
||||
Subdocument || (Subdocument = require('../types/embedded'));
|
||||
|
||||
// compile an embedded document for this schema
|
||||
function EmbeddedDocument() {
|
||||
Subdocument.apply(this, arguments);
|
||||
}
|
||||
|
||||
EmbeddedDocument.prototype = Object.create(Subdocument.prototype);
|
||||
EmbeddedDocument.prototype.$__setSchema(schema);
|
||||
EmbeddedDocument.schema = schema;
|
||||
EmbeddedDocument.prototype.constructor = EmbeddedDocument;
|
||||
EmbeddedDocument.$isArraySubdocument = true;
|
||||
|
||||
// apply methods
|
||||
for (var i in schema.methods) {
|
||||
EmbeddedDocument.prototype[i] = schema.methods[i];
|
||||
}
|
||||
|
||||
// apply statics
|
||||
for (i in schema.statics) {
|
||||
EmbeddedDocument[i] = schema.statics[i];
|
||||
}
|
||||
|
||||
for (i in EventEmitter.prototype) {
|
||||
EmbeddedDocument[i] = EventEmitter.prototype[i];
|
||||
}
|
||||
|
||||
EmbeddedDocument.options = options;
|
||||
|
||||
return EmbeddedDocument;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Ignore
|
||||
*/
|
||||
|
||||
DocumentArray.prototype.discriminator = function(name, schema) {
|
||||
if (typeof name === 'function') {
|
||||
name = utils.getFunctionName(name);
|
||||
}
|
||||
|
||||
schema = discriminator(this.casterConstructor, name, schema);
|
||||
|
||||
var EmbeddedDocument = _createConstructor(schema);
|
||||
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
|
||||
*/
|
||||
|
||||
DocumentArray.prototype.doValidate = function(array, fn, scope, options) {
|
||||
// lazy load
|
||||
MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentarray'));
|
||||
|
||||
var _this = this;
|
||||
SchemaType.prototype.doValidate.call(this, array, function(err) {
|
||||
if (err) {
|
||||
return fn(err);
|
||||
}
|
||||
|
||||
var count = array && array.length;
|
||||
var 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) {
|
||||
error = err;
|
||||
}
|
||||
--count || fn(error);
|
||||
}
|
||||
|
||||
for (var i = 0, len = count; i < len; ++i) {
|
||||
// sidestep sparse entries
|
||||
var doc = array[i];
|
||||
if (!doc) {
|
||||
--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)) {
|
||||
doc = array[i] = new _this.casterConstructor(doc, array, undefined,
|
||||
undefined, i);
|
||||
}
|
||||
|
||||
// HACK: use $__original_validate to avoid promises so bluebird doesn't
|
||||
// complain
|
||||
if (doc.$__original_validate) {
|
||||
doc.$__original_validate({__noPromise: true}, callback);
|
||||
} else {
|
||||
doc.validate({__noPromise: true}, callback);
|
||||
}
|
||||
}
|
||||
}, scope);
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs local validations first, then validations on each embedded doc.
|
||||
*
|
||||
* ####Note:
|
||||
*
|
||||
* This method ignores the asynchronous validators.
|
||||
*
|
||||
* @return {MongooseError|undefined}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
DocumentArray.prototype.doValidateSync = function(array, scope) {
|
||||
var schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope);
|
||||
if (schemaTypeError) {
|
||||
return schemaTypeError;
|
||||
}
|
||||
|
||||
var count = array && array.length,
|
||||
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 (var i = 0, len = count; i < len; ++i) {
|
||||
// only first error
|
||||
if (resultError) {
|
||||
break;
|
||||
}
|
||||
// sidestep sparse entries
|
||||
var 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)) {
|
||||
doc = array[i] = new this.casterConstructor(doc, array, undefined,
|
||||
undefined, i);
|
||||
}
|
||||
|
||||
var subdocValidateError = doc.validateSync();
|
||||
|
||||
if (subdocValidateError) {
|
||||
resultError = subdocValidateError;
|
||||
}
|
||||
}
|
||||
|
||||
return resultError;
|
||||
};
|
||||
|
||||
/**
|
||||
* Casts contents
|
||||
*
|
||||
* @param {Object} value
|
||||
* @param {Document} document that triggers the casting
|
||||
* @api private
|
||||
*/
|
||||
|
||||
DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
|
||||
// lazy load
|
||||
MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentarray'));
|
||||
|
||||
var selected;
|
||||
var subdoc;
|
||||
var i;
|
||||
var _opts = { transform: false, virtuals: false };
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (!(value && value.isMongooseDocumentArray) &&
|
||||
(!options || !options.skipDocumentArrayCast)) {
|
||||
value = new MongooseDocumentArray(value, this.path, doc);
|
||||
if (prev && prev._handlers) {
|
||||
for (var key in prev._handlers) {
|
||||
doc.removeListener(key, prev._handlers[key]);
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
i = value.length;
|
||||
|
||||
while (i--) {
|
||||
if (!value[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var Constructor = this.casterConstructor;
|
||||
if (Constructor.discriminators &&
|
||||
typeof value[i][Constructor.schema.options.discriminatorKey] === 'string' &&
|
||||
Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]]) {
|
||||
Constructor = Constructor.discriminators[value[i][Constructor.schema.options.discriminatorKey]];
|
||||
}
|
||||
|
||||
// Check if the document has a different schema (re gh-3701)
|
||||
if ((value[i] instanceof Document) &&
|
||||
value[i].schema !== Constructor.schema) {
|
||||
value[i] = value[i].toObject({ transform: false, virtuals: false });
|
||||
}
|
||||
if (!(value[i] instanceof Subdocument) && value[i]) {
|
||||
if (init) {
|
||||
if (doc) {
|
||||
selected || (selected = scopePaths(this, doc.$__.selected, init));
|
||||
} else {
|
||||
selected = true;
|
||||
}
|
||||
|
||||
subdoc = new Constructor(null, value, true, selected, i);
|
||||
value[i] = subdoc.init(value[i]);
|
||||
} else {
|
||||
if (prev && (subdoc = prev.id(value[i]._id))) {
|
||||
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) {
|
||||
var valueInErrorMessage = util.inspect(value[i]);
|
||||
throw new CastError('embedded', valueInErrorMessage,
|
||||
value._path, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Scopes paths selected in a query to this array.
|
||||
* Necessary for proper default application of subdocument values.
|
||||
*
|
||||
* @param {DocumentArray} 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;
|
||||
}
|
||||
|
||||
var path = array.path + '.';
|
||||
var keys = Object.keys(fields);
|
||||
var i = keys.length;
|
||||
var selected = {};
|
||||
var hasKeys;
|
||||
var key;
|
||||
var sub;
|
||||
|
||||
while (i--) {
|
||||
key = keys[i];
|
||||
if (key.indexOf(path) === 0) {
|
||||
sub = key.substring(path.length);
|
||||
if (sub === '$') {
|
||||
continue;
|
||||
}
|
||||
if (sub.indexOf('$.') === 0) {
|
||||
sub = sub.substr(2);
|
||||
}
|
||||
hasKeys || (hasKeys = true);
|
||||
selected[sub] = fields[key];
|
||||
}
|
||||
}
|
||||
|
||||
return hasKeys && selected || undefined;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = DocumentArray;
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var $exists = require('./operators/exists');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var SchemaType = require('../schematype');
|
||||
var castToNumber = require('./operators/helpers').castToNumber;
|
||||
var discriminator = require('../services/model/discriminator');
|
||||
var geospatial = require('./operators/geospatial');
|
||||
|
||||
var Subdocument;
|
||||
|
||||
module.exports = Embedded;
|
||||
|
||||
/**
|
||||
* Sub-schema schematype constructor
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @param {String} key
|
||||
* @param {Object} options
|
||||
* @inherits SchemaType
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Embedded(schema, path, options) {
|
||||
this.caster = _createConstructor(schema);
|
||||
this.caster.prototype.$basePath = path;
|
||||
this.schema = schema;
|
||||
this.$isSingleNested = true;
|
||||
SchemaType.call(this, path, options, 'Embedded');
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
Embedded.prototype = Object.create(SchemaType.prototype);
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _createConstructor(schema) {
|
||||
// lazy load
|
||||
Subdocument || (Subdocument = require('../types/subdocument'));
|
||||
|
||||
var _embedded = function SingleNested(value, path, parent) {
|
||||
var _this = this;
|
||||
|
||||
this.$parent = parent;
|
||||
Subdocument.apply(this, arguments);
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
_embedded.prototype = Object.create(Subdocument.prototype);
|
||||
_embedded.prototype.$__setSchema(schema);
|
||||
_embedded.prototype.constructor = _embedded;
|
||||
_embedded.schema = schema;
|
||||
_embedded.$isSingleNested = true;
|
||||
_embedded.prototype.toBSON = function() {
|
||||
return this.toObject({
|
||||
transform: false,
|
||||
retainKeyOrder: true,
|
||||
virtuals: false,
|
||||
_skipDepopulateTopLevel: true,
|
||||
depopulate: true,
|
||||
flattenDecimals: false
|
||||
});
|
||||
};
|
||||
|
||||
// apply methods
|
||||
for (var i in schema.methods) {
|
||||
_embedded.prototype[i] = schema.methods[i];
|
||||
}
|
||||
|
||||
// apply statics
|
||||
for (i in schema.statics) {
|
||||
_embedded[i] = schema.statics[i];
|
||||
}
|
||||
|
||||
for (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
|
||||
*/
|
||||
|
||||
Embedded.prototype.$conditionalHandlers.$geoWithin = function handle$geoWithin(val) {
|
||||
return { $geometry: this.castForQuery(val.$geometry) };
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
Embedded.prototype.$conditionalHandlers.$near =
|
||||
Embedded.prototype.$conditionalHandlers.$nearSphere = geospatial.cast$near;
|
||||
|
||||
Embedded.prototype.$conditionalHandlers.$within =
|
||||
Embedded.prototype.$conditionalHandlers.$geoWithin = geospatial.cast$within;
|
||||
|
||||
Embedded.prototype.$conditionalHandlers.$geoIntersects =
|
||||
geospatial.cast$geoIntersects;
|
||||
|
||||
Embedded.prototype.$conditionalHandlers.$minDistance = castToNumber;
|
||||
Embedded.prototype.$conditionalHandlers.$maxDistance = castToNumber;
|
||||
|
||||
Embedded.prototype.$conditionalHandlers.$exists = $exists;
|
||||
|
||||
/**
|
||||
* Casts contents
|
||||
*
|
||||
* @param {Object} value
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Embedded.prototype.cast = function(val, doc, init, priorVal) {
|
||||
if (val && val.$isSingleNested) {
|
||||
return val;
|
||||
}
|
||||
|
||||
var Constructor = this.caster;
|
||||
var discriminatorKey = Constructor.schema.options.discriminatorKey;
|
||||
if (val != null &&
|
||||
Constructor.discriminators &&
|
||||
typeof val[discriminatorKey] === 'string' &&
|
||||
Constructor.discriminators[val[discriminatorKey]]) {
|
||||
Constructor = Constructor.discriminators[val[discriminatorKey]];
|
||||
}
|
||||
|
||||
var subdoc;
|
||||
if (init) {
|
||||
subdoc = new Constructor(void 0, doc ? doc.$__.selected : void 0, doc);
|
||||
subdoc.init(val);
|
||||
} else {
|
||||
if (Object.keys(val).length === 0) {
|
||||
return new Constructor({}, doc ? doc.$__.selected : void 0, doc);
|
||||
}
|
||||
|
||||
return new Constructor(val, doc ? doc.$__.selected : void 0, 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
|
||||
*/
|
||||
|
||||
Embedded.prototype.castForQuery = function($conditional, val) {
|
||||
var 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);
|
||||
}
|
||||
|
||||
return new this.caster(val);
|
||||
};
|
||||
|
||||
/**
|
||||
* Async validation on this single nested doc.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Embedded.prototype.doValidate = function(value, fn, scope) {
|
||||
var Constructor = this.caster;
|
||||
var discriminatorKey = Constructor.schema.options.discriminatorKey;
|
||||
if (value != null &&
|
||||
Constructor.discriminators &&
|
||||
typeof value[discriminatorKey] === 'string' &&
|
||||
Constructor.discriminators[value[discriminatorKey]]) {
|
||||
Constructor = Constructor.discriminators[value[discriminatorKey]];
|
||||
}
|
||||
|
||||
SchemaType.prototype.doValidate.call(this, value, function(error) {
|
||||
if (error) {
|
||||
return fn(error);
|
||||
}
|
||||
if (!value) {
|
||||
return fn(null);
|
||||
}
|
||||
|
||||
if (!(value instanceof Constructor)) {
|
||||
value = new Constructor(value);
|
||||
}
|
||||
value.validate({__noPromise: true}, fn);
|
||||
}, scope);
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously validate this single nested doc
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Embedded.prototype.doValidateSync = function(value, scope) {
|
||||
var schemaTypeError = SchemaType.prototype.doValidateSync.call(this, value, scope);
|
||||
if (schemaTypeError) {
|
||||
return schemaTypeError;
|
||||
}
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
return value.validateSync();
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a discriminator to this property
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Schema} schema fields to add to the schema for instances of this sub-class
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Embedded.prototype.discriminator = function(name, schema) {
|
||||
discriminator(this.caster, name, schema);
|
||||
|
||||
this.caster.discriminators[name] = _createConstructor(schema);
|
||||
|
||||
return this.caster.discriminators[name];
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
exports.String = require('./string');
|
||||
|
||||
exports.Number = require('./number');
|
||||
|
||||
exports.Boolean = require('./boolean');
|
||||
|
||||
exports.DocumentArray = require('./documentarray');
|
||||
|
||||
exports.Embedded = require('./embedded');
|
||||
|
||||
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');
|
||||
|
||||
// alias
|
||||
|
||||
exports.Oid = exports.ObjectId;
|
||||
exports.Object = exports.Mixed;
|
||||
exports.Bool = exports.Boolean;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var SchemaType = require('../schematype');
|
||||
var utils = require('../utils');
|
||||
|
||||
/**
|
||||
* Mixed SchemaType constructor.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Object} options
|
||||
* @inherits SchemaType
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Mixed(path, options) {
|
||||
if (options && options.default) {
|
||||
var def = options.default;
|
||||
if (Array.isArray(def) && def.length === 0) {
|
||||
// make sure empty array defaults are handled
|
||||
options.default = Array;
|
||||
} else if (!options.shared && utils.isObject(def) && Object.keys(def).length === 0) {
|
||||
// prevent odd "shared" objects between documents
|
||||
options.default = function() {
|
||||
return {};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
SchemaType.call(this, path, options, 'Mixed');
|
||||
}
|
||||
|
||||
/**
|
||||
* This schema type's name, to defend against minifiers that mangle
|
||||
* function names.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
Mixed.schemaName = 'Mixed';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
Mixed.prototype = Object.create(SchemaType.prototype);
|
||||
Mixed.prototype.constructor = Mixed;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
/*!
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var SchemaType = require('../schematype');
|
||||
var CastError = SchemaType.CastError;
|
||||
var handleBitwiseOperator = require('./operators/bitwise');
|
||||
var MongooseError = require('../error');
|
||||
var utils = require('../utils');
|
||||
var Document;
|
||||
|
||||
/**
|
||||
* Number SchemaType constructor.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Object} options
|
||||
* @inherits SchemaType
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function SchemaNumber(key, options) {
|
||||
SchemaType.call(this, key, options, 'Number');
|
||||
}
|
||||
|
||||
/**
|
||||
* This schema type's name, to defend against minifiers that mangle
|
||||
* function names.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
SchemaNumber.schemaName = 'Number';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
SchemaNumber.prototype = Object.create(SchemaType.prototype);
|
||||
SchemaNumber.prototype.constructor = SchemaNumber;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
return typeof value === 'number' || value instanceof Number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a minimum number validator.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var s = new Schema({ n: { type: Number, min: 10 })
|
||||
* var M = db.model('M', s)
|
||||
* var 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
|
||||
* var min = [10, 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).'];
|
||||
* var schema = new Schema({ n: { type: Number, min: min })
|
||||
* var M = mongoose.model('Measurement', schema);
|
||||
* var 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) {
|
||||
var 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:
|
||||
*
|
||||
* var s = new Schema({ n: { type: Number, max: 10 })
|
||||
* var M = db.model('M', s)
|
||||
* var 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
|
||||
* var max = [10, 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).'];
|
||||
* var schema = new Schema({ n: { type: Number, max: max })
|
||||
* var M = mongoose.model('Measurement', schema);
|
||||
* var 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) {
|
||||
var 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// Handle the case where user directly sets a populated
|
||||
// path to a plain object; cast to the Model used in
|
||||
// the population query.
|
||||
var path = doc.$__fullPath(this.path);
|
||||
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
|
||||
var pop = owner.populated(path, true);
|
||||
var ret = new pop.options.model(value);
|
||||
ret.$__.wasPopulated = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var val = value && typeof value._id !== 'undefined' ?
|
||||
value._id : // documents
|
||||
value;
|
||||
|
||||
if (!isNaN(val)) {
|
||||
if (val === null) {
|
||||
return val;
|
||||
}
|
||||
if (val === '') {
|
||||
return null;
|
||||
}
|
||||
if (typeof val === 'string' || typeof val === 'boolean') {
|
||||
val = Number(val);
|
||||
}
|
||||
if (val instanceof Number) {
|
||||
return val;
|
||||
}
|
||||
if (typeof val === 'number') {
|
||||
return val;
|
||||
}
|
||||
if (val.toString && !Array.isArray(val) && val.toString() == Number(val)) {
|
||||
return new Number(val);
|
||||
}
|
||||
}
|
||||
|
||||
throw new CastError('number', value, this.path);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function handleSingle(val) {
|
||||
return this.cast(val);
|
||||
}
|
||||
|
||||
function handleArray(val) {
|
||||
var _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) {
|
||||
var handler;
|
||||
if (arguments.length === 2) {
|
||||
handler = this.$conditionalHandlers[$conditional];
|
||||
if (!handler) {
|
||||
throw new Error('Can\'t use ' + $conditional + ' with Number.');
|
||||
}
|
||||
return handler.call(this, val);
|
||||
}
|
||||
val = this._castForQuery($conditional);
|
||||
return val;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = SchemaNumber;
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
/* eslint no-empty: 1 */
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var SchemaType = require('../schematype'),
|
||||
CastError = SchemaType.CastError,
|
||||
oid = require('../types/objectid'),
|
||||
utils = require('../utils'),
|
||||
Document;
|
||||
|
||||
/**
|
||||
* ObjectId SchemaType constructor.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Object} options
|
||||
* @inherits SchemaType
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function ObjectId(key, options) {
|
||||
var isKeyHexStr = typeof key === 'string' && key.length === 24 && /^a-f0-9$/i.test(key);
|
||||
var 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';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
ObjectId.prototype = Object.create(SchemaType.prototype);
|
||||
ObjectId.prototype.constructor = ObjectId;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
return value instanceof oid;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// Handle the case where user directly sets a populated
|
||||
// path to a plain object; cast to the Model used in
|
||||
// the population query.
|
||||
var path = doc.$__fullPath(this.path);
|
||||
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
|
||||
var pop = owner.populated(path, true);
|
||||
var 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.model(value);
|
||||
ret.$__.wasPopulated = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value instanceof oid) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value._id) {
|
||||
if (value._id instanceof oid) {
|
||||
return value._id;
|
||||
}
|
||||
if (value._id.toString instanceof Function) {
|
||||
try {
|
||||
return new oid(value._id.toString());
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value.toString instanceof Function) {
|
||||
try {
|
||||
return new oid(value.toString());
|
||||
} catch (err) {
|
||||
throw new CastError('ObjectId', value, this.path);
|
||||
}
|
||||
}
|
||||
|
||||
throw new CastError('ObjectId', value, this.path);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function handleSingle(val) {
|
||||
return this.cast(val);
|
||||
}
|
||||
|
||||
ObjectId.prototype.$conditionalHandlers =
|
||||
utils.options(SchemaType.prototype.$conditionalHandlers, {
|
||||
$gt: handleSingle,
|
||||
$gte: handleSingle,
|
||||
$lt: handleSingle,
|
||||
$lte: handleSingle
|
||||
});
|
||||
|
||||
/**
|
||||
* Casts contents for queries.
|
||||
*
|
||||
* @param {String} $conditional
|
||||
* @param {any} [val]
|
||||
* @api private
|
||||
*/
|
||||
|
||||
ObjectId.prototype.castForQuery = function($conditional, val) {
|
||||
var handler;
|
||||
if (arguments.length === 2) {
|
||||
handler = this.$conditionalHandlers[$conditional];
|
||||
if (!handler) {
|
||||
throw new Error('Can\'t use ' + $conditional + ' with ObjectId.');
|
||||
}
|
||||
return handler.call(this, val);
|
||||
}
|
||||
return this._castForQuery($conditional);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function defaultId() {
|
||||
return new oid();
|
||||
}
|
||||
|
||||
function resetId(v) {
|
||||
Document || (Document = require('./../document'));
|
||||
|
||||
if (v === void 0) {
|
||||
var _v = new oid;
|
||||
this.$__._id = _v;
|
||||
return _v;
|
||||
}
|
||||
|
||||
if (this instanceof Document) {
|
||||
this.$__._id = v;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = ObjectId;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var CastError = require('../../error/cast');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function handleBitwiseOperator(val) {
|
||||
var _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) {
|
||||
var v = Number(num);
|
||||
if (isNaN(v)) {
|
||||
throw new CastError('number', num, path);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
module.exports = handleBitwiseOperator;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function(val) {
|
||||
if (typeof val !== 'boolean') {
|
||||
throw new Error('$exists parameter must be a boolean!');
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*!
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var castArraysOfNumbers = require('./helpers').castArraysOfNumbers;
|
||||
var castToNumber = require('./helpers').castToNumber;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
exports.cast$geoIntersects = cast$geoIntersects;
|
||||
exports.cast$near = cast$near;
|
||||
exports.cast$within = cast$within;
|
||||
|
||||
function cast$near(val) {
|
||||
var SchemaArray = require('../array');
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
castArraysOfNumbers(val, this);
|
||||
return val;
|
||||
}
|
||||
|
||||
_castMinMaxDistance(this, val);
|
||||
|
||||
if (val && val.$geometry) {
|
||||
return cast$geometry(val, this);
|
||||
}
|
||||
|
||||
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(this, val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
function cast$within(val) {
|
||||
_castMinMaxDistance(this, val);
|
||||
|
||||
if (val.$box || val.$polygon) {
|
||||
var type = val.$box ? '$box' : '$polygon';
|
||||
val[type].forEach(function(arr) {
|
||||
if (!Array.isArray(arr)) {
|
||||
var msg = 'Invalid $within $box argument. '
|
||||
+ 'Expected an array, received ' + arr;
|
||||
throw new TypeError(msg);
|
||||
}
|
||||
arr.forEach(function(v, i) {
|
||||
arr[i] = castToNumber.call(this, v);
|
||||
});
|
||||
});
|
||||
} else if (val.$center || val.$centerSphere) {
|
||||
type = val.$center ? '$center' : '$centerSphere';
|
||||
val[type].forEach(function(item, i) {
|
||||
if (Array.isArray(item)) {
|
||||
item.forEach(function(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) {
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Types = {
|
||||
Number: require('../number')
|
||||
};
|
||||
|
||||
/*!
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
exports.castToNumber = castToNumber;
|
||||
exports.castArraysOfNumbers = castArraysOfNumbers;
|
||||
|
||||
/*!
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
function castToNumber(val) {
|
||||
return Types.Number.prototype.cast.call(this, val);
|
||||
}
|
||||
|
||||
function castArraysOfNumbers(arr, self) {
|
||||
arr.forEach(function(v, i) {
|
||||
if (Array.isArray(v)) {
|
||||
castArraysOfNumbers(v, self);
|
||||
} else {
|
||||
arr[i] = castToNumber.call(self, v);
|
||||
}
|
||||
});
|
||||
}
|
||||
+13
@@ -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;
|
||||
};
|
||||
+538
@@ -0,0 +1,538 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var SchemaType = require('../schematype');
|
||||
var CastError = SchemaType.CastError;
|
||||
var MongooseError = require('../error');
|
||||
var utils = require('../utils');
|
||||
var 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';
|
||||
|
||||
/*!
|
||||
* Inherits from SchemaType.
|
||||
*/
|
||||
SchemaString.prototype = Object.create(SchemaType.prototype);
|
||||
SchemaString.prototype.constructor = SchemaString;
|
||||
|
||||
/**
|
||||
* Adds an enum validator
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var states = ['opening', 'open', 'closing', 'closed']
|
||||
* var s = new Schema({ state: { type: String, enum: states }})
|
||||
* var M = db.model('M', s)
|
||||
* var 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
|
||||
* var enum = {
|
||||
* values: ['opening', 'open', 'closing', 'closed'],
|
||||
* message: 'enum validator failed for path `{PATH}` with value `{VALUE}`'
|
||||
* }
|
||||
* var s = new Schema({ state: { type: String, enum: enum })
|
||||
* var M = db.model('M', s)
|
||||
* var 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;
|
||||
}
|
||||
|
||||
var values;
|
||||
var errorMessage;
|
||||
|
||||
if (utils.isObject(arguments[0])) {
|
||||
values = arguments[0].values;
|
||||
errorMessage = arguments[0].message;
|
||||
} else {
|
||||
values = arguments;
|
||||
errorMessage = MongooseError.messages.String.enum;
|
||||
}
|
||||
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
if (undefined !== values[i]) {
|
||||
this.enumValues.push(this.cast(values[i]));
|
||||
}
|
||||
}
|
||||
|
||||
var 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:
|
||||
*
|
||||
* var s = new Schema({ email: { type: String, lowercase: true }})
|
||||
* var M = db.model('M', s);
|
||||
* var m = new M({ email: 'SomeEmail@example.COM' });
|
||||
* console.log(m.email) // someemail@example.com
|
||||
*
|
||||
* NOTE: Setters do not run on queries by default. Use the `runSettersOnQuery` option:
|
||||
*
|
||||
* // Must use `runSettersOnQuery` as shown below, otherwise `email` will
|
||||
* // **not** be lowercased.
|
||||
* M.updateOne({}, { $set: { email: 'SomeEmail@example.COM' } }, { runSettersOnQuery: true });
|
||||
*
|
||||
* @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:
|
||||
*
|
||||
* var s = new Schema({ caps: { type: String, uppercase: true }})
|
||||
* var M = db.model('M', s);
|
||||
* var m = new M({ caps: 'an example' });
|
||||
* console.log(m.caps) // AN EXAMPLE
|
||||
*
|
||||
* NOTE: Setters do not run on queries by default. Use the `runSettersOnQuery` option:
|
||||
*
|
||||
* // Must use `runSettersOnQuery` as shown below, otherwise `email` will
|
||||
* // **not** be lowercased.
|
||||
* M.updateOne({}, { $set: { email: 'SomeEmail@example.COM' } }, { runSettersOnQuery: true });
|
||||
*
|
||||
* @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:
|
||||
*
|
||||
* var s = new Schema({ name: { type: String, trim: true }})
|
||||
* var M = db.model('M', s)
|
||||
* var string = ' some name '
|
||||
* console.log(string.length) // 11
|
||||
* var m = new M({ name: string })
|
||||
* console.log(m.name.length) // 9
|
||||
*
|
||||
* NOTE: Setters do not run on queries by default. Use the `runSettersOnQuery` option:
|
||||
*
|
||||
* // Must use `runSettersOnQuery` as shown below, otherwise `email` will
|
||||
* // **not** be lowercased.
|
||||
* M.updateOne({}, { $set: { email: 'SomeEmail@example.COM' } }, { runSettersOnQuery: true });
|
||||
*
|
||||
* @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:
|
||||
*
|
||||
* var schema = new Schema({ postalCode: { type: String, minlength: 5 })
|
||||
* var Address = db.model('Address', schema)
|
||||
* var 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
|
||||
* var minlength = [5, 'The value of path `{PATH}` (`{VALUE}`) is shorter than the minimum allowed length ({MINLENGTH}).'];
|
||||
* var schema = new Schema({ postalCode: { type: String, minlength: minlength })
|
||||
* var Address = mongoose.model('Address', schema);
|
||||
* var 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) {
|
||||
var 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:
|
||||
*
|
||||
* var schema = new Schema({ postalCode: { type: String, maxlength: 9 })
|
||||
* var Address = db.model('Address', schema)
|
||||
* var 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
|
||||
* var maxlength = [9, 'The value of path `{PATH}` (`{VALUE}`) exceeds the maximum allowed length ({MAXLENGTH}).'];
|
||||
* var schema = new Schema({ postalCode: { type: String, maxlength: maxlength })
|
||||
* var Address = mongoose.model('Address', schema);
|
||||
* var 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) {
|
||||
var 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:
|
||||
*
|
||||
* var s = new Schema({ name: { type: String, match: /^a/ }})
|
||||
* var M = db.model('M', s)
|
||||
* var 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
|
||||
* var match = [ /\.html$/, "That file doesn't end in .html ({VALUE})" ];
|
||||
* var s = new Schema({ file: { type: String, match: match }})
|
||||
* var M = db.model('M', s);
|
||||
* var 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.
|
||||
*
|
||||
* var 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
|
||||
|
||||
var msg = message || MongooseError.messages.String.match;
|
||||
|
||||
var matchValidator = function(v) {
|
||||
if (!regExp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var 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;
|
||||
}
|
||||
return (value instanceof String || typeof value === 'string') && value.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// Handle the case where user directly sets a populated
|
||||
// path to a plain object; cast to the Model used in
|
||||
// the population query.
|
||||
var path = doc.$__fullPath(this.path);
|
||||
var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
|
||||
var pop = owner.populated(path, true);
|
||||
var ret = new pop.options.model(value);
|
||||
ret.$__.wasPopulated = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// If null or undefined
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
// 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) {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
throw new CastError('string', value, this.path);
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function handleSingle(val) {
|
||||
return this.castForQuery(val);
|
||||
}
|
||||
|
||||
function handleArray(val) {
|
||||
var _this = this;
|
||||
if (!Array.isArray(val)) {
|
||||
return [this.castForQuery(val)];
|
||||
}
|
||||
return val.map(function(m) {
|
||||
return _this.castForQuery(m);
|
||||
});
|
||||
}
|
||||
|
||||
SchemaString.prototype.$conditionalHandlers =
|
||||
utils.options(SchemaType.prototype.$conditionalHandlers, {
|
||||
$all: handleArray,
|
||||
$gt: handleSingle,
|
||||
$gte: handleSingle,
|
||||
$lt: handleSingle,
|
||||
$lte: handleSingle,
|
||||
$options: handleSingle,
|
||||
$regex: handleSingle,
|
||||
$not: handleSingle
|
||||
});
|
||||
|
||||
/**
|
||||
* Casts contents for queries.
|
||||
*
|
||||
* @param {String} $conditional
|
||||
* @param {any} [val]
|
||||
* @api private
|
||||
*/
|
||||
|
||||
SchemaString.prototype.castForQuery = function($conditional, val) {
|
||||
var 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;
|
||||
+1110
File diff suppressed because it is too large
Load Diff
+87
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var ObjectId = require('../types/objectid');
|
||||
var utils = require('../utils');
|
||||
|
||||
exports.flatten = flatten;
|
||||
exports.modifiedPaths = modifiedPaths;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function flatten(update, path, options) {
|
||||
var keys;
|
||||
if (update && utils.isMongooseObject(update) && !Buffer.isBuffer(update)) {
|
||||
keys = Object.keys(update.toObject({ transform: false, virtuals: false }));
|
||||
} else {
|
||||
keys = Object.keys(update || {});
|
||||
}
|
||||
|
||||
var numKeys = keys.length;
|
||||
var result = {};
|
||||
path = path ? path + '.' : '';
|
||||
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
var key = keys[i];
|
||||
var val = update[key];
|
||||
result[path + key] = val;
|
||||
if (shouldFlatten(val)) {
|
||||
if (options && options.skipArrays && Array.isArray(val)) {
|
||||
continue;
|
||||
}
|
||||
var flat = flatten(val, path + key, options);
|
||||
for (var k in flat) {
|
||||
result[k] = flat[k];
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
result[path + key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function modifiedPaths(update, path, result) {
|
||||
var keys = Object.keys(update || {});
|
||||
var numKeys = keys.length;
|
||||
result = result || {};
|
||||
path = path ? path + '.' : '';
|
||||
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
var key = keys[i];
|
||||
var val = update[key];
|
||||
|
||||
result[path + key] = true;
|
||||
if (utils.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);
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var PromiseProvider = require('../../promise_provider');
|
||||
var async = require('async');
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var Promise = PromiseProvider.get();
|
||||
var parallel = options.parallel || 1;
|
||||
|
||||
var handleNextResult = function(doc, callback) {
|
||||
var promise = fn(doc);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
var iterate = function(callback) {
|
||||
var drained = false;
|
||||
var nextQueue = async.queue(function(task, cb) {
|
||||
if (drained) return cb();
|
||||
next(function(err, doc) {
|
||||
if (err) return cb(err);
|
||||
if (!doc) drained = true;
|
||||
cb(null, doc);
|
||||
});
|
||||
}, 1);
|
||||
|
||||
var getAndRun = function(cb) {
|
||||
nextQueue.push({}, function(err, doc) {
|
||||
if (err) return cb(err);
|
||||
if (!doc) return cb();
|
||||
handleNextResult(doc, function(err) {
|
||||
if (err) return cb(err);
|
||||
// Make sure to clear the stack re: gh-4697
|
||||
setTimeout(function() {
|
||||
getAndRun(cb);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
async.times(parallel, function(n, cb) {
|
||||
getAndRun(cb);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
return new Promise.ES6(function(resolve, reject) {
|
||||
iterate(function(error) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
return reject(error);
|
||||
}
|
||||
callback && callback(null);
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function cleanModifiedSubpaths(doc, path) {
|
||||
var _modifiedPaths = Object.keys(doc.$__.activePaths.states.modify);
|
||||
var _numModifiedPaths = _modifiedPaths.length;
|
||||
var deleted = 0;
|
||||
for (var j = 0; j < _numModifiedPaths; ++j) {
|
||||
if (_modifiedPaths[j].indexOf(path + '.') === 0) {
|
||||
delete doc.$__.activePaths.states.modify[_modifiedPaths[j]];
|
||||
++deleted;
|
||||
}
|
||||
}
|
||||
return deleted;
|
||||
};
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
'use strict';
|
||||
|
||||
var Document;
|
||||
var utils = require('../../utils');
|
||||
|
||||
/*!
|
||||
* exports
|
||||
*/
|
||||
|
||||
exports.compile = compile;
|
||||
exports.defineKey = defineKey;
|
||||
|
||||
/*!
|
||||
* Compiles schemas.
|
||||
*/
|
||||
|
||||
function compile(tree, proto, prefix, options) {
|
||||
Document = Document || require('../../document');
|
||||
var keys = Object.keys(tree);
|
||||
var i = keys.length;
|
||||
var len = keys.length;
|
||||
var limb;
|
||||
var key;
|
||||
|
||||
if (options.retainKeyOrder) {
|
||||
for (i = 0; i < len; ++i) {
|
||||
key = keys[i];
|
||||
limb = tree[key];
|
||||
|
||||
defineKey(key,
|
||||
((utils.getFunctionName(limb.constructor) === 'Object'
|
||||
&& Object.keys(limb).length)
|
||||
&& (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type))
|
||||
? limb
|
||||
: null)
|
||||
, proto
|
||||
, prefix
|
||||
, keys
|
||||
, options);
|
||||
}
|
||||
} else {
|
||||
while (i--) {
|
||||
key = keys[i];
|
||||
limb = tree[key];
|
||||
|
||||
defineKey(key,
|
||||
((utils.getFunctionName(limb.constructor) === 'Object'
|
||||
&& Object.keys(limb).length)
|
||||
&& (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type))
|
||||
? limb
|
||||
: null)
|
||||
, 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');
|
||||
var path = (prefix ? prefix + '.' : '') + prop;
|
||||
prefix = prefix || '';
|
||||
|
||||
if (subprops) {
|
||||
Object.defineProperty(prototype, prop, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: function() {
|
||||
var _this = this;
|
||||
if (!this.$__.getters) {
|
||||
this.$__.getters = {};
|
||||
}
|
||||
|
||||
if (!this.$__.getters[path]) {
|
||||
var nested = Object.create(Document.prototype, getOwnPropertyDescriptors(this));
|
||||
|
||||
// save scope for nested getters/setters
|
||||
if (!prefix) {
|
||||
nested.$__.scope = this;
|
||||
}
|
||||
nested.$__.nestedPath = path;
|
||||
|
||||
Object.defineProperty(nested, 'schema', {
|
||||
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), { retainKeyOrder: true });
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, 'toJSON', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: function() {
|
||||
return _this.get(path);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(nested, '$__isNested', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: false,
|
||||
value: true
|
||||
});
|
||||
|
||||
compile(subprops, nested, path, options);
|
||||
this.$__.getters[path] = nested;
|
||||
}
|
||||
|
||||
return this.$__.getters[path];
|
||||
},
|
||||
set: function(v) {
|
||||
if (v instanceof Document) {
|
||||
v = v.toObject({ transform: false });
|
||||
}
|
||||
var doc = this.$__.scope || this;
|
||||
return doc.$set(path, v);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.defineProperty(prototype, prop, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: function() {
|
||||
return this.get.call(this.$__.scope || this, path);
|
||||
},
|
||||
set: function(v) {
|
||||
return this.$set.call(this.$__.scope || this, path, v);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// gets descriptors for all properties of `object`
|
||||
// makes all properties non-enumerable to match previous behavior to #2211
|
||||
function getOwnPropertyDescriptors(object) {
|
||||
var 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'].indexOf(key) === -1;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
'use strict';
|
||||
|
||||
var PromiseProvider = require('../../promise_provider');
|
||||
var VersionError = require('../../error').VersionError;
|
||||
|
||||
module.exports = applyHooks;
|
||||
|
||||
/*!
|
||||
* Register hooks for this model
|
||||
*
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
|
||||
function applyHooks(model, schema) {
|
||||
var q = schema && schema.callQueue;
|
||||
var toWrapEl;
|
||||
var len;
|
||||
var i;
|
||||
var j;
|
||||
var pointCut;
|
||||
var keys;
|
||||
var newName;
|
||||
|
||||
model.$appliedHooks = true;
|
||||
for (i = 0; i < schema.childSchemas.length; ++i) {
|
||||
var childModel = schema.childSchemas[i].model;
|
||||
if (childModel.$appliedHooks) {
|
||||
continue;
|
||||
}
|
||||
applyHooks(childModel, schema.childSchemas[i].schema);
|
||||
if (childModel.discriminators != null) {
|
||||
keys = Object.keys(childModel.discriminators);
|
||||
for (j = 0; j < keys.length; ++j) {
|
||||
applyHooks(childModel.discriminators[keys[j]],
|
||||
childModel.discriminators[keys[j]].schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!q.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we are only interested in 'pre' hooks, and group by point-cut
|
||||
var toWrap = { post: [] };
|
||||
var pair;
|
||||
|
||||
for (i = 0; i < q.length; ++i) {
|
||||
pair = q[i];
|
||||
if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') {
|
||||
continue;
|
||||
}
|
||||
var args = [].slice.call(pair[1]);
|
||||
pointCut = pair[0] === 'on' ? 'post' : args[0];
|
||||
if (!(pointCut in toWrap)) {
|
||||
toWrap[pointCut] = {post: [], pre: []};
|
||||
}
|
||||
if (pair[0] === 'post') {
|
||||
toWrap[pointCut].post.push(args);
|
||||
} else if (pair[0] === 'on') {
|
||||
toWrap[pointCut].push(args);
|
||||
} else {
|
||||
toWrap[pointCut].pre.push(args);
|
||||
}
|
||||
}
|
||||
|
||||
// 'post' hooks are simpler
|
||||
len = toWrap.post.length;
|
||||
toWrap.post.forEach(function(args) {
|
||||
model.on.apply(model, args);
|
||||
});
|
||||
delete toWrap.post;
|
||||
|
||||
// 'init' should be synchronous on subdocuments
|
||||
if (toWrap.init && (model.$isSingleNested || model.$isArraySubdocument)) {
|
||||
if (toWrap.init.pre) {
|
||||
toWrap.init.pre.forEach(function(args) {
|
||||
model.prototype.$pre.apply(model.prototype, args);
|
||||
});
|
||||
}
|
||||
if (toWrap.init.post) {
|
||||
toWrap.init.post.forEach(function(args) {
|
||||
model.prototype.$post.apply(model.prototype, args);
|
||||
});
|
||||
}
|
||||
delete toWrap.init;
|
||||
}
|
||||
if (toWrap.set) {
|
||||
// Set hooks also need to be sync re: gh-3479
|
||||
newName = '$__original_set';
|
||||
model.prototype[newName] = model.prototype.set;
|
||||
if (toWrap.set.pre) {
|
||||
toWrap.set.pre.forEach(function(args) {
|
||||
model.prototype.$pre.apply(model.prototype, args);
|
||||
});
|
||||
}
|
||||
if (toWrap.set.post) {
|
||||
toWrap.set.post.forEach(function(args) {
|
||||
model.prototype.$post.apply(model.prototype, args);
|
||||
});
|
||||
}
|
||||
delete toWrap.set;
|
||||
}
|
||||
|
||||
toWrap.validate = toWrap.validate || { pre: [], post: [] };
|
||||
|
||||
keys = Object.keys(toWrap);
|
||||
len = keys.length;
|
||||
for (i = 0; i < len; ++i) {
|
||||
pointCut = keys[i];
|
||||
// this is so we can wrap everything into a promise;
|
||||
newName = ('$__original_' + pointCut);
|
||||
if (!model.prototype[pointCut]) {
|
||||
continue;
|
||||
}
|
||||
if (model.prototype[pointCut].$isWrapped) {
|
||||
continue;
|
||||
}
|
||||
if (!model.prototype[pointCut].$originalFunction) {
|
||||
model.prototype[newName] = model.prototype[pointCut];
|
||||
}
|
||||
model.prototype[pointCut] = (function(_newName) {
|
||||
return function wrappedPointCut() {
|
||||
var Promise = PromiseProvider.get();
|
||||
|
||||
var _this = this;
|
||||
var args = [].slice.call(arguments);
|
||||
var lastArg = args.pop();
|
||||
var fn;
|
||||
var originalError = new Error();
|
||||
var $results;
|
||||
if (lastArg && typeof lastArg !== 'function') {
|
||||
args.push(lastArg);
|
||||
} else {
|
||||
fn = lastArg;
|
||||
}
|
||||
|
||||
var promise = new Promise.ES6(function(resolve, reject) {
|
||||
args.push(function(error) {
|
||||
if (error) {
|
||||
// gh-2633: since VersionError is very generic, take the
|
||||
// stack trace of the original save() function call rather
|
||||
// than the async trace
|
||||
if (error instanceof VersionError) {
|
||||
error.stack = originalError.stack;
|
||||
}
|
||||
if (!fn) {
|
||||
_this.$__handleReject(error);
|
||||
}
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// There may be multiple results and promise libs other than
|
||||
// mpromise don't support passing multiple values to `resolve()`
|
||||
$results = Array.prototype.slice.call(arguments, 1);
|
||||
resolve.apply(promise, $results);
|
||||
});
|
||||
|
||||
_this[_newName].apply(_this, args);
|
||||
});
|
||||
if (fn) {
|
||||
if (this.constructor.$wrapCallback) {
|
||||
fn = this.constructor.$wrapCallback(fn);
|
||||
}
|
||||
promise.then(
|
||||
function() {
|
||||
process.nextTick(function() {
|
||||
fn.apply(null, [null].concat($results));
|
||||
});
|
||||
},
|
||||
function(error) {
|
||||
process.nextTick(function() {
|
||||
fn(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
})(newName);
|
||||
model.prototype[pointCut].$originalFunction = newName;
|
||||
model.prototype[pointCut].$isWrapped = true;
|
||||
|
||||
toWrapEl = toWrap[pointCut];
|
||||
var _len = toWrapEl.pre.length;
|
||||
for (j = 0; j < _len; ++j) {
|
||||
args = toWrapEl.pre[j];
|
||||
args[0] = newName;
|
||||
model.prototype.$pre.apply(model.prototype, args);
|
||||
}
|
||||
|
||||
_len = toWrapEl.post.length;
|
||||
for (j = 0; j < _len; ++j) {
|
||||
args = toWrapEl.post[j];
|
||||
args[0] = newName;
|
||||
model.prototype.$post.apply(model.prototype, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* 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() {
|
||||
var h = {};
|
||||
for (var k in schema.methods[method]) {
|
||||
h[k] = schema.methods[method][k].bind(this);
|
||||
}
|
||||
return h;
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
for (var method in schema.methods) {
|
||||
if (schema.tree.hasOwnProperty(method)) {
|
||||
throw new Error('You have a method and a property in your schema both ' +
|
||||
'named "' + method + '"');
|
||||
}
|
||||
if (typeof schema.methods[method] === 'function') {
|
||||
model.prototype[method] = schema.methods[method];
|
||||
} else {
|
||||
apply(method, schema);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively call `applyMethods()` on child schemas
|
||||
model.$appliedMethods = true;
|
||||
for (var i = 0; i < schema.childSchemas.length; ++i) {
|
||||
if (schema.childSchemas[i].model.$appliedMethods) {
|
||||
continue;
|
||||
}
|
||||
applyMethods(schema.childSchemas[i].model, schema.childSchemas[i].schema);
|
||||
}
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* Register statics for this model
|
||||
* @param {Model} model
|
||||
* @param {Schema} schema
|
||||
*/
|
||||
module.exports = function applyStatics(model, schema) {
|
||||
for (var i in schema.statics) {
|
||||
model[i] = schema.statics[i];
|
||||
}
|
||||
};
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
'use strict';
|
||||
|
||||
var defineKey = require('../document/compile').defineKey;
|
||||
var utils = require('../../utils');
|
||||
|
||||
var CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
|
||||
toJSON: true,
|
||||
toObject: true,
|
||||
_id: true,
|
||||
id: true
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function discriminator(model, name, schema) {
|
||||
if (!(schema && schema.instanceOfSchema)) {
|
||||
throw new Error('You must pass a valid discriminator Schema');
|
||||
}
|
||||
|
||||
if (model.base && model.base.options.applyPluginsToDiscriminators) {
|
||||
model.base._applyPlugins(schema);
|
||||
}
|
||||
|
||||
if (model.schema.discriminatorMapping &&
|
||||
!model.schema.discriminatorMapping.isRoot) {
|
||||
throw new Error('Discriminator "' + name +
|
||||
'" can only be a discriminator of the root model');
|
||||
}
|
||||
|
||||
var key = model.schema.options.discriminatorKey;
|
||||
|
||||
var baseSchemaAddition = {};
|
||||
baseSchemaAddition[key] = {
|
||||
default: void 0,
|
||||
select: true,
|
||||
set: function(newName) {
|
||||
if (newName === name) {
|
||||
return name;
|
||||
}
|
||||
throw new Error('Can\'t set discriminator key "' + key + '", "' +
|
||||
name + '" !== "' + newName + '"');
|
||||
},
|
||||
$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 + '"');
|
||||
}
|
||||
|
||||
function merge(schema, baseSchema) {
|
||||
if (baseSchema.paths._id &&
|
||||
baseSchema.paths._id.options &&
|
||||
!baseSchema.paths._id.options.auto) {
|
||||
var originalSchema = schema;
|
||||
utils.merge(schema, originalSchema, { retainKeyOrder: true });
|
||||
delete schema.paths._id;
|
||||
delete schema.tree._id;
|
||||
}
|
||||
utils.merge(schema, baseSchema, {
|
||||
retainKeyOrder: true,
|
||||
omit: { discriminators: true }
|
||||
});
|
||||
|
||||
var obj = {};
|
||||
obj[key] = {
|
||||
default: name,
|
||||
select: true,
|
||||
set: function(newName) {
|
||||
if (newName === name) {
|
||||
return name;
|
||||
}
|
||||
throw new Error('Can\'t set discriminator key "' + key + '"');
|
||||
},
|
||||
$skipDiscriminatorCheck: true
|
||||
};
|
||||
obj[key][schema.options.typeKey] = String;
|
||||
schema.add(obj);
|
||||
schema.discriminatorMapping = {key: key, value: name, isRoot: false};
|
||||
|
||||
if (baseSchema.options.collection) {
|
||||
schema.options.collection = baseSchema.options.collection;
|
||||
}
|
||||
|
||||
var toJSON = schema.options.toJSON;
|
||||
var toObject = schema.options.toObject;
|
||||
var _id = schema.options._id;
|
||||
var id = schema.options.id;
|
||||
|
||||
var keys = Object.keys(schema.options);
|
||||
schema.options.discriminatorKey = baseSchema.options.discriminatorKey;
|
||||
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
var _key = keys[i];
|
||||
if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) {
|
||||
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(baseSchema.plugins);
|
||||
schema.callQueue = baseSchema.callQueue.
|
||||
concat(schema.callQueue.slice(schema._defaultMiddleware.length));
|
||||
schema._requiredpaths = undefined; // 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};
|
||||
model.schema.discriminators = {};
|
||||
}
|
||||
|
||||
model.schema.discriminators[name] = schema;
|
||||
|
||||
if (model.discriminators[name]) {
|
||||
throw new Error('Discriminator with name "' + name + '" already exists');
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
var Mixed = require('../../schema/mixed');
|
||||
var mpath = require('mpath');
|
||||
|
||||
/*!
|
||||
* @param {Schema} schema
|
||||
* @param {Object} doc POJO
|
||||
* @param {string} path
|
||||
*/
|
||||
|
||||
module.exports = function getSchemaTypes(schema, doc, path) {
|
||||
var pathschema = schema.path(path);
|
||||
|
||||
if (pathschema) {
|
||||
return pathschema;
|
||||
}
|
||||
|
||||
function search(parts, schema) {
|
||||
var p = parts.length + 1;
|
||||
var foundschema;
|
||||
var 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 Mixed) {
|
||||
return foundschema.caster;
|
||||
}
|
||||
|
||||
var schemas = null;
|
||||
if (doc != null && foundschema.schema != null && foundschema.schema.discriminators != null) {
|
||||
var discriminators = foundschema.schema.discriminators;
|
||||
var keys = mpath.get(trypath + '.' + foundschema.schema.options.discriminatorKey,
|
||||
doc);
|
||||
schemas = Object.keys(discriminators).
|
||||
reduce(function(cur, discriminator) {
|
||||
if (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) {
|
||||
var ret;
|
||||
if (parts[p] === '$') {
|
||||
if (p + 1 === parts.length) {
|
||||
// comments.$
|
||||
return foundschema;
|
||||
}
|
||||
// comments.$.comments.$.title
|
||||
ret = search(parts.slice(p + 1), schema);
|
||||
if (ret) {
|
||||
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
|
||||
!foundschema.schema.$isSingleNested;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (schemas != null && schemas.length > 0) {
|
||||
ret = [];
|
||||
for (var i = 0; i < schemas.length; ++i) {
|
||||
var _ret = search(parts.slice(p), schemas[i]);
|
||||
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);
|
||||
|
||||
if (ret) {
|
||||
ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
|
||||
!foundschema.schema.$isSingleNested;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundschema;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// look for arrays
|
||||
var parts = path.split('.');
|
||||
for (var 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);
|
||||
};
|
||||
Generated
Vendored
+18
@@ -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;
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
var isDefiningProjection = require('./isDefiningProjection');
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isInclusive(projection) {
|
||||
if (projection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var props = Object.keys(projection);
|
||||
var numProps = props.length;
|
||||
if (numProps === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < numProps; ++i) {
|
||||
var prop = props[i];
|
||||
// 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;
|
||||
};
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function isPathSelectedInclusive(fields, path) {
|
||||
var chunks = path.split('.');
|
||||
var cur = '';
|
||||
var j;
|
||||
var keys;
|
||||
var numKeys;
|
||||
for (var 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;
|
||||
};
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
'use strict';
|
||||
|
||||
var StrictModeError = require('../../error/strict');
|
||||
var ValidationError = require('../../error/validation');
|
||||
var 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) {
|
||||
if (!obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var ops = Object.keys(obj);
|
||||
var i = ops.length;
|
||||
var ret = {};
|
||||
var hasKeys;
|
||||
var val;
|
||||
var hasDollarKey = false;
|
||||
var overwrite = options.overwrite;
|
||||
|
||||
while (i--) {
|
||||
var 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;
|
||||
|
||||
// if we get passed {} for the update, we still need to respect that when it
|
||||
// is an overwrite scenario
|
||||
if (overwrite) {
|
||||
hasKeys = true;
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
op = ops[i];
|
||||
val = ret[op];
|
||||
hasDollarKey = hasDollarKey || op.charAt(0) === '$';
|
||||
if (val &&
|
||||
typeof val === 'object' &&
|
||||
(!overwrite || hasDollarKey)) {
|
||||
hasKeys |= walkUpdatePath(schema, val, op, options.strict, context);
|
||||
} else if (overwrite && ret && typeof ret === 'object') {
|
||||
// if we are just using overwrite, cast the query and then we will
|
||||
// *always* return the value, even if it is an empty object. We need to
|
||||
// set hasKeys above because we need to account for the case where the
|
||||
// user passes {} and wants to clobber the whole document
|
||||
// Also, _walkUpdatePath expects an operation, so give it $set since that
|
||||
// is basically what we're doing
|
||||
walkUpdatePath(schema, ret, '$set', options.strict, context);
|
||||
} else {
|
||||
var msg = 'Invalid atomic update value for ' + op + '. '
|
||||
+ 'Expected an object, received ' + typeof val;
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return hasKeys && ret;
|
||||
};
|
||||
|
||||
/*!
|
||||
* 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 {Boolean|String} strict
|
||||
* @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, strict, context, pref) {
|
||||
var prefix = pref ? pref + '.' : '';
|
||||
var keys = Object.keys(obj);
|
||||
var i = keys.length;
|
||||
var hasKeys = false;
|
||||
var schematype;
|
||||
var key;
|
||||
var val;
|
||||
|
||||
var hasError = false;
|
||||
var aggregatedError = new ValidationError();
|
||||
|
||||
var useNestedStrict = schema.options.useNestedStrict;
|
||||
|
||||
while (i--) {
|
||||
key = keys[i];
|
||||
val = obj[key];
|
||||
|
||||
if (val && val.constructor.name === 'Object') {
|
||||
// watch for embedded doc schemas
|
||||
schematype = schema._getSchema(prefix + key);
|
||||
if (schematype && schematype.caster && op in castOps) {
|
||||
// embedded doc schema
|
||||
hasKeys = true;
|
||||
|
||||
if ('$each' in val) {
|
||||
try {
|
||||
obj[key] = {
|
||||
$each: castUpdateVal(schematype, val.$each, op, context)
|
||||
};
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
_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 || val.$position === 0) {
|
||||
obj[key].$position = val.$position;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, context);
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
_handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
}
|
||||
} else if ((op === '$currentDate') || (op in castOps && schematype)) {
|
||||
// $currentDate can take an object
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, context);
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
_handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
|
||||
hasKeys = true;
|
||||
} else {
|
||||
var pathToCheck = (prefix + key);
|
||||
var v = schema._getPathType(pathToCheck);
|
||||
var _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, strict, context, prefix + key) ||
|
||||
(utils.isObject(val) && Object.keys(val).length === 0);
|
||||
}
|
||||
} else {
|
||||
var checkPath = (key === '$each' || key === '$or' || key === '$and' || key === '$in') ?
|
||||
pref : prefix + key;
|
||||
schematype = schema._getSchema(checkPath);
|
||||
|
||||
var pathDetails = schema._getPathType(checkPath);
|
||||
var isStrict = strict;
|
||||
if (useNestedStrict &&
|
||||
pathDetails &&
|
||||
pathDetails.schema &&
|
||||
'strict' in pathDetails.schema.options) {
|
||||
isStrict = pathDetails.schema.options.strict;
|
||||
}
|
||||
|
||||
var skip = isStrict &&
|
||||
!schematype &&
|
||||
!/real|nested/.test(pathDetails.pathType);
|
||||
|
||||
if (skip) {
|
||||
if (isStrict === 'throw') {
|
||||
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;
|
||||
}
|
||||
|
||||
hasKeys = true;
|
||||
try {
|
||||
obj[key] = castUpdateVal(schematype, val, op, key, context);
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
_handleCastError(error, context, key, aggregatedError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
throw aggregatedError;
|
||||
}
|
||||
|
||||
return hasKeys;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _handleCastError(error, query, key, aggregatedError) {
|
||||
if (typeof query !== 'object' || !query.options.multipleCastError) {
|
||||
throw error;
|
||||
}
|
||||
aggregatedError.addError(key, error);
|
||||
}
|
||||
|
||||
/*!
|
||||
* These operators should be cast to numbers instead
|
||||
* of their path schema type.
|
||||
*/
|
||||
|
||||
var numberOps = {
|
||||
$pop: 1,
|
||||
$unset: 1,
|
||||
$inc: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* These operators require casting docs
|
||||
* to real Documents for Update operations.
|
||||
*/
|
||||
|
||||
var castOps = {
|
||||
$push: 1,
|
||||
$pushAll: 1,
|
||||
$addToSet: 1,
|
||||
$set: 1,
|
||||
$setOnInsert: 1
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
var 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) {
|
||||
if (!schema) {
|
||||
// non-existing schema path
|
||||
return op in numberOps
|
||||
? Number(val)
|
||||
: val;
|
||||
}
|
||||
|
||||
var cond = schema.caster && op in castOps &&
|
||||
(utils.isObject(val) || Array.isArray(val));
|
||||
if (cond) {
|
||||
// Cast values for ops that add data to MongoDB.
|
||||
// Ensures embedded documents get ObjectIds etc.
|
||||
var tmp = schema.cast(val);
|
||||
if (Array.isArray(val)) {
|
||||
val = tmp;
|
||||
} else if (Array.isArray(tmp)) {
|
||||
val = tmp[0];
|
||||
} else {
|
||||
val = tmp;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
if (op in numberOps) {
|
||||
if (op === '$inc') {
|
||||
return schema.castForQueryWrapper({ val: val, context: context });
|
||||
}
|
||||
return Number(val);
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
return schema.castForQueryWrapper({ val: val, context: context });
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function(obj) {
|
||||
var keys = Object.keys(obj);
|
||||
var len = keys.length;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (keys[i].charAt(0) === '$') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
module.exports = function selectPopulatedFields(query) {
|
||||
var opts = query._mongooseOptions;
|
||||
|
||||
if (opts.populate != null) {
|
||||
var paths = Object.keys(opts.populate);
|
||||
var i;
|
||||
var userProvidedFields = query._userProvidedFields || {};
|
||||
if (query.selectedInclusively()) {
|
||||
for (i = 0; i < paths.length; ++i) {
|
||||
if (!isPathInFields(userProvidedFields, paths[i])) {
|
||||
query.select(paths[i]);
|
||||
}
|
||||
}
|
||||
} else if (query.selectedExclusively()) {
|
||||
for (i = 0; i < paths.length; ++i) {
|
||||
if (userProvidedFields[paths[i]] == null) {
|
||||
delete query._fields[paths[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function isPathInFields(userProvidedFields, path) {
|
||||
var pieces = path.split('.');
|
||||
var len = pieces.length;
|
||||
var cur = pieces[0];
|
||||
for (var i = 1; i < len; ++i) {
|
||||
if (userProvidedFields[cur] != null) {
|
||||
return true;
|
||||
}
|
||||
cur += '.' + pieces[i];
|
||||
}
|
||||
return userProvidedFields[cur] != null;
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
'use strict';
|
||||
|
||||
var modifiedPaths = require('./common').modifiedPaths;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var keys = Object.keys(castedDoc || {});
|
||||
var updatedKeys = {};
|
||||
var updatedValues = {};
|
||||
var numKeys = keys.length;
|
||||
var hasDollarUpdate = false;
|
||||
var modified = {};
|
||||
|
||||
if (options && options.upsert) {
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
if (keys[i].charAt(0) === '$') {
|
||||
modifiedPaths(castedDoc[keys[i]], '', modified);
|
||||
hasDollarUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDollarUpdate) {
|
||||
modifiedPaths(castedDoc, '', modified);
|
||||
}
|
||||
|
||||
var paths = Object.keys(filter);
|
||||
var numPaths = paths.length;
|
||||
for (i = 0; i < numPaths; ++i) {
|
||||
var path = paths[i];
|
||||
var condition = filter[path];
|
||||
if (condition && typeof condition === 'object') {
|
||||
var conditionKeys = Object.keys(condition);
|
||||
var numConditionKeys = conditionKeys.length;
|
||||
var hasDollarKey = false;
|
||||
for (var j = 0; j < numConditionKeys; ++j) {
|
||||
if (conditionKeys[j].charAt(0) === '$') {
|
||||
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;
|
||||
}
|
||||
|
||||
if (options.setDefaultsOnInsert) {
|
||||
schema.eachPath(function(path, schemaType) {
|
||||
if (path === '_id') {
|
||||
// Ignore _id for now because it causes bugs in 2.4
|
||||
return;
|
||||
}
|
||||
if (schemaType.$isSingleNested) {
|
||||
// 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') {
|
||||
// Ignore _id for now because it causes bugs in 2.4
|
||||
return;
|
||||
}
|
||||
|
||||
var 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 {
|
||||
var def = schemaType.getDefault(null, true);
|
||||
if (!isModified(modified, path) && typeof def !== 'undefined') {
|
||||
castedDoc = castedDoc || {};
|
||||
castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
|
||||
castedDoc.$setOnInsert[path] = def;
|
||||
updatedValues[path] = def;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return castedDoc;
|
||||
};
|
||||
|
||||
function isModified(modified, path) {
|
||||
if (modified[path]) {
|
||||
return true;
|
||||
}
|
||||
var sp = path.split('.');
|
||||
var cur = sp[0];
|
||||
for (var i = 0; i < sp.length; ++i) {
|
||||
if (modified[cur]) {
|
||||
return true;
|
||||
}
|
||||
cur += '.' + sp[i];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Mixed = require('../schema/mixed');
|
||||
var ValidationError = require('../error/validation');
|
||||
var parallel = require('async/parallel');
|
||||
var flatten = require('./common').flatten;
|
||||
var 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) {
|
||||
var _keys;
|
||||
var keys = Object.keys(castedDoc || {});
|
||||
var updatedKeys = {};
|
||||
var updatedValues = {};
|
||||
var arrayAtomicUpdates = {};
|
||||
var numKeys = keys.length;
|
||||
var hasDollarUpdate = false;
|
||||
var modified = {};
|
||||
var currentUpdate;
|
||||
var key;
|
||||
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
if (keys[i].charAt(0) === '$') {
|
||||
hasDollarUpdate = true;
|
||||
if (keys[i] === '$push' || keys[i] === '$addToSet') {
|
||||
_keys = Object.keys(castedDoc[keys[i]]);
|
||||
for (var 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);
|
||||
var flat = flatten(castedDoc[keys[i]]);
|
||||
var paths = Object.keys(flat);
|
||||
var numPaths = paths.length;
|
||||
for (var j = 0; j < numPaths; ++j) {
|
||||
var updatedPath = paths[j].replace('.$.', '.0.');
|
||||
updatedPath = updatedPath.replace(/\.\$$/, '.0');
|
||||
key = keys[i];
|
||||
if (key === '$set' || key === '$setOnInsert' ||
|
||||
key === '$pull' || key === '$pullAll') {
|
||||
updatedValues[updatedPath] = flat[paths[j]];
|
||||
} else if (key === '$unset') {
|
||||
updatedValues[updatedPath] = undefined;
|
||||
}
|
||||
updatedKeys[updatedPath] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDollarUpdate) {
|
||||
modifiedPaths(castedDoc, '', modified);
|
||||
updatedValues = flatten(castedDoc);
|
||||
updatedKeys = Object.keys(updatedValues);
|
||||
}
|
||||
|
||||
var updates = Object.keys(updatedValues);
|
||||
var numUpdates = updates.length;
|
||||
var validatorsToExecute = [];
|
||||
var validationErrors = [];
|
||||
function iter(i, v) {
|
||||
var schemaPath = schema._getSchema(updates[i]);
|
||||
if (schemaPath) {
|
||||
// gh-4305: `_getSchema()` will report all sub-fields of a 'Mixed' path
|
||||
// as 'Mixed', so avoid double validating them.
|
||||
if (schemaPath instanceof Mixed && schemaPath.$fullPath !== updates[i]) {
|
||||
return;
|
||||
}
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
v,
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = updates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
options && options.context === 'query' ? query : null,
|
||||
{updateValidator: true});
|
||||
});
|
||||
}
|
||||
}
|
||||
for (i = 0; i < numUpdates; ++i) {
|
||||
iter(i, updatedValues[updates[i]]);
|
||||
}
|
||||
|
||||
var arrayUpdates = Object.keys(arrayAtomicUpdates);
|
||||
var numArrayUpdates = arrayUpdates.length;
|
||||
for (i = 0; i < numArrayUpdates; ++i) {
|
||||
(function(i) {
|
||||
var schemaPath = schema._getSchema(arrayUpdates[i]);
|
||||
if (schemaPath && schemaPath.$isMongooseDocumentArray) {
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
arrayAtomicUpdates[arrayUpdates[i]],
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = arrayUpdates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
options && options.context === 'query' ? query : null);
|
||||
});
|
||||
} else {
|
||||
schemaPath = schema._getSchema(arrayUpdates[i] + '.0');
|
||||
for (var j = 0; j < arrayAtomicUpdates[arrayUpdates[i]].length; ++j) {
|
||||
(function(j) {
|
||||
validatorsToExecute.push(function(callback) {
|
||||
schemaPath.doValidate(
|
||||
arrayAtomicUpdates[arrayUpdates[i]][j],
|
||||
function(err) {
|
||||
if (err) {
|
||||
err.path = arrayUpdates[i];
|
||||
validationErrors.push(err);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
options && options.context === 'query' ? query : null,
|
||||
{ updateValidator: true });
|
||||
});
|
||||
})(j);
|
||||
}
|
||||
}
|
||||
})(i);
|
||||
}
|
||||
|
||||
return function(callback) {
|
||||
parallel(validatorsToExecute, function() {
|
||||
if (validationErrors.length) {
|
||||
var err = new ValidationError(null);
|
||||
for (var i = 0; i < validationErrors.length; ++i) {
|
||||
err.addError(validationErrors[i].path, validationErrors[i]);
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
};
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('./utils');
|
||||
|
||||
/*!
|
||||
* StateMachine represents a minimal `interface` for the
|
||||
* constructors it builds via StateMachine.ctor(...).
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var 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() {
|
||||
var states = utils.args(arguments);
|
||||
|
||||
var ctor = function() {
|
||||
StateMachine.apply(this, arguments);
|
||||
this.paths = {};
|
||||
this.states = {};
|
||||
this.stateNames = states;
|
||||
|
||||
var 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) {
|
||||
var 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) {
|
||||
var keys = Object.keys(this.states[state]),
|
||||
i = keys.length,
|
||||
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() {
|
||||
var _this = this;
|
||||
var 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() {
|
||||
var numArgs = arguments.length,
|
||||
states = utils.args(arguments, 0, numArgs - 1),
|
||||
callback = arguments[numArgs - 1];
|
||||
|
||||
if (!states.length) states = this.stateNames;
|
||||
|
||||
var _this = this;
|
||||
|
||||
var 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);
|
||||
};
|
||||
+837
@@ -0,0 +1,837 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var EmbeddedDocument = require('./embedded');
|
||||
var Document = require('../document');
|
||||
var ObjectId = require('./objectid');
|
||||
var cleanModifiedSubpaths = require('../services/document/cleanModifiedSubpaths');
|
||||
var utils = require('../utils');
|
||||
|
||||
var isMongooseObject = utils.isMongooseObject;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var arr = [].concat(values);
|
||||
|
||||
var keysMA = Object.keys(MongooseArray.mixin);
|
||||
var numKeys = keysMA.length;
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
arr[keysMA[i]] = MongooseArray.mixin[keysMA[i]];
|
||||
}
|
||||
|
||||
arr._path = path;
|
||||
arr.isMongooseArray = true;
|
||||
arr.validators = [];
|
||||
arr._atomics = {};
|
||||
arr._schema = 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._parent = doc;
|
||||
arr._schema = doc.schema.path(path);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
MongooseArray.mixin = {
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
toBSON: function() {
|
||||
return this.toObject({
|
||||
transform: false,
|
||||
virtuals: false,
|
||||
_skipDepopulateTopLevel: true,
|
||||
depopulate: true,
|
||||
flattenDecimals: false
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores a queue of atomic operations to perform
|
||||
*
|
||||
* @property _atomics
|
||||
* @api private
|
||||
*/
|
||||
|
||||
_atomics: undefined,
|
||||
|
||||
/**
|
||||
* Parent owner document
|
||||
*
|
||||
* @property _parent
|
||||
* @api private
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
_parent: undefined,
|
||||
|
||||
/**
|
||||
* Casts a member based on this arrays schema.
|
||||
*
|
||||
* @param {any} value
|
||||
* @return value the casted value
|
||||
* @method _cast
|
||||
* @api private
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
_cast: function(value) {
|
||||
var populated = false;
|
||||
var Model;
|
||||
|
||||
if (this._parent) {
|
||||
populated = this._parent.populated(this._path, true);
|
||||
}
|
||||
|
||||
if (populated && value !== null && value !== undefined) {
|
||||
// cast to the populated Models schema
|
||||
Model = populated.options.model || populated.options.Model;
|
||||
|
||||
// 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
|
||||
var isDisc = value.schema && value.schema.discriminatorMapping &&
|
||||
value.schema.discriminatorMapping.key !== undefined;
|
||||
if (!isDisc) {
|
||||
value = new Model(value);
|
||||
}
|
||||
return this._schema.caster.applySetters(value, this._parent, true);
|
||||
}
|
||||
|
||||
return this._schema.caster.applySetters(value, this._parent, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
_markModified: function(elem, embeddedPath) {
|
||||
var parent = this._parent,
|
||||
dirtyPath;
|
||||
|
||||
if (parent) {
|
||||
dirtyPath = this._path;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
_registerAtomic: function(op, val) {
|
||||
if (op === '$set') {
|
||||
// $set takes precedence over all other ops.
|
||||
// mark entire array modified.
|
||||
this._atomics = {$set: val};
|
||||
return this;
|
||||
}
|
||||
|
||||
var atomics = this._atomics;
|
||||
|
||||
// reset pop/shift after save
|
||||
if (op === '$pop' && !('$pop' in atomics)) {
|
||||
var _this = this;
|
||||
this._parent.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._atomics.$set ||
|
||||
Object.keys(atomics).length && !(op in atomics)) {
|
||||
// a different op was previously registered.
|
||||
// save the entire thing.
|
||||
this._atomics = {$set: this};
|
||||
return this;
|
||||
}
|
||||
|
||||
var selector;
|
||||
|
||||
if (op === '$pullAll' || op === '$pushAll' || op === '$addToSet') {
|
||||
atomics[op] || (atomics[op] = []);
|
||||
atomics[op] = atomics[op].concat(val);
|
||||
} else if (op === '$pullDocs') {
|
||||
var 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 {
|
||||
atomics[op] = val;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @api private
|
||||
*/
|
||||
|
||||
$__getAtomics: function() {
|
||||
var ret = [];
|
||||
var keys = Object.keys(this._atomics);
|
||||
var i = keys.length;
|
||||
|
||||
if (i === 0) {
|
||||
ret[0] = ['$set', this.toObject({depopulate: 1, transform: false, _isNested: true, virtuals: false})];
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
var op = keys[i];
|
||||
var val = this._atomics[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 (isMongooseObject(val)) {
|
||||
val = val.toObject({depopulate: 1, transform: false, _isNested: true, virtuals: false});
|
||||
} else if (Array.isArray(val)) {
|
||||
val = this.toObject.call(val, {depopulate: 1, transform: false, _isNested: true});
|
||||
} else if (val.valueOf) {
|
||||
val = val.valueOf();
|
||||
}
|
||||
|
||||
if (op === '$addToSet') {
|
||||
val = {$each: val};
|
||||
}
|
||||
|
||||
ret.push([op, val]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the number of pending atomic operations to send to the db for this array.
|
||||
*
|
||||
* @api private
|
||||
* @return {Number}
|
||||
* @method hasAtomics
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
hasAtomics: function hasAtomics() {
|
||||
if (!(this._atomics && this._atomics.constructor.name === 'Object')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Object.keys(this._atomics).length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal helper for .map()
|
||||
*
|
||||
* @api private
|
||||
* @return {Number}
|
||||
* @method _mapCast
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
_mapCast: function(val, index) {
|
||||
return this._cast(val, this.length + index);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
push: function() {
|
||||
_checkManualPopulation(this, arguments);
|
||||
var values = [].map.call(arguments, this._mapCast, this);
|
||||
values = this._schema.applySetters(values, this._parent, undefined,
|
||||
undefined, { skipDocumentArrayCast: true });
|
||||
var ret = [].push.apply(this, values);
|
||||
|
||||
// $pushAll might be fibbed (could be $push). But it makes it easier to
|
||||
// handle what could have been $push, $pushAll combos
|
||||
this._registerAtomic('$pushAll', values);
|
||||
this._markModified();
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
nonAtomicPush: function() {
|
||||
var values = [].map.call(arguments, this._mapCast, this);
|
||||
var ret = [].push.apply(this, values);
|
||||
this._registerAtomic('$set', this);
|
||||
this._markModified();
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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];
|
||||
*
|
||||
* var 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
|
||||
* @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop
|
||||
* @method $pop
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
$pop: function() {
|
||||
this._registerAtomic('$pop', 1);
|
||||
this._markModified();
|
||||
|
||||
// only allow popping once
|
||||
if (this._popped) {
|
||||
return;
|
||||
}
|
||||
this._popped = true;
|
||||
|
||||
return [].pop.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
pop: function() {
|
||||
var ret = [].pop.call(this);
|
||||
this._registerAtomic('$set', this);
|
||||
this._markModified();
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Atomically shifts the array 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];
|
||||
*
|
||||
* var 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
|
||||
* @method $shift
|
||||
* @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop
|
||||
*/
|
||||
|
||||
$shift: function $shift() {
|
||||
this._registerAtomic('$pop', -1);
|
||||
this._markModified();
|
||||
|
||||
// only allow shifting once
|
||||
if (this._shifted) {
|
||||
return;
|
||||
}
|
||||
this._shifted = true;
|
||||
|
||||
return [].shift.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Wraps [`Array#shift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* doc.array = [2,3];
|
||||
* var 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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
shift: function() {
|
||||
var ret = [].shift.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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
pull: function() {
|
||||
var values = [].map.call(arguments, this._cast, this),
|
||||
cur = this._parent.get(this._path),
|
||||
i = cur.length,
|
||||
mem;
|
||||
|
||||
while (i--) {
|
||||
mem = cur[i];
|
||||
if (mem instanceof Document) {
|
||||
var 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._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._parent, this._path) > 0) {
|
||||
this._registerAtomic('$set', this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
splice: function splice() {
|
||||
var ret;
|
||||
var vals;
|
||||
var i;
|
||||
|
||||
_checkManualPopulation(this, Array.prototype.slice.call(arguments, 2));
|
||||
|
||||
if (arguments.length) {
|
||||
vals = [];
|
||||
for (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);
|
||||
this._markModified();
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 overwritting any changes that happen between when you retrieved the object and when you save it._
|
||||
*
|
||||
* @api public
|
||||
* @method unshift
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
unshift: function() {
|
||||
_checkManualPopulation(this, arguments);
|
||||
|
||||
var values = [].map.call(arguments, this._cast, this);
|
||||
values = this._schema.applySetters(values, this._parent);
|
||||
[].unshift.apply(this, values);
|
||||
this._registerAtomic('$set', this);
|
||||
this._markModified();
|
||||
return this.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
sort: function() {
|
||||
var ret = [].sort.apply(this, arguments);
|
||||
this._registerAtomic('$set', this);
|
||||
this._markModified();
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds values to the array if not already present.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* console.log(doc.array) // [2,3,4]
|
||||
* var 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
|
||||
* @receiver MongooseArray
|
||||
* @api public
|
||||
* @method addToSet
|
||||
*/
|
||||
|
||||
addToSet: function addToSet() {
|
||||
_checkManualPopulation(this, arguments);
|
||||
|
||||
var values = [].map.call(arguments, this._mapCast, this);
|
||||
values = this._schema.applySetters(values, this._parent);
|
||||
var added = [];
|
||||
var type = '';
|
||||
if (values[0] instanceof EmbeddedDocument) {
|
||||
type = 'doc';
|
||||
} else if (values[0] instanceof Date) {
|
||||
type = 'date';
|
||||
}
|
||||
|
||||
values.forEach(function(v) {
|
||||
var found;
|
||||
switch (type) {
|
||||
case 'doc':
|
||||
found = this.some(function(doc) {
|
||||
return doc.equals(v);
|
||||
});
|
||||
break;
|
||||
case 'date':
|
||||
var val = +v;
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the casted `val` at index `i` and marks the array modified.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* // given documents based on the following
|
||||
* var Doc = mongoose.model('Doc', new Schema({ array: [Number] }));
|
||||
*
|
||||
* var 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
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
set: function set(i, val) {
|
||||
var value = this._cast(val, i);
|
||||
this[i] = value;
|
||||
this._markModified(i);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a native js Array.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Array}
|
||||
* @api public
|
||||
* @method toObject
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
toObject: function(options) {
|
||||
if (options && options.depopulate) {
|
||||
options._isNested = true;
|
||||
return this.map(function(doc) {
|
||||
return doc instanceof Document
|
||||
? doc.toObject(options)
|
||||
: doc;
|
||||
});
|
||||
}
|
||||
|
||||
return this.slice();
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper for console.log
|
||||
*
|
||||
* @api public
|
||||
* @method inspect
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
inspect: function() {
|
||||
return JSON.stringify(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the index of `obj` or `-1` if not found.
|
||||
*
|
||||
* @param {Object} obj the item to look for
|
||||
* @return {Number}
|
||||
* @api public
|
||||
* @method indexOf
|
||||
* @receiver MongooseArray
|
||||
*/
|
||||
|
||||
indexOf: function indexOf(obj) {
|
||||
if (obj instanceof ObjectId) {
|
||||
obj = obj.toString();
|
||||
}
|
||||
for (var i = 0, len = this.length; i < len; ++i) {
|
||||
if (obj == this[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias of [pull](#types_array_MongooseArray-pull)
|
||||
*
|
||||
* @see MongooseArray#pull #types_array_MongooseArray-pull
|
||||
* @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull
|
||||
* @api public
|
||||
* @memberOf MongooseArray
|
||||
* @method remove
|
||||
*/
|
||||
|
||||
MongooseArray.mixin.remove = MongooseArray.mixin.pull;
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _isAllSubdocs(docs, ref) {
|
||||
if (!ref) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < docs.length; ++i) {
|
||||
var arg = docs[i];
|
||||
if (arg == null) {
|
||||
return false;
|
||||
}
|
||||
var model = arg.constructor;
|
||||
if (!(arg instanceof Document) ||
|
||||
(model.modelName !== ref && model.baseModelName !== ref)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
|
||||
function _checkManualPopulation(arr, docs) {
|
||||
var ref = arr._schema.caster.options && arr._schema.caster.options.ref;
|
||||
if (arr.length === 0 &&
|
||||
docs.length > 0) {
|
||||
if (_isAllSubdocs(docs, ref)) {
|
||||
arr._parent.populated(arr._path, [], { model: docs[0].constructor });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = MongooseArray;
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Binary = require('../drivers').Binary,
|
||||
utils = require('../utils');
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var length = arguments.length;
|
||||
var val;
|
||||
|
||||
if (length === 0 || arguments[0] === null || arguments[0] === undefined) {
|
||||
val = 0;
|
||||
} else {
|
||||
val = value;
|
||||
}
|
||||
|
||||
var encoding;
|
||||
var path;
|
||||
var doc;
|
||||
|
||||
if (Array.isArray(encode)) {
|
||||
// internal casting
|
||||
path = encode[0];
|
||||
doc = encode[1];
|
||||
} else {
|
||||
encoding = encode;
|
||||
}
|
||||
|
||||
var buf = new Buffer(val, encoding, offset);
|
||||
utils.decorate(buf, MongooseBuffer.mixin);
|
||||
buf.isMongooseBuffer = true;
|
||||
|
||||
// make sure these internal props don't show up in Object.keys()
|
||||
Object.defineProperties(buf, {
|
||||
validators: {
|
||||
value: [],
|
||||
enumerable: false
|
||||
},
|
||||
_path: {
|
||||
value: path,
|
||||
enumerable: false
|
||||
},
|
||||
_parent: {
|
||||
value: doc,
|
||||
enumerable: false
|
||||
}
|
||||
});
|
||||
|
||||
if (doc && typeof path === 'string') {
|
||||
Object.defineProperty(buf, '_schema', {
|
||||
value: doc.schema.path(path)
|
||||
});
|
||||
}
|
||||
|
||||
buf._subtype = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherit from Buffer.
|
||||
*/
|
||||
|
||||
// MongooseBuffer.prototype = new Buffer(0);
|
||||
|
||||
MongooseBuffer.mixin = {
|
||||
|
||||
/**
|
||||
* Parent owner document
|
||||
*
|
||||
* @api private
|
||||
* @property _parent
|
||||
* @receiver MongooseBuffer
|
||||
*/
|
||||
|
||||
_parent: undefined,
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
var parent = this._parent;
|
||||
|
||||
if (parent) {
|
||||
parent.markModified(this._path);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the buffer.
|
||||
*
|
||||
* @api public
|
||||
* @method write
|
||||
* @receiver MongooseBuffer
|
||||
*/
|
||||
|
||||
write: function() {
|
||||
var 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) {
|
||||
var 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() {
|
||||
var ret = Buffer.prototype[method].apply(this, arguments);
|
||||
this._markModified();
|
||||
return ret;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts this buffer to its Binary type representation.
|
||||
*
|
||||
* ####SubTypes:
|
||||
*
|
||||
* var 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) {
|
||||
var subtype = typeof options === 'number'
|
||||
? options
|
||||
: (this._subtype || 0);
|
||||
return new Binary(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 (var 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:
|
||||
*
|
||||
* var 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;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* ObjectId type constructor
|
||||
*
|
||||
* ####Example
|
||||
*
|
||||
* var id = new mongoose.Types.ObjectId;
|
||||
*
|
||||
* @constructor ObjectId
|
||||
*/
|
||||
|
||||
module.exports = require('../drivers').Decimal128;
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var MongooseArray = require('./array'),
|
||||
ObjectId = require('./objectid'),
|
||||
ObjectIdSchema = require('../schema/objectid'),
|
||||
utils = require('../utils'),
|
||||
Document = require('../document');
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var arr = [].concat(values);
|
||||
arr._path = path;
|
||||
|
||||
var props = {
|
||||
isMongooseArray: true,
|
||||
isMongooseDocumentArray: true,
|
||||
validators: [],
|
||||
_atomics: {},
|
||||
_schema: void 0,
|
||||
_handlers: void 0
|
||||
};
|
||||
|
||||
// Values always have to be passed to the constructor to initialize, since
|
||||
// otherwise MongooseArray#push will mark the array as modified to the parent.
|
||||
var keysMA = Object.keys(MongooseArray.mixin);
|
||||
var numKeys = keysMA.length;
|
||||
for (var j = 0; j < numKeys; ++j) {
|
||||
arr[keysMA[j]] = MongooseArray.mixin[keysMA[j]];
|
||||
}
|
||||
|
||||
var keysMDA = Object.keys(MongooseDocumentArray.mixin);
|
||||
numKeys = keysMDA.length;
|
||||
for (var i = 0; i < numKeys; ++i) {
|
||||
arr[keysMDA[i]] = MongooseDocumentArray.mixin[keysMDA[i]];
|
||||
}
|
||||
|
||||
var keysP = Object.keys(props);
|
||||
numKeys = keysP.length;
|
||||
for (var k = 0; k < numKeys; ++k) {
|
||||
arr[keysP[k]] = props[keysP[k]];
|
||||
}
|
||||
|
||||
// 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._parent = doc;
|
||||
arr._schema = doc.schema.path(path);
|
||||
arr._handlers = {
|
||||
isNew: arr.notify('isNew'),
|
||||
save: arr.notify('save')
|
||||
};
|
||||
|
||||
doc.on('save', arr._handlers.save);
|
||||
doc.on('isNew', arr._handlers.isNew);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inherits from MongooseArray
|
||||
*/
|
||||
// MongooseDocumentArray.mixin = Object.create( MongooseArray.mixin );
|
||||
MongooseDocumentArray.mixin = {
|
||||
/*!
|
||||
* ignore
|
||||
*/
|
||||
toBSON: function() {
|
||||
return this.toObject({
|
||||
transform: false,
|
||||
virtuals: false,
|
||||
_skipDepopulateTopLevel: true,
|
||||
depopulate: true,
|
||||
flattenDecimals: false
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Overrides MongooseArray#cast
|
||||
*
|
||||
* @method _cast
|
||||
* @api private
|
||||
* @receiver MongooseDocumentArray
|
||||
*/
|
||||
|
||||
_cast: function(value, index) {
|
||||
var Constructor = this._schema.casterConstructor;
|
||||
if (value instanceof Constructor ||
|
||||
// Hack re: #5001, see #5005
|
||||
(value && value.constructor && value.constructor.baseCasterConstructor === Constructor)) {
|
||||
if (!(value.__parent && value.__parentArray)) {
|
||||
// value may have been created using array.create()
|
||||
value.__parent = this._parent;
|
||||
value.__parentArray = this;
|
||||
}
|
||||
value.__index = 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.options.discriminatorKey &&
|
||||
typeof value[Constructor.schema.options.discriminatorKey] === 'string' &&
|
||||
Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]]) {
|
||||
Constructor = Constructor.discriminators[value[Constructor.schema.options.discriminatorKey]];
|
||||
}
|
||||
|
||||
return new Constructor(value, this, undefined, undefined, index);
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches array items for the first document with a matching _id.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var 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: function(id) {
|
||||
var casted,
|
||||
sid,
|
||||
_id;
|
||||
|
||||
try {
|
||||
var casted_ = ObjectIdSchema.prototype.cast.call({}, id);
|
||||
if (casted_) {
|
||||
casted = String(casted_);
|
||||
}
|
||||
} catch (e) {
|
||||
casted = null;
|
||||
}
|
||||
|
||||
for (var i = 0, l = this.length; i < l; i++) {
|
||||
if (!this[i]) {
|
||||
continue;
|
||||
}
|
||||
_id = this[i].get('_id');
|
||||
|
||||
if (_id === null || typeof _id === 'undefined') {
|
||||
continue;
|
||||
} else if (_id instanceof Document) {
|
||||
sid || (sid = String(id));
|
||||
if (sid == _id._id) {
|
||||
return this[i];
|
||||
}
|
||||
} else if (!(id instanceof ObjectId) && !(_id instanceof ObjectId)) {
|
||||
if (utils.deepEqual(id, _id)) {
|
||||
return this[i];
|
||||
}
|
||||
} else if (casted == _id) {
|
||||
return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
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: function(options) {
|
||||
return this.map(function(doc) {
|
||||
return doc && doc.toObject(options) || null;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper for console.log
|
||||
*
|
||||
* @method inspect
|
||||
* @api public
|
||||
* @receiver MongooseDocumentArray
|
||||
*/
|
||||
|
||||
inspect: function() {
|
||||
return Array.prototype.slice.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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: function(obj) {
|
||||
var Constructor = this._schema.casterConstructor;
|
||||
if (obj &&
|
||||
Constructor.discriminators &&
|
||||
Constructor.schema.options.discriminatorKey &&
|
||||
typeof obj[Constructor.schema.options.discriminatorKey] === 'string' &&
|
||||
Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]]) {
|
||||
Constructor = Constructor.discriminators[obj[Constructor.schema.options.discriminatorKey]];
|
||||
}
|
||||
|
||||
return new Constructor(obj);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a fn that notifies all child docs of `event`.
|
||||
*
|
||||
* @param {String} event
|
||||
* @return {Function}
|
||||
* @method notify
|
||||
* @api private
|
||||
* @receiver MongooseDocumentArray
|
||||
*/
|
||||
|
||||
notify: function notify(event) {
|
||||
var _this = this;
|
||||
return function notify(val) {
|
||||
var i = _this.length;
|
||||
while (i--) {
|
||||
if (!_this[i]) {
|
||||
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;
|
||||
}
|
||||
_this[i].emit(event, val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = MongooseDocumentArray;
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
/* eslint no-func-assign: 1 */
|
||||
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Document = require('../document_provider')();
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var PromiseProvider = require('../promise_provider');
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.__parentArray = parentArr;
|
||||
this.__parent = parentArr._parent;
|
||||
} else {
|
||||
this.__parentArray = undefined;
|
||||
this.__parent = undefined;
|
||||
}
|
||||
this.__index = index;
|
||||
|
||||
Document.call(this, obj, fields, skipId);
|
||||
|
||||
var _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 (var i in EventEmitter.prototype) {
|
||||
EmbeddedDocument[i] = EventEmitter.prototype[i];
|
||||
}
|
||||
|
||||
EmbeddedDocument.prototype.toBSON = function() {
|
||||
return this.toObject({
|
||||
transform: false,
|
||||
virtuals: false,
|
||||
_skipDepopulateTopLevel: true,
|
||||
depopulate: true,
|
||||
flattenDecimals: false
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Marks the embedded doc modified.
|
||||
*
|
||||
* ####Example:
|
||||
*
|
||||
* var 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;
|
||||
}
|
||||
|
||||
if (this.isNew) {
|
||||
// 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(fn) {
|
||||
var Promise = PromiseProvider.get();
|
||||
return new Promise.ES6(function(resolve) {
|
||||
fn && fn();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
/*!
|
||||
* Registers remove event listeners for triggering
|
||||
* on subdocuments.
|
||||
*
|
||||
* @param {EmbeddedDocument} sub
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function registerRemoveListener(sub) {
|
||||
var 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
var _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,
|
||||
retainKeyOrder: true,
|
||||
virtuals: false,
|
||||
flattenDecimals: false
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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, first) {
|
||||
if (!this.__parent) {
|
||||
Document.prototype.invalidate.call(this, path, err, val);
|
||||
if (err.$isValidatorError) {
|
||||
return true;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
var index = this.__index;
|
||||
if (typeof index !== 'undefined') {
|
||||
var parentPath = this.__parentArray._path;
|
||||
var fullPath = [parentPath, index, path].join('.');
|
||||
this.__parent.invalidate(fullPath, err, val);
|
||||
}
|
||||
|
||||
if (first) {
|
||||
this.$__.validationError = this.ownerDocument().$__.validationError;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.__parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = this.__index;
|
||||
if (typeof index !== 'undefined') {
|
||||
var parentPath = this.__parentArray._path;
|
||||
var fullPath = [parentPath, index, path].join('.');
|
||||
this.__parent.$markValid(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) {
|
||||
var index = this.__index;
|
||||
if (typeof index !== 'undefined' && this.__parent) {
|
||||
return !this.__parent.$__.validationError ||
|
||||
!this.__parent.$__.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;
|
||||
}
|
||||
|
||||
var parent = this.__parent;
|
||||
if (!parent) {
|
||||
return this;
|
||||
}
|
||||
|
||||
while (parent.__parent || parent.$parent) {
|
||||
parent = parent.__parent || 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
|
||||
*/
|
||||
|
||||
EmbeddedDocument.prototype.$__fullPath = function(path) {
|
||||
if (!this.$__.fullPath) {
|
||||
var parent = this; // eslint-disable-line consistent-this
|
||||
if (!parent.__parent) {
|
||||
return path;
|
||||
}
|
||||
|
||||
var paths = [];
|
||||
while (parent.__parent || parent.$parent) {
|
||||
if (parent.__parent) {
|
||||
paths.unshift(parent.__parentArray._path);
|
||||
} else {
|
||||
paths.unshift(parent.$basePath);
|
||||
}
|
||||
parent = parent.__parent || 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.__parent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns this sub-documents parent array.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
EmbeddedDocument.prototype.parentArray = function() {
|
||||
return this.__parentArray;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = EmbeddedDocument;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
/*!
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
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.Subdocument = require('./subdocument');
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* ObjectId type constructor
|
||||
*
|
||||
* ####Example
|
||||
*
|
||||
* var id = new mongoose.Types.ObjectId;
|
||||
*
|
||||
* @constructor ObjectId
|
||||
*/
|
||||
|
||||
var ObjectId = require('../drivers').ObjectId;
|
||||
|
||||
module.exports = ObjectId;
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
var Document = require('../document');
|
||||
var PromiseProvider = require('../promise_provider');
|
||||
|
||||
module.exports = Subdocument;
|
||||
|
||||
/**
|
||||
* Subdocument constructor.
|
||||
*
|
||||
* @inherits Document
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Subdocument(value, fields, parent, skipId, options) {
|
||||
this.$isSingleNested = true;
|
||||
Document.call(this, value, fields, skipId, options);
|
||||
}
|
||||
|
||||
Subdocument.prototype = Object.create(Document.prototype);
|
||||
|
||||
Subdocument.prototype.toBSON = function() {
|
||||
return this.toObject({
|
||||
transform: false,
|
||||
virtuals: false,
|
||||
_skipDepopulateTopLevel: true,
|
||||
depopulate: true,
|
||||
flattenDecimals: false
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
Subdocument.prototype.save = function(fn) {
|
||||
var Promise = PromiseProvider.get();
|
||||
return new Promise.ES6(function(resolve) {
|
||||
fn && fn();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
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.$markValid = function(path) {
|
||||
Document.prototype.$markValid.call(this, path);
|
||||
if (this.$parent && this.$basePath) {
|
||||
this.$parent.$markValid([this.$basePath, path].join('.'));
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the top level document of this sub-document.
|
||||
*
|
||||
* @return {Document}
|
||||
*/
|
||||
|
||||
Subdocument.prototype.ownerDocument = function() {
|
||||
if (this.$__.ownerDocument) {
|
||||
return this.$__.ownerDocument;
|
||||
}
|
||||
|
||||
var parent = this.$parent;
|
||||
if (!parent) {
|
||||
return this;
|
||||
}
|
||||
|
||||
while (parent.$parent || parent.__parent) {
|
||||
parent = parent.$parent || parent.__parent;
|
||||
}
|
||||
this.$__.ownerDocument = parent;
|
||||
return this.$__.ownerDocument;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns this sub-documents parent document.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Subdocument.prototype.parent = function() {
|
||||
return this.$parent;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 {EmbeddedDocument} sub
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function registerRemoveListener(sub) {
|
||||
var 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);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user