diff options
Diffstat (limited to 'node_modules/mongoose/lib')
37 files changed, 9309 insertions, 0 deletions
diff --git a/node_modules/mongoose/lib/collection.js b/node_modules/mongoose/lib/collection.js new file mode 100644 index 0000000..c4d5e79 --- /dev/null +++ b/node_modules/mongoose/lib/collection.js @@ -0,0 +1,167 @@ + +/** + * Collection constructor + * + * @param {String} collection name + * @param {Collection} connection object + * @api public + */ + +function Collection (name, conn) { + this.name = name; + this.conn = conn; + this.buffer = true; + this.queue = []; + if (this.conn.readyState == 1) this.onOpen(); +}; + +/** + * The collection name + * + * @api public + */ + +Collection.prototype.name; + +/** + * The Connection instance + * + * @api public + */ + +Collection.prototype.conn; + +/** + * Called when the database connects + * + * @api private + */ + +Collection.prototype.onOpen = function () { + var self = this; + this.buffer = false; + self.doQueue(); +}; + +/** + * Called when the database disconnects + * + * @api private + */ + +Collection.prototype.onClose = function () { + this.buffer = true; +}; + +/** + * Adds a callback to the queue + * + * @param {String} method name + * @param {Array} arguments + * @api private + */ + +Collection.prototype.addQueue = function (name, args) { + this.queue.push([name, args]); + return this; +}; + +/** + * Executes the current queue + * + * @api private + */ + +Collection.prototype.doQueue = function () { + for (var i = 0, l = this.queue.length; i < l; i++){ + this[this.queue[i][0]].apply(this, this.queue[i][1]); + } + this.queue = []; + return this; +}; + +/** + * Ensure index function + * + * @api private + */ + +Collection.prototype.ensureIndex = function(){ + throw new Error('Collection#ensureIndex unimplemented by driver'); +}; + +/** + * FindAndModify command + * + * @api private + */ + +Collection.prototype.findAndModify = function(){ + throw new Error('Collection#findAndModify unimplemented by driver'); +}; + +/** + * FindOne command + * + * @api private + */ + +Collection.prototype.findOne = function(){ + throw new Error('Collection#findOne unimplemented by driver'); +}; + +/** + * Find command + * + * @api private + */ + +Collection.prototype.find = function(){ + throw new Error('Collection#find unimplemented by driver'); +}; + +/** + * Insert command + * + * @api private + */ + +Collection.prototype.insert = function(){ + throw new Error('Collection#insert unimplemented by driver'); +}; + +/** + * Update command + * + * @api private + */ + +Collection.prototype.save = function(){ + throw new Error('Collection#save unimplemented by driver'); +}; + +/** + * Insert command + * + * @api private + */ + +Collection.prototype.update = function(){ + throw new Error('Collection#update unimplemented by driver'); +}; + +/** + * getIndexes command + * + * @api private + */ + +Collection.prototype.getIndexes = function(){ + throw new Error('Collection#getIndexes unimplemented by driver'); +}; + +/** + * Module exports. + */ + +module.exports = Collection; diff --git a/node_modules/mongoose/lib/connection.js b/node_modules/mongoose/lib/connection.js new file mode 100644 index 0000000..37f7e2a --- /dev/null +++ b/node_modules/mongoose/lib/connection.js @@ -0,0 +1,517 @@ +/** + * Module dependencies. + */ + +var url = require('url') + , utils = require('./utils') + , EventEmitter = utils.EventEmitter + , driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native' + , Model = require('./model') + , Schema = require('./schema') + , Collection = require(driver + '/collection'); + +/** + * Connection constructor. For practical reasons, a Connection equals a Db + * + * @param {Mongoose} mongoose base + * @api public + */ + +function Connection (base) { + this.base = base; + this.collections = {}; + this.models = {}; +}; + +/** + * Inherit from EventEmitter. + * + */ + +Connection.prototype.__proto__ = EventEmitter.prototype; + +/** + * Connection ready state: + * 0 = Disconnected + * 1 = Connected + * 2 = Connecting + * 3 = Disconnecting + * + * @api public + */ + +Connection.prototype.readyState = 0; + +/** + * A hash of the collections associated with this connection + */ + +Connection.prototype.collections; + +/** + * The mongodb.Db instance, set when the connection is opened + * + * @api public + */ + +Connection.prototype.db; + +/** + * Establishes the connection + * + * `options` is a hash with the following optional properties: + * + * options.db - passed to the connection db instance + * options.server - passed to the connection server instance(s) + * options.replset - passed to the connection ReplSetServer instance + * options.user - username for authentication + * options.pass - password for authentication + * + * Notes: + * + * Mongoose forces the db option `forceServerObjectId` false and cannot + * be overridden. + * + * Mongoose defaults the server `auto_reconnect` options to true which + * can be overridden. + * + * See the node-mongodb-native driver instance for options that it + * understands. + * + * @param {String} mongodb://uri + * @return {Connection} self + * @see https://github.com/christkv/node-mongodb-native + * @api public + */ + +Connection.prototype.open = function (host, database, port, options, callback) { + var self = this + , uri; + + if ('string' === typeof database) { + switch (arguments.length) { + case 2: + port = 27017; + case 3: + switch (typeof port) { + case 'function': + callback = port, port = 27017; + break; + case 'object': + options = port, port = 27017; + break; + } + break; + case 4: + if ('function' === typeof options) + callback = options, options = {}; + } + } else { + switch (typeof database) { + case 'function': + callback = database, database = undefined; + break; + case 'object': + options = database; + database = undefined; + callback = port; + break; + } + + uri = url.parse(host); + host = uri.hostname; + port = uri.port || 27017; + database = uri.pathname && uri.pathname.replace(/\//g, ''); + } + + callback = callback || noop; + this.options = this.defaultOptions(options); + + // make sure we can open + if (0 !== this.readyState) { + var err = new Error('Trying to open unclosed connection.'); + err.state = this.readyState; + callback(err); + return this; + } + + if (!host) { + callback(new Error('Missing connection hostname.')); + return this; + } + + if (!database) { + callback(new Error('Missing connection database.')); + return this; + } + + // handle authentication + if (uri && uri.auth) { + var auth = uri.auth.split(':'); + this.user = auth[0]; + this.pass = auth[1]; + + // Check hostname for user/pass + } else if (/@/.test(host) && /:/.test(host.split('@')[0])) { + host = host.split('@'); + var auth = host.shift().split(':'); + host = host.pop(); + this.user = auth[0]; + this.pass = auth[1]; + + // user/pass options + } else if (options && options.user && options.pass) { + this.user = options.user; + this.pass = options.pass; + + } else { + this.user = this.pass = undefined; + } + + this.name = database; + this.host = host; + this.port = port; + + // signal connecting + this.readyState = 2; + this.emit('opening'); + + // open connection + this.doOpen(function (err) { + if (err) { + if (self._events && self._events.error && + ('function' == typeof self._events.error || self._events.error.length)) { + self.emit("error", err); + } + self.readyState = 0; + } else { + self.onOpen(); + } + + callback(err || null); + }); + + return this; +}; + +/** + * Connects to a replica set. + * + * Supply a comma-separted list of mongodb:// URIs. You only need to specify + * the database name and/or auth to one of them. + * + * The options parameter is passed to the low level connection. See the + * node-mongodb-native driver instance for detail. + * + * @param {String} comma-separated mongodb:// URIs + * @param {String} optional database name + * @param {Object} optional options + * @param {Function} optional callback + */ + +Connection.prototype.openSet = function (uris, database, options, callback) { + var uris = uris.split(',') + , self = this; + + switch (arguments.length) { + case 3: + this.name = database; + if ('function' === typeof options) callback = options, options = {}; + break; + case 2: + switch (typeof database) { + case 'string': + this.name = database; + case 'function': + callback = database, database = null; + break; + case 'object': + options = database, database = null; + break; + } + } + + this.options = options = this.defaultOptions(options); + callback = callback || noop; + + if (uris.length < 2) { + callback(new Error('Please provide comma-separated URIs')); + return this; + } + + this.host = []; + this.port = []; + + uris.forEach(function (uri) { + var uri = url.parse(uri); + + self.host.push(uri.hostname); + self.port.push(uri.port || 27017); + + if (!self.name && uri.pathname.replace(/\//g, '')) + self.name = uri.pathname.replace(/\//g, ''); + + if (!self.user && uri.auth) { + var auth = uri.auth.split(':'); + self.user = auth[0]; + self.pass = auth[1]; + } + }); + + if (!this.name) { + callback(new Error('No database name provided for replica set')); + return this; + } + + this.readyState = 2; + this.emit('opening'); + + // open connection + this.doOpenSet(function (err) { + if (err) { + if (self._events && self._events.error && self._events.error.length) { + self.emit("error", err); + } + self.readyState = 0; + } else { + self.onOpen(); + } + + callback(err || null); + }); +}; + +/** + * Called when the connection is opened + * + * @api private + */ + +Connection.prototype.onOpen = function () { + var self = this; + + function open () { + self.readyState = 1; + + // avoid having the collection subscribe to our event emitter + // to prevent 0.3 warning + for (var i in self.collections) + self.collections[i].onOpen(); + + self.emit('open'); + }; + + // re-authenticate + if (self.user && self.pass) + self.db.authenticate(self.user, self.pass, open); + else + open(); +}; + +/** + * Closes the connection + * + * @param {Function} optional callback + * @return {Connection} self + * @api public + */ + +Connection.prototype.close = function (callback) { + var self = this + , callback = callback || function(){}; + + switch (this.readyState){ + case 0: // disconnected + callback(null); + break; + + case 1: // connected + this.readyState = 3; + this.doClose(function(err){ + if (err){ + callback(err); + } else { + self.onClose(); + callback(null); + } + }); + break; + + case 2: // connecting + this.once('open', function(){ + self.close(callback); + }); + break; + + case 3: // disconnecting + this.once('close', function () { + callback(null); + }); + break; + } + + return this; +}; + +/** + * Called when the connection closes + * + * @api private + */ + +Connection.prototype.onClose = function () { + this.readyState = 0; + + // avoid having the collection subscribe to our event emitter + // to prevent 0.3 warning + for (var i in this.collections) + this.collections[i].onClose(); + + this.emit('close'); +}; + +/** + * Retrieves a collection, creating it if not cached. + * + * @param {String} collection name + * @return {Collection} collection instance + * @api public + */ + +Connection.prototype.collection = function (name) { + if (!(name in this.collections)) + this.collections[name] = new Collection(name, this); + return this.collections[name]; +}; + +/** + * Defines a model or retrieves it + * + * @param {String} model name + * @param {Schema} schema object + * @param {String} collection name (optional, induced from model name) + * @api public + */ + +Connection.prototype.model = function (name, schema, collection) { + if (!this.models[name]) { + var model = this.base.model(name, schema, collection, true) + , Model + + if (this != model.prototype.connection) { + // subclass model using this connection and collection name + Model = function Model () { + model.apply(this, arguments); + }; + + Model.__proto__ = model; + Model.prototype.__proto__ = model.prototype; + Model.prototype.db = this; + + // collection name discovery + if ('string' === typeof schema) { + collection = schema; + } + + if (!collection) { + collection = model.prototype.schema.set('collection') || utils.toCollectionName(name); + } + + Model.prototype.collection = this.collection(collection); + Model.init(); + } + + this.models[name] = Model || model; + } + + return this.models[name]; +}; + +/** + * Set profiling level. + * + * @param {Int|String} level - Either off (0), slow (1), or all (2) + * @param {Int} [ms] If profiling `level` is set to 1, this determines + * the threshold in milliseconds above which queries + * will be logged. Defaults to 100. (optional) + * @param {Function} callback + * @api public + */ + +Connection.prototype.setProfiling = function (level, ms, callback) { + if (1 !== this.readyState) { + return this.on('open', this.setProfiling.bind(this, level, ms, callback)); + } + + if (!callback) callback = ms, ms = 100; + + var cmd = {}; + + switch (level) { + case 0: + case 'off': + cmd.profile = 0; + break; + case 1: + case 'slow': + cmd.profile = 1; + if ('number' !== typeof ms) { + ms = parseInt(ms, 10); + if (isNaN(ms)) ms = 100; + } + cmd.slowms = ms; + break; + case 2: + case 'all': + cmd.profile = 2; + break; + default: + return callback(new Error('Invalid profiling level: '+ level)); + } + + this.db.executeDbCommand(cmd, function (err, resp) { + if (err) return callback(err); + + var doc = resp.documents[0]; + + err = 1 === doc.ok + ? null + : new Error('Could not set profiling level to: '+ level) + + callback(err, doc); + }); +}; + +/** + * Prepares default connection options. + * + * @param {Object} options + * @api private + */ + +Connection.prototype.defaultOptions = function (options) { + var o = options || {}; + + o.server = o.server || {}; + + if (!('auto_reconnect' in o.server)) { + o.server.auto_reconnect = true; + } + + o.db = o.db || {}; + o.db.forceServerObjectId = false; + + return o; +} + +/** + * Noop. + */ + +function noop () {} + +/** + * Module exports. + */ + +module.exports = Connection; diff --git a/node_modules/mongoose/lib/document.js b/node_modules/mongoose/lib/document.js new file mode 100644 index 0000000..0c80765 --- /dev/null +++ b/node_modules/mongoose/lib/document.js @@ -0,0 +1,1223 @@ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , MongooseError = require('./error') + , MixedSchema = require('./schema/mixed') + , Schema = require('./schema') + , ValidatorError = require('./schematype').ValidatorError + , utils = require('./utils') + , clone = utils.clone + , isMongooseObject = utils.isMongooseObject + , inspect = require('util').inspect + , StateMachine = require('./statemachine') + , ActiveRoster = StateMachine.ctor('require', 'modify', 'init') + , deepEqual = utils.deepEqual + , hooks = require('hooks') + , DocumentArray + +/** + * Document constructor. + * + * @param {Object} values to set + * @api private + */ + +function Document (obj, fields) { + // node <0.4.3 bug + if (!this._events) this._events = {}; + this.setMaxListeners(0); + + this._strictMode = this.schema.options && this.schema.options.strict; + + if ('boolean' === typeof fields) { + this._strictMode = fields; + fields = undefined; + } else { + this._selected = fields; + } + + this._doc = this.buildDoc(fields); + this._activePaths = new ActiveRoster(); + var self = this; + this.schema.requiredPaths.forEach(function (path) { + self._activePaths.require(path); + }); + + this._saveError = null; + this._validationError = null; + this.isNew = true; + + if (obj) this.set(obj, undefined, true); + + this._registerHooks(); + this.doQueue(); + + this.errors = undefined; + this._shardval = undefined; +}; + +/** + * Inherit from EventEmitter. + */ + +Document.prototype.__proto__ = EventEmitter.prototype; + +/** + * Base Mongoose instance for the model. Set by the Mongoose instance upon + * pre-compilation. + * + * @api public + */ + +Document.prototype.base; + +/** + * Document schema as a nested structure. + * + * @api public + */ + +Document.prototype.schema; + +/** + * Whether the document is new. + * + * @api public + */ + +Document.prototype.isNew; + +/** + * Validation errors. + * + * @api public + */ + +Document.prototype.errors; + +/** + * Builds the default doc structure + * + * @api private + */ + +Document.prototype.buildDoc = function (fields) { + var doc = {} + , self = this + , exclude + , keys + , key + , ki + + // determine if this doc is a result of a query with + // excluded fields + if (fields && 'Object' === fields.constructor.name) { + keys = Object.keys(fields); + ki = keys.length; + + while (ki--) { + if ('_id' !== keys[ki]) { + exclude = 0 === fields[keys[ki]]; + break; + } + } + } + + var paths = Object.keys(this.schema.paths) + , plen = paths.length + , ii = 0 + + for (; ii < plen; ++ii) { + var p = paths[ii] + , type = this.schema.paths[p] + , path = p.split('.') + , len = path.length + , last = len-1 + , doc_ = doc + , i = 0 + + for (; i < len; ++i) { + var piece = path[i] + , def + + if (i === last) { + if (fields) { + if (exclude) { + // apply defaults to all non-excluded fields + if (p in fields) continue; + + def = type.getDefault(self); + if ('undefined' !== typeof def) doc_[piece] = def; + + } else { + // do nothing. only the fields specified in + // the query will be populated + } + } else { + def = type.getDefault(self); + if ('undefined' !== typeof def) doc_[piece] = def; + } + } else { + doc_ = doc_[piece] || (doc_[piece] = {}); + } + } + }; + + return doc; +}; + +/** + * Inits (hydrates) the document without setters. + * + * Called internally after a document is returned + * from mongodb. + * + * @param {Object} document returned by mongo + * @param {Function} callback + * @api private + */ + +Document.prototype.init = function (doc, fn) { + this.isNew = false; + + init(this, doc, this._doc); + this._storeShard(); + + this.emit('init'); + if (fn) fn(null); + return this; +}; + +/** + * Init helper. + * @param {Object} instance + * @param {Object} obj - raw mongodb doc + * @param {Object} doc - object we are initializing + * @private + */ + +function init (self, obj, doc, prefix) { + prefix = prefix || ''; + + var keys = Object.keys(obj) + , len = keys.length + , schema + , path + , i; + + while (len--) { + i = keys[len]; + path = prefix + i; + schema = self.schema.path(path); + + if (!schema && obj[i] && 'Object' === obj[i].constructor.name) { + // assume nested object + doc[i] = {}; + init(self, obj[i], doc[i], path + '.'); + } else { + if (obj[i] === null) { + doc[i] = null; + } else if (obj[i] !== undefined) { + if (schema) { + self.try(function(){ + doc[i] = schema.cast(obj[i], self, true); + }); + } else { + doc[i] = obj[i]; + } + } + // mark as hydrated + self._activePaths.init(path); + } + } +}; + +/** + * _storeShard + * + * Stores the current values of the shard keys + * for use later in the doc.save() where clause. + * + * Shard key values do not / are not allowed to change. + * + * @param {Object} document + * @private + */ + +Document.prototype._storeShard = function _storeShard () { + var key = this.schema.options.shardkey; + if (!(key && 'Object' == key.constructor.name)) 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 (isMongooseObject(val)) { + orig[paths[i]] = val.toObject({ depopulate: true }) + } else if (val.valueOf) { + orig[paths[i]] = val.valueOf(); + } else { + orig[paths[i]] = val; + } + } +} + +// Set up middleware support +for (var k in hooks) { + Document.prototype[k] = Document[k] = hooks[k]; +} + +/** + * Sets a path, or many paths + * + * Examples: + * // path, value + * doc.set(path, value) + * + * // object + * doc.set({ + * path : value + * , path2 : { + * path : value + * } + * } + * + * @param {String|Object} key path, or object + * @param {Object} value, or undefined or a prefix if first parameter is an object + * @param @optional {Schema|String|...} specify a type if this is an on-the-fly attribute + * @api public + */ + +Document.prototype.set = function (path, val, type) { + var constructing = true === type + , adhoc = type && true !== type + , adhocs + + if (adhoc) { + adhocs = this._adhocPaths || (this._adhocPaths = {}); + adhocs[path] = Schema.interpretAsType(path, type); + } + + if ('string' !== typeof path) { + // new Document({ key: val }) + + if (null === path || undefined === path) { + var _ = path; + path = val; + val = _; + + } else { + var prefix = val + ? val + '.' + : ''; + + if (path instanceof Document) path = path._doc; + + var keys = Object.keys(path) + , i = keys.length + , pathtype + , key + + while (i--) { + key = keys[i]; + if (null != path[key] && 'Object' === path[key].constructor.name + && !(this._path(prefix + key) instanceof MixedSchema)) { + this.set(path[key], prefix + key, constructing); + } else if (this._strictMode) { + pathtype = this.schema.pathType(prefix + key); + if ('real' === pathtype || 'virtual' === pathtype) { + this.set(prefix + key, path[key], constructing); + } + } else if (undefined !== path[key]) { + this.set(prefix + key, path[key], constructing); + } + } + + return this; + } + } + + var schema; + if ('virtual' === this.schema.pathType(path)) { + schema = this.schema.virtualpath(path); + schema.applySetters(val, this); + return this; + } else { + schema = this._path(path); + } + + var parts = path.split('.') + , obj = this._doc + , self = this + , pathToMark + , subpaths + , subpath + + // When using the $set operator the path to the field must already exist. + // Else mongodb throws: "LEFT_SUBFIELD only supports Object" + + if (parts.length <= 1) { + pathToMark = path; + } else { + subpaths = parts.map(function (part, i) { + return parts.slice(0, i).concat(part).join('.'); + }); + + for (var i = 0, l = subpaths.length; i < l; i++) { + subpath = subpaths[i]; + if (this.isDirectModified(subpath) // earlier prefixes that are already + // marked as dirty have precedence + || this.get(subpath) === null) { + pathToMark = subpath; + break; + } + } + + if (!pathToMark) pathToMark = path; + } + + if ((!schema || null === val || undefined === val) || + this.try(function(){ + // if this doc is being constructed we should not + // trigger getters. + var cur = constructing ? undefined : self.get(path); + var casted = schema.cast(val, self, false, cur); + val = schema.applySetters(casted, self); + })) { + + if (this.isNew) { + this.markModified(pathToMark); + } else { + var priorVal = this.get(path); + + if (!this.isDirectModified(pathToMark)) { + if (undefined === val && !this.isSelected(path)) { + // special case: + // when a path is not selected in a query its initial + // value will be undefined. + this.markModified(pathToMark); + } else if (!deepEqual(val, priorVal)) { + this.markModified(pathToMark); + } + } + } + + for (var i = 0, l = parts.length; i < l; i++) { + var next = i + 1 + , last = next === l; + + if (last) { + obj[parts[i]] = val; + } else { + if (obj[parts[i]] && 'Object' === obj[parts[i]].constructor.name) { + obj = obj[parts[i]]; + } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) { + obj = obj[parts[i]]; + } else { + obj = obj[parts[i]] = {}; + } + } + } + } + + return this; +}; + +/** + * Gets a raw value from a path (no getters) + * + * @param {String} path + * @api private + */ + +Document.prototype.getValue = function (path) { + var parts = path.split('.') + , obj = this._doc + , part; + + for (var i = 0, l = parts.length; i < l-1; i++) { + part = parts[i]; + path = convertIfInt(path); + obj = obj.getValue + ? obj.getValue(part) // If we have an embedded array document member + : obj[part]; + if (!obj) return obj; + } + + part = parts[l-1]; + path = convertIfInt(path); + + return obj.getValue + ? obj.getValue(part) // If we have an embedded array document member + : obj[part]; +}; + +function convertIfInt (string) { + if (/^\d+$/.test(string)) { + return parseInt(string, 10); + } + return string; +} + +/** + * Sets a raw value for a path (no casting, setters, transformations) + * + * @param {String} path + * @param {Object} value + * @api private + */ + +Document.prototype.setValue = function (path, val) { + var parts = path.split('.') + , obj = this._doc; + + for (var i = 0, l = parts.length; i < l-1; i++) { + obj = obj[parts[i]]; + } + + obj[parts[l-1]] = val; + return this; +}; + +/** + * Triggers casting on a specific path + * + * @todo - deprecate? not used anywhere + * @param {String} path + * @api public + */ + +Document.prototype.doCast = function (path) { + var schema = this.schema.path(path); + if (schema) + this.setValue(path, this.getValue(path)); +}; + +/** + * Gets a path + * + * @param {String} key path + * @param @optional {Schema|String|...} specify a type if this is an on-the-fly attribute + * @api public + */ + +Document.prototype.get = function (path, type) { + var adhocs; + if (type) { + adhocs = this._adhocPaths || (this._adhocPaths = {}); + adhocs[path] = Schema.interpretAsType(path, type); + } + + var schema = this._path(path) || this.schema.virtualpath(path) + , pieces = path.split('.') + , obj = this._doc; + + for (var i = 0, l = pieces.length; i < l; i++) { + obj = null == obj ? null : obj[pieces[i]]; + } + + if (schema) { + obj = schema.applyGetters(obj, this); + } + + return obj; +}; + +/** + * Finds the path in the ad hoc type schema list or + * in the schema's list of type schemas + * @param {String} path + * @api private + */ + +Document.prototype._path = function (path) { + var adhocs = this._adhocPaths + , adhocType = adhocs && adhocs[path]; + + if (adhocType) { + return adhocType; + } else { + return this.schema.path(path); + } +}; + +/** + * Commits a path, marking as modified if needed. Useful for mixed keys + * + * @api public + */ + +Document.prototype.commit = +Document.prototype.markModified = function (path) { + this._activePaths.modify(path); +}; + +/** + * Captures an exception that will be bubbled to `save` + * + * @param {Function} function to execute + * @param {Object} scope + */ + +Document.prototype.try = function (fn, scope) { + var res; + try { + fn.call(scope); + res = true; + } catch (e) { + this.error(e); + res = false; + } + return res; +}; + +/** + * modifiedPaths + * + * Returns the list of paths that have been modified. + * + * If we set `documents.0.title` to 'newTitle' + * then `documents`, `documents.0`, and `documents.0.title` + * are modified. + * + * @api public + * @returns Boolean + */ + +Document.prototype.__defineGetter__('modifiedPaths', function () { + var directModifiedPaths = Object.keys(this._activePaths.states.modify); + + return directModifiedPaths.reduce(function (list, path) { + var parts = path.split('.'); + return list.concat(parts.reduce(function (chains, part, i) { + return chains.concat(parts.slice(0, i).concat(part).join('.')); + }, [])); + }, []); +}); + +/** + * Checks if a path or any full path containing path as part of + * its path chain has been directly modified. + * + * e.g., if we set `documents.0.title` to 'newTitle' + * then we have directly modified `documents.0.title` + * but not directly modified `documents` or `documents.0`. + * Nonetheless, we still say `documents` and `documents.0` + * are modified. They just are not considered direct modified. + * The distinction is important because we need to distinguish + * between what has been directly modified and what hasn't so + * that we can determine the MINIMUM set of dirty data + * that we want to send to MongoDB on a Document save. + * + * @param {String} path + * @returns Boolean + * @api public + */ + +Document.prototype.isModified = function (path) { + return !!~this.modifiedPaths.indexOf(path); +}; + +/** + * Checks if a path has been directly set and modified. False if + * the path is only part of a larger path that was directly set. + * + * e.g., if we set `documents.0.title` to 'newTitle' + * then we have directly modified `documents.0.title` + * but not directly modified `documents` or `documents.0`. + * Nonetheless, we still say `documents` and `documents.0` + * are modified. They just are not considered direct modified. + * The distinction is important because we need to distinguish + * between what has been directly modified and what hasn't so + * that we can determine the MINIMUM set of dirty data + * that we want to send to MongoDB on a Document save. + * + * @param {String} path + * @returns Boolean + * @api public + */ + +Document.prototype.isDirectModified = function (path) { + return (path in this._activePaths.states.modify); +}; + +/** + * Checks if a certain path was initialized + * + * @param {String} path + * @returns Boolean + * @api public + */ + +Document.prototype.isInit = function (path) { + return (path in this._activePaths.states.init); +}; + +/** + * Checks if a path was selected. + * @param {String} path + * @return Boolean + * @api public + */ + +Document.prototype.isSelected = function isSelected (path) { + if (this._selected) { + + if ('_id' === path) { + return 0 !== this._selected._id; + } + + var paths = Object.keys(this._selected) + , i = paths.length + , inclusive = false + , cur + + while (i--) { + cur = paths[i]; + if ('_id' == cur) continue; + inclusive = !! this._selected[cur]; + break; + } + + if (path in this._selected) { + return inclusive; + } + + i = paths.length; + + while (i--) { + cur = paths[i]; + if ('_id' == cur) continue; + + if (0 === cur.indexOf(path + '.')) { + return inclusive; + } + + if (0 === (path + '.').indexOf(cur)) { + return inclusive; + } + } + + return ! inclusive; + } + + return true; +} + +/** + * Validation middleware + * + * @param {Function} next + * @api public + */ + +Document.prototype.validate = function (next) { + var total = 0 + , self = this + , validating = {} + + if (!this._activePaths.some('require', 'init', 'modify')) { + return complete(); + } + + function complete () { + next(self._validationError); + self._validationError = null; + } + + this._activePaths.forEach('require', 'init', 'modify', function validatePath (path) { + if (validating[path]) return; + + validating[path] = true; + total++; + + process.nextTick(function(){ + var p = self.schema.path(path); + if (!p) return --total || complete(); + + p.doValidate(self.getValue(path), function (err) { + if (err) self.invalidate(path, err); + --total || complete(); + }, self); + }); + }); + + return this; +}; + +/** + * Marks a path as invalid, causing a subsequent validation to fail. + * + * @param {String} path of the field to invalidate + * @param {String/Error} error of the path. + * @api public + */ + +Document.prototype.invalidate = function (path, err) { + if (!this._validationError) { + this._validationError = new ValidationError(this); + } + + if (!err || 'string' === typeof err) { + err = new ValidatorError(path, err); + } + + this._validationError.errors[path] = err; +} + +/** + * Resets the atomics and modified states of this document. + * + * @private + * @return {this} + */ + +Document.prototype._reset = function reset () { + var self = this; + DocumentArray || (DocumentArray = require('./types/documentarray')); + + this._activePaths + .map('init', 'modify', function (i) { + return self.getValue(i); + }) + .filter(function (val) { + return (val && val instanceof DocumentArray && val.length); + }) + .forEach(function (array) { + array.forEach(function (doc) { + doc._reset(); + }); + }); + + // clear atomics + this._dirty().forEach(function (dirt) { + var type = dirt.value; + if (type && type._path && type.doAtomics) { + type._atomics = {}; + } + }); + + // Clear 'modify'('dirty') cache + this._activePaths.clear('modify'); + var self = this; + this.schema.requiredPaths.forEach(function (path) { + self._activePaths.require(path); + }); + + return this; +} + +/** + * Returns the dirty paths / vals + * + * @api private + */ + +Document.prototype._dirty = function _dirty () { + var self = this; + + var all = this._activePaths.map('modify', function (path) { + return { path: path + , value: self.getValue(path) + , schema: self._path(path) }; + }); + + // Sort dirty paths in a flat hierarchy. + all.sort(function (a, b) { + return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0)); + }); + + // Ignore "foo.a" if "foo" is dirty already. + var minimal = [] + , lastReference = null; + + all.forEach(function (item, i) { + if (item.path.indexOf(lastReference) !== 0) { + lastReference = item.path + '.'; + minimal.push(item); + } + }); + + return minimal; +} + +/** + * Returns if the document has been modified + * + * @return {Boolean} + * @api public + */ + +Document.prototype.__defineGetter__('modified', function () { + return this._activePaths.some('modify'); +}); + +/** + * Compiles schemas. + * @api private + */ + +function compile (tree, proto, prefix) { + var keys = Object.keys(tree) + , i = keys.length + , limb + , key; + + while (i--) { + key = keys[i]; + limb = tree[key]; + + define(key + , (('Object' === limb.constructor.name + && Object.keys(limb).length) + && (!limb.type || limb.type.type) + ? limb + : null) + , proto + , prefix + , keys); + } +}; + +/** + * Defines the accessor named prop on the incoming prototype. + * @api private + */ + +function define (prop, subprops, prototype, prefix, keys) { + var prefix = prefix || '' + , path = (prefix ? prefix + '.' : '') + prop; + + if (subprops) { + + Object.defineProperty(prototype, prop, { + enumerable: true + , get: function () { + if (!this.__getters) + this.__getters = {}; + + if (!this.__getters[path]) { + var nested = Object.create(this); + + // save scope for nested getters/setters + if (!prefix) nested._scope = this; + + // shadow inherited getters from sub-objects so + // thing.nested.nested.nested... doesn't occur (gh-366) + var i = 0 + , len = keys.length; + + for (; i < len; ++i) { + // over-write the parents getter without triggering it + Object.defineProperty(nested, keys[i], { + enumerable: false // It doesn't show up. + , writable: true // We can set it later. + , configurable: true // We can Object.defineProperty again. + , value: undefined // It shadows its parent. + }); + } + + nested.toObject = function () { + return this.get(path); + }; + + compile(subprops, nested, path); + this.__getters[path] = nested; + } + + return this.__getters[path]; + } + , set: function (v) { + return this.set(v, path); + } + }); + + } else { + + Object.defineProperty(prototype, prop, { + enumerable: true + , get: function ( ) { return this.get.call(this._scope || this, path); } + , set: function (v) { return this.set.call(this._scope || this, path, v); } + }); + } +}; + +/** + * We override the schema setter to compile accessors + * + * @api private + */ + +Document.prototype.__defineSetter__('schema', function (schema) { + compile(schema.tree, this); + this._schema = schema; +}); + +/** + * We override the schema getter to return the internal reference + * + * @api private + */ + +Document.prototype.__defineGetter__('schema', function () { + return this._schema; +}); + +/** + * Register default hooks + * + * @api private + */ + +Document.prototype._registerHooks = function _registerHooks () { + if (!this.save) return; + + DocumentArray || (DocumentArray = require('./types/documentarray')); + + this.pre('save', function (next) { + // we keep the error semaphore to make sure we don't + // call `save` unnecessarily (we only need 1 error) + var subdocs = 0 + , error = false + , self = this; + + var arrays = this._activePaths + .map('init', 'modify', function (i) { + return self.getValue(i); + }) + .filter(function (val) { + return (val && val instanceof DocumentArray && val.length); + }); + + if (!arrays.length) + return next(); + + arrays.forEach(function (array) { + subdocs += array.length; + array.forEach(function (value) { + if (!error) + value.save(function (err) { + if (!error) { + if (err) { + error = true; + next(err); + } else + --subdocs || next(); + } + }); + }); + }); + }, function (err) { + this.db.emit('error', err); + }).pre('save', function checkForExistingErrors (next) { + if (this._saveError) { + next(this._saveError); + this._saveError = null; + } else { + next(); + } + }).pre('save', function validation (next) { + return this.validate(next); + }); +}; + +/** + * Registers an error + * + * @TODO underscore this method + * @param {Error} error + * @api private + */ + +Document.prototype.error = function (err) { + this._saveError = err; + return this; +}; + +/** + * Executes methods queued from the Schema definition + * + * @TODO underscore this method + * @api private + */ + +Document.prototype.doQueue = function () { + if (this.schema && this.schema.callQueue) + for (var i = 0, l = this.schema.callQueue.length; i < l; i++) { + this[this.schema.callQueue[i][0]].apply(this, this.schema.callQueue[i][1]); + } + return this; +}; + +/** + * Gets the document + * + * Available options: + * + * - getters: apply all getters (path and virtual getters) + * - virtuals: apply virtual getters (can override `getters` option) + * + * Example of only applying path getters: + * + * doc.toObject({ getters: true, virtuals: false }) + * + * Example of only applying virtual getters: + * + * doc.toObject({ virtuals: true }) + * + * Example of applying both path and virtual getters: + * + * doc.toObject({ getters: true }) + * + * @return {Object} plain object + * @api public + */ + +Document.prototype.toObject = function (options) { + options || (options = {}); + options.minimize = true; + + var ret = clone(this._doc, options); + + if (options.virtuals || options.getters && false !== options.virtuals) { + applyGetters(this, ret, 'virtuals'); + } + + if (options.getters) { + applyGetters(this, ret, 'paths'); + } + + return ret; +}; + +/** + * Applies virtuals properties to `json`. + * + * @param {Document} self + * @param {Object} json + * @param {String} either `virtuals` or `paths` + * @return json + * @private + */ + +function applyGetters (self, json, type) { + var schema = self.schema + , paths = Object.keys(schema[type]) + , i = paths.length + , path + + while (i--) { + path = paths[i]; + + var parts = path.split('.') + , plen = parts.length + , last = plen - 1 + , branch = json + , part + + for (var ii = 0; ii < plen; ++ii) { + part = parts[ii]; + if (ii === last) { + branch[part] = self.get(path); + } else { + branch = branch[part] || (branch[part] = {}); + } + } + } + + return json; +} + +/** + * JSON.stringify helper. + * + * Implicitly called when a document is passed + * to JSON.stringify() + * + * @return {Object} + * @api public + */ + +Document.prototype.toJSON = function (options) { + if ('undefined' === typeof options) options = {}; + options.json = true; + return this.toObject(options); +}; + +/** + * Helper for console.log + * + * @api public + */ + +Document.prototype.toString = +Document.prototype.inspect = function (options) { + return inspect(this.toObject(options)); +}; + +/** + * Returns true if the Document stores the same data as doc. + * @param {Document} doc to compare to + * @return {Boolean} + * @api public + */ + +Document.prototype.equals = function (doc) { + return this.get('_id') === doc.get('_id'); +}; + +/** + * Module exports. + */ + +module.exports = Document; + +/** + * Document Validation Error + */ + +function ValidationError (instance) { + MongooseError.call(this, "Validation failed"); + Error.captureStackTrace(this, arguments.callee); + this.name = 'ValidationError'; + this.errors = instance.errors = {}; +}; + +ValidationError.prototype.toString = function () { + return this.name + ': ' + Object.keys(this.errors).map(function (key) { + return String(this.errors[key]); + }, this).join(', '); +}; + +/** + * Inherits from MongooseError. + */ + +ValidationError.prototype.__proto__ = MongooseError.prototype; + +Document.ValidationError = ValidationError; + +/** + * Document Error + * + * @param text + */ + +function DocumentError () { + MongooseError.call(this, msg); + Error.captureStackTrace(this, arguments.callee); + this.name = 'DocumentError'; +}; + +/** + * Inherits from MongooseError. + */ + +DocumentError.prototype.__proto__ = MongooseError.prototype; + +exports.Error = DocumentError; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js new file mode 100644 index 0000000..52e6a03 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js @@ -0,0 +1,8 @@ + +/** + * Module dependencies. + */ + +var Binary = require('mongodb').BSONPure.Binary; + +module.exports = exports = Binary; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js new file mode 100644 index 0000000..06176da --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js @@ -0,0 +1,176 @@ + +/** + * Module dependencies. + */ + +var Collection = require('../../collection') + , NativeCollection = require('mongodb').Collection + , utils = require('../../utils') + +/** + * Native collection + * + * @api private + */ + +function MongooseCollection () { + Collection.apply(this, arguments); +}; + +/** + * Inherit from abstract Collection. + */ + +MongooseCollection.prototype.__proto__ = Collection.prototype; + +/** + * Called when the connection opens + * + * @api private + */ + +MongooseCollection.prototype.onOpen = function () { + var self = this; + this.conn.db.collection(this.name, function (err, collection) { + if (err) { + // likely a strict mode error + self.conn.emit('error', err); + } else { + self.collection = collection; + Collection.prototype.onOpen.call(self); + } + }); +}; + +/** + * Called when the connection closes + * + * @api private + */ + +MongooseCollection.prototype.onClose = function () { + Collection.prototype.onClose.call(this); +}; + +/** + * Copy the collection methods and make them subject to queues + */ + +for (var i in NativeCollection.prototype) + (function(i){ + MongooseCollection.prototype[i] = function () { + // BENCHMARKME: is it worth caching the prototype methods? probably + if (!this.buffer) { + var collection = this.collection + , args = arguments + , self = this; + + process.nextTick(function(){ + var debug = self.conn.base.options.debug; + + if (debug) { + if ('function' === typeof debug) { + debug.apply(debug + , [self.name, i].concat(utils.args(args, 0, args.length-1))); + } else { + console.error('\x1B[0;36mMongoose:\x1B[0m %s.%s(%s) %s %s %s' + , self.name + , i + , print(args[0]) + , print(args[1]) + , print(args[2]) + , print(args[3])) + } + } + + collection[i].apply(collection, args); + }); + } else { + this.addQueue(i, arguments); + } + }; + })(i); + +/** + * Debug print helper + * @private + */ + +function print (arg) { + var type = typeof arg; + if ('function' === type || 'undefined' === type) return ''; + return format(arg); +} + +/** + * Debug print helper + * @private + */ + +function format (obj, sub) { + var x = utils.clone(obj); + if (x) { + if ('Binary' === x.constructor.name) { + x = '[object Buffer]'; + } else if ('Object' === x.constructor.name) { + var keys = Object.keys(x) + , i = keys.length + , key + while (i--) { + key = keys[i]; + if (x[key]) { + if ('Binary' === x[key].constructor.name) { + x[key] = '[object Buffer]'; + } else if ('Object' === x[key].constructor.name) { + x[key] = format(x[key], true); + } else if (Array.isArray(x[key])) { + x[key] = x[key].map(function (o) { + return format(o, true) + }); + } + } + } + } + if (sub) return x; + } + + return require('util') + .inspect(x, false, 10, true) + .replace(/\n/g, '') + .replace(/\s{2,}/g, ' ') +} + +/** + * Implement getIndexes + * + * @param {Function} callback + * @api private + */ + +MongooseCollection.prototype.getIndexes = +MongooseCollection.prototype.indexInformation; + +/** + * Override signature of ensureIndex. -native one is not standard. + * + * @param {Object} spec + * @param {Object} options + * @param {Function} callback + * @api public + */ + +var oldEnsureIndex = NativeCollection.prototype.ensureIndex; + +function noop () {}; + +NativeCollection.prototype.ensureIndex = function(fields, options, fn){ + if (!this.buffer) { + return oldEnsureIndex.apply(this, arguments); + } +}; + +/** + * Module exports. + */ + +module.exports = MongooseCollection; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js new file mode 100644 index 0000000..3becd1b --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js @@ -0,0 +1,106 @@ +/** + * Module dependencies. + */ + +var Connection = require('../../connection') + , mongo = require('mongodb') + , Server = mongo.Server + , ReplSetServers = mongo.ReplSetServers; + +/** + * Connection for mongodb-native driver + * + * @api private + */ + +function NativeConnection() { + Connection.apply(this, arguments); +}; + +/** + * Inherits from Connection. + */ + +NativeConnection.prototype.__proto__ = Connection.prototype; + +/** + * Opens the connection. + * + * Example server options: + * auto_reconnect (default: false) + * poolSize (default: 1) + * + * Example db options: + * pk - custom primary key factory to generate `_id` values + * + * Some of these may break Mongoose. Use at your own risk. You have been warned. + * + * @param {Function} callback + * @api private + */ + +NativeConnection.prototype.doOpen = function (fn) { + var server; + + if (!this.db) { + server = new mongo.Server(this.host, Number(this.port), this.options.server); + this.db = new mongo.Db(this.name, server, this.options.db); + } + + this.db.open(fn); + + return this; +}; + +/** + * Opens a set connection + * + * See description of doOpen for server options. In this case options.replset + * is also passed to ReplSetServers. Some additional options there are + * + * reconnectWait (default: 1000) + * retries (default: 30) + * rs_name (default: false) + * read_secondary (default: false) Are reads allowed from secondaries? + * + * @param {Function} fn + * @api private + */ + +NativeConnection.prototype.doOpenSet = function (fn) { + if (!this.db) { + var servers = [] + , ports = this.port + , self = this + + this.host.forEach(function (host, i) { + servers.push(new mongo.Server(host, Number(ports[i]), self.options.server)); + }); + + var server = new ReplSetServers(servers, this.options.replset); + this.db = new mongo.Db(this.name, server, this.options.db); + } + + this.db.open(fn); + + return this; +}; + +/** + * Closes the connection + * + * @param {Function} callback + * @api private + */ + +NativeConnection.prototype.doClose = function (fn) { + this.db.close(); + if (fn) fn(); + return this; +} + +/** + * Module exports. + */ + +module.exports = NativeConnection; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js new file mode 100644 index 0000000..ee6d598 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js @@ -0,0 +1,43 @@ + +/** + * Module dependencies. + */ + +var ObjectId = require('mongodb').BSONPure.ObjectID; + +/** + * Constructor export + * + * @api private + */ + +var ObjectIdToString = ObjectId.toString.bind(ObjectId); + +module.exports = exports = ObjectId; +/** + * Creates an ObjectID for this driver + * + * @param {Object} hex string or ObjectId + * @api private + */ + +exports.fromString = function(str){ + // patch native driver bug in V0.9.6.4 + if (!('string' === typeof str && 24 === str.length)) { + throw new Error("Invalid ObjectId"); + } + + return ObjectId.createFromHexString(str); +}; + +/** + * Gets an ObjectId and converts it to string. + * + * @param {ObjectId} -native objectid + * @api private + */ + +exports.toString = function(oid){ + if (!arguments.length) return ObjectIdToString(); + return oid.toHexString(); +}; diff --git a/node_modules/mongoose/lib/error.js b/node_modules/mongoose/lib/error.js new file mode 100644 index 0000000..bd4ee61 --- /dev/null +++ b/node_modules/mongoose/lib/error.js @@ -0,0 +1,25 @@ + +/** + * Mongoose error + * + * @api private + */ + +function MongooseError (msg) { + Error.call(this); + Error.captureStackTrace(this, arguments.callee); + this.message = msg; + this.name = 'MongooseError'; +}; + +/** + * Inherits from Error. + */ + +MongooseError.prototype.__proto__ = Error.prototype; + +/** + * Module exports. + */ + +module.exports = MongooseError; diff --git a/node_modules/mongoose/lib/index.js b/node_modules/mongoose/lib/index.js new file mode 100644 index 0000000..6fd449e --- /dev/null +++ b/node_modules/mongoose/lib/index.js @@ -0,0 +1,368 @@ + +/** + * Module dependencies. + */ + +var Schema = require('./schema') + , SchemaType = require('./schematype') + , VirtualType = require('./virtualtype') + , SchemaTypes = Schema.Types + , SchemaDefaults = require('./schemadefault') + , Types = require('./types') + , Query = require('./query') + , Promise = require('./promise') + , Model = require('./model') + , Document = require('./document') + , utils = require('./utils'); + +/** + * Mongoose constructor. Most apps will only use one instance. + * + * @api public + */ + +function Mongoose () { + this.connections = []; + this.plugins = []; + this.models = {}; + this.modelSchemas = {}; + this.options = {}; + this.createConnection(); // default connection +}; + +/** + * Sets/gets mongoose options + * + * Examples: + * mongoose.set('test') // returns the 'test' value + * mongoose.set('test', value) // sets the 'test' value + * + * @param {String} key + * @param {String} value + * @api public + */ + +Mongoose.prototype.set = +Mongoose.prototype.get = function (key, value) { + if (arguments.length == 1) + return this.options[key]; + this.options[key] = value; + return this; +}; + +/** + * Creates a Connection instance. + * + * Examples: + * + * // with mongodb:// URI + * db = mongoose.createConnection('mongodb://localhost:port/database'); + * + * // with [host, database_name[, port] signature + * db = mongoose.createConnection('localhost', 'database', port) + * + * // initialize now, connect later + * db = mongoose.createConnection(); + * db.open('localhost', 'database', port); + * + * @param {String} mongodb:// URI + * @return {Connection} the created Connection object + * @api public + */ + +Mongoose.prototype.createConnection = function () { + var conn = new Connection(this); + this.connections.push(conn); + if (arguments.length) + conn.open.apply(conn, arguments); + return conn; +}; + +/** + * Creates a replica set connection + * + * @see {Mongoose#createConnection} + * @api public + */ + +Mongoose.prototype.createSetConnection = function () { + var conn = new Connection(this); + this.connections.push(conn); + if (arguments.length) + conn.openSet.apply(conn, arguments); + return conn; +}; + +/** + * Connects the default mongoose connection + * + * @see {Mongoose#createConnection} + * @api public + */ + +Mongoose.prototype.connect = function (){ + this.connection.open.apply(this.connection, arguments); + return this; +}; + +/** + * Connects the default mongoose connection to a replica set + * + * @see {Mongoose#createConnection} + * @api public + */ + +Mongoose.prototype.connectSet = function (){ + this.connection.openSet.apply(this.connection, arguments); + return this; +}; + +/** + * Disconnects from all connections. + * + * @param {Function} optional callback + * @api public + */ + +Mongoose.prototype.disconnect = function (fn) { + var count = this.connections.length; + this.connections.forEach(function(conn){ + conn.close(function(err){ + if (err) return fn(err); + if (fn) + --count || fn(); + }); + }); + return this; +}; + +/** + * Defines a model or retrieves it + * + * @param {String} model name + * @param {Schema} schema object + * @param {String} collection name (optional, induced from model name) + * @param {Boolean} whether to skip initialization (defaults to false) + * @api public + */ + +Mongoose.prototype.model = function (name, schema, collection, skipInit) { + // normalize collection + if (!(schema instanceof Schema)) { + collection = schema; + schema = false; + } + + if ('boolean' === typeof collection) { + skipInit = collection; + collection = null; + } + + // look up models for the collection + if (!this.modelSchemas[name]) { + if (!schema && name in SchemaDefaults) { + schema = SchemaDefaults[name]; + } + + if (schema) { + this.modelSchemas[name] = schema; + for (var i = 0, l = this.plugins.length; i < l; i++) { + schema.plugin(this.plugins[i][0], this.plugins[i][1]); + } + } else { + throw new Error('Schema hasn\'t been registered for model "' + name + '".\n' + + 'Use mongoose.model(name, schema)'); + } + } + + if (!schema) { + schema = this.modelSchemas[name]; + } + + if (!collection) { + collection = schema.set('collection') || utils.toCollectionName(name); + } + + if (!this.models[name]) { + var model = Model.compile(name + , this.modelSchemas[name] + , collection + , this.connection + , this); + + if (!skipInit) model.init(); + + this.models[name] = model; + } + + return this.models[name]; +}; + +/** + * Declares a plugin executed on Schemas. Equivalent to calling `.plugin(fn)` + * on each Schema you create. + * + * @param {Function} plugin callback + * @api public + */ + +Mongoose.prototype.plugin = function (fn, opts) { + this.plugins.push([fn, opts]); + return this; +}; + +/** + * Default connection + * + * @api public + */ + +Mongoose.prototype.__defineGetter__('connection', function(){ + return this.connections[0]; +}); + +/** + * Driver depentend APIs + */ + +var driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native'; + +/** + * Connection + * + * @api public + */ + +var Connection = require(driver + '/connection'); + +/** + * Collection + * + * @api public + */ + +var Collection = require(driver + '/collection'); + +/** + * Export default singleton. + * + * @api public + */ + +module.exports = exports = new Mongoose(); + +/** + * Collection + * + * @api public + */ + +exports.Collection = Collection; + +/** + * Connection + * + * @api public + */ + +exports.Connection = Connection; + +/** + * Exports Mongoose version + * + * @param version + */ + +exports.version = JSON.parse( + require('fs').readFileSync(__dirname + '/../package.json', 'utf8') +).version; + +/** + * Export Mongoose constructor + * + * @api public + */ + +exports.Mongoose = Mongoose; + +/** + * Export Schema constructor + * + * @api public + */ + +exports.Schema = Schema; + +/** + * Export SchemaType constructor. + * + * @api public + */ + +exports.SchemaType = SchemaType; + +/** + * Export VirtualType constructor. + * + * @api public + */ + +exports.VirtualType = VirtualType; + +/** + * Export Schema types + * + * @api public + */ + +exports.SchemaTypes = SchemaTypes; + +/** + * Export types + * + * @api public + */ + +exports.Types = Types; + +/** + * Export Query + * + * @api public + */ + +exports.Query = Query; + +/** + * Export Promise + * + * @api public + */ + +exports.Promise = Promise; + +/** + * Export Model constructor + * + * @api public + */ + +exports.Model = Model; + +/** + * Export Document constructor + * + * @api public + */ + +exports.Document = Document; + +/** + * Export MongooseError + * + * @api public + */ + +exports.Error = require('./error'); + +exports.mongo = require('mongodb'); diff --git a/node_modules/mongoose/lib/model.js b/node_modules/mongoose/lib/model.js new file mode 100644 index 0000000..75bfda3 --- /dev/null +++ b/node_modules/mongoose/lib/model.js @@ -0,0 +1,917 @@ + +/** + * Module dependencies. + */ + +var Document = require('./document') + , MongooseArray = require('./types/array') + , MongooseBuffer = require('./types/buffer') + , MongooseError = require('./error') + , Query = require('./query') + , utils = require('./utils') + , isMongooseObject = utils.isMongooseObject + , EventEmitter = utils.EventEmitter + , merge = utils.merge + , Promise = require('./promise') + , tick = utils.tick + +/** + * Model constructor + * + * @param {Object} values to set + * @api public + */ + +function Model (doc, fields) { + Document.call(this, doc, fields); +}; + +/** + * Inherits from Document. + */ + +Model.prototype.__proto__ = Document.prototype; + +/** + * Connection the model uses. Set by the Connection or if absent set to the + * default mongoose connection; + * + * @api public + */ + +Model.prototype.db; + +/** + * Collection the model uses. Set by Mongoose instance + * + * @api public + */ + +Model.prototype.collection; + +/** + * Model name. + * + * @api public + */ + +Model.prototype.modelName; + +/** + * Returns what paths can be populated + * + * @param {query} query object + * @return {Object] population paths + * @api private + */ + +Model.prototype._getPopulationKeys = function getPopulationKeys (query) { + if (!(query && query.options.populate)) return; + + var names = Object.keys(query.options.populate) + , n = names.length + , name + , paths = {} + , hasKeys + , schema + + while (n--) { + name = names[n]; + schema = this.schema.path(name); + hasKeys = true; + + if (!schema) { + // if the path is not recognized, it's potentially embedded docs + // walk path atoms from right to left to find a matching path + var pieces = name.split('.') + , i = pieces.length; + + while (i--) { + var path = pieces.slice(0, i).join('.') + , pathSchema = this.schema.path(path); + + // loop until we find an array schema + if (pathSchema && pathSchema.caster) { + if (!paths[path]) { + paths[path] = { sub: {} }; + } + + paths[path].sub[pieces.slice(i).join('.')] = query.options.populate[name]; + hasKeys || (hasKeys = true); + break; + } + } + } else { + paths[name] = query.options.populate[name]; + hasKeys || (hasKeys = true); + } + } + + return hasKeys && paths; +}; + +/** + * Populates an object + * + * @param {SchemaType} schema type for the oid + * @param {Object} object id or array of object ids + * @param {Object} object specifying query conditions, fields, and options + * @param {Function} callback + * @api private + */ + +Model.prototype._populate = function populate (schema, oid, query, fn) { + if (!Array.isArray(oid)) { + var conditions = query.conditions || {}; + conditions._id = oid; + + return this + .model(schema.options.ref) + .findOne(conditions, query.fields, query.options, fn); + } + + if (!oid.length) { + return fn(null, oid); + } + + var model = this.model(schema.caster.options.ref) + , conditions = query && query.conditions || {}; + conditions._id || (conditions._id = { $in: oid }); + + model.find(conditions, query.fields, query.options, function (err, docs) { + if (err) return fn(err); + + // user specified sort order? + if (query.options && query.options.sort) { + return fn(null, docs); + } + + // put back in original id order (using a hash reduces complexity from n*n to 2n) + var docHash = {}; + docs.forEach(function (doc) { + docHash[doc._id] = doc; + }); + + var arr = []; + oid.forEach(function (id) { + if (id in docHash) arr.push(docHash[id]); + }); + + fn(null, arr); + }); +}; + +/** + * Performs auto-population of relations. + * + * @param {Object} document returned by mongo + * @param {Query} query that originated the initialization + * @param {Function} callback + * @api private + */ + +Model.prototype.init = function init (doc, query, fn) { + if ('function' == typeof query) { + fn = query; + query = null; + } + + var populate = this._getPopulationKeys(query); + + if (!populate) { + return Document.prototype.init.call(this, doc, fn); + } + + // population from other models is necessary + var self = this; + + init(doc, '', function (err) { + if (err) return fn(err); + Document.prototype.init.call(self, doc, fn); + }); + + return this; + + function init (obj, prefix, fn) { + prefix = prefix || ''; + + var keys = Object.keys(obj) + , len = keys.length; + + function next () { + if (--len < 0) return fn(); + + var i = keys[len] + , path = prefix + i + , schema = self.schema.path(path) + , total = 0 + , poppath + + if (!schema && obj[i] && 'Object' === obj[i].constructor.name) { + // assume nested object + return init(obj[i], path + '.', next); + } + + if (!(obj[i] && schema && populate[path])) return next(); + + // this query object is re-used and passed around. we clone + // it to prevent query condition contamination between + // one populate call to the next. + poppath = utils.clone(populate[path]); + + if (poppath.sub) { + obj[i].forEach(function (subobj) { + var pkeys = Object.keys(poppath.sub) + , pi = pkeys.length + , key + + while (pi--) { + key = pkeys[pi]; + + if (subobj[key]) (function (key) { + + total++; + self._populate(schema.schema.path(key), subobj[key], poppath.sub[key], done); + function done (err, doc) { + if (err) return error(err); + subobj[key] = doc; + --total || next(); + } + })(key); + } + }); + + if (0 === total) return next(); + + } else { + self._populate(schema, obj[i], poppath, function (err, doc) { + if (err) return error(err); + obj[i] = doc; + next(); + }); + } + }; + + next(); + }; + + function error (err) { + if (error.err) return; + fn(error.err = err); + } +}; + +function handleSave (promise, self) { + return tick(function handleSave (err, result) { + if (err) return promise.error(err); + + self._storeShard(); + + var numAffected; + if (result) { + numAffected = result.length + ? result.length + : result; + } else { + numAffected = 0; + } + + self.emit('save', self, numAffected); + promise.complete(self, numAffected); + promise = null; + self = null; + }); +} + +/** + * Saves this document. + * + * @see Model#registerHooks + * @param {Function} fn + * @api public + */ + +Model.prototype.save = function save (fn) { + var promise = new Promise(fn) + , complete = handleSave(promise, this) + , options = {} + + if (this.options.safe) { + options.safe = this.options.safe; + } + + if (this.isNew) { + // send entire doc + this.collection.insert(this.toObject({ depopulate: 1 }), options, complete); + this._reset(); + this.isNew = false; + this.emit('isNew', false); + + } else { + var delta = this._delta(); + this._reset(); + + if (delta) { + var where = this._where(); + this.collection.update(where, delta, options, complete); + } else { + complete(null); + } + + this.emit('isNew', false); + } +}; + +/** + * Produces a special query document of the modified properties. + * @api private + */ + +Model.prototype._delta = function _delta () { + var dirty = this._dirty(); + + if (!dirty.length) return; + + var self = this + , useSet = this.options['use$SetOnSave']; + + return dirty.reduce(function (delta, data) { + var type = data.value + , schema = data.schema + , atomics + , val + , obj + + if (type === undefined) { + if (!delta.$unset) delta.$unset = {}; + delta.$unset[data.path] = 1; + + } else if (type === null) { + if (!delta.$set) delta.$set = {}; + delta.$set[data.path] = type; + + } else if (type._path && type.doAtomics) { + // a MongooseArray or MongooseNumber + atomics = type._atomics; + + var ops = Object.keys(atomics) + , i = ops.length + , op; + + while (i--) { + op = ops[i] + + if (op === '$pushAll' || op === '$pullAll') { + if (atomics[op].length === 1) { + val = atomics[op][0]; + delete atomics[op]; + op = op.replace('All', ''); + atomics[op] = val; + } + } + + val = atomics[op]; + obj = delta[op] = delta[op] || {}; + + if (op === '$pull' || op === '$push') { + if ('Object' !== val.constructor.name) { + if (Array.isArray(val)) val = [val]; + // TODO Should we place pull and push casting into the pull and push methods? + val = schema.cast(val)[0]; + } + } + + obj[data.path] = isMongooseObject(val) + ? val.toObject({ depopulate: 1 }) // MongooseArray + : Array.isArray(val) + ? val.map(function (mem) { + return isMongooseObject(mem) + ? mem.toObject({ depopulate: 1 }) + : mem.valueOf + ? mem.valueOf() + : mem; + }) + : val.valueOf + ? val.valueOf() // Numbers + : val; + + if ('$addToSet' === op) { + if (val.length > 1) { + obj[data.path] = { $each: obj[data.path] }; + } else { + obj[data.path] = obj[data.path][0]; + } + } + } + } else { + if (type instanceof MongooseArray || + type instanceof MongooseBuffer) { + type = type.toObject({ depopulate: 1 }); + } else if (type._path) { + type = type.valueOf(); + } else { + // nested object literal + type = utils.clone(type); + } + + if (useSet) { + if (!('$set' in delta)) + delta['$set'] = {}; + + delta['$set'][data.path] = type; + } else + delta[data.path] = type; + } + + return delta; + }, {}); +} + +/** + * _where + * + * Returns a query object which applies shardkeys if + * they exist. + * + * @private + */ + +Model.prototype._where = function _where () { + var where = {}; + + if (this._shardval) { + var paths = Object.keys(this._shardval) + , len = paths.length + + for (var i = 0; i < len; ++i) { + where[paths[i]] = this._shardval[paths[i]]; + } + } + + var id = this._doc._id.valueOf // MongooseNumber + ? this._doc._id.valueOf() + : this._doc._id; + + where._id = id; + return where; +} + +/** + * Remove the document + * + * @param {Function} callback + * @api public + */ + +Model.prototype.remove = function remove (fn) { + if (this._removing) return this; + + var promise = this._removing = new Promise(fn) + , where = this._where() + , self = this; + + this.collection.remove(where, tick(function (err) { + if (err) { + this._removing = null; + return promise.error(err); + } + promise.complete(); + self.emit('remove', self); + })); + + return this; +}; + +/** + * Register hooks override + * + * @api private + */ + +Model.prototype._registerHooks = function registerHooks () { + Document.prototype._registerHooks.call(this); +}; + +/** + * Shortcut to access another model. + * + * @param {String} model name + * @api public + */ + +Model.prototype.model = function model (name) { + return this.db.model(name); +}; + +/** + * Access the options defined in the schema + * + * @api private + */ + +Model.prototype.__defineGetter__('options', function () { + return this.schema ? this.schema.options : {}; +}); + +/** + * Give the constructor the ability to emit events. + */ + +for (var i in EventEmitter.prototype) + Model[i] = EventEmitter.prototype[i]; + +/** + * Called when the model compiles + * + * @api private + */ + +Model.init = function init () { + // build indexes + var self = this + , indexes = this.schema.indexes + , safe = this.schema.options.safe + , count = indexes.length; + + indexes.forEach(function (index) { + var options = index[1]; + options.safe = safe; + self.collection.ensureIndex(index[0], options, tick(function (err) { + if (err) return self.db.emit('error', err); + --count || self.emit('index'); + })); + }); + + this.schema.emit('init', this); +}; + +/** + * Document schema + * + * @api public + */ + +Model.schema; + +/** + * Database instance the model uses. + * + * @api public + */ + +Model.db; + +/** + * Collection the model uses. + * + * @api public + */ + +Model.collection; + +/** + * Define properties that access the prototype. + */ + +['db', 'collection', 'schema', 'options', 'model'].forEach(function(prop){ + Model.__defineGetter__(prop, function(){ + return this.prototype[prop]; + }); +}); + +/** + * Module exports. + */ + +module.exports = exports = Model; + +Model.remove = function remove (conditions, callback) { + if ('function' === typeof conditions) { + callback = conditions; + conditions = {}; + } + + var query = new Query(conditions).bind(this, 'remove'); + + if ('undefined' === typeof callback) + return query; + + this._applyNamedScope(query); + return query.remove(callback); +}; + +/** + * Finds documents + * + * Examples: + * // retrieve only certain keys + * MyModel.find({ name: /john/i }, ['name', 'friends'], function () { }) + * + * // pass options + * MyModel.find({ name: /john/i }, [], { skip: 10 } ) + * + * @param {Object} conditions + * @param {Object/Function} (optional) fields to hydrate or callback + * @param {Function} callback + * @api public + */ + +Model.find = function find (conditions, fields, options, callback) { + if ('function' == typeof conditions) { + callback = conditions; + conditions = {}; + fields = null; + options = null; + } else if ('function' == typeof fields) { + callback = fields; + fields = null; + options = null; + } else if ('function' == typeof options) { + callback = options; + options = null; + } + + var query = new Query(conditions, options); + query.bind(this, 'find'); + query.select(fields); + + if ('undefined' === typeof callback) + return query; + + this._applyNamedScope(query); + return query.find(callback); +}; + +/** + * Merges the current named scope query into `query`. + * + * @param {Query} query + * @api private + */ + +Model._applyNamedScope = function _applyNamedScope (query) { + var cQuery = this._cumulativeQuery; + + if (cQuery) { + merge(query._conditions, cQuery._conditions); + if (query._fields && cQuery._fields) + merge(query._fields, cQuery._fields); + if (query.options && cQuery.options) + merge(query.options, cQuery.options); + delete this._cumulativeQuery; + } + + return query; +} + +/** + * Finds by id + * + * @param {ObjectId/Object} objectid, or a value that can be casted to it + * @api public + */ + +Model.findById = function findById (id, fields, options, callback) { + return this.findOne({ _id: id }, fields, options, callback); +}; + +/** + * Finds one document + * + * @param {Object} conditions + * @param {Object/Function} (optional) fields to hydrate or callback + * @param {Function} callback + * @api public + */ + +Model.findOne = function findOne (conditions, fields, options, callback) { + if ('function' == typeof options) { + // TODO Handle all 3 of the following scenarios + // Hint: Only some of these scenarios are possible if cQuery is present + // Scenario: findOne(conditions, fields, callback); + // Scenario: findOne(fields, options, callback); + // Scenario: findOne(conditions, options, callback); + callback = options; + options = null; + } else if ('function' == typeof fields) { + // TODO Handle all 2 of the following scenarios + // Scenario: findOne(conditions, callback) + // Scenario: findOne(fields, callback) + // Scenario: findOne(options, callback); + callback = fields; + fields = null; + options = null; + } else if ('function' == typeof conditions) { + callback = conditions; + conditions = {}; + fields = null; + options = null; + } + + var query = new Query(conditions, options).select(fields).bind(this, 'findOne'); + + if ('undefined' == typeof callback) + return query; + + this._applyNamedScope(query); + return query.findOne(callback); +}; + +/** + * Counts documents + * + * @param {Object} conditions + * @param {Function} optional callback + * @api public + */ + +Model.count = function count (conditions, callback) { + if ('function' === typeof conditions) + callback = conditions, conditions = {}; + + var query = new Query(conditions).bind(this, 'count'); + if ('undefined' == typeof callback) + return query; + + this._applyNamedScope(query); + return query.count(callback); +}; + +Model.distinct = function distinct (field, conditions, callback) { + var query = new Query(conditions).bind(this, 'distinct'); + if ('undefined' == typeof callback) { + query._distinctArg = field; + return query; + } + + this._applyNamedScope(query); + return query.distinct(field, callback); +}; + +/** + * `where` enables a very nice sugary api for doing your queries. + * For example, instead of writing: + * User.find({age: {$gte: 21, $lte: 65}}, callback); + * we can instead write more readably: + * User.where('age').gte(21).lte(65); + * Moreover, you can also chain a bunch of these together like: + * User + * .where('age').gte(21).lte(65) + * .where('name', /^b/i) // All names that begin where b or B + * .where('friends').slice(10); + * @param {String} path + * @param {Object} val (optional) + * @return {Query} + * @api public + */ + +Model.where = function where (path, val) { + var q = new Query().bind(this, 'find'); + return q.where.apply(q, arguments); +}; + +/** + * Sometimes you need to query for things in mongodb using a JavaScript + * expression. You can do so via find({$where: javascript}), or you can + * use the mongoose shortcut method $where via a Query chain or from + * your mongoose Model. + * + * @param {String|Function} js is a javascript string or anonymous function + * @return {Query} + * @api public + */ + +Model.$where = function $where () { + var q = new Query().bind(this, 'find'); + return q.$where.apply(q, arguments); +}; + +/** + * Shortcut for creating a new Document that is automatically saved + * to the db if valid. + * + * @param {Object} doc + * @param {Function} callback + * @api public + */ + +Model.create = function create (doc, fn) { + if (1 === arguments.length) { + return 'function' === typeof doc && doc(null); + } + + var self = this + , docs = [null] + , promise + , count + , args + + if (Array.isArray(doc)) { + args = doc; + } else { + args = utils.args(arguments, 0, arguments.length - 1); + fn = arguments[arguments.length - 1]; + } + + if (0 === args.length) return fn(null); + + promise = new Promise(fn); + count = args.length; + + args.forEach(function (arg, i) { + var doc = new self(arg); + docs[i+1] = doc; + doc.save(function (err) { + if (err) return promise.error(err); + --count || fn.apply(null, docs); + }); + }); + + // TODO + // utilize collection.insertAll for batch processing? +}; + +/** + * Updates documents. + * + * Examples: + * + * MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn); + * MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, fn); + * + * Valid options: + * + * - safe (boolean) safe mode (defaults to value set in schema (true)) + * - upsert (boolean) whether to create the doc if it doesn't match (false) + * - multi (boolean) whether multiple documents should be updated (false) + * + * @param {Object} conditions + * @param {Object} doc + * @param {Object} options + * @param {Function} callback + * @return {Query} + * @api public + */ + +Model.update = function update (conditions, doc, options, callback) { + if (arguments.length < 4) { + if ('function' === typeof options) { + // Scenario: update(conditions, doc, callback) + callback = options; + options = null; + } else if ('function' === typeof doc) { + // Scenario: update(doc, callback); + callback = doc; + doc = conditions; + conditions = {}; + options = null; + } + } + + var query = new Query(conditions, options).bind(this, 'update', doc); + + if ('undefined' == typeof callback) + return query; + + this._applyNamedScope(query); + return query.update(doc, callback); +}; + +/** + * Compiler utility. + * + * @param {String} model name + * @param {Schema} schema object + * @param {String} collection name + * @param {Connection} connection to use + * @param {Mongoose} mongoose instance + * @api private + */ + +Model.compile = function compile (name, schema, collectionName, connection, base) { + // generate new class + function model () { + Model.apply(this, arguments); + }; + + model.modelName = name; + model.__proto__ = Model; + model.prototype.__proto__ = Model.prototype; + model.prototype.base = base; + model.prototype.schema = schema; + model.prototype.db = connection; + model.prototype.collection = connection.collection(collectionName); + + // apply methods + for (var i in schema.methods) + model.prototype[i] = schema.methods[i]; + + // apply statics + for (var i in schema.statics) + model[i] = schema.statics[i]; + + // apply named scopes + if (schema.namedScopes) schema.namedScopes.compile(model); + + return model; +}; diff --git a/node_modules/mongoose/lib/namedscope.js b/node_modules/mongoose/lib/namedscope.js new file mode 100644 index 0000000..1b3f5d4 --- /dev/null +++ b/node_modules/mongoose/lib/namedscope.js @@ -0,0 +1,70 @@ +var Query = require('./query'); +function NamedScope () {} + +NamedScope.prototype.query; + +NamedScope.prototype.where = function () { + var q = this.query || (this.query = new Query()); + q.where.apply(q, arguments); + return q; +}; + +/** + * Decorate + * + * @param {NamedScope} target + * @param {Object} getters + * @api private + */ + +NamedScope.prototype.decorate = function (target, getters) { + var name = this.name + , block = this.block + , query = this.query; + if (block) { + if (block.length === 0) { + Object.defineProperty(target, name, { + get: getters.block0(block) + }); + } else { + target[name] = getters.blockN(block); + } + } else { + Object.defineProperty(target, name, { + get: getters.basic(query) + }); + } +}; + +NamedScope.prototype.compile = function (model) { + var allScopes = this.scopesByName + , scope; + for (var k in allScopes) { + scope = allScopes[k]; + scope.decorate(model, { + block0: function (block) { + return function () { + var cquery = this._cumulativeQuery || (this._cumulativeQuery = new Query().bind(this)); + block.call(cquery); + return this; + }; + }, + blockN: function (block) { + return function () { + var cquery = this._cumulativeQuery || (this._cumulativeQuery = new Query().bind(this)); + block.apply(cquery, arguments); + return this; + }; + }, + basic: function (query) { + return function () { + var cquery = this._cumulativeQuery || (this._cumulativeQuery = new Query().bind(this)); + cquery.find(query); + return this; + }; + } + }); + } +}; + +module.exports = NamedScope; diff --git a/node_modules/mongoose/lib/promise.js b/node_modules/mongoose/lib/promise.js new file mode 100644 index 0000000..c632a03 --- /dev/null +++ b/node_modules/mongoose/lib/promise.js @@ -0,0 +1,145 @@ + +/** + * Module dependencies. + */ + +var util = require('./utils'); +var EventEmitter = util.EventEmitter; + +/** + * Promise constructor. + * + * @param {Function} a callback+errback that takes err, ... as signature + * @api public + */ + +function Promise (back) { + this.emitted = {}; + if ('function' == typeof back) + this.addBack(back); +}; + +/** + * Inherits from EventEmitter. + */ + +Promise.prototype.__proto__ = EventEmitter.prototype; + +/** + * Adds an event or fires the callback right away. + * + * @return promise + * @api public + */ + +Promise.prototype.on = function (event, callback) { + if (this.emitted[event]) + callback.apply(this, this.emitted[event]); + else + EventEmitter.prototype.on.call(this, event, callback); + + return this; +}; + +/** + * Keeps track of emitted events to run them on `on` + * + * @api private + */ + +Promise.prototype.emit = function (event) { + // ensures a promise can't be complete() or error() twice + if (event == 'err' || event == 'complete'){ + if (this.emitted.err || this.emitted.complete) { + return this; + } + this.emitted[event] = util.args(arguments, 1); + } + + return EventEmitter.prototype.emit.apply(this, arguments); +}; + +/** + * Shortcut for emitting complete event + * + * @api public + */ + +Promise.prototype.complete = function () { + var args = util.args(arguments); + return this.emit.apply(this, ['complete'].concat(args)); +}; + +/** + * Shortcut for emitting err event + * + * @api public + */ + +Promise.prototype.error = function () { + var args = util.args(arguments); + return this.emit.apply(this, ['err'].concat(args)); +}; + +/** + * Shortcut for `.on('complete', fn)` + * + * @return promise + * @api public + */ + +Promise.prototype.addCallback = function (fn) { + return this.on('complete', fn); +}; + +/** + * Shortcut for `.on('err', fn)` + * + * @return promise + * @api public + */ + +Promise.prototype.addErrback = function (fn) { + return this.on('err', fn); +}; + +/** + * Adds a single function that's both callback and errback + * + * @return promise + * @api private + */ + +Promise.prototype.addBack = function (fn) { + this.on('err', function(err){ + fn.call(this, err); + }); + + this.on('complete', function(){ + var args = util.args(arguments); + fn.apply(this, [null].concat(args)); + }); + + return this; +}; + +/** + * Sugar for handling cases where you may be + * resolving to either an error condition or a + * success condition. + * + * @param {Error} optional error or null + * @param {Object} value to complete the promise with + * @api public + */ + +Promise.prototype.resolve = function (err, val) { + if (err) return this.error(err); + return this.complete(val); +}; + +/** + * Module exports. + */ + +module.exports = Promise; diff --git a/node_modules/mongoose/lib/query.js b/node_modules/mongoose/lib/query.js new file mode 100644 index 0000000..29c03ab --- /dev/null +++ b/node_modules/mongoose/lib/query.js @@ -0,0 +1,1527 @@ +/** + * Module dependencies. + */ + +var utils = require('./utils') + , merge = utils.merge + , Promise = require('./promise') + , Document = require('./document') + , inGroupsOf = utils.inGroupsOf + , tick = utils.tick + , QueryStream = require('./querystream') + +/** + * Query constructor + * + * @api private + */ + +function Query (criteria, options) { + options = this.options = options || {}; + this.safe = options.safe + + // normalize population options + var pop = this.options.populate; + this.options.populate = {}; + + if (pop && Array.isArray(pop)) { + for (var i = 0, l = pop.length; i < l; i++) { + this.options.populate[pop[i]] = {}; + } + } + + this._conditions = {}; + if (criteria) this.find(criteria); +} + +/** + * Binds this query to a model. + * @param {Function} param + * @return {Query} + * @api public + */ + +Query.prototype.bind = function bind (model, op, updateArg) { + this.model = model; + this.op = op; + if (op === 'update') this._updateArg = updateArg; + return this; +}; + +/** + * Executes the query returning a promise. + * + * Examples: + * query.run(); + * query.run(callback); + * query.run('update'); + * query.run('find', callback); + * + * @param {String|Function} op (optional) + * @param {Function} callback (optional) + * @return {Promise} + * @api public + */ + +Query.prototype.run = +Query.prototype.exec = function (op, callback) { + var promise = new Promise(); + + switch (typeof op) { + case 'function': + callback = op; + op = null; + break; + case 'string': + this.op = op; + break; + } + + if (callback) promise.addBack(callback); + + if (!this.op) { + promise.complete(); + return promise; + } + + if ('update' == this.op) { + this.update(this._updateArg, promise.resolve.bind(promise)); + return promise; + } + + if ('distinct' == this.op) { + this.distinct(this._distinctArg, promise.resolve.bind(promise)); + return promise; + } + + this[this.op](promise.resolve.bind(promise)); + return promise; +}; + +/** + * Finds documents. + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ + +Query.prototype.find = function (criteria, callback) { + this.op = 'find'; + if ('function' === typeof criteria) { + callback = criteria; + criteria = {}; + } else if (criteria instanceof Query) { + // TODO Merge options, too + merge(this._conditions, criteria._conditions); + } else if (criteria instanceof Document) { + merge(this._conditions, criteria.toObject()); + } else if (criteria && 'Object' === criteria.constructor.name) { + merge(this._conditions, criteria); + } + if (!callback) return this; + return this.execFind(callback); +}; + +/** + * Casts obj, or if obj is not present, then this._conditions, + * based on the model's schema. + * + * @param {Function} model + * @param {Object} obj (optional) + * @api public + */ + +Query.prototype.cast = function (model, obj) { + obj || (obj= this._conditions); + + var schema = model.schema + , paths = Object.keys(obj) + , i = paths.length + , any$conditionals + , schematype + , nested + , path + , type + , val; + + while (i--) { + path = paths[i]; + val = obj[path]; + + if ('$or' === path || '$nor' === path) { + var k = val.length + , orComponentQuery; + + while (k--) { + orComponentQuery = new Query(val[k]); + orComponentQuery.cast(model); + val[k] = orComponentQuery._conditions; + } + + } else if (path === '$where') { + type = typeof val; + + if ('string' !== type && 'function' !== type) { + throw new Error("Must have a string or function for $where"); + } + + if ('function' === type) { + obj[path] = val.toString(); + } + + continue; + + } 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 + , castingQuery; + + // 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; + castingQuery = new Query(remainingConds); + castingQuery.cast(schematype.caster); + obj[path] = castingQuery._conditions[pathLastHalf]; + } else { + obj[path] = val; + } + } + + } else if (val === null || val === undefined) { + continue; + } else if ('Object' === val.constructor.name) { + + any$conditionals = Object.keys(val).some(function (k) { + return k.charAt(0) === '$' && k !== '$id' && k !== '$ref'; + }); + + if (!any$conditionals) { + obj[path] = schematype.castForQuery(val); + } else { + + var ks = Object.keys(val) + , k = ks.length + , $cond; + + while (k--) { + $cond = ks[k]; + nested = val[$cond]; + + if ('$exists' === $cond) { + if ('boolean' !== typeof nested) { + throw new Error("$exists parameter must be Boolean"); + } + continue; + } + + if ('$type' === $cond) { + if ('number' !== typeof nested) { + throw new Error("$type parameter must be Number"); + } + continue; + } + + if ('$not' === $cond) { + this.cast(model, nested); + } else { + val[$cond] = schematype.castForQuery($cond, nested); + } + } + } + } else { + obj[path] = schematype.castForQuery(val); + } + } + } +}; + +/** + * Returns default options. + * @api private + */ + +Query.prototype._optionsForExec = function (model) { + var options = utils.clone(this.options, { retainKeyOrder: true }); + delete options.populate; + if (! ('safe' in options)) options.safe = model.options.safe; + return options; +}; + +/** + * Applies schematype selected options to this query. + * @api private + */ + +Query.prototype._applyPaths = function applyPaths () { + // determine if query is selecting or excluding fields + + var fields = this._fields + , exclude + , keys + , ki + + if (fields) { + keys = Object.keys(fields); + ki = keys.length; + + while (ki--) { + if ('_id' == keys[ki]) continue; + exclude = 0 === fields[keys[ki]]; + break; + } + } + + // if selecting, apply default schematype select:true fields + // if excluding, apply schematype select:false fields + // if not specified, apply both + + var selected = [] + , excluded = [] + + this.model.schema.eachPath(function (path, type) { + if ('boolean' != typeof type.selected) return; + ;(type.selected ? selected : excluded).push(path); + }); + + switch (exclude) { + case true: + this.exclude(excluded); + break; + case false: + this.select(selected); + break; + case undefined: + excluded.length && this.exclude(excluded); + selected.length && this.select(selected); + break; + } +} + +/** + * Sometimes you need to query for things in mongodb using a JavaScript + * expression. You can do so via find({$where: javascript}), or you can + * use the mongoose shortcut method $where via a Query chain or from + * your mongoose Model. + * + * @param {String|Function} js is a javascript string or anonymous function + * @return {Query} + * @api public + */ + +Query.prototype.$where = function (js) { + this._conditions['$where'] = js; + return this; +}; + +/** + * `where` enables a very nice sugary api for doing your queries. + * For example, instead of writing: + * + * User.find({age: {$gte: 21, $lte: 65}}, callback); + * + * we can instead write more readably: + * + * User.where('age').gte(21).lte(65); + * + * Moreover, you can also chain a bunch of these together like: + * + * User + * .where('age').gte(21).lte(65) + * .where('name', /^b/i) // All names that begin where b or B + * .where('friends').slice(10); + * + * @param {String} path + * @param {Object} val (optional) + * @return {Query} + * @api public + */ + +Query.prototype.where = function (path, val) { + if (2 === arguments.length) { + this._conditions[path] = val; + } + this._currPath = path; + return this; +}; + +/** + * `equals` sugar. + * + * User.where('age').equals(49); + * + * Same as + * + * User.where('age', 49); + * + * @param {object} val + * @return {Query} + * @api public + */ + +Query.prototype.equals = function equals (val) { + var path = this._currPath; + if (!path) throw new Error('equals() must be used after where()'); + this._conditions[path] = val; + return this; +} + +/** + * $or + */ + +Query.prototype.or = +Query.prototype.$or = function $or (array) { + var or = this._conditions.$or || (this._conditions.$or = []); + if (!Array.isArray(array)) array = [array]; + or.push.apply(or, array); + return this; +} + +/** + * $nor + */ + +Query.prototype.nor = +Query.prototype.$nor = function $nor (array) { + var nor = this._conditions.$nor || (this._conditions.$nor = []); + if (!Array.isArray(array)) array = [array]; + nor.push.apply(nor, array); + return this; +} + +/** + * $gt, $gte, $lt, $lte, $ne, $in, $nin, $all, $regex, $size, $maxDistance + * + * Can be used on Numbers or Dates. + * + * Thing.where('type').$nin(array) + */ + +'gt gte lt lte ne in nin all regex size maxDistance'.split(' ').forEach( function ($conditional) { + Query.prototype['$' + $conditional] = + Query.prototype[$conditional] = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$' + $conditional] = val; + return this; + }; +}); + +/** + * notEqualTo + * + * alias of `query.$ne()` + */ + +Query.prototype.notEqualTo = Query.prototype.ne; + +/** + * $mod, $near + */ + +;['mod', 'near'].forEach( function ($conditional) { + Query.prototype['$' + $conditional] = + Query.prototype[$conditional] = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath + } else if (arguments.length === 2 && !Array.isArray(val)) { + val = utils.args(arguments); + path = this._currPath; + } else if (arguments.length === 3) { + val = utils.args(arguments, 1); + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$' + $conditional] = val; + return this; + }; +}); + +/** + * $exists + */ + +Query.prototype.$exists = +Query.prototype.exists = function (path, val) { + if (arguments.length === 0) { + path = this._currPath + val = true; + } else if (arguments.length === 1) { + if ('boolean' === typeof path) { + val = path; + path = this._currPath; + } else { + val = true; + } + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$exists'] = val; + return this; +}; + +/** + * $elemMatch + */ + +Query.prototype.$elemMatch = +Query.prototype.elemMatch = function (path, criteria) { + var block; + if ('Object' === path.constructor.name) { + criteria = path; + path = this._currPath; + } else if ('function' === typeof path) { + block = path; + path = this._currPath; + } else if ('Object' === criteria.constructor.name) { + } else if ('function' === typeof criteria) { + block = criteria; + } else { + throw new Error("Argument error"); + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + if (block) { + criteria = new Query(); + block(criteria); + conds['$elemMatch'] = criteria._conditions; + } else { + conds['$elemMatch'] = criteria; + } + return this; +}; + +/** + * @private + */ + +function me () { return this } + +/** + * Spatial queries + */ + +// query.within.box() +// query.within.center() +var within = 'within $within'.split(' '); +within.push('wherein', '$wherein'); // deprecated, an old mistake possibly? +within.forEach(function (getter) { + Object.defineProperty(Query.prototype, getter, { + get: me + }); +}); + +Query.prototype['$box'] = +Query.prototype.box = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath; + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$within'] = { '$box': [val.ll, val.ur] }; + return this; +}; + +Query.prototype['$center'] = +Query.prototype.center = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath; + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$within'] = { '$center': [val.center, val.radius] }; + return this; +}; + +Query.prototype['$centerSphere'] = +Query.prototype.centerSphere = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath; + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$within'] = { '$centerSphere': [val.center, val.radius] }; + return this; +}; + +/** + * select + * + * _also aliased as fields()_ + * + * Chainable method for specifying which fields + * to include or exclude from the document that is + * returned from MongoDB. + * + * Examples: + * query.fields({a: 1, b: 1, c: 1, _id: 0}); + * query.fields('a b c'); + * + * @param {Object} + */ + +Query.prototype.select = +Query.prototype.fields = function () { + var arg0 = arguments[0]; + if (!arg0) return this; + if ('Object' === arg0.constructor.name || Array.isArray(arg0)) { + this._applyFields(arg0); + } else if (arguments.length === 1 && typeof arg0 === 'string') { + this._applyFields({only: arg0}); + } else { + this._applyFields({only: this._parseOnlyExcludeFields.apply(this, arguments)}); + } + return this; +}; + +/** + * only + * + * Chainable method for adding the specified fields to the + * object of fields to only include. + * + * Examples: + * query.only('a b c'); + * query.only('a', 'b', 'c'); + * query.only(['a', 'b', 'c']); + * + * @param {String|Array} space separated list of fields OR + * an array of field names + * We can also take arguments as the "array" of field names + * @api public + */ + +Query.prototype.only = function (fields) { + fields = this._parseOnlyExcludeFields.apply(this, arguments); + this._applyFields({ only: fields }); + return this; +}; + +/** + * exclude + * + * Chainable method for adding the specified fields to the + * object of fields to exclude. + * + * Examples: + * query.exclude('a b c'); + * query.exclude('a', 'b', 'c'); + * query.exclude(['a', 'b', 'c']); + * + * @param {String|Array} space separated list of fields OR + * an array of field names + * We can also take arguments as the "array" of field names + * @api public + */ + +Query.prototype.exclude = function (fields) { + fields = this._parseOnlyExcludeFields.apply(this, arguments); + this._applyFields({ exclude: fields }); + return this; +}; + +/** + * $slice() + */ + +Query.prototype['$slice'] = +Query.prototype.slice = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath + } else if (arguments.length === 2) { + if ('number' === typeof path) { + val = [path, val]; + path = this._currPath; + } + } else if (arguments.length === 3) { + val = utils.args(arguments, 1); + } + var myFields = this._fields || (this._fields = {}); + myFields[path] = { '$slice': val }; + return this; +}; + +/** + * Private method for interpreting the different ways + * you can pass in fields to both Query.prototype.only + * and Query.prototype.exclude. + * + * @param {String|Array|Object} fields + * @api private + */ + +Query.prototype._parseOnlyExcludeFields = function (fields) { + if (1 === arguments.length && 'string' === typeof fields) { + fields = fields.split(' '); + } else if (Array.isArray(fields)) { + // do nothing + } else { + fields = utils.args(arguments); + } + return fields; +}; + +/** + * Private method for interpreting and applying the different + * ways you can specify which fields you want to include + * or exclude. + * + * Example 1: Include fields 'a', 'b', and 'c' via an Array + * query.fields('a', 'b', 'c'); + * query.fields(['a', 'b', 'c']); + * + * Example 2: Include fields via 'only' shortcut + * query.only('a b c'); + * + * Example 3: Exclude fields via 'exclude' shortcut + * query.exclude('a b c'); + * + * Example 4: Include fields via MongoDB's native format + * query.fields({a: 1, b: 1, c: 1}) + * + * Example 5: Exclude fields via MongoDB's native format + * query.fields({a: 0, b: 0, c: 0}); + * + * @param {Object|Array} the formatted collection of fields to + * include and/or exclude + * @api private + */ + +Query.prototype._applyFields = function (fields) { + var $fields + , pathList; + + if (Array.isArray(fields)) { + $fields = fields.reduce(function ($fields, field) { + $fields[field] = 1; + return $fields; + }, {}); + } else if (pathList = fields.only || fields.exclude) { + $fields = + this._parseOnlyExcludeFields(pathList) + .reduce(function ($fields, field) { + $fields[field] = fields.only ? 1: 0; + return $fields; + }, {}); + } else if ('Object' === fields.constructor.name) { + $fields = fields; + } else { + throw new Error("fields is invalid"); + } + + var myFields = this._fields || (this._fields = {}); + for (var k in $fields) myFields[k] = $fields[k]; +}; + +/** + * sort + * + * Sets the sort + * + * Examples: + * query.sort('test', 1) + * query.sort('field', -1) + * query.sort('field', -1, 'test', 1) + * + * @api public + */ + +Query.prototype.sort = function () { + var sort = this.options.sort || (this.options.sort = []); + + inGroupsOf(2, arguments, function (field, value) { + sort.push([field, value]); + }); + + return this; +}; + +/** + * asc + * + * Sorts ascending. + * + * query.asc('name', 'age'); + */ + +Query.prototype.asc = function () { + var sort = this.options.sort || (this.options.sort = []); + for (var i = 0, l = arguments.length; i < l; i++) { + sort.push([arguments[i], 1]); + } + return this; +}; + +/** + * desc + * + * Sorts descending. + * + * query.desc('name', 'age'); + */ + +Query.prototype.desc = function () { + var sort = this.options.sort || (this.options.sort = []); + for (var i = 0, l = arguments.length; i < l; i++) { + sort.push([arguments[i], -1]); + } + return this; +}; + +/** + * limit, skip, maxscan, snapshot, batchSize, comment + * + * Sets these associated options. + * + * query.comment('feed query'); + */ + +;['limit', 'skip', 'maxscan', 'snapshot', 'batchSize', 'comment'].forEach( function (method) { + Query.prototype[method] = function (v) { + this.options[method] = v; + return this; + }; +}); + +/** + * hint + * + * Sets query hints. + * + * Examples: + * new Query().hint({ indexA: 1, indexB: -1}) + * new Query().hint("indexA", 1, "indexB", -1) + * + * @param {Object|String} v + * @param {Int} [multi] + * @return {Query} + * @api public + */ + +Query.prototype.hint = function (v, multi) { + var hint = this.options.hint || (this.options.hint = {}) + , k + + if (multi) { + inGroupsOf(2, arguments, function (field, val) { + hint[field] = val; + }); + } else if ('Object' === v.constructor.name) { + // must keep object keys in order so don't use Object.keys() + for (k in v) { + hint[k] = v[k]; + } + } + + return this; +}; + +/** + * slaveOk + * + * Sets slaveOk option. + * + * new Query().slaveOk() <== true + * new Query().slaveOk(true) + * new Query().slaveOk(false) + * + * @param {Boolean} v (defaults to true) + * @api public + */ + +Query.prototype.slaveOk = function (v) { + this.options.slaveOk = arguments.length ? !!v : true; + return this; +}; + +/** + * tailable + * + * Sets tailable option. + * + * new Query().tailable() <== true + * new Query().tailable(true) + * new Query().tailable(false) + * + * @param {Boolean} v (defaults to true) + * @api public + */ + +Query.prototype.tailable = function (v) { + this.options.tailable = arguments.length ? !!v : true; + return this; +}; + +/** + * execFind + * + * @api private + */ + +Query.prototype.execFind = function (callback) { + var model = this.model + , promise = new Promise(callback); + + try { + this.cast(model); + } catch (err) { + return promise.error(err); + } + + // apply default schematype path selections + this._applyPaths(); + + var self = this + , castQuery = this._conditions + , options = this._optionsForExec(model) + + var fields = utils.clone(options.fields = this._fields); + + model.collection.find(castQuery, options, function (err, cursor) { + if (err) return promise.error(err); + cursor.toArray(tick(cb)); + }); + + function cb (err, docs) { + if (err) return promise.error(err); + + var arr = [] + , count = docs.length; + + if (!count) return promise.complete([]); + + for (var i = 0, l = docs.length; i < l; i++) { + arr[i] = new model(undefined, fields); + + // skip _id for pre-init hooks + delete arr[i]._doc._id; + + arr[i].init(docs[i], self, function (err) { + if (err) return promise.error(err); + --count || promise.complete(arr); + }); + } + } + + return this; +}; + +/** + * each() + * + * Streaming cursors. + * + * The `callback` is called repeatedly for each document + * found in the collection as it's streamed. If an error + * occurs streaming stops. + * + * Example: + * query.each(function (err, user) { + * if (err) return res.end("aww, received an error. all done."); + * if (user) { + * res.write(user.name + '\n') + * } else { + * res.end("reached end of cursor. all done."); + * } + * }); + * + * A third parameter may also be used in the callback which + * allows you to iterate the cursor manually. + * + * Example: + * query.each(function (err, user, next) { + * if (err) return res.end("aww, received an error. all done."); + * if (user) { + * res.write(user.name + '\n') + * doSomethingAsync(next); + * } else { + * res.end("reached end of cursor. all done."); + * } + * }); + * + * @param {Function} callback + * @return {Query} + * @api public + */ + +Query.prototype.each = function (callback) { + var model = this.model + , options = this._optionsForExec(model) + , manual = 3 == callback.length + , self = this + + try { + this.cast(model); + } catch (err) { + return callback(err); + } + + var fields = utils.clone(options.fields = this._fields); + + function complete (err, val) { + if (complete.ran) return; + complete.ran = true; + callback(err, val); + } + + model.collection.find(this._conditions, options, function (err, cursor) { + if (err) return complete(err); + + var ticks = 0; + next(); + + function next () { + // nextTick is necessary to avoid stack overflows when + // dealing with large result sets. yield occasionally. + if (!(++ticks % 20)) { + process.nextTick(function () { + cursor.nextObject(onNextObject); + }); + } else { + cursor.nextObject(onNextObject); + } + } + + function onNextObject (err, doc) { + if (err) return complete(err); + + // when doc is null we hit the end of the cursor + if (!doc) return complete(null, null); + + var instance = new model(undefined, fields); + + // skip _id for pre-init hooks + delete instance._doc._id; + + instance.init(doc, self, function (err) { + if (err) return complete(err); + + if (manual) { + callback(null, instance, next); + } else { + callback(null, instance); + next(); + } + }); + } + + }); + + return this; +} + +/** + * findOne + * + * Casts the query, sends the findOne command to mongodb. + * Upon receiving the document, we initialize a mongoose + * document based on the returned document from mongodb, + * and then we invoke a callback on our mongoose document. + * + * @param {Function} callback function (err, found) + * @api public + */ + +Query.prototype.findOne = function (callback) { + this.op = 'findOne'; + + if (!callback) return this; + + var model = this.model; + var promise = new Promise(callback); + + try { + this.cast(model); + } catch (err) { + promise.error(err); + return this; + } + + // apply default schematype path selections + this._applyPaths(); + + var self = this + , castQuery = this._conditions + , options = this._optionsForExec(model) + + var fields = utils.clone(options.fields = this._fields); + + model.collection.findOne(castQuery, options, tick(function (err, doc) { + if (err) return promise.error(err); + if (!doc) return promise.complete(null); + + var casted = new model(undefined, fields); + + // skip _id for pre-init hooks + delete casted._doc._id; + + casted.init(doc, self, function (err) { + if (err) return promise.error(err); + promise.complete(casted); + }); + })); + + return this; +}; + +/** + * count + * + * Casts this._conditions and sends a count + * command to mongodb. Invokes a callback upon + * receiving results + * + * @param {Function} callback fn(err, cardinality) + * @api public + */ + +Query.prototype.count = function (callback) { + this.op = 'count'; + var model = this.model; + + try { + this.cast(model); + } catch (err) { + return callback(err); + } + + var castQuery = this._conditions; + model.collection.count(castQuery, tick(callback)); + + return this; +}; + +/** + * distinct + * + * Casts this._conditions and sends a distinct + * command to mongodb. Invokes a callback upon + * receiving results + * + * @param {Function} callback fn(err, cardinality) + * @api public + */ + +Query.prototype.distinct = function (field, callback) { + this.op = 'distinct'; + var model = this.model; + + try { + this.cast(model); + } catch (err) { + return callback(err); + } + + var castQuery = this._conditions; + model.collection.distinct(field, castQuery, tick(callback)); + + return this; +}; + +/** + * These operators require casting docs + * to real Documents for Update operations. + * @private + */ + +var castOps = { + $push: 1 + , $pushAll: 1 + , $addToSet: 1 + , $set: 1 +}; + +/** + * These operators should be cast to numbers instead + * of their path schema type. + * @private + */ + +var numberOps = { + $pop: 1 + , $unset: 1 + , $inc: 1 +} + +/** + * update + * + * Casts the `doc` according to the model Schema and + * sends an update command to MongoDB. + * + * _All paths passed that are not $atomic operations + * will become $set ops so we retain backwards compatibility._ + * + * Example: + * `Model.update({..}, { title: 'remove words' }, ...)` + * + * becomes + * + * `Model.update({..}, { $set: { title: 'remove words' }}, ...)` + * + * + * _Passing an empty object `{}` as the doc will result + * in a no-op. The update operation will be ignored and the + * callback executed without sending the command to MongoDB so as + * to prevent accidently overwritting the collection._ + * + * @param {Object} doc - the update + * @param {Function} callback - fn(err) + * @api public + */ + +Query.prototype.update = function update (doc, callback) { + this.op = 'update'; + this._updateArg = doc; + + var model = this.model + , options = this._optionsForExec(model) + , useSet = model.options['use$SetOnSave'] + , castQuery + , castDoc + + try { + this.cast(model); + castQuery = this._conditions; + } catch (err) { + return callback(err); + } + + try { + castDoc = this._castUpdate(doc); + } catch (err) { + return callback(err); + } + + if (castDoc) { + model.collection.update(castQuery, castDoc, options, tick(callback)); + } else { + process.nextTick(function () { + callback(null); + }); + } + + return this; +}; + +/** + * Casts obj for an update command. + * + * @param {Object} obj + * @return {Object} obj after casting its values + * @api private + */ + +Query.prototype._castUpdate = function _castUpdate (obj) { + var ops = Object.keys(obj) + , i = ops.length + , ret = {} + , hasKeys + , val + + while (i--) { + var op = ops[i]; + hasKeys = true; + if ('$' !== op[0]) { + // 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 ('$set' === op) { + if (!ret.$set) { + ret[op] = obj[op]; + } + } else { + ret[op] = obj[op]; + } + } + + // cast each value + i = ops.length; + + while (i--) { + op = ops[i]; + val = ret[op]; + if ('Object' === val.constructor.name) { + this._walkUpdatePath(val, op); + } 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 {Object} obj - part of a query + * @param {String} op - the atomic operator ($pull, $set, etc) + * @param {String} pref - path prefix (internal only) + * @private + */ + +Query.prototype._walkUpdatePath = function _walkUpdatePath (obj, op, pref) { + var strict = this.model.schema.options.strict + , prefix = pref ? pref + '.' : '' + , keys = Object.keys(obj) + , i = keys.length + , schema + , key + , val + + while (i--) { + key = keys[i]; + val = obj[key]; + + if (val && 'Object' === val.constructor.name) { + // watch for embedded doc schemas + schema = this._getSchema(prefix + key); + if (schema && schema.caster && op in castOps) { + // embedded doc schema + + if (strict && !schema) { + // path is not in our strict schema. do not include + delete obj[key]; + } else { + if ('$each' in val) { + obj[key] = { + $each: this._castUpdateVal(schema, val.$each, op) + } + } else { + obj[key] = this._castUpdateVal(schema, val, op); + } + } + } else { + this._walkUpdatePath(val, op, prefix + key); + } + } else { + schema = '$each' === key + ? this._getSchema(pref) + : this._getSchema(prefix + key); + + if (strict && !schema) { + delete obj[key]; + } else { + obj[key] = this._castUpdateVal(schema, val, op, key); + } + } + } +} + +/** + * Casts `val` according to `schema` and atomic `op`. + * + * @param {Schema} schema + * @param {Object} val + * @param {String} op - the atomic operator ($pull, $set, etc) + * @param {String} [$conditional] + * @private + */ + +Query.prototype._castUpdateVal = function _castUpdateVal (schema, val, op, $conditional) { + if (!schema) { + // non-existing schema path + return op in numberOps + ? Number(val) + : val + } + + if (schema.caster && op in castOps && + ('Object' === val.constructor.name || Array.isArray(val))) { + // 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 { + val = tmp[0]; + } + } + + if (op in numberOps) return Number(val); + if (/^\$/.test($conditional)) return schema.castForQuery($conditional, val); + return schema.castForQuery(val) +} + +/** + * Finds the schema for `path`. This is different than + * calling `schema.path` as it also resolves paths with + * positional selectors (something.$.another.$.path). + * + * @param {String} path + * @private + */ + +Query.prototype._getSchema = function _getSchema (path) { + var schema = this.model.schema + , pathschema = schema.path(path); + + if (pathschema) + return pathschema; + + // look for arrays + return (function search (parts, schema) { + var p = parts.length + 1 + , foundschema + , trypath + + while (p--) { + trypath = parts.slice(0, p).join('.'); + foundschema = schema.path(trypath); + if (foundschema) { + if (foundschema.caster) { + // Now that we found the array, we need to check if there + // are remaining document paths to look up for casting. + // Also we need to handle array.$.path since schema.path + // doesn't work for that. + if (p !== parts.length) { + if ('$' === parts[p]) { + // comments.$.comments.$.title + return search(parts.slice(p+1), foundschema.schema); + } else { + // this is the last path of the selector + return search(parts.slice(p), foundschema.schema); + } + } + } + return foundschema; + } + } + })(path.split('.'), schema) +} + +/** + * remove + * + * Casts the query, sends the remove command to + * mongodb where the query contents, and then + * invokes a callback upon receiving the command + * result. + * + * @param {Function} callback + * @api public + */ + +Query.prototype.remove = function (callback) { + this.op = 'remove'; + + var model = this.model + , options = this._optionsForExec(model); + + try { + this.cast(model); + } catch (err) { + return callback(err); + } + + var castQuery = this._conditions; + model.collection.remove(castQuery, options, tick(callback)); + return this; +}; + +/** + * populate + * + * Sets population options. + * @api public + */ + +Query.prototype.populate = function (path, fields, conditions, options) { + // The order of fields/conditions args is opposite Model.find but + // necessary to keep backward compatibility (fields could be + // an array, string, or object literal). + this.options.populate[path] = + new PopulateOptions(fields, conditions, options); + + return this; +}; + +/** + * Populate options constructor + * @private + */ + +function PopulateOptions (fields, conditions, options) { + this.conditions = conditions; + this.fields = fields; + this.options = options; +} + +// make it compatible with utils.clone +PopulateOptions.prototype.constructor = Object; + +/** + * stream + * + * Returns a stream interface + * + * Example: + * Thing.find({ name: /^hello/ }).stream().pipe(res) + * + * @api public + */ + +Query.prototype.stream = function stream () { + return new QueryStream(this); +} + +/** + * @private + * @TODO + */ + +Query.prototype.explain = function () { + throw new Error("Unimplemented"); +}; + +// TODO Add being able to skip casting -- e.g., this would be nice for scenarios like +// if you're migrating to usernames from user id numbers: +// query.where('user_id').in([4444, 'brian']); +// TODO "immortal" cursors - (only work on capped collections) +// TODO geoNear command + +/** + * Exports. + */ + +module.exports = Query; +module.exports.QueryStream = QueryStream; diff --git a/node_modules/mongoose/lib/querystream.js b/node_modules/mongoose/lib/querystream.js new file mode 100644 index 0000000..4093e60 --- /dev/null +++ b/node_modules/mongoose/lib/querystream.js @@ -0,0 +1,179 @@ + +/** + * Module dependencies. + */ + +var Stream = require('stream').Stream +var utils = require('./utils') + +/** + * QueryStream + * + * Returns a stream interface for the `query`. + * + * @param {Query} query + * @return {Stream} + */ + +function QueryStream (query) { + Stream.call(this); + + this.query = query; + this.readable = true; + this.paused = false; + this._cursor = null; + this._destroyed = null; + this._fields = null; + this._ticks = 0; + + // give time to hook up events + var self = this; + process.nextTick(function () { + self._init(); + }); +} + +/** + * Inherit from Stream + * @private + */ + +QueryStream.prototype.__proto__ = Stream.prototype; + +/** + * Flag stating whether or not this stream is readable. + */ + +QueryStream.prototype.readable; + +/** + * Flag stating whether or not this stream is paused. + */ + +QueryStream.prototype.paused; + +/** + * Initialize the query. + * @private + */ + +QueryStream.prototype._init = function () { + if (this._destroyed) return; + + var query = this.query + , model = query.model + , options = query._optionsForExec(model) + , self = this + + try { + query.cast(model); + } catch (err) { + return self.destroy(err); + } + + self._fields = utils.clone(options.fields = query._fields); + + model.collection.find(query._conditions, options, function (err, cursor) { + if (err) return self.destroy(err); + self._cursor = cursor; + self._next(); + }); +} + +/** + * Pull the next document from the cursor. + * @private + */ + +QueryStream.prototype._next = function () { + if (this.paused || this._destroyed) return; + + var self = this; + + // nextTick is necessary to avoid stack overflows when + // dealing with large result sets. yield occasionally. + if (!(++this._ticks % 20)) { + process.nextTick(function () { + self._cursor.nextObject(function (err, doc) { + self._onNextObject(err, doc); + }); + }); + } else { + self._cursor.nextObject(function (err, doc) { + self._onNextObject(err, doc); + }); + } +} + +/** + * Handle each document as its returned from the cursor + * transforming the raw `doc` from -native into a model + * instance. + * + * @private + */ + +QueryStream.prototype._onNextObject = function (err, doc) { + if (err) return this.destroy(err); + + // when doc is null we hit the end of the cursor + if (!doc) { + return this.destroy(); + } + + var instance = new this.query.model(undefined, this._fields); + + // skip _id for pre-init hooks + delete instance._doc._id; + + var self = this; + instance.init(doc, this.query, function (err) { + if (err) return self.destroy(err); + self.emit('data', instance); + self._next(); + }); +} + +/** + * Pauses this stream. + */ + +QueryStream.prototype.pause = function () { + this.paused = true; +} + +/** + * Resumes this stream. + */ + +QueryStream.prototype.resume = function () { + this.paused = false; + this._next(); +} + +/** + * Destroys the stream, closing the underlying + * cursor. No more events will be emitted. + */ + +QueryStream.prototype.destroy = function (err) { + if (this._destroyed) return; + this._destroyed = true; + this.readable = false; + + if (this._cursor) { + this._cursor.close(); + } + + if (err) { + this.emit('error', err); + } + + this.emit('close'); +} + +// TODO - maybe implement the -native raw option to pass binary? +//QueryStream.prototype.setEncoding = function () { +//} + +module.exports = exports = QueryStream; diff --git a/node_modules/mongoose/lib/schema.js b/node_modules/mongoose/lib/schema.js new file mode 100644 index 0000000..1dda865 --- /dev/null +++ b/node_modules/mongoose/lib/schema.js @@ -0,0 +1,565 @@ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , VirtualType = require('./virtualtype') + , utils = require('./utils') + , NamedScope + , Query + , Types + +/** + * Schema constructor. + * + * @param {Object} definition + * @api public + */ + +function Schema (obj, options) { + this.paths = {}; + this.virtuals = {}; + this.inherits = {}; + this.callQueue = []; + this._indexes = []; + this.methods = {}; + this.statics = {}; + this.tree = {}; + + // set options + this.options = utils.options({ + safe: true + , 'use$SetOnSave': true + , strict: false + }, options); + + // build paths + if (obj) + this.add(obj); + + if (!this.paths['_id'] && !this.options.noId) { + this.add({ _id: {type: ObjectId, auto: true} }); + } + + if (!this.paths['id'] && !this.options.noVirtualId) { + this.virtual('id').get(function () { + if (this.__id) { + return this.__id; + } + + return this.__id = null == this._id + ? null + : this._id.toString(); + }); + } + + delete this.options.noVirtualId; +}; + +/** + * Inherit from EventEmitter. + */ + +Schema.prototype.__proto__ = EventEmitter.prototype; + +/** + * Schema by paths + * + * Example (embedded doc): + * { + * 'test' : SchemaType, + * , 'test.test' : SchemaType, + * , 'first_name' : SchemaType + * } + * + * @api private + */ + +Schema.prototype.paths; + +/** + * Schema as a tree + * + * Example: + * { + * '_id' : ObjectId + * , 'nested' : { + * 'key': String + * } + * } + * + * @api private + */ + +Schema.prototype.tree; + +/** + * Sets the keys + * + * @param {Object} keys + * @param {String} prefix + * @api public + */ + +Schema.prototype.add = function add (obj, prefix) { + prefix = prefix || ''; + for (var i in obj) { + if (null == obj[i]) { + throw new TypeError('Invalid value for schema path `'+ prefix + i +'`'); + } + + if (obj[i].constructor.name == 'Object' && (!obj[i].type || obj[i].type.type)) { + if (Object.keys(obj[i]).length) + this.add(obj[i], prefix + i + '.'); + else + this.path(prefix + i, obj[i]); // mixed type + } else + this.path(prefix + i, obj[i]); + } +}; + +/** + * Sets a path (if arity 2) + * Gets a path (if arity 1) + * + * @param {String} path + * @param {Object} constructor + * @api public + */ + +Schema.prototype.path = function (path, obj) { + if (obj == undefined) { + if (this.paths[path]) return this.paths[path]; + + // Sometimes path will come in as + // pathNameA.4.pathNameB where 4 means the index + // of an embedded document in an embedded array. + // In this case, we need to jump to the Array's + // schema and call path() from there to resolve to + // the correct path type + + var last + , self = this + , subpaths = path.split(/\.(\d+)\.?/) + .filter(Boolean) // removes empty strings + + if (subpaths.length > 1) { + last = subpaths.length - 1; + return subpaths.reduce(function (val, subpath, i) { + if (val && !val.schema) { + if (i === last && !/\D/.test(subpath) && val instanceof Types.Array) { + return val.caster; // StringSchema, NumberSchema, etc + } else { + return val; + } + } + + if (!/\D/.test(subpath)) { // 'path.0.subpath' on path 0 + return val; + } + + return val ? val.schema.path(subpath) + : self.path(subpath); + }, null); + } + + return this.paths[subpaths[0]]; + } + + // update the tree + var subpaths = path.split(/\./) + , last = subpaths.pop() + , branch = this.tree; + + subpaths.forEach(function(path) { + if (!branch[path]) branch[path] = {}; + branch = branch[path]; + }); + + branch[last] = utils.clone(obj); + + this.paths[path] = Schema.interpretAsType(path, obj); + return this; +}; + +/** + * Converts -- e.g., Number, [SomeSchema], + * { type: String, enum: ['m', 'f'] } -- into + * the appropriate Mongoose Type, which we use + * later for casting, validation, etc. + * @param {String} path + * @param {Object} constructor + */ + +Schema.interpretAsType = function (path, obj) { + if (obj.constructor.name != 'Object') + obj = { type: obj }; + + // Get the type making sure to allow keys named "type" + // and default to mixed if not specified. + // { type: { type: String, default: 'freshcut' } } + var type = obj.type && !obj.type.type + ? obj.type + : {}; + + if (type.constructor.name == 'Object') { + return new Types.Mixed(path, obj); + } + + if (Array.isArray(type) || type == Array) { + // if it was specified through { type } look for `cast` + var cast = type == Array + ? obj.cast + : type[0]; + + if (cast instanceof Schema) { + return new Types.DocumentArray(path, cast, obj); + } + + return new Types.Array(path, cast || Types.Mixed, obj); + } + + if (undefined == Types[type.name]) { + throw new TypeError('Undefined type at `' + path + + '`\n Did you try nesting Schemas? ' + + 'You can only nest using refs or arrays.'); + } + + return new Types[type.name](path, obj); +}; + +/** + * Iterates through the schema's paths, passing the path string and type object + * to the callback. + * + * @param {Function} callback function - fn(pathstring, type) + * @return {Schema} this for chaining + * @api public + */ + +Schema.prototype.eachPath = function (fn) { + var keys = Object.keys(this.paths) + , len = keys.length; + + for (var i = 0; i < len; ++i) { + fn(keys[i], this.paths[keys[i]]); + } + + return this; +}; + +/** + * Returns an Array of path strings that are required. + * @api public + */ + +Object.defineProperty(Schema.prototype, 'requiredPaths', { + get: function () { + var paths = this.paths + , pathnames = Object.keys(paths) + , i = pathnames.length + , pathname, path + , requiredPaths = []; + while (i--) { + pathname = pathnames[i]; + path = paths[pathname]; + if (path.isRequired) requiredPaths.push(pathname); + } + return requiredPaths; + } +}); + +/** + * Given a path, returns whether it is a real, virtual, or + * ad-hoc/undefined path + * + * @param {String} path + * @return {String} + * @api public + */ +Schema.prototype.pathType = function (path) { + if (path in this.paths) return 'real'; + if (path in this.virtuals) return 'virtual'; + return 'adhocOrUndefined'; +}; + +/** + * Adds a method call to the queue + * + * @param {String} method name + * @param {Array} arguments + * @api private + */ + +Schema.prototype.queue = function(name, args){ + this.callQueue.push([name, args]); + return this; +}; + +/** + * Defines a pre for the document + * + * @param {String} method + * @param {Function} callback + * @api public + */ + +Schema.prototype.pre = function(){ + return this.queue('pre', arguments); +}; + +/** + * Defines a post for the document + * + * @param {String} method + * @param {Function} callback + * @api public + */ + +Schema.prototype.post = function(method, fn){ + return this.queue('on', arguments); +}; + +/** + * Registers a plugin for this schema + * + * @param {Function} plugin callback + * @api public + */ + +Schema.prototype.plugin = function (fn, opts) { + fn(this, opts); + return this; +}; + +/** + * Adds a method + * + * @param {String} method name + * @param {Function} handler + * @api public + */ + +Schema.prototype.method = function (name, fn) { + if ('string' != typeof name) + for (var i in name) + this.methods[i] = name[i]; + else + this.methods[name] = fn; + return this; +}; + +/** + * Defines a static method + * + * @param {String} name + * @param {Function} handler + * @api public + */ + +Schema.prototype.static = function(name, fn) { + if ('string' != typeof name) + for (var i in name) + this.statics[i] = name[i]; + else + this.statics[name] = fn; + return this; +}; + +/** + * Defines an index (most likely compound) + * Example: + * schema.index({ first: 1, last: -1 }) + * + * @param {Object} field + * @param {Object} optional options object + * @api public + */ + +Schema.prototype.index = function (fields, options) { + this._indexes.push([fields, options || {}]); + return this; +}; + +/** + * Sets/gets an option + * + * @param {String} key + * @param {Object} optional value + * @api public + */ + +Schema.prototype.set = function (key, value) { + if (arguments.length == 1) + return this.options[key]; + this.options[key] = value; + return this; +}; + +/** + * Compiles indexes from fields and schema-level indexes + * + * @api public + */ + +Schema.prototype.__defineGetter__('indexes', function () { + var indexes = [] + , seenSchemas = []; + + collectIndexes(this); + + return indexes; + + function collectIndexes (schema, prefix) { + if (~seenSchemas.indexOf(schema)) return; + seenSchemas.push(schema); + + var index; + var paths = schema.paths; + prefix = prefix || ''; + + for (var i in paths) { + if (paths[i]) { + if (paths[i] instanceof Types.DocumentArray) { + collectIndexes(paths[i].schema, i + '.'); + } else { + index = paths[i]._index; + + if (index !== false && index !== null){ + var field = {}; + field[prefix + i] = '2d' === index ? index : 1; + indexes.push([field, 'Object' === index.constructor.name ? index : {} ]); + } + } + } + } + + if (prefix) { + fixSubIndexPaths(schema, prefix); + } else { + indexes = indexes.concat(schema._indexes); + } + } + + /** + * Checks for indexes added to subdocs using Schema.index(). + * These indexes need their paths prefixed properly. + * + * schema._indexes = [ [indexObj, options], [indexObj, options] ..] + */ + + function fixSubIndexPaths (schema, prefix) { + var subindexes = schema._indexes + , len = subindexes.length + , indexObj + , newindex + , klen + , keys + , key + , i = 0 + , j + + for (i = 0; i < len; ++i) { + indexObj = subindexes[i][0]; + keys = Object.keys(indexObj); + klen = keys.length; + newindex = {}; + + // use forward iteration, order matters + for (j = 0; j < klen; ++j) { + key = keys[j]; + newindex[prefix + key] = indexObj[key]; + } + + indexes.push([newindex, subindexes[i][1]]); + } + } + +}); + +/** + * Retrieves or creates the virtual type with the given name. + * + * @param {String} name + * @return {VirtualType} + */ + +Schema.prototype.virtual = function (name, options) { + var virtuals = this.virtuals || (this.virtuals = {}); + var parts = name.split('.'); + return virtuals[name] = parts.reduce(function (mem, part, i) { + mem[part] || (mem[part] = (i === parts.length-1) + ? new VirtualType(options) + : {}); + return mem[part]; + }, this.tree); +}; + +/** + * Fetches the virtual type with the given name. + * Should be distinct from virtual because virtual auto-defines a new VirtualType + * if the path doesn't exist. + * + * @param {String} name + * @return {VirtualType} + */ + +Schema.prototype.virtualpath = function (name) { + return this.virtuals[name]; +}; + +Schema.prototype.namedScope = function (name, fn) { + var namedScopes = this.namedScopes || (this.namedScopes = new NamedScope) + , newScope = Object.create(namedScopes) + , allScopes = namedScopes.scopesByName || (namedScopes.scopesByName = {}); + allScopes[name] = newScope; + newScope.name = name; + newScope.block = fn; + newScope.query = new Query(); + newScope.decorate(namedScopes, { + block0: function (block) { + return function () { + block.call(this.query); + return this; + }; + }, + blockN: function (block) { + return function () { + block.apply(this.query, arguments); + return this; + }; + }, + basic: function (query) { + return function () { + this.query.find(query); + return this; + }; + } + }); + return newScope; +}; + +/** + * ObjectId schema identifier. Not an actual ObjectId, only used for Schemas. + * + * @api public + */ + +function ObjectId () { + throw new Error('This is an abstract interface. Its only purpose is to mark ' + + 'fields as ObjectId in the schema creation.'); +} + +/** + * Module exports. + */ + +module.exports = exports = Schema; + +// require down here because of reference issues +exports.Types = Types = require('./schema/index'); +NamedScope = require('./namedscope') +Query = require('./query'); + +exports.ObjectId = ObjectId; + diff --git a/node_modules/mongoose/lib/schema/array.js b/node_modules/mongoose/lib/schema/array.js new file mode 100644 index 0000000..6458a73 --- /dev/null +++ b/node_modules/mongoose/lib/schema/array.js @@ -0,0 +1,232 @@ +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError + , NumberSchema = require('./number') + , Types = { + Boolean: require('./boolean') + , Date: require('./date') + , Number: ArrayNumberSchema + , String: require('./string') + , ObjectId: require('./objectid') + , Buffer: require('./buffer') + } + , MongooseArray = require('../types').Array + , Mixed = require('./mixed') + , Query = require('../query') + , isMongooseObject = require('../utils').isMongooseObject + +/** + * Array SchemaType constructor + * + * @param {String} key + * @param {SchemaType} cast + * @api private + */ + +function SchemaArray (key, cast, options) { + SchemaType.call(this, key, options); + + if (cast) { + var castOptions = {}; + + if ('Object' === cast.constructor.name) { + if (cast.type) { + // support { type: Woot } + castOptions = cast; + cast = cast.type; + delete castOptions.type; + } else { + cast = Mixed; + } + } + + var caster = cast.name in Types ? Types[cast.name] : cast; + this.casterConstructor = caster; + this.caster = new caster(null, castOptions); + } + + var self = this + , defaultArr + , fn; + + if (this.defaultValue) { + defaultArr = this.defaultValue; + fn = 'function' == typeof defaultArr; + } + + this.default(function(){ + var arr = fn ? defaultArr() : defaultArr || []; + return new MongooseArray(arr, self.path, this); + }); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaArray.prototype.__proto__ = SchemaType.prototype; + +/** + * Check required + * + * @api private + */ + +SchemaArray.prototype.checkRequired = function (value) { + return !!(value && value.length); +}; + +/** + * Overrides the getters application for the population special-case + * TODO: implement this in SchemaObjectIdArray + * + * @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 contents + * + * @param {Object} value + * @param {Document} document that triggers the casting + * @param {Boolean} whether this is an initialization cast + * @api private + */ + +SchemaArray.prototype.cast = function (value, doc, init) { + if (Array.isArray(value)) { + if (!(value instanceof MongooseArray)) { + value = new MongooseArray(value, this.path, doc); + } + + if (this.caster) { + try { + for (var i = 0, l = value.length; i < l; i++) { + value[i] = this.caster.cast(value[i], doc, init); + } + } catch (e) { + // rethrow + throw new CastError(e.type, value); + } + } + + return value; + } else { + return this.cast([value], doc, init); + } +}; + +SchemaArray.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 Array."); + val = handler.call(this, val); + } else { + val = $conditional; + var proto = this.casterConstructor.prototype; + var method = proto.castForQuery || proto.cast; + if (Array.isArray(val)) { + val = val.map(function (v) { + if (method) v = method.call(proto, v); + return isMongooseObject(v) + ? v.toObject() + : v; + }); + } else if (method) { + val = method.call(proto, val); + } + } + return val && isMongooseObject(val) + ? val.toObject() + : val; +}; + +SchemaArray.prototype.$conditionalHandlers = { + '$all': function handle$all (val) { + if (!Array.isArray(val)) { + val = [val]; + } + + val = val.map(function (v) { + if (v && 'Object' === v.constructor.name) { + var o = {}; + o[this.path] = v; + var query = new Query(o); + query.cast(this.casterConstructor); + return query._conditions[this.path]; + } + return v; + }, this); + + return this.castForQuery(val); + } + , '$elemMatch': function (val) { + var query = new Query(val); + query.cast(this.casterConstructor); + return query._conditions; + } + , '$size': function (val) { + return ArrayNumberSchema.prototype.cast.call(this, val); + } + , '$ne': SchemaArray.prototype.castForQuery + , '$in': SchemaArray.prototype.castForQuery + , '$nin': SchemaArray.prototype.castForQuery + , '$regex': SchemaArray.prototype.castForQuery + , '$near': SchemaArray.prototype.castForQuery + , '$nearSphere': SchemaArray.prototype.castForQuery + , '$within': function(val) { + var query = new Query(val); + query.cast(this.casterConstructor) + return query._conditions; + } + , '$maxDistance': function (val) { + return ArrayNumberSchema.prototype.cast.call(this, val); + } +}; + +/** + * Number casting for arrays (equivalent, but without MongoseNumber) + * + * @see GH-176 + * @param {Object} value + * @api private + */ + +// subclass number schema to override casting +// to disallow non-numbers being saved +function ArrayNumberSchema (key, options) { + NumberSchema.call(this, key, options); +} + +ArrayNumberSchema.prototype.__proto__ = NumberSchema.prototype; + +ArrayNumberSchema.prototype.cast = function (value) { + if (!isNaN(value)) { + if (value instanceof Number || typeof value == 'number' || + (value.toString && value.toString() == Number(value))) + return Number(value); + } + + throw new CastError('number', value); +}; + +/** + * Module exports. + */ + +module.exports = SchemaArray; diff --git a/node_modules/mongoose/lib/schema/boolean.js b/node_modules/mongoose/lib/schema/boolean.js new file mode 100644 index 0000000..574fdd8 --- /dev/null +++ b/node_modules/mongoose/lib/schema/boolean.js @@ -0,0 +1,59 @@ + +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype'); + +/** + * Boolean SchemaType constructor. + * + * @param {String} path + * @param {Object} options + * @api private + */ + +function SchemaBoolean (path, options) { + SchemaType.call(this, path, options); +}; + +/** + * Inherits from SchemaType. + */ +SchemaBoolean.prototype.__proto__ = SchemaType.prototype; + +/** + * Required validator for date + * + * @api private + */ + +SchemaBoolean.prototype.checkRequired = function (value) { + return value === true || value === false; +}; + +/** + * Casts to boolean + * + * @param {Object} value to cast + * @api private + */ + +SchemaBoolean.prototype.cast = function (value) { + if (value === null) return value; + if (value === '0') return false; + return !!value; +}; + +SchemaBoolean.prototype.castForQuery = function ($conditional, val) { + if (arguments.length === 1) { + val = $conditional; + } + return this.cast(val); +}; + +/** + * Module exports. + */ + +module.exports = SchemaBoolean; diff --git a/node_modules/mongoose/lib/schema/buffer.js b/node_modules/mongoose/lib/schema/buffer.js new file mode 100644 index 0000000..64e12a2 --- /dev/null +++ b/node_modules/mongoose/lib/schema/buffer.js @@ -0,0 +1,106 @@ +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError + , BufferNumberSchema = function () {} + , MongooseBuffer = require('../types').Buffer + , Binary = MongooseBuffer.Binary + , Query = require('../query'); + +/** + * Buffer SchemaType constructor + * + * @param {String} key + * @param {SchemaType} cast + * @api private + */ + +function SchemaBuffer (key, options) { + SchemaType.call(this, key, options, 'Buffer'); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaBuffer.prototype.__proto__ = SchemaType.prototype; + +/** + * Check required + * + * @api private + */ + +SchemaBuffer.prototype.checkRequired = function (value) { + return !!(value && value.length); +}; + +/** + * Casts contents + * + * @param {Object} value + * @param {Document} document that triggers the casting + * @api private + */ + +SchemaBuffer.prototype.cast = function (value, doc, init) { + if (SchemaType._isRef(this, value, init)) return value; + + if (Buffer.isBuffer(value)) { + if (!(value instanceof MongooseBuffer)) { + value = new MongooseBuffer(value, [this.path, doc]); + } + + return value; + } else if (value instanceof Binary) { + return new MongooseBuffer(value.value(true), [this.path, doc]); + } + + if ('string' === typeof value || Array.isArray(value)) { + return new MongooseBuffer(value, [this.path, doc]); + } + + throw new CastError('buffer', value); +}; + +function handleSingle (val) { + return this.castForQuery(val); +} + +function handleArray (val) { + var self = this; + return val.map( function (m) { + return self.castForQuery(m); + }); +} + +SchemaBuffer.prototype.$conditionalHandlers = { + '$ne' : handleSingle + , '$in' : handleArray + , '$nin': handleArray + , '$gt' : handleSingle + , '$lt' : handleSingle + , '$gte': handleSingle + , '$lte': handleSingle +}; + +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); + } else { + val = $conditional; + return this.cast(val).toObject(); + } +}; + +/** + * Module exports. + */ + +module.exports = SchemaBuffer; diff --git a/node_modules/mongoose/lib/schema/date.js b/node_modules/mongoose/lib/schema/date.js new file mode 100644 index 0000000..3a7bda8 --- /dev/null +++ b/node_modules/mongoose/lib/schema/date.js @@ -0,0 +1,116 @@ + +/** + * Module requirements. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError; + +/** + * Date SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @api private + */ + +function SchemaDate (key, options) { + SchemaType.call(this, key, options); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaDate.prototype.__proto__ = SchemaType.prototype; + +/** + * Required validator for date + * + * @api private + */ + +SchemaDate.prototype.checkRequired = function (value) { + return value instanceof Date; +}; + +/** + * Casts to date + * + * @param {Object} value to cast + * @api private + */ + +SchemaDate.prototype.cast = function (value) { + if (value === null || value === '') + return null; + + if (value instanceof Date) + return value; + + var date; + + // support for timestamps + if (value instanceof Number || 'number' == typeof value + || String(value) == Number(value)) + date = new Date(Number(value)); + + // support for date strings + else if (value.toString) + date = new Date(value.toString()); + + if (date.toString() != 'Invalid Date') + return date; + + throw new CastError('date', value); +}; + +/** + * Date Query casting. + * + * @api private + */ + +function handleSingle (val) { + return this.cast(val); +} + +function handleArray (val) { + var self = this; + return val.map( function (m) { + return self.cast(m); + }); +} + +SchemaDate.prototype.$conditionalHandlers = { + '$lt': handleSingle + , '$lte': handleSingle + , '$gt': handleSingle + , '$gte': handleSingle + , '$ne': handleSingle + , '$in': handleArray + , '$nin': handleArray + , '$all': handleArray +}; + +SchemaDate.prototype.castForQuery = function ($conditional, val) { + var handler; + + if (2 !== arguments.length) { + return this.cast($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; diff --git a/node_modules/mongoose/lib/schema/documentarray.js b/node_modules/mongoose/lib/schema/documentarray.js new file mode 100644 index 0000000..38276ff --- /dev/null +++ b/node_modules/mongoose/lib/schema/documentarray.js @@ -0,0 +1,141 @@ + +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , ArrayType = require('./array') + , MongooseDocumentArray = require('../types/documentarray') + , Subdocument = require('../types/embedded') + , CastError = SchemaType.CastError + , Document = require('../document'); + +/** + * SubdocsArray SchemaType constructor + * + * @param {String} key + * @param {Schema} schema + * @param {Object} options + * @api private + */ + +function DocumentArray (key, schema, options) { + // compile an embedded document for this schema + // TODO Move this into parent model compilation for performance improvement? + function EmbeddedDocument () { + Subdocument.apply(this, arguments); + }; + + EmbeddedDocument.prototype.__proto__ = Subdocument.prototype; + EmbeddedDocument.prototype.schema = schema; + EmbeddedDocument.schema = schema; + + // apply methods + for (var i in schema.methods) { + EmbeddedDocument.prototype[i] = schema.methods[i]; + } + + // apply statics + for (var i in schema.statics) + EmbeddedDocument[i] = schema.statics[i]; + + ArrayType.call(this, key, EmbeddedDocument, options); + + this.caster = EmbeddedDocument; + this.caster.options = options; + + var self = this; + + this.schema = schema; + this.default(function(){ + return new MongooseDocumentArray([], self.path, this); + }); +}; + +/** + * Inherits from ArrayType. + */ + +DocumentArray.prototype.__proto__ = ArrayType.prototype; + +/** + * Performs local validations first, then validations on each embedded doc + * + * @api private + */ + +DocumentArray.prototype.doValidate = function (array, fn, scope) { + var self = this; + SchemaType.prototype.doValidate.call(this, array, function(err){ + if (err) return fn(err); + + var count = array && array.length + , error = false; + + if (!count) return fn(); + + array.forEach(function(doc, index){ + doc.validate(function(err){ + if (err && !error){ + // rewrite they key + err.key = self.key + '.' + index + '.' + err.key; + fn(err); + error = true; + } else { + --count || fn(); + } + }); + }); + }, scope); +}; + +/** + * Casts contents + * + * @param {Object} value + * @param {Document} document that triggers the casting + * @api private + */ + +DocumentArray.prototype.cast = function (value, doc, init, prev) { + var subdoc + , i + + if (Array.isArray(value)) { + if (!(value instanceof MongooseDocumentArray)) { + value = new MongooseDocumentArray(value, this.path, doc); + } + + i = value.length; + + while (i--) { + if (!(value[i] instanceof Subdocument)) { + if (init) { + subdoc = new this.caster(null, value); + // skip _id for pre-init hooks + delete subdoc._doc._id; + value[i] = subdoc.init(value[i]); + } else { + subdoc = prev && prev.id(value[i]._id) || + new this.caster(null, value); + subdoc.set(value[i]); + // if set() is hooked it will have no return value + // see gh-746 + value[i] = subdoc; + } + } + } + + return value; + } else { + return this.cast([value], doc, init, prev); + } + + throw new CastError('documentarray', value); +}; + +/** + * Module exports. + */ + +module.exports = DocumentArray; diff --git a/node_modules/mongoose/lib/schema/index.js b/node_modules/mongoose/lib/schema/index.js new file mode 100644 index 0000000..ac424c3 --- /dev/null +++ b/node_modules/mongoose/lib/schema/index.js @@ -0,0 +1,22 @@ + +/** + * Module exports. + */ + +exports.String = require('./string'); + +exports.Number = require('./number'); + +exports.Boolean = require('./boolean'); + +exports.DocumentArray = require('./documentarray'); + +exports.Array = require('./array'); + +exports.Buffer = require('./buffer'); + +exports.Date = require('./date'); + +exports.ObjectId = require('./objectid'); + +exports.Mixed = require('./mixed'); diff --git a/node_modules/mongoose/lib/schema/mixed.js b/node_modules/mongoose/lib/schema/mixed.js new file mode 100644 index 0000000..6f98be0 --- /dev/null +++ b/node_modules/mongoose/lib/schema/mixed.js @@ -0,0 +1,63 @@ + +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype'); + +/** + * Mixed SchemaType constructor. + * + * @param {String} path + * @param {Object} options + * @api private + */ + +function Mixed (path, options) { + // make sure empty array defaults are handled + if (options && + options.default && + Array.isArray(options.default) && + 0 === options.default.length) { + options.default = Array; + } + + SchemaType.call(this, path, options); +}; + +/** + * Inherits from SchemaType. + */ +Mixed.prototype.__proto__ = SchemaType.prototype; + +/** + * Required validator for mixed type + * + * @api private + */ + +Mixed.prototype.checkRequired = function (val) { + return true; +}; + +/** + * Noop casting + * + * @param {Object} value to cast + * @api private + */ + +Mixed.prototype.cast = function (val) { + return val; +}; + +Mixed.prototype.castForQuery = function ($cond, val) { + if (arguments.length === 2) return val; + return $cond; +}; + +/** + * Module exports. + */ + +module.exports = Mixed; diff --git a/node_modules/mongoose/lib/schema/number.js b/node_modules/mongoose/lib/schema/number.js new file mode 100644 index 0000000..75a14f6 --- /dev/null +++ b/node_modules/mongoose/lib/schema/number.js @@ -0,0 +1,142 @@ +/** + * Module requirements. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError + , MongooseNumber = require('../types/number'); + +/** + * Number SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @api private + */ + +function SchemaNumber (key, options) { + SchemaType.call(this, key, options, 'Number'); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaNumber.prototype.__proto__ = SchemaType.prototype; + +/** + * Required validator for number + * + * @api private + */ + +SchemaNumber.prototype.checkRequired = function checkRequired (value) { + if (SchemaType._isRef(this, value, true)) { + return null != value; + } else { + return typeof value == 'number' || value instanceof Number; + } +}; + +/** + * Sets a maximum number validator + * + * @param {Number} minimum number + * @api public + */ + +SchemaNumber.prototype.min = function (value, message) { + if (this.minValidator) + this.validators = this.validators.filter(function(v){ + return v[1] != 'min'; + }); + if (value != null) + this.validators.push([function(v){ + return v === null || v >= value; + }, 'min']); + return this; +}; + +/** + * Sets a maximum number validator + * + * @param {Number} maximum number + * @api public + */ + +SchemaNumber.prototype.max = function (value, message) { + if (this.maxValidator) + this.validators = this.validators.filter(function(v){ + return v[1] != 'max'; + }); + if (value != null) + this.validators.push([this.maxValidator = function(v){ + return v === null || v <= value; + }, 'max']); + return this; +}; + +/** + * Casts to number + * + * @param {Object} value to cast + * @param {Document} document that triggers the casting + * @api private + */ + +SchemaNumber.prototype.cast = function (value, doc, init) { + if (SchemaType._isRef(this, value, init)) return value; + + if (!isNaN(value)){ + if (null === value) return value; + if ('' === value) return null; + if ('string' === typeof value) value = Number(value); + if (value instanceof Number || typeof value == 'number' || + (value.toString && value.toString() == Number(value))) + return new MongooseNumber(value, this.path, doc); + } + + throw new CastError('number', value); +}; + +function handleSingle (val) { + return this.cast(val).valueOf(); +} + +function handleArray (val) { + var self = this; + return val.map( function (m) { + return self.cast(m).valueOf(); + }); +} + +SchemaNumber.prototype.$conditionalHandlers = { + '$lt' : handleSingle + , '$lte': handleSingle + , '$gt' : handleSingle + , '$gte': handleSingle + , '$ne' : handleSingle + , '$in' : handleArray + , '$nin': handleArray + , '$mod': handleArray + , '$all': handleArray +}; + +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); + } else { + val = this.cast($conditional); + return val == null ? val : val.valueOf(); + } +}; + +/** + * Module exports. + */ + +module.exports = SchemaNumber; diff --git a/node_modules/mongoose/lib/schema/objectid.js b/node_modules/mongoose/lib/schema/objectid.js new file mode 100644 index 0000000..a83f95d --- /dev/null +++ b/node_modules/mongoose/lib/schema/objectid.js @@ -0,0 +1,126 @@ +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError + , driver = global.MONGOOSE_DRIVER_PATH || './../drivers/node-mongodb-native' + , oid = require('../types/objectid'); + + +/** + * ObjectId SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @api private + */ + +function ObjectId (key, options) { + SchemaType.call(this, key, options, 'ObjectID'); +}; + +/** + * Inherits from SchemaType. + */ + +ObjectId.prototype.__proto__ = SchemaType.prototype; + +/** + * Check required + * + * @api private + */ + +ObjectId.prototype.checkRequired = function checkRequired (value) { + if (SchemaType._isRef(this, value, true)) { + return null != value; + } else { + return value instanceof oid; + } +}; + +/** + * Casts to ObjectId + * + * @param {Object} value + * @param {Object} scope + * @param {Boolean} whether this is an initialization cast + * @api private + */ + +ObjectId.prototype.cast = function (value, scope, init) { + if (SchemaType._isRef(this, value, init)) return value; + + if (value === null) return value; + + if (value instanceof oid) + return value; + + if (value._id && value._id instanceof oid) + return value._id; + + if (value.toString) + return oid.fromString(value.toString()); + + throw new CastError('object id', value); +}; + +function handleSingle (val) { + return this.cast(val); +} + +function handleArray (val) { + var self = this; + return val.map(function (m) { + return self.cast(m); + }); +} + +ObjectId.prototype.$conditionalHandlers = { + '$ne': handleSingle + , '$in': handleArray + , '$nin': handleArray + , '$gt': handleSingle + , '$lt': handleSingle + , '$gte': handleSingle + , '$lte': handleSingle + , '$all': handleArray +}; + +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); + } else { + val = $conditional; + return this.cast(val); + } +}; + +/** + * Adds an auto-generated ObjectId default if turnOn is true. + * @param {Boolean} turnOn auto generated ObjectId defaults + * @api private + */ + +ObjectId.prototype.auto = function (turnOn) { + if (turnOn) { + this.default(function(){ + return new oid(); + }); + this.set(function (v) { + this.__id = null; + return v; + }) + } +}; + +/** + * Module exports. + */ + +module.exports = ObjectId; diff --git a/node_modules/mongoose/lib/schema/string.js b/node_modules/mongoose/lib/schema/string.js new file mode 100644 index 0000000..5187e4f --- /dev/null +++ b/node_modules/mongoose/lib/schema/string.js @@ -0,0 +1,180 @@ + +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError; + +/** + * String SchemaType constructor. + * + * @param {String} key + * @api private + */ + +function SchemaString (key, options) { + this.enumValues = []; + this.regExp = null; + SchemaType.call(this, key, options, 'String'); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaString.prototype.__proto__ = SchemaType.prototype; + +/** + * Adds enumeration values + * + * @param {multiple} enumeration values + * @api public + */ + +SchemaString.prototype.enum = function () { + var len = arguments.length; + if (!len || undefined === arguments[0] || false === arguments[0]) { + if (this.enumValidator){ + this.enumValidator = false; + this.validators = this.validators.filter(function(v){ + return v[1] != 'enum'; + }); + } + return; + } + + for (var i = 0; i < len; i++) { + if (undefined !== arguments[i]) { + this.enumValues.push(this.cast(arguments[i])); + } + } + + if (!this.enumValidator) { + var values = this.enumValues; + this.enumValidator = function(v){ + return ~values.indexOf(v); + }; + this.validators.push([this.enumValidator, 'enum']); + } +}; + +/** + * Adds a lowercase setter + * + * @api public + */ + +SchemaString.prototype.lowercase = function () { + return this.set(function (v) { + return v.toLowerCase(); + }); +}; + +/** + * Adds an uppercase setter + * + * @api public + */ + +SchemaString.prototype.uppercase = function () { + return this.set(function (v) { + return v.toUpperCase(); + }); +}; + +/** + * Adds a trim setter + * + * @api public + */ + +SchemaString.prototype.trim = function () { + return this.set(function (v) { + return v.trim(); + }); +}; + +/** + * Sets a regexp test + * + * @param {RegExp} regular expression to test against + * @param {String} optional validator message + * @api public + */ + +SchemaString.prototype.match = function(regExp){ + this.validators.push([function(v){ + return regExp.test(v); + }, 'regexp']); +}; + +/** + * Check required + * + * @api private + */ + +SchemaString.prototype.checkRequired = function checkRequired (value) { + if (SchemaType._isRef(this, value, true)) { + return null != value; + } else { + return (value instanceof String || typeof value == 'string') && value.length; + } +}; + +/** + * Casts to String + * + * @api private + */ + +SchemaString.prototype.cast = function (value, scope, init) { + if (SchemaType._isRef(this, value, init)) return value; + if (value === null) return value; + if ('undefined' !== typeof value && value.toString) return value.toString(); + throw new CastError('string', value); +}; + +function handleSingle (val) { + return this.castForQuery(val); +} + +function handleArray (val) { + var self = this; + return val.map(function (m) { + return self.castForQuery(m); + }); +} + +SchemaString.prototype.$conditionalHandlers = { + '$ne' : handleSingle + , '$in' : handleArray + , '$nin': handleArray + , '$gt' : handleSingle + , '$lt' : handleSingle + , '$gte': handleSingle + , '$lte': handleSingle + , '$all': handleArray + , '$regex': handleSingle +}; + +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); + } else { + val = $conditional; + if (val instanceof RegExp) return val; + return this.cast(val); + } +}; + +/** + * Module exports. + */ + +module.exports = SchemaString; diff --git a/node_modules/mongoose/lib/schemadefault.js b/node_modules/mongoose/lib/schemadefault.js new file mode 100644 index 0000000..a468c85 --- /dev/null +++ b/node_modules/mongoose/lib/schemadefault.js @@ -0,0 +1,32 @@ + +/** + * Module dependencies. + */ + +var Schema = require('./schema') + +/** + * Default model for querying the system.profiles + * collection (it only exists when profiling is + * enabled. + */ + +exports['system.profile'] = new Schema({ + ts: Date + , info: String // deprecated + , millis: Number + , op: String + , ns: String + , query: Schema.Types.Mixed + , updateobj: Schema.Types.Mixed + , ntoreturn: Number + , nreturned: Number + , nscanned: Number + , responseLength: Number + , client: String + , user: String + , idhack: Boolean + , scanAndOrder: Boolean + , keyUpdates: Number + , cursorid: Number +}, { noVirtualId: true, noId: true }); diff --git a/node_modules/mongoose/lib/schematype.js b/node_modules/mongoose/lib/schematype.js new file mode 100644 index 0000000..f2accb6 --- /dev/null +++ b/node_modules/mongoose/lib/schematype.js @@ -0,0 +1,428 @@ +/** + * Module dependencies. + */ + +var MongooseError = require('./error'); +var utils = require('./utils'); + +/** + * SchemaType constructor + * + * @param {String} path + * @api public + */ + +function SchemaType (path, options, instance) { + this.path = path; + this.instance = instance; + this.validators = []; + this.setters = []; + this.getters = []; + this.options = options; + this._index = null; + this.selected; + + for (var i in options) if (this[i] && 'function' == typeof this[i]) { + var opts = Array.isArray(options[i]) + ? options[i] + : [options[i]]; + + this[i].apply(this, opts); + } +}; + +/** + * Base schema. Set by Schema when instantiated. + * + * @api private + */ + +SchemaType.prototype.base; + +/** + * Sets a default + * + * @param {Object} default value + * @api public + */ + +SchemaType.prototype.default = function (val) { + if (1 === arguments.length) { + this.defaultValue = typeof val === 'function' + ? val + : this.cast(val); + return this; + } else if (arguments.length > 1) { + this.defaultValue = utils.args(arguments); + } + return this.defaultValue; +}; + +/** + * Sets index. It can be a boolean or a hash of options + * Example: + * Schema.path('my.path').index(true); + * Schema.path('my.path').index({ unique: true }); + * + * "Direction doesn't matter for single key indexes" + * http://www.mongodb.org/display/DOCS/Indexes#Indexes-CompoundKeysIndexes + * + * @param {Object} true/ + * @api public + */ + +SchemaType.prototype.index = function (index) { + this._index = index; + return this; +}; + +/** + * Adds an unique index + * + * @param {Boolean} + * @api private + */ + +SchemaType.prototype.unique = function (bool) { + if (!this._index || 'Object' !== this._index.constructor.name) { + this._index = {}; + } + + this._index.unique = bool; + return this; +}; + +/** + * Adds an unique index + * + * @param {Boolean} + * @api private + */ + +SchemaType.prototype.sparse = function (bool) { + if (!this._index || 'Object' !== this._index.constructor.name) { + this._index = {}; + } + + this._index.sparse = bool; + return this; +}; + +/** + * Adds a setter + * + * @param {Function} setter + * @api public + */ + +SchemaType.prototype.set = function (fn) { + if ('function' != typeof fn) + throw new Error('A setter must be a function.'); + this.setters.push(fn); + return this; +}; + +/** + * Adds a getter + * + * @param {Function} getter + * @api public + */ + +SchemaType.prototype.get = function (fn) { + if ('function' != typeof fn) + throw new Error('A getter must be a function.'); + this.getters.push(fn); + return this; +}; + +/** + * ##validate + * + * Adds validators. + * + * Examples: + * + * function validator () { ... } + * + * var single = [validator, 'failed'] + * new Schema({ name: { type: String, validate: single }}); + * + * var many = [ + * { validator: validator, msg: 'uh oh' } + * , { validator: fn, msg: 'failed' } + * ] + * new Schema({ name: { type: String, validate: many }}); + * + * @param {Object} validator + * @param {String} optional error message + * @api public + */ + +SchemaType.prototype.validate = function (obj, error) { + if ('function' == typeof obj || obj && 'RegExp' === obj.constructor.name) { + this.validators.push([obj, error]); + return this; + } + + var i = arguments.length + , arg + + while (i--) { + arg = arguments[i]; + this.validators.push([arg.validator, arg.msg]); + } + + return this; +}; + +/** + * Adds a required validator + * + * @param {Boolean} enable/disable the validator + * @api public + */ + +SchemaType.prototype.required = function (required) { + var self = this; + + function __checkRequired (v) { + // in here, `this` refers to the validating document. + // no validation when this path wasn't selected in the query. + if ('isSelected' in this && + !this.isSelected(self.path) && + !this.isModified(self.path)) return true; + return self.checkRequired(v); + } + + if (false === required) { + this.isRequired = false; + this.validators = this.validators.filter(function (v) { + return v[0].name !== '__checkRequired'; + }); + } else { + this.isRequired = true; + this.validators.push([__checkRequired, 'required']); + } + + return this; +}; + +/** + * Gets the default value + * + * @param {Object} scope for callback defaults + * @api private + */ + +SchemaType.prototype.getDefault = function (scope) { + var ret = 'function' === typeof this.defaultValue + ? this.defaultValue.call(scope) + : this.defaultValue; + + if (null !== ret && undefined !== ret) { + return this.cast(ret, scope); + } else { + return ret; + } +}; + +/** + * Applies setters + * + * @param {Object} value + * @param {Object} scope + * @api private + */ + +SchemaType.prototype.applySetters = function (value, scope, init) { + if (SchemaType._isRef(this, value, init)) return value; + + var v = value + , setters = this.setters + , len = setters.length; + + for (var k = len - 1; k >= 0; k--) { + v = setters[k].call(scope, v, this); + if (null === v || undefined === v) return v; + v = this.cast(v, scope); + } + + if (!len) { + if (null === v || undefined === v) return v; + if (!init) { + // if we just initialized we dont recast + v = this.cast(v, scope, init); + } + } + + return v; +}; + +/** + * Applies getters to a value + * + * @param {Object} value + * @param {Object} scope + * @api private + */ + +SchemaType.prototype.applyGetters = function (value, scope) { + if (SchemaType._isRef(this, value, true)) return value; + + var v = value + , getters = this.getters + , len = getters.length; + + for (var k = len - 1; k >= 0; k--) { + v = this.getters[k].call(scope, v, this); + if (null === v || undefined === v) return v; + v = this.cast(v, scope); + } + + if (!len) { + if (null === v || undefined === v) return v; + v = this.cast(v, scope); + } + + return v; +}; + +/** + * ## select + * + * Set default select() behavior for this path. True if + * this path should always be included in the results, + * false if it should be excluded by default. This setting + * can be overridden at the query level. + * + * T = db.model('T', new Schema({ x: { type: String, select: true }})); + * T.find(..); // x will always be selected .. + * // .. unless overridden; + * T.find().select({ x: 0 }).exec(); + */ + +SchemaType.prototype.select = function select (val) { + this.selected = !! val; +} + +/** + * Performs a validation + * + * @param {Function} callback + * @param {Object} scope + * @api private + */ + +SchemaType.prototype.doValidate = function (value, fn, scope) { + var err = false + , path = this.path + , count = this.validators.length; + + if (!count) return fn(null); + + function validate (val, msg) { + if (err) return; + if (val === undefined || val) { + --count || fn(null); + } else { + fn(err = new ValidatorError(path, msg)); + } + } + + this.validators.forEach(function (v) { + var validator = v[0] + , message = v[1]; + + if (validator instanceof RegExp) { + validate(validator.test(value), message); + } else if ('function' === typeof validator) { + if (2 === validator.length) { + validator.call(scope, value, function (val) { + validate(val, message); + }); + } else { + validate(validator.call(scope, value), message); + } + } + }); +}; + +/** + * Determines if value is a valid Reference. + * + * @param {SchemaType} self + * @param {object} value + * @param {Boolean} init + * @param {MongooseType} instance + * @return Boolean + * @private + */ + +SchemaType._isRef = function (self, value, init) { + if (self.options && self.options.ref && init) { + if (null == value) return true; + if (value._id && value._id.constructor.name === self.instance) return true; + } + + return false; +} + +/** + * Schema validator error + * + * @param {String} path + * @param {String} msg + * @api private + */ + +function ValidatorError (path, type) { + var msg = type + ? '"' + type + '" ' + : ''; + MongooseError.call(this, 'Validator ' + msg + 'failed for path ' + path); + Error.captureStackTrace(this, arguments.callee); + this.name = 'ValidatorError'; + this.path = path; + this.type = type; +}; + +ValidatorError.prototype.toString = function () { + return this.message; +} + +/** + * Inherits from MongooseError + */ + +ValidatorError.prototype.__proto__ = MongooseError.prototype; + +/** + * Cast error + * + * @api private + */ + +function CastError (type, value) { + MongooseError.call(this, 'Cast to ' + type + ' failed for value "' + value + '"'); + Error.captureStackTrace(this, arguments.callee); + this.name = 'CastError'; + this.type = type; + this.value = value; +}; + +/** + * Inherits from MongooseError. + */ + +CastError.prototype.__proto__ = MongooseError.prototype; + +/** + * Module exports. + */ + +module.exports = exports = SchemaType; + +exports.CastError = CastError; + +exports.ValidatorError = ValidatorError; diff --git a/node_modules/mongoose/lib/statemachine.js b/node_modules/mongoose/lib/statemachine.js new file mode 100644 index 0000000..fb8ceb5 --- /dev/null +++ b/node_modules/mongoose/lib/statemachine.js @@ -0,0 +1,179 @@ + +/** + * 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 () { + this.paths = {}; + this.states = {}; +} + +/** + * 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.stateNames = states; + + var i = states.length + , state; + + while (i--) { + state = states[i]; + this.states[state] = {}; + } + }; + + ctor.prototype.__proto__ = StateMachine.prototype; + + 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 prevState = this.paths[path] + , prevBucket = this.states[prevState]; + + delete this.paths[path]; + if (prevBucket) delete prevBucket[path]; + + this.paths[path] = nextState; + this.states[nextState][path] = true; +} + +StateMachine.prototype.stateOf = function stateOf (path) { + return this.paths[path]; +} + +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 self = this; + var what = arguments.length ? arguments : this.stateNames; + return Array.prototype.some.call(what, function (state) { + return Object.keys(self.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 self = this; + + var paths = states.reduce(function (paths, state) { + return paths.concat(Object.keys(self.states[state])); + }, []); + + return paths[iterMethod](function (path, i, paths) { + return callback(path, i, paths); + }); + }; +} + +/** + * Iterates over the paths that belong to one of the parameter states. + * + * The function profile can look like: + * this.forEach(state1, fn); // iterates over all paths in state1 + * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2 + * this.forEach(fn); // iterates over all paths in all states + * + * @param {String} [state] + * @param {String} [state] + * @param {Function} callback + * @private + */ + +StateMachine.prototype.forEach = function forEach () { + this.forEach = this._iter('forEach'); + return this.forEach.apply(this, arguments); +} + +/** + * Maps over the paths that belong to one of the parameter states. + * + * The function profile can look like: + * this.forEach(state1, fn); // iterates over all paths in state1 + * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2 + * this.forEach(fn); // iterates over all paths in all states + * + * @param {String} [state] + * @param {String} [state] + * @param {Function} callback + * @return {Array} + * @private + */ + +StateMachine.prototype.map = function map () { + this.map = this._iter('map'); + return this.map.apply(this, arguments); +} + diff --git a/node_modules/mongoose/lib/types/array.js b/node_modules/mongoose/lib/types/array.js new file mode 100644 index 0000000..67f88b6 --- /dev/null +++ b/node_modules/mongoose/lib/types/array.js @@ -0,0 +1,422 @@ + +/** + * Module dependencies. + */ + +var EmbeddedDocument = require('./embedded'); +var Document = require('../document'); +var ObjectId = require('./objectid'); + +/** + * Mongoose Array constructor. + * Values always have to be passed to the constructor to initialize, since + * otherwise MongooseArray#push will mark the array as modified to the parent. + * + * @param {Array} values + * @param {String} key path + * @param {Document} parent document + * @api private + * @see http://bit.ly/f6CnZU + */ + +function MongooseArray (values, path, doc) { + var arr = []; + arr.push.apply(arr, values); + arr.__proto__ = MongooseArray.prototype; + + arr._atomics = {}; + arr.validators = []; + arr._path = path; + + if (doc) { + arr._parent = doc; + arr._schema = doc.schema.path(path); + } + + return arr; +}; + +/** + * Inherit from Array + */ + +MongooseArray.prototype = new Array; + +/** + * Stores a queue of atomic operations to perform + * + * @api private + */ + +MongooseArray.prototype._atomics; + +/** + * Parent owner document + * + * @api private + */ + +MongooseArray.prototype._parent; + +/** + * Casts a member + * + * @api private + */ + +MongooseArray.prototype._cast = function (value) { + var cast = this._schema.caster.cast + , doc = this._parent; + + return cast.call(null, value, doc); +}; + +/** + * Marks this array as modified. + * It is called during a nonAtomicPush, an atomic opteration, + * or by an existing embedded document that is modified. + * + * If it bubbles up from an embedded document change, + * then it takes the following arguments (otherwise, takes + * 0 arguments) + * @param {EmbeddedDocument} embeddedDoc that invokes this method on the Array + * @param {String} embeddedPath is what changed inthe embeddedDoc + * + * @api public + */ + +MongooseArray.prototype._markModified = function (embeddedDoc, embeddedPath) { + var parent = this._parent + , dirtyPath; + + if (parent) { + if (arguments.length) { + // If an embedded doc bubbled up the change + dirtyPath = [this._path, this.indexOf(embeddedDoc), embeddedPath].join('.'); + } else { + dirtyPath = this._path; + } + parent.markModified(dirtyPath); + } + + return this; +}; + +/** + * Register an atomic operation with the parent + * + * @param {Array} operation + * @api private + */ + +MongooseArray.prototype._registerAtomic = function (op, val) { + var atomics = this._atomics + 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'] = {}) + , selector = pullOp['_id'] || (pullOp['_id'] = {'$in' : [] }); + selector['$in'] = selector['$in'].concat(val); + } else { + atomics[op] = val; + } + this._markModified(); + return this; +}; + +/** + * Returns true if we have to perform atomics for this, and no normal + * operations + * + * @api public + */ + +MongooseArray.prototype.__defineGetter__('doAtomics', function () { + if (!(this._atomics && 'Object' === this._atomics.constructor.name)) { + return 0; + } + + return Object.keys(this._atomics).length; +}); + +/** + * Pushes item/s to the array atomically. Overrides Array#push + * + * @param {Object} value + * @api public + */ + +MongooseArray.prototype.$push = +MongooseArray.prototype.push = function () { + var values = [].map.call(arguments, this._cast, this) + , 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); + return ret; +}; + +/** + * Pushes item/s to the array non-atomically + * + * @param {Object} value + * @api public + */ + +MongooseArray.prototype.nonAtomicPush = function () { + var values = [].map.call(arguments, this._cast, this) + , ret = [].push.apply(this, values); + this._markModified(); + return ret; +}; + +/** + * Pushes several items at once to the array atomically + * + * @param {Array} values + * @api public + */ + +MongooseArray.prototype.$pushAll = function (value) { + var length = this.length; + this.nonAtomicPush.apply(this, value); + // make sure we access the casted elements + this._registerAtomic('$pushAll', this.slice(length)); + return this; +}; + +/** + * Pops the array atomically + * + * @api public + */ + +MongooseArray.prototype.$pop = function () { + this._registerAtomic('$pop', 1); + return this.pop(); +}; + +/** + * Atomically shifts the array. + * + * Only works once per doc.save(). Calling this mulitple + * times on an array before saving is the same as calling + * it once. + * + * @api public + */ + +MongooseArray.prototype.$shift = function () { + this._registerAtomic('$pop', -1); + return this.shift(); +}; + +/** + * Removes items from an array atomically + * + * Examples: + * doc.array.remove(ObjectId) + * doc.array.remove('tag 1', 'tag 2') + * + * @param {Object} value to remove + * @api public + */ + +MongooseArray.prototype.remove = function () { + var args = [].map.call(arguments, this._cast, this); + if (args.length == 1) + this.$pull(args[0]); + else + this.$pullAll(args); + return args; +}; + +/** + * Pulls from the array + * + * @api public + */ + +MongooseArray.prototype.pull = +MongooseArray.prototype.$pull = function () { + var values = [].map.call(arguments, this._cast, this) + , oldArr = this._parent.get(this._path) + , i = oldArr.length + , mem; + + while (i--) { + mem = oldArr[i]; + if (mem instanceof EmbeddedDocument) { + if (values.some(function (v) { return v.equals(mem); } )) { + oldArr.splice(i, 1); + } + } else if (~values.indexOf(mem)) { + oldArr.splice(i, 1); + } + } + + if (values[0] instanceof EmbeddedDocument) { + this._registerAtomic('$pullDocs', values.map( function (v) { return v._id; } )); + } else { + this._registerAtomic('$pullAll', values); + } + + return this; +}; + +/** + * Pulls many items from an array + * + * @api public + */ + +MongooseArray.prototype.$pullAll = function (values) { + if (values && values.length) { + var oldArr = this._parent.get(this._path) + , i = oldArr.length, mem; + while (i--) { + mem = oldArr[i]; + if (mem instanceof EmbeddedDocument) { + if (values.some( function (v) { return v.equals(mem); } )) { + oldArr.splice(i, 1); + } + } else if (~values.indexOf(mem)) oldArr.splice(i, 1); + } + if (values[0] instanceof EmbeddedDocument) { + this._registerAtomic('$pullDocs', values.map( function (v) { return v._id; } )); + } else { + this._registerAtomic('$pullAll', values); + } + } + return this; +}; + +/** + * Splices the array. + * + * 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. + * + * @api public + */ + +MongooseArray.prototype.splice = function () { + var result = [].splice.apply(this, arguments); + this._markModified(); + return result; +}; + +/** + * Non-atomically unshifts onto the array. + * + * 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. + * + * @api public + */ + +MongooseArray.prototype.unshift = function () { + var values = [].map.call(arguments, this._cast, this); + [].unshift.apply(this, values); + this._markModified(); + return this.length; +}; + +/** + * Adds values to the array if not already present. + * @api public + */ + +MongooseArray.prototype.$addToSet = +MongooseArray.prototype.addToSet = function addToSet () { + var values = [].map.call(arguments, this._cast, this) + , added = [] + , type = values[0] instanceof EmbeddedDocument ? 'doc' : + values[0] instanceof Date ? '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); + [].push.call(added, v); + } + }, this); + + return added; +}; + +/** + * Returns an Array + * + * @return {Array} + * @api public + */ + +MongooseArray.prototype.toObject = function (options) { + if (options && options.depopulate && this[0] instanceof Document) { + return this.map(function (doc) { + return doc._id; + }); + } + + return this.map(function (doc) { + return doc; + }); +}; + +/** + * Helper for console.log + * + * @api public + */ + +MongooseArray.prototype.inspect = function () { + return '[' + this.map(function (doc) { + return ' ' + doc; + }) + ' ]'; +}; + +/** + * Return the index of `obj` or `-1.` + * + * @param {Object} obj + * @return {Number} + * @api public + */ + +MongooseArray.prototype.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; +}; + +/** + * Module exports. + */ + +module.exports = exports = MongooseArray; diff --git a/node_modules/mongoose/lib/types/buffer.js b/node_modules/mongoose/lib/types/buffer.js new file mode 100644 index 0000000..9092f20 --- /dev/null +++ b/node_modules/mongoose/lib/types/buffer.js @@ -0,0 +1,171 @@ + +/** + * Access driver. + */ + +var driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native'; + +/** + * Module dependencies. + */ + +var Binary = require(driver + '/binary'); + +/** + * Mongoose Buffer constructor. + * Values always have to be passed to the constructor to initialize, since + * otherwise MongooseBuffer#push will mark the buffer as modified to the parent. + * + * @param {Buffer} value + * @param {String} key path + * @param {Document} parent document + * @api private + * @see http://bit.ly/f6CnZU + */ + +function MongooseBuffer (value, encode, offset) { + var length = arguments.length; + var val; + + if (0 === length || null === arguments[0] || undefined === arguments[0]) { + 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); + buf.__proto__ = MongooseBuffer.prototype; + + // make sure these internal props don't show up in Object.keys() + Object.defineProperties(buf, { + validators: { value: [] } + , _path: { value: path } + , _parent: { value: doc } + }); + + if (doc && "string" === typeof path) { + Object.defineProperty(buf, '_schema', { + value: doc.schema.path(path) + }); + } + + return buf; +}; + +/** + * Inherit from Buffer. + */ + +MongooseBuffer.prototype = new Buffer(0); + +/** + * Parent owner document + * + * @api private + */ + +MongooseBuffer.prototype._parent; + + +/** +* Marks this buffer as modified. +* +* @api public +*/ + +MongooseBuffer.prototype._markModified = function () { + var parent = this._parent; + + if (parent) { + parent.markModified(this._path); + } + return this; +}; + +/** +* Writes the buffer. +*/ + +MongooseBuffer.prototype.write = function () { + var written = Buffer.prototype.write.apply(this, arguments); + + if (written > 0) { + this._markModified(); + } + + return written; +}; + +/** +* Copy the buffer. +* +* Note: Buffer#copy will not mark target as modified so +* you must copy from a MongooseBuffer for it to work +* as expected. +* +* Work around since copy modifies the target, not this. +*/ + +MongooseBuffer.prototype.copy = function (target) { + var ret = Buffer.prototype.copy.apply(this, arguments); + + if (target instanceof MongooseBuffer) { + 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.prototype[method] = new Function( + 'var ret = Buffer.prototype.'+method+'.apply(this, arguments);' + + 'this._markModified();' + + 'return ret;' + ) +}); + +/** + * Returns a Binary. + * + * @return {Buffer} + * @api public + */ + +MongooseBuffer.prototype.toObject = function () { + return new Binary(this, 0x00); +}; + +/** + * Module exports. + */ + +MongooseBuffer.Binary = Binary; + +module.exports = MongooseBuffer; diff --git a/node_modules/mongoose/lib/types/documentarray.js b/node_modules/mongoose/lib/types/documentarray.js new file mode 100644 index 0000000..f3c66bb --- /dev/null +++ b/node_modules/mongoose/lib/types/documentarray.js @@ -0,0 +1,133 @@ + +/** + * Module dependencies. + */ + +var MongooseArray = require('./array') + , driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native' + , ObjectId = require(driver + '/objectid') + , ObjectIdSchema = require('../schema/objectid'); + +/** + * Array of embedded documents + * Values always have to be passed to the constructor to initialize, since + * otherwise MongooseArray#push will mark the array as modified to the parent. + * + * @param {Array} values + * @param {String} key path + * @param {Document} parent document + * @api private + * @see http://bit.ly/f6CnZU + */ + +function MongooseDocumentArray (values, path, doc) { + var arr = []; + arr.push.apply(arr, values); + arr.__proto__ = MongooseDocumentArray.prototype; + + arr._atomics = {}; + arr.validators = []; + arr._path = path; + + if (doc) { + arr._parent = doc; + arr._schema = doc.schema.path(path); + doc.on('save', arr.notify('save')); + doc.on('isNew', arr.notify('isNew')); + } + + return arr; +}; + +/** + * Inherits from MongooseArray + */ + +MongooseDocumentArray.prototype.__proto__ = MongooseArray.prototype; + +/** + * Overrides cast + * + * @api private + */ + +MongooseDocumentArray.prototype._cast = function (value) { + var doc = new this._schema.caster(value, this); + return doc; +}; + +/** + * Filters items by id + * + * @param {Object} id + * @api public + */ + +MongooseDocumentArray.prototype.id = function (id) { + try { + var casted = ObjectId.toString(ObjectIdSchema.prototype.cast.call({}, id)); + } catch (e) { + var casted = null; + } + + for (var i = 0, l = this.length; i < l; i++) { + if (!(this[i].get('_id') instanceof ObjectId)) { + if (String(id) == this[i].get('_id').toString()) + return this[i]; + } else { + if (casted == this[i].get('_id').toString()) + return this[i]; + } + } + + return null; +}; + +/** + * Returns an Array and converts any Document + * members toObject. + * + * @return {Array} + * @api public + */ + +MongooseDocumentArray.prototype.toObject = function () { + return this.map(function (doc) { + return doc && doc.toObject() || null; + }); +}; + +/** + * Helper for console.log + * + * @api public + */ + +MongooseDocumentArray.prototype.inspect = function () { + return '[' + this.map(function (doc) { + return doc && doc.inspect() || 'null'; + }).join('\n') + ']'; +}; + +/** + * Create fn that notifies all child docs of event. + * + * @param {String} event + * @api private + */ + +MongooseDocumentArray.prototype.notify = function notify (event) { + var self = this; + return function notify (val) { + var i = self.length; + while (i--) { + self[i].emit(event, val); + } + } +} + +/** + * Module exports. + */ + +module.exports = MongooseDocumentArray; diff --git a/node_modules/mongoose/lib/types/embedded.js b/node_modules/mongoose/lib/types/embedded.js new file mode 100644 index 0000000..dff1fe3 --- /dev/null +++ b/node_modules/mongoose/lib/types/embedded.js @@ -0,0 +1,103 @@ + +/** + * Module dependencies. + */ + +var Document = require('../document') + , inspect = require('util').inspect; + +/** + * EmbeddedDocument constructor. + * + * @param {Object} object from db + * @param {MongooseDocumentArray} parent array + * @api private + */ + +function EmbeddedDocument (obj, parentArr) { + this.parentArray = parentArr; + this.parent = parentArr._parent; + Document.call(this, obj); + + var self = this; + this.on('isNew', function (val) { + self.isNew = val; + }); +}; + +/** + * Inherit from Document + */ + +EmbeddedDocument.prototype.__proto__ = Document.prototype; + +/** + * Marks parent array as modified + * + * @api private + */ + +EmbeddedDocument.prototype.commit = +EmbeddedDocument.prototype.markModified = function (path) { + this._activePaths.modify(path); + + 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); +}; + +/** + * Noop. Does not actually save the doc to the db. + * + * @api public + */ + +EmbeddedDocument.prototype.save = function(fn) { + if (fn) + fn(null); + return this; +}; + +/** + * Remove the subdocument + * + * @api public + */ + +EmbeddedDocument.prototype.remove = function (fn) { + 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; + } + + if (fn) + fn(null); + + return this; +}; + +/** + * Helper for console.log + * + * @api public + */ + +EmbeddedDocument.prototype.inspect = function () { + return inspect(this.toObject()); +}; + +/** + * Module exports. + */ + +module.exports = EmbeddedDocument; diff --git a/node_modules/mongoose/lib/types/index.js b/node_modules/mongoose/lib/types/index.js new file mode 100644 index 0000000..d07878f --- /dev/null +++ b/node_modules/mongoose/lib/types/index.js @@ -0,0 +1,14 @@ + +/** + * Module exports. + */ + +exports.Array = require('./array'); +exports.Buffer = require('./buffer'); + +exports.Document = // @deprecate +exports.Embedded = require('./embedded'); + +exports.DocumentArray = require('./documentarray'); +exports.Number = require('./number'); +exports.ObjectId = require('./objectid'); diff --git a/node_modules/mongoose/lib/types/number.js b/node_modules/mongoose/lib/types/number.js new file mode 100644 index 0000000..b2a69b6 --- /dev/null +++ b/node_modules/mongoose/lib/types/number.js @@ -0,0 +1,84 @@ + +/** + * Module dependencies. + */ + +/** + * MongooseNumber constructor. + * + * @param {Object} value to pass to Number + * @param {Document} parent document + * @deprecated (remove in 3.x) + * @api private + */ + +function MongooseNumber (value, path, doc) { + var number = new Number(value); + number.__proto__ = MongooseNumber.prototype; + number._atomics = {}; + number._path = path; + number._parent = doc; + return number; +}; + +/** + * Inherits from Number. + */ + +MongooseNumber.prototype = new Number(); + +/** + * Atomic increment + * + * @api public + */ + +MongooseNumber.prototype.$inc = +MongooseNumber.prototype.increment = function increment (value) { + var schema = this._parent.schema.path(this._path) + , value = Number(value) || 1; + if (isNaN(value)) value = 1; + this._parent.setValue(this._path, schema.cast(this + value)); + this._parent.getValue(this._path)._atomics['$inc'] = value || 1; + this._parent._activePaths.modify(this._path); + return this; +}; + +/** + * Returns true if we have to perform atomics for this, and no normal + * operations + * + * @api public + */ + +MongooseNumber.prototype.__defineGetter__('doAtomics', function () { + return Object.keys(this._atomics).length; +}); + +/** + * Atomic decrement + * + * @api public + */ + +MongooseNumber.prototype.decrement = function(){ + this.increment(-1); +}; + +/** + * Re-declare toString (for `console.log`) + * + * @api public + */ + +MongooseNumber.prototype.inspect = +MongooseNumber.prototype.toString = function () { + return String(this.valueOf()); +}; + + +/** + * Module exports + */ + +module.exports = MongooseNumber; diff --git a/node_modules/mongoose/lib/types/objectid.js b/node_modules/mongoose/lib/types/objectid.js new file mode 100644 index 0000000..e811fdf --- /dev/null +++ b/node_modules/mongoose/lib/types/objectid.js @@ -0,0 +1,12 @@ + +/** + * Access driver. + */ + +var driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native'; + +/** + * Module exports. + */ + +module.exports = require(driver + '/objectid'); diff --git a/node_modules/mongoose/lib/utils.js b/node_modules/mongoose/lib/utils.js new file mode 100644 index 0000000..2d8f8ee --- /dev/null +++ b/node_modules/mongoose/lib/utils.js @@ -0,0 +1,434 @@ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , ObjectId = require('./types/objectid') + , MongooseBuffer + , MongooseArray + , Document + +/** + * Produces a collection name from a model name + * + * @param {String} model name + * @return {String} collection name + * @api private + */ + +exports.toCollectionName = function (name) { + if ('system.profile' === name) return name; + if ('system.indexes' === name) return name; + return pluralize(name.toLowerCase()); +}; + +/** + * Pluralization rules. + */ + +var rules = [ + [/(m)an$/gi, '$1en'], + [/(pe)rson$/gi, '$1ople'], + [/(child)$/gi, '$1ren'], + [/^(ox)$/gi, '$1en'], + [/(ax|test)is$/gi, '$1es'], + [/(octop|vir)us$/gi, '$1i'], + [/(alias|status)$/gi, '$1es'], + [/(bu)s$/gi, '$1ses'], + [/(buffal|tomat|potat)o$/gi, '$1oes'], + [/([ti])um$/gi, '$1a'], + [/sis$/gi, 'ses'], + [/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'], + [/(hive)$/gi, '$1s'], + [/([^aeiouy]|qu)y$/gi, '$1ies'], + [/(x|ch|ss|sh)$/gi, '$1es'], + [/(matr|vert|ind)ix|ex$/gi, '$1ices'], + [/([m|l])ouse$/gi, '$1ice'], + [/(quiz)$/gi, '$1zes'], + [/s$/gi, 's'], + [/$/gi, 's'] +]; + +/** + * Uncountable words. + */ + +var uncountables = [ + 'advice', + 'energy', + 'excretion', + 'digestion', + 'cooperation', + 'health', + 'justice', + 'labour', + 'machinery', + 'equipment', + 'information', + 'pollution', + 'sewage', + 'paper', + 'money', + 'species', + 'series', + 'rain', + 'rice', + 'fish', + 'sheep', + 'moose', + 'deer', + 'news' +]; + +/** + * Pluralize function. + * + * @author TJ Holowaychuk (extracted from _ext.js_) + * @param {String} string to pluralize + * @api private + */ + +function pluralize (str) { + var rule, found; + if (!~uncountables.indexOf(str.toLowerCase())){ + found = rules.filter(function(rule){ + return str.match(rule[0]); + }); + if (found[0]) return str.replace(found[0][0], found[0][1]); + } + return str; +}; + +/** + * Add `once` to EventEmitter if absent + * + * @param {String} event name + * @param {Function} listener + * @api private + */ + +var Events = EventEmitter; + +if (!('once' in EventEmitter.prototype)){ + + Events = function () { + EventEmitter.apply(this, arguments); + }; + + /** + * Inherit from EventEmitter. + */ + + Events.prototype.__proto__ = EventEmitter.prototype; + + /** + * Add `once`. + */ + + Events.prototype.once = function (type, listener) { + var self = this; + self.on(type, function g(){ + self.removeListener(type, g); + listener.apply(this, arguments); + }); + }; + +} + +exports.EventEmitter = Events; + +// Modified from node/lib/assert.js +exports.deepEqual = function deepEqual (a, b) { + if (a === b) return true; + + if (a instanceof Date && b instanceof Date) + return a.getTime() === b.getTime(); + + if (a instanceof ObjectId && b instanceof ObjectId) { + return a.toString() === b.toString(); + } + + if (typeof a !== 'object' && typeof b !== 'object') + return a == b; + + if (a === null || b === null || a === undefined || b === undefined) + return false + + if (a.prototype !== b.prototype) return false; + + // Handle MongooseNumbers + if (a instanceof Number && b instanceof Number) { + return a.valueOf() === b.valueOf(); + } + + if (Buffer.isBuffer(a)) { + if (!Buffer.isBuffer(b)) return false; + if (a.length !== b.length) return false; + for (var i = 0, len = a.length; i < len; ++i) { + if (a[i] !== b[i]) return false; + } + return true; + } + + if (isMongooseObject(a)) a = a.toObject(); + if (isMongooseObject(b)) b = b.toObject(); + + try { + var ka = Object.keys(a), + kb = Object.keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!deepEqual(a[key], b[key])) return false; + } + + return true; +}; + +/** + * Object clone with Mongoose natives support. + * Creates a minimal data Object. + * It does not clone empty Arrays, empty Objects, + * and undefined values. + * This makes the data payload sent to MongoDB as minimal + * as possible. + * + * @param {Object} object to clone + * @param {Object} options - minimize , retainKeyOrder + * @return {Object} cloned object + * @api private + */ + +var clone = exports.clone = function clone (obj, options) { + if (obj === undefined || obj === null) + return obj; + + if (Array.isArray(obj)) + return cloneArray(obj, options); + + if (isMongooseObject(obj)) { + if (options && options.json && 'function' === typeof obj.toJSON) { + return obj.toJSON(options); + } else { + return obj.toObject(options); + } + } + + if ('Object' === obj.constructor.name) + return cloneObject(obj, options); + + if ('Date' === obj.constructor.name || 'Function' === obj.constructor.name) + return new obj.constructor(+obj); + + if ('RegExp' === obj.constructor.name) + return new RegExp(obj.source); + + if (obj instanceof ObjectId) + return ObjectId.fromString(ObjectId.toString(obj)); + + if (obj.valueOf) + return obj.valueOf(); +}; + +function cloneObject (obj, options) { + var retainKeyOrder = options && options.retainKeyOrder + , minimize = options && options.minimize + , ret = {} + , hasKeys + , keys + , val + , k + , i + + if (retainKeyOrder) { + for (k in obj) { + val = clone(obj[k], options); + + if (!minimize || ('undefined' !== typeof val)) { + hasKeys || (hasKeys = true); + ret[k] = val; + } + } + } else { + // faster + + keys = Object.keys(obj); + i = keys.length; + + while (i--) { + k = keys[i]; + val = clone(obj[k], options); + + if (!minimize || ('undefined' !== typeof val)) { + if (!hasKeys) hasKeys = true; + ret[k] = val; + } + } + } + + return minimize + ? hasKeys && ret + : ret; +}; + +function cloneArray (arr, options) { + var ret = []; + for (var i = 0, l = arr.length; i < l; i++) + ret.push(clone(arr[i], options)); + return ret; +}; + +/** + * Copies and merges options with defaults. + * + * @param {Object} defaults + * @param {Object} options + * @return {Object} (merged) object + * @api private + */ + +exports.options = function (defaults, options) { + var keys = Object.keys(defaults) + , i = keys.length + , k ; + + options = options || {}; + + while (i--) { + k = keys[i]; + if (!(k in options)) { + options[k] = defaults[k]; + } + } + + return options; +}; + +/** + * Generates a random string + * + * @api private + */ + +exports.random = function () { + return Math.random().toString().substr(3); +}; + +exports.inGroupsOf = function inGroupsOf (card, arr, fn) { + var group = []; + for (var i = 0, l = arr.length; i < l; i++) { + if (i && i % card === 0) { + fn.apply(this, group); + group.length = 0; + } + group.push(arr[i]); + } + fn.apply(this, group); +}; + +/** + * Merges `from` into `to` without overwriting + * existing properties of `to`. + * + * @param {Object} to + * @param {Object} from + */ + +exports.merge = function merge (to, from) { + var keys = Object.keys(from) + , i = keys.length + , key + + while (i--) { + key = keys[i]; + if ('undefined' === typeof to[key]) { + to[key] = from[key]; + } else { + merge(to[key], from[key]); + } + } +}; + +/** + * A faster Array.prototype.slice.call(arguments) alternative + */ + +exports.args = function (args, slice, sliceEnd) { + var ret = []; + var start = slice || 0; + var end = 3 === arguments.length + ? sliceEnd + : args.length; + + for (var i = start; i < end; ++i) { + ret[i - start] = args[i]; + } + + return ret; +} + +/** + * process.nextTick helper. + * + * Wraps `callback` in a try/catch + nextTick. + * + * -native has a habit of state corruption + * when an error is immediately thrown from within + * a collection callback. + * + * @param {Function} callback + * @api private + */ + +exports.tick = function tick (callback) { + if ('function' !== typeof callback) return; + return function () { + try { + callback.apply(this, arguments); + } catch (err) { + // only nextTick on err to get out of + // the event loop and avoid state corruption. + process.nextTick(function () { + throw err; + }); + } + } +} + +/** + * Returns if `v` is a mongoose object that has + * a `toObject()` method we can use. This is for + * compatibility with libs like Date.js which do + * foolish things to Natives. + */ + +var isMongooseObject = exports.isMongooseObject = function (v) { + Document || (Document = require('./document')); + MongooseArray || (MongooseArray = require('./types').Array); + MongooseBuffer || (MongooseBuffer = require('./types').Buffer); + + return v instanceof Document || + v instanceof MongooseArray || + v instanceof MongooseBuffer +} diff --git a/node_modules/mongoose/lib/virtualtype.js b/node_modules/mongoose/lib/virtualtype.js new file mode 100644 index 0000000..5779df7 --- /dev/null +++ b/node_modules/mongoose/lib/virtualtype.js @@ -0,0 +1,74 @@ +/** + * VirtualType constructor + * + * This is what mongoose uses to define virtual attributes via + * `Schema.prototype.virtual` + * + * @api public + */ + +function VirtualType (options) { + this.getters = []; + this.setters = []; + this.options = options || {}; +} + +/** + * Adds a getter + * + * @param {Function} fn + * @return {VirtualType} this + * @api public + */ + +VirtualType.prototype.get = function (fn) { + this.getters.push(fn); + return this; +}; + +/** + * Adds a setter + * + * @param {Function} fn + * @return {VirtualType} this + * @api public + */ + +VirtualType.prototype.set = function (fn) { + this.setters.push(fn); + return this; +}; + +/** + * Applies getters + * + * @param {Object} value + * @param {Object} scope + * @api public + */ + +VirtualType.prototype.applyGetters = function (value, scope) { + var v = value; + for (var l = this.getters.length - 1; l >= 0; l--){ + v = this.getters[l].call(scope, v); + } + return v; +}; + +/** + * Applies setters + * + * @param {Object} value + * @param {Object} scope + * @api public + */ + +VirtualType.prototype.applySetters = function (value, scope) { + var v = value; + for (var l = this.setters.length - 1; l >= 0; l--){ + this.setters[l].call(scope, v); + } + return v; +}; + +module.exports = VirtualType; |
