This commit is contained in:
root
2019-11-28 20:40:02 +00:00
commit 1c78566f5b
2275 changed files with 272351 additions and 0 deletions
+26
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
File diff suppressed because it is too large Load Diff
+27
View File
@@ -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
View File
@@ -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
View File
@@ -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;
File diff suppressed because it is too large Load Diff
+30
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,4 @@
# Driver Spec
TODO
@@ -0,0 +1,5 @@
/*!
* ignore
*/
module.exports = function() {};
+12
View File
@@ -0,0 +1,12 @@
/*!
* Module dependencies.
*/
var Binary = require('bson').Binary;
/*!
* Module exports.
*/
module.exports = exports = Binary;
@@ -0,0 +1,5 @@
/*!
* ignore
*/
module.exports = require('bson').Decimal128;
+8
View File
@@ -0,0 +1,8 @@
/*!
* Module exports.
*/
exports.Binary = require('./binary');
exports.Decimal128 = require('./decimal128');
exports.ObjectId = require('./objectid');
exports.ReadPreference = require('./ReadPreference');
+14
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,5 @@
/*!
* ignore
*/
module.exports = require('./browser');
@@ -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);
};
@@ -0,0 +1,8 @@
/*!
* Module dependencies.
*/
var Binary = require('mongodb').Binary;
module.exports = exports = Binary;
@@ -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;
@@ -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;
@@ -0,0 +1,5 @@
/*!
* ignore
*/
module.exports = require('mongodb').Decimal128;
@@ -0,0 +1,8 @@
/*!
* Module exports.
*/
exports.Binary = require('./binary');
exports.Decimal128 = require('./decimal128');
exports.ObjectId = require('./objectid');
exports.ReadPreference = require('./ReadPreference');
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+26
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+219
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+349
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
File diff suppressed because it is too large Load Diff
+87
View File
@@ -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
View File
@@ -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();
});
});
};
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
};
@@ -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);
};
@@ -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;
};
@@ -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;
};
@@ -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
View File
@@ -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 });
}
@@ -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;
};
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,11 @@
/**
* ObjectId type constructor
*
* ####Example
*
* var id = new mongoose.Types.ObjectId;
*
* @constructor ObjectId
*/
module.exports = require('../drivers').Decimal128;
+286
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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