diff options
Diffstat (limited to 'node_modules/mongoose/lib/schema.js')
| -rw-r--r-- | node_modules/mongoose/lib/schema.js | 565 |
1 files changed, 565 insertions, 0 deletions
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; + |
