3139 lines
105 KiB
JavaScript
3139 lines
105 KiB
JavaScript
//////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// This is a generated file. You can view the original //
|
||
// source in your browser if your browser supports source maps. //
|
||
// Source maps are supported by all recent versions of Chrome, Safari, //
|
||
// and Firefox, and by Internet Explorer 11. //
|
||
// //
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
(function () {
|
||
|
||
/* Imports */
|
||
var Meteor = Package.meteor.Meteor;
|
||
var global = Package.meteor.global;
|
||
var meteorEnv = Package.meteor.meteorEnv;
|
||
var Tracker = Package.tracker.Tracker;
|
||
var Deps = Package.tracker.Deps;
|
||
var _ = Package.underscore._;
|
||
var check = Package.check.check;
|
||
var Match = Package.check.Match;
|
||
var Random = Package.random.Random;
|
||
|
||
/* Package-scope variables */
|
||
var humanize, MongoObject, Utility, SimpleSchema, doValidation1, doValidation2, SimpleSchemaValidationContext;
|
||
|
||
(function(){
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// packages/aldeed_simple-schema/string-polyfills.js //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
if (!String.prototype.startsWith) {
|
||
String.prototype.startsWith = function(searchString, position) {
|
||
position = position || 0;
|
||
return this.indexOf(searchString, position) === position;
|
||
};
|
||
}
|
||
|
||
if (!String.prototype.trim) {
|
||
String.prototype.trim = function () {
|
||
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
|
||
};
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
}).call(this);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
(function(){
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// packages/aldeed_simple-schema/string-humanize.js //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
/*
|
||
Code source:
|
||
https://github.com/jxson/string-humanize
|
||
https://github.com/jxson/string-capitalize
|
||
*/
|
||
|
||
function capitalize(string){
|
||
string = string || '';
|
||
string = string.trim();
|
||
|
||
if (string[0]) {
|
||
string = string[0].toUpperCase() + string.substr(1).toLowerCase();
|
||
}
|
||
|
||
return string;
|
||
}
|
||
|
||
humanize = function humanize(string){
|
||
string = string || '';
|
||
string = string.toString(); // might be a number
|
||
string = string.trim();
|
||
string = string.replace(extname(string), '');
|
||
string = underscore(string);
|
||
string = string.replace(/[\W_]+/g, ' ');
|
||
|
||
return capitalize(string);
|
||
}
|
||
|
||
|
||
function underscore(string){
|
||
string = string || '';
|
||
string = string.toString(); // might be a number
|
||
string = string.trim();
|
||
string = string.replace(/([a-z\d])([A-Z]+)/g, '$1_$2');
|
||
string = string.replace(/[-\s]+/g, '_').toLowerCase();
|
||
|
||
return string;
|
||
}
|
||
|
||
function extname(string){
|
||
var index = string.lastIndexOf('.');
|
||
var ext = string.substring(index, string.length);
|
||
|
||
return (index === -1) ? '' : ext;
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
}).call(this);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
(function(){
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// packages/aldeed_simple-schema/mongo-object.js //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
/* global MongoObject:true */
|
||
|
||
|
||
var isObject = function(obj) {
|
||
return obj === Object(obj);
|
||
};
|
||
|
||
// getPrototypeOf polyfill
|
||
if (typeof Object.getPrototypeOf !== "function") {
|
||
if (typeof "".__proto__ === "object") {
|
||
Object.getPrototypeOf = function(object) {
|
||
return object.__proto__;
|
||
};
|
||
} else {
|
||
Object.getPrototypeOf = function(object) {
|
||
// May break if the constructor has been tampered with
|
||
return object.constructor.prototype;
|
||
};
|
||
}
|
||
}
|
||
|
||
/* Tests whether "obj" is an Object as opposed to
|
||
* something that inherits from Object
|
||
*
|
||
* @param {any} obj
|
||
* @returns {Boolean}
|
||
*/
|
||
var isBasicObject = function(obj) {
|
||
return isObject(obj) && Object.getPrototypeOf(obj) === Object.prototype;
|
||
};
|
||
|
||
/* Takes a specific string that uses mongo-style dot notation
|
||
* and returns a generic string equivalent. Replaces all numeric
|
||
* "pieces" with a dollar sign ($).
|
||
*
|
||
* @param {type} name
|
||
* @returns {unresolved}
|
||
*/
|
||
var makeGeneric = function makeGeneric(name) {
|
||
if (typeof name !== "string") {
|
||
return null;
|
||
}
|
||
return name.replace(/\.[0-9]+\./g, '.$.').replace(/\.[0-9]+$/g, '.$');
|
||
};
|
||
|
||
var appendAffectedKey = function appendAffectedKey(affectedKey, key) {
|
||
if (key === "$each") {
|
||
return affectedKey;
|
||
} else {
|
||
return (affectedKey ? affectedKey + "." + key : key);
|
||
}
|
||
};
|
||
|
||
// Extracts operator piece, if present, from position string
|
||
var extractOp = function extractOp(position) {
|
||
var firstPositionPiece = position.slice(0, position.indexOf("["));
|
||
return (firstPositionPiece.substring(0, 1) === "$") ? firstPositionPiece : null;
|
||
};
|
||
|
||
/*
|
||
* @constructor
|
||
* @param {Object} objOrModifier
|
||
* @param {string[]} blackBoxKeys - A list of the names of keys that shouldn't be traversed
|
||
* @returns {undefined}
|
||
*
|
||
* Creates a new MongoObject instance. The object passed as the first argument
|
||
* will be modified in place by calls to instance methods. Also, immediately
|
||
* upon creation of the instance, the object will have any `undefined` keys
|
||
* removed recursively.
|
||
*/
|
||
MongoObject = function(objOrModifier, blackBoxKeys) {
|
||
var self = this;
|
||
self._obj = objOrModifier;
|
||
self._affectedKeys = {};
|
||
self._genericAffectedKeys = {};
|
||
self._parentPositions = [];
|
||
self._positionsInsideArrays = [];
|
||
self._objectPositions = [];
|
||
|
||
function parseObj(val, currentPosition, affectedKey, operator, adjusted, isWithinArray) {
|
||
|
||
// Adjust for first-level modifier operators
|
||
if (!operator && affectedKey && affectedKey.substring(0, 1) === "$") {
|
||
operator = affectedKey;
|
||
affectedKey = null;
|
||
}
|
||
|
||
var affectedKeyIsBlackBox = false;
|
||
var affectedKeyGeneric;
|
||
var stop = false;
|
||
if (affectedKey) {
|
||
|
||
// Adjust for $push and $addToSet and $pull and $pop
|
||
if (!adjusted) {
|
||
if (operator === "$push" || operator === "$addToSet" || operator === "$pop") {
|
||
// Adjust for $each
|
||
// We can simply jump forward and pretend like the $each array
|
||
// is the array for the field. This has the added benefit of
|
||
// skipping past any $slice, which we also don't care about.
|
||
if (isBasicObject(val) && "$each" in val) {
|
||
val = val.$each;
|
||
currentPosition = currentPosition + "[$each]";
|
||
} else {
|
||
affectedKey = affectedKey + ".0";
|
||
}
|
||
adjusted = true;
|
||
} else if (operator === "$pull") {
|
||
affectedKey = affectedKey + ".0";
|
||
if (isBasicObject(val)) {
|
||
stop = true;
|
||
}
|
||
adjusted = true;
|
||
}
|
||
}
|
||
|
||
// Make generic key
|
||
affectedKeyGeneric = makeGeneric(affectedKey);
|
||
|
||
// Determine whether affected key should be treated as a black box
|
||
affectedKeyIsBlackBox = _.contains(blackBoxKeys, affectedKeyGeneric);
|
||
|
||
// Mark that this position affects this generic and non-generic key
|
||
if (currentPosition) {
|
||
self._affectedKeys[currentPosition] = affectedKey;
|
||
self._genericAffectedKeys[currentPosition] = affectedKeyGeneric;
|
||
|
||
// If we're within an array, mark this position so we can omit it from flat docs
|
||
isWithinArray && self._positionsInsideArrays.push(currentPosition);
|
||
}
|
||
}
|
||
|
||
if (stop) {
|
||
return;
|
||
}
|
||
|
||
// Loop through arrays
|
||
if (_.isArray(val) && !_.isEmpty(val)) {
|
||
if (currentPosition) {
|
||
// Mark positions with arrays that should be ignored when we want endpoints only
|
||
self._parentPositions.push(currentPosition);
|
||
}
|
||
|
||
// Loop
|
||
_.each(val, function(v, i) {
|
||
parseObj(v, (currentPosition ? currentPosition + "[" + i + "]" : i), affectedKey + '.' + i, operator, adjusted, true);
|
||
});
|
||
}
|
||
|
||
// Loop through object keys, only for basic objects,
|
||
// but always for the passed-in object, even if it
|
||
// is a custom object.
|
||
else if ((isBasicObject(val) && !affectedKeyIsBlackBox) || !currentPosition) {
|
||
if (currentPosition && !_.isEmpty(val)) {
|
||
// Mark positions with objects that should be ignored when we want endpoints only
|
||
self._parentPositions.push(currentPosition);
|
||
// Mark positions with objects that should be left out of flat docs.
|
||
self._objectPositions.push(currentPosition);
|
||
}
|
||
// Loop
|
||
_.each(val, function(v, k) {
|
||
if (v === void 0) {
|
||
delete val[k];
|
||
} else if (k !== "$slice") {
|
||
parseObj(v, (currentPosition ? currentPosition + "[" + k + "]" : k), appendAffectedKey(affectedKey, k), operator, adjusted, isWithinArray);
|
||
}
|
||
});
|
||
}
|
||
|
||
}
|
||
parseObj(self._obj);
|
||
|
||
function reParseObj() {
|
||
self._affectedKeys = {};
|
||
self._genericAffectedKeys = {};
|
||
self._parentPositions = [];
|
||
self._positionsInsideArrays = [];
|
||
self._objectPositions = [];
|
||
parseObj(self._obj);
|
||
}
|
||
|
||
/**
|
||
* @method MongoObject.forEachNode
|
||
* @param {Function} func
|
||
* @param {Object} [options]
|
||
* @param {Boolean} [options.endPointsOnly=true] - Only call function for endpoints and not for nodes that contain other nodes
|
||
* @returns {undefined}
|
||
*
|
||
* Runs a function for each endpoint node in the object tree, including all items in every array.
|
||
* The function arguments are
|
||
* (1) the value at this node
|
||
* (2) a string representing the node position
|
||
* (3) the representation of what would be changed in mongo, using mongo dot notation
|
||
* (4) the generic equivalent of argument 3, with "$" instead of numeric pieces
|
||
*/
|
||
self.forEachNode = function(func, options) {
|
||
if (typeof func !== "function") {
|
||
throw new Error("filter requires a loop function");
|
||
}
|
||
|
||
options = _.extend({
|
||
endPointsOnly: true
|
||
}, options);
|
||
|
||
var updatedValues = {};
|
||
_.each(self._affectedKeys, function(affectedKey, position) {
|
||
if (options.endPointsOnly && _.contains(self._parentPositions, position)) {
|
||
return; //only endpoints
|
||
}
|
||
func.call({
|
||
value: self.getValueForPosition(position),
|
||
operator: extractOp(position),
|
||
position: position,
|
||
key: affectedKey,
|
||
genericKey: self._genericAffectedKeys[position],
|
||
updateValue: function(newVal) {
|
||
updatedValues[position] = newVal;
|
||
},
|
||
remove: function() {
|
||
updatedValues[position] = void 0;
|
||
}
|
||
});
|
||
});
|
||
|
||
// Actually update/remove values as instructed
|
||
_.each(updatedValues, function(newVal, position) {
|
||
self.setValueForPosition(position, newVal);
|
||
});
|
||
|
||
};
|
||
|
||
self.getValueForPosition = function(position) {
|
||
var subkey, subkeys = position.split("["), current = self._obj;
|
||
for (var i = 0, ln = subkeys.length; i < ln; i++) {
|
||
subkey = subkeys[i];
|
||
// If the subkey ends in "]", remove the ending
|
||
if (subkey.slice(-1) === "]") {
|
||
subkey = subkey.slice(0, -1);
|
||
}
|
||
current = current[subkey];
|
||
if (!_.isArray(current) && !isBasicObject(current) && i < ln - 1) {
|
||
return;
|
||
}
|
||
}
|
||
return current;
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.prototype.setValueForPosition
|
||
* @param {String} position
|
||
* @param {Any} value
|
||
* @returns {undefined}
|
||
*/
|
||
self.setValueForPosition = function(position, value) {
|
||
var nextPiece, subkey, subkeys = position.split("["), current = self._obj;
|
||
|
||
for (var i = 0, ln = subkeys.length; i < ln; i++) {
|
||
subkey = subkeys[i];
|
||
// If the subkey ends in "]", remove the ending
|
||
if (subkey.slice(-1) === "]") {
|
||
subkey = subkey.slice(0, -1);
|
||
}
|
||
// If we've reached the key in the object tree that needs setting or
|
||
// deleting, do it.
|
||
if (i === ln - 1) {
|
||
current[subkey] = value;
|
||
//if value is undefined, delete the property
|
||
if (value === void 0) {
|
||
delete current[subkey];
|
||
}
|
||
}
|
||
// Otherwise attempt to keep moving deeper into the object.
|
||
else {
|
||
// If we're setting (as opposed to deleting) a key and we hit a place
|
||
// in the ancestor chain where the keys are not yet created, create them.
|
||
if (current[subkey] === void 0 && value !== void 0) {
|
||
//see if the next piece is a number
|
||
nextPiece = subkeys[i + 1];
|
||
nextPiece = parseInt(nextPiece, 10);
|
||
current[subkey] = isNaN(nextPiece) ? {} : [];
|
||
}
|
||
|
||
// Move deeper into the object
|
||
current = current[subkey];
|
||
|
||
// If we can go no further, then quit
|
||
if (!_.isArray(current) && !isBasicObject(current) && i < ln - 1) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
reParseObj();
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.prototype.removeValueForPosition
|
||
* @param {String} position
|
||
* @returns {undefined}
|
||
*/
|
||
self.removeValueForPosition = function(position) {
|
||
self.setValueForPosition(position, void 0);
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.prototype.getKeyForPosition
|
||
* @param {String} position
|
||
* @returns {undefined}
|
||
*/
|
||
self.getKeyForPosition = function(position) {
|
||
return self._affectedKeys[position];
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.prototype.getGenericKeyForPosition
|
||
* @param {String} position
|
||
* @returns {undefined}
|
||
*/
|
||
self.getGenericKeyForPosition = function(position) {
|
||
return self._genericAffectedKeys[position];
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.getInfoForKey
|
||
* @param {String} key - Non-generic key
|
||
* @returns {undefined|Object}
|
||
*
|
||
* Returns the value and operator of the requested non-generic key.
|
||
* Example: {value: 1, operator: "$pull"}
|
||
*/
|
||
self.getInfoForKey = function(key) {
|
||
// Get the info
|
||
var position = self.getPositionForKey(key);
|
||
if (position) {
|
||
return {
|
||
value: self.getValueForPosition(position),
|
||
operator: extractOp(position)
|
||
};
|
||
}
|
||
|
||
// If we haven't returned yet, check to see if there is an array value
|
||
// corresponding to this key
|
||
// We find the first item within the array, strip the last piece off the
|
||
// position string, and then return whatever is at that new position in
|
||
// the original object.
|
||
var positions = self.getPositionsForGenericKey(key + ".$"), p, v;
|
||
for (var i = 0, ln = positions.length; i < ln; i++) {
|
||
p = positions[i];
|
||
v = self.getValueForPosition(p) || self.getValueForPosition(p.slice(0, p.lastIndexOf("[")));
|
||
if (v) {
|
||
return {
|
||
value: v,
|
||
operator: extractOp(p)
|
||
};
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.getPositionForKey
|
||
* @param {String} key - Non-generic key
|
||
* @returns {undefined|String} Position string
|
||
*
|
||
* Returns the position string for the place in the object that
|
||
* affects the requested non-generic key.
|
||
* Example: 'foo[bar][0]'
|
||
*/
|
||
self.getPositionForKey = function(key) {
|
||
// Get the info
|
||
for (var position in self._affectedKeys) {
|
||
if (self._affectedKeys.hasOwnProperty(position)) {
|
||
if (self._affectedKeys[position] === key) {
|
||
// We return the first one we find. While it's
|
||
// possible that multiple update operators could
|
||
// affect the same non-generic key, we'll assume that's not the case.
|
||
return position;
|
||
}
|
||
}
|
||
}
|
||
|
||
// If we haven't returned yet, we need to check for affected keys
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.getPositionsForGenericKey
|
||
* @param {String} key - Generic key
|
||
* @returns {String[]} Array of position strings
|
||
*
|
||
* Returns an array of position strings for the places in the object that
|
||
* affect the requested generic key.
|
||
* Example: ['foo[bar][0]']
|
||
*/
|
||
self.getPositionsForGenericKey = function(key) {
|
||
// Get the info
|
||
var list = [];
|
||
for (var position in self._genericAffectedKeys) {
|
||
if (self._genericAffectedKeys.hasOwnProperty(position)) {
|
||
if (self._genericAffectedKeys[position] === key) {
|
||
list.push(position);
|
||
}
|
||
}
|
||
}
|
||
|
||
return list;
|
||
};
|
||
|
||
/**
|
||
* @deprecated Use getInfoForKey
|
||
* @method MongoObject.getValueForKey
|
||
* @param {String} key - Non-generic key
|
||
* @returns {undefined|Any}
|
||
*
|
||
* Returns the value of the requested non-generic key
|
||
*/
|
||
self.getValueForKey = function(key) {
|
||
var position = self.getPositionForKey(key);
|
||
if (position) {
|
||
return self.getValueForPosition(position);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.prototype.addKey
|
||
* @param {String} key - Key to set
|
||
* @param {Any} val - Value to give this key
|
||
* @param {String} op - Operator under which to set it, or `null` for a non-modifier object
|
||
* @returns {undefined}
|
||
*
|
||
* Adds `key` with value `val` under operator `op` to the source object.
|
||
*/
|
||
self.addKey = function(key, val, op) {
|
||
var position = op ? op + "[" + key + "]" : MongoObject._keyToPosition(key);
|
||
self.setValueForPosition(position, val);
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.prototype.removeGenericKeys
|
||
* @param {String[]} keys
|
||
* @returns {undefined}
|
||
*
|
||
* Removes anything that affects any of the generic keys in the list
|
||
*/
|
||
self.removeGenericKeys = function(keys) {
|
||
for (var position in self._genericAffectedKeys) {
|
||
if (self._genericAffectedKeys.hasOwnProperty(position)) {
|
||
if (_.contains(keys, self._genericAffectedKeys[position])) {
|
||
self.removeValueForPosition(position);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.removeGenericKey
|
||
* @param {String} key
|
||
* @returns {undefined}
|
||
*
|
||
* Removes anything that affects the requested generic key
|
||
*/
|
||
self.removeGenericKey = function(key) {
|
||
for (var position in self._genericAffectedKeys) {
|
||
if (self._genericAffectedKeys.hasOwnProperty(position)) {
|
||
if (self._genericAffectedKeys[position] === key) {
|
||
self.removeValueForPosition(position);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.removeKey
|
||
* @param {String} key
|
||
* @returns {undefined}
|
||
*
|
||
* Removes anything that affects the requested non-generic key
|
||
*/
|
||
self.removeKey = function(key) {
|
||
// We don't use getPositionForKey here because we want to be sure to
|
||
// remove for all positions if there are multiple.
|
||
for (var position in self._affectedKeys) {
|
||
if (self._affectedKeys.hasOwnProperty(position)) {
|
||
if (self._affectedKeys[position] === key) {
|
||
self.removeValueForPosition(position);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.removeKeys
|
||
* @param {String[]} keys
|
||
* @returns {undefined}
|
||
*
|
||
* Removes anything that affects any of the non-generic keys in the list
|
||
*/
|
||
self.removeKeys = function(keys) {
|
||
for (var i = 0, ln = keys.length; i < ln; i++) {
|
||
self.removeKey(keys[i]);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.filterGenericKeys
|
||
* @param {Function} test - Test function
|
||
* @returns {undefined}
|
||
*
|
||
* Passes all affected keys to a test function, which
|
||
* should return false to remove whatever is affecting that key
|
||
*/
|
||
self.filterGenericKeys = function(test) {
|
||
var gk, checkedKeys = [], keysToRemove = [];
|
||
for (var position in self._genericAffectedKeys) {
|
||
if (self._genericAffectedKeys.hasOwnProperty(position)) {
|
||
gk = self._genericAffectedKeys[position];
|
||
if (!_.contains(checkedKeys, gk)) {
|
||
checkedKeys.push(gk);
|
||
if (gk && !test(gk)) {
|
||
keysToRemove.push(gk);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
_.each(keysToRemove, function(key) {
|
||
self.removeGenericKey(key);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.setValueForKey
|
||
* @param {String} key
|
||
* @param {Any} val
|
||
* @returns {undefined}
|
||
*
|
||
* Sets the value for every place in the object that affects
|
||
* the requested non-generic key
|
||
*/
|
||
self.setValueForKey = function(key, val) {
|
||
// We don't use getPositionForKey here because we want to be sure to
|
||
// set the value for all positions if there are multiple.
|
||
for (var position in self._affectedKeys) {
|
||
if (self._affectedKeys.hasOwnProperty(position)) {
|
||
if (self._affectedKeys[position] === key) {
|
||
self.setValueForPosition(position, val);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.setValueForGenericKey
|
||
* @param {String} key
|
||
* @param {Any} val
|
||
* @returns {undefined}
|
||
*
|
||
* Sets the value for every place in the object that affects
|
||
* the requested generic key
|
||
*/
|
||
self.setValueForGenericKey = function(key, val) {
|
||
// We don't use getPositionForKey here because we want to be sure to
|
||
// set the value for all positions if there are multiple.
|
||
for (var position in self._genericAffectedKeys) {
|
||
if (self._genericAffectedKeys.hasOwnProperty(position)) {
|
||
if (self._genericAffectedKeys[position] === key) {
|
||
self.setValueForPosition(position, val);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.getObject
|
||
* @returns {Object}
|
||
*
|
||
* Get the source object, potentially modified by other method calls on this
|
||
* MongoObject instance.
|
||
*/
|
||
self.getObject = function() {
|
||
return self._obj;
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.getFlatObject
|
||
* @returns {Object}
|
||
*
|
||
* Gets a flat object based on the MongoObject instance.
|
||
* In a flat object, the key is the name of the non-generic affectedKey,
|
||
* with mongo dot notation if necessary, and the value is the value for
|
||
* that key.
|
||
*
|
||
* With `keepArrays: true`, we don't flatten within arrays. Currently
|
||
* MongoDB does not see a key such as `a.0.b` and automatically assume
|
||
* an array. Instead it would create an object with key "0" if there
|
||
* wasn't already an array saved as the value of `a`, which is rarely
|
||
* if ever what we actually want. To avoid this confusion, we
|
||
* set entire arrays.
|
||
*/
|
||
self.getFlatObject = function(options) {
|
||
options = options || {};
|
||
var newObj = {};
|
||
_.each(self._affectedKeys, function(affectedKey, position) {
|
||
if (typeof affectedKey === "string" &&
|
||
(options.keepArrays === true && !_.contains(self._positionsInsideArrays, position) && !_.contains(self._objectPositions, position)) ||
|
||
(!options.keepArrays && !_.contains(self._parentPositions, position))
|
||
) {
|
||
newObj[affectedKey] = self.getValueForPosition(position);
|
||
}
|
||
});
|
||
return newObj;
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.affectsKey
|
||
* @param {String} key
|
||
* @returns {Object}
|
||
*
|
||
* Returns true if the non-generic key is affected by this object
|
||
*/
|
||
self.affectsKey = function(key) {
|
||
return !!self.getPositionForKey(key);
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.affectsGenericKey
|
||
* @param {String} key
|
||
* @returns {Object}
|
||
*
|
||
* Returns true if the generic key is affected by this object
|
||
*/
|
||
self.affectsGenericKey = function(key) {
|
||
for (var position in self._genericAffectedKeys) {
|
||
if (self._genericAffectedKeys.hasOwnProperty(position)) {
|
||
if (self._genericAffectedKeys[position] === key) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject.affectsGenericKeyImplicit
|
||
* @param {String} key
|
||
* @returns {Object}
|
||
*
|
||
* Like affectsGenericKey, but will return true if a child key is affected
|
||
*/
|
||
self.affectsGenericKeyImplicit = function(key) {
|
||
for (var position in self._genericAffectedKeys) {
|
||
if (self._genericAffectedKeys.hasOwnProperty(position)) {
|
||
var affectedKey = self._genericAffectedKeys[position];
|
||
|
||
// If the affected key is the test key
|
||
if (affectedKey === key) {
|
||
return true;
|
||
}
|
||
|
||
// If the affected key implies the test key because the affected key
|
||
// starts with the test key followed by a period
|
||
if (affectedKey.substring(0, key.length + 1) === key + ".") {
|
||
return true;
|
||
}
|
||
|
||
// If the affected key implies the test key because the affected key
|
||
// starts with the test key and the test key ends with ".$"
|
||
var lastTwo = key.slice(-2);
|
||
if (lastTwo === ".$" && key.slice(0, -2) === affectedKey) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
};
|
||
|
||
/** Takes a string representation of an object key and its value
|
||
* and updates "obj" to contain that key with that value.
|
||
*
|
||
* Example keys and results if val is 1:
|
||
* "a" -> {a: 1}
|
||
* "a[b]" -> {a: {b: 1}}
|
||
* "a[b][0]" -> {a: {b: [1]}}
|
||
* "a[b.0.c]" -> {a: {'b.0.c': 1}}
|
||
*/
|
||
|
||
/** Takes a string representation of an object key and its value
|
||
* and updates "obj" to contain that key with that value.
|
||
*
|
||
* Example keys and results if val is 1:
|
||
* "a" -> {a: 1}
|
||
* "a[b]" -> {a: {b: 1}}
|
||
* "a[b][0]" -> {a: {b: [1]}}
|
||
* "a[b.0.c]" -> {a: {'b.0.c': 1}}
|
||
*
|
||
* @param {any} val
|
||
* @param {String} key
|
||
* @param {Object} obj
|
||
* @returns {undefined}
|
||
*/
|
||
MongoObject.expandKey = function(val, key, obj) {
|
||
var nextPiece, subkey, subkeys = key.split("["), current = obj;
|
||
for (var i = 0, ln = subkeys.length; i < ln; i++) {
|
||
subkey = subkeys[i];
|
||
if (subkey.slice(-1) === "]") {
|
||
subkey = subkey.slice(0, -1);
|
||
}
|
||
if (i === ln - 1) {
|
||
//last iteration; time to set the value; always overwrite
|
||
current[subkey] = val;
|
||
//if val is undefined, delete the property
|
||
if (val === void 0) {
|
||
delete current[subkey];
|
||
}
|
||
} else {
|
||
//see if the next piece is a number
|
||
nextPiece = subkeys[i + 1];
|
||
nextPiece = parseInt(nextPiece, 10);
|
||
if (!current[subkey]) {
|
||
current[subkey] = isNaN(nextPiece) ? {} : [];
|
||
}
|
||
}
|
||
current = current[subkey];
|
||
}
|
||
};
|
||
|
||
MongoObject._keyToPosition = function keyToPosition(key, wrapAll) {
|
||
var position = '';
|
||
_.each(key.split("."), function (piece, i) {
|
||
if (i === 0 && !wrapAll) {
|
||
position += piece;
|
||
} else {
|
||
position += "[" + piece + "]";
|
||
}
|
||
});
|
||
return position;
|
||
};
|
||
|
||
/**
|
||
* @method MongoObject._positionToKey
|
||
* @param {String} position
|
||
* @returns {String} The key that this position in an object would affect.
|
||
*
|
||
* This is different from MongoObject.prototype.getKeyForPosition in that
|
||
* this method does not depend on the requested position actually being
|
||
* present in any particular MongoObject.
|
||
*/
|
||
MongoObject._positionToKey = function positionToKey(position) {
|
||
//XXX Probably a better way to do this, but this is
|
||
//foolproof for now.
|
||
var mDoc = new MongoObject({});
|
||
mDoc.setValueForPosition(position, 1); //value doesn't matter
|
||
var key = mDoc.getKeyForPosition(position);
|
||
mDoc = null;
|
||
return key;
|
||
};
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
}).call(this);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
(function(){
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// packages/aldeed_simple-schema/simple-schema-utility.js //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
/* global Utility:true */
|
||
|
||
Utility = {
|
||
appendAffectedKey: function appendAffectedKey(affectedKey, key) {
|
||
if (key === "$each") {
|
||
return affectedKey;
|
||
} else {
|
||
return (affectedKey ? affectedKey + "." + key : key);
|
||
}
|
||
},
|
||
shouldCheck: function shouldCheck(key) {
|
||
if (key === "$pushAll") {
|
||
throw new Error("$pushAll is not supported; use $push + $each");
|
||
}
|
||
return !_.contains(["$pull", "$pullAll", "$pop", "$slice"], key);
|
||
},
|
||
errorObject: function errorObject(errorType, keyName, keyValue) {
|
||
return {name: keyName, type: errorType, value: keyValue};
|
||
},
|
||
// Tests whether it's an Object as opposed to something that inherits from Object
|
||
isBasicObject: function isBasicObject(obj) {
|
||
return _.isObject(obj) && Object.getPrototypeOf(obj) === Object.prototype;
|
||
},
|
||
// The latest Safari returns false for Uint8Array, etc. instanceof Function
|
||
// unlike other browsers.
|
||
safariBugFix: function safariBugFix(type) {
|
||
return (typeof Uint8Array !== "undefined" && type === Uint8Array) ||
|
||
(typeof Uint16Array !== "undefined" && type === Uint16Array) ||
|
||
(typeof Uint32Array !== "undefined" && type === Uint32Array) ||
|
||
(typeof Uint8ClampedArray !== "undefined" && type === Uint8ClampedArray);
|
||
},
|
||
isNotNullOrUndefined: function isNotNullOrUndefined(val) {
|
||
return val !== void 0 && val !== null;
|
||
},
|
||
// Extracts operator piece, if present, from position string
|
||
extractOp: function extractOp(position) {
|
||
var firstPositionPiece = position.slice(0, position.indexOf("["));
|
||
return (firstPositionPiece.substring(0, 1) === "$") ? firstPositionPiece : null;
|
||
},
|
||
deleteIfPresent: function deleteIfPresent(obj, key) {
|
||
if (key in obj) {
|
||
delete obj[key];
|
||
}
|
||
},
|
||
looksLikeModifier: function looksLikeModifier(obj) {
|
||
for (var key in obj) {
|
||
if (obj.hasOwnProperty(key) && key.substring(0, 1) === "$") {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
},
|
||
dateToDateString: function dateToDateString(date) {
|
||
var m = (date.getUTCMonth() + 1);
|
||
if (m < 10) {
|
||
m = "0" + m;
|
||
}
|
||
var d = date.getUTCDate();
|
||
if (d < 10) {
|
||
d = "0" + d;
|
||
}
|
||
return date.getUTCFullYear() + '-' + m + '-' + d;
|
||
}
|
||
};
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
}).call(this);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
(function(){
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// packages/aldeed_simple-schema/simple-schema.js //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
/* global SimpleSchema:true */
|
||
/* global SimpleSchemaValidationContext */
|
||
/* global MongoObject */
|
||
/* global Utility */
|
||
|
||
var schemaDefinition = {
|
||
type: Match.Any,
|
||
label: Match.Optional(Match.OneOf(String, Function)),
|
||
optional: Match.Optional(Match.OneOf(Boolean, Function)),
|
||
min: Match.Optional(Match.OneOf(Number, Date, Function)),
|
||
max: Match.Optional(Match.OneOf(Number, Date, Function)),
|
||
minCount: Match.Optional(Match.OneOf(Number, Function)),
|
||
maxCount: Match.Optional(Match.OneOf(Number, Function)),
|
||
allowedValues: Match.Optional(Match.OneOf([Match.Any], Function)),
|
||
decimal: Match.Optional(Boolean),
|
||
exclusiveMax: Match.Optional(Boolean),
|
||
exclusiveMin: Match.Optional(Boolean),
|
||
regEx: Match.Optional(Match.OneOf(RegExp, [RegExp])),
|
||
custom: Match.Optional(Function),
|
||
blackbox: Match.Optional(Boolean),
|
||
autoValue: Match.Optional(Function),
|
||
defaultValue: Match.Optional(Match.Any),
|
||
trim: Match.Optional(Boolean)
|
||
};
|
||
|
||
/*
|
||
* PRIVATE FUNCTIONS
|
||
*/
|
||
|
||
//called by clean()
|
||
var typeconvert = function(value, type) {
|
||
var parsedDate;
|
||
|
||
if (_.isArray(value) || (_.isObject(value) && !(value instanceof Date))) {
|
||
return value; //can't and shouldn't convert arrays or objects
|
||
}
|
||
if (type === String) {
|
||
if (typeof value !== "undefined" && value !== null && typeof value !== "string") {
|
||
return value.toString();
|
||
}
|
||
return value;
|
||
}
|
||
if (type === Number) {
|
||
if (typeof value === "string" && !_.isEmpty(value)) {
|
||
//try to convert numeric strings to numbers
|
||
var numberVal = Number(value);
|
||
if (!isNaN(numberVal)) {
|
||
return numberVal;
|
||
} else {
|
||
return value; //leave string; will fail validation
|
||
}
|
||
}
|
||
return value;
|
||
}
|
||
//
|
||
// If target type is a Date we can safely convert from either a
|
||
// number (Integer value representing the number of milliseconds
|
||
// since 1 January 1970 00:00:00 UTC) or a string that can be parsed
|
||
// by Date.
|
||
//
|
||
if (type === Date) {
|
||
if (typeof value === "string") {
|
||
parsedDate = Date.parse(value);
|
||
if (isNaN(parsedDate) === false) {
|
||
return new Date(parsedDate);
|
||
}
|
||
}
|
||
if (typeof value === "number") {
|
||
return new Date(value);
|
||
}
|
||
}
|
||
return value;
|
||
};
|
||
|
||
var expandSchema = function(schema) {
|
||
// Flatten schema by inserting nested definitions
|
||
_.each(schema, function(val, key) {
|
||
var dot, type;
|
||
if (!val) {
|
||
return;
|
||
}
|
||
if (Match.test(val.type, SimpleSchema)) {
|
||
dot = '.';
|
||
type = val.type;
|
||
val.type = Object;
|
||
} else if (Match.test(val.type, [SimpleSchema])) {
|
||
dot = '.$.';
|
||
type = val.type[0];
|
||
val.type = [Object];
|
||
} else {
|
||
return;
|
||
}
|
||
//add child schema definitions to parent schema
|
||
_.each(type._schema, function(subVal, subKey) {
|
||
var newKey = key + dot + subKey;
|
||
if (!(newKey in schema)) {
|
||
schema[newKey] = subVal;
|
||
}
|
||
});
|
||
});
|
||
return schema;
|
||
};
|
||
|
||
var adjustArrayFields = function(schema) {
|
||
_.each(schema, function(def, existingKey) {
|
||
if (_.isArray(def.type) || def.type === Array) {
|
||
// Copy some options to array-item definition
|
||
var itemKey = existingKey + ".$";
|
||
if (!(itemKey in schema)) {
|
||
schema[itemKey] = {};
|
||
}
|
||
if (_.isArray(def.type)) {
|
||
schema[itemKey].type = def.type[0];
|
||
}
|
||
if (def.label) {
|
||
schema[itemKey].label = def.label;
|
||
}
|
||
schema[itemKey].optional = true;
|
||
if (typeof def.min !== "undefined") {
|
||
schema[itemKey].min = def.min;
|
||
}
|
||
if (typeof def.max !== "undefined") {
|
||
schema[itemKey].max = def.max;
|
||
}
|
||
if (typeof def.allowedValues !== "undefined") {
|
||
schema[itemKey].allowedValues = def.allowedValues;
|
||
}
|
||
if (typeof def.decimal !== "undefined") {
|
||
schema[itemKey].decimal = def.decimal;
|
||
}
|
||
if (typeof def.exclusiveMax !== "undefined") {
|
||
schema[itemKey].exclusiveMax = def.exclusiveMax;
|
||
}
|
||
if (typeof def.exclusiveMin !== "undefined") {
|
||
schema[itemKey].exclusiveMin = def.exclusiveMin;
|
||
}
|
||
if (typeof def.regEx !== "undefined") {
|
||
schema[itemKey].regEx = def.regEx;
|
||
}
|
||
if (typeof def.blackbox !== "undefined") {
|
||
schema[itemKey].blackbox = def.blackbox;
|
||
}
|
||
// Remove copied options and adjust type
|
||
def.type = Array;
|
||
_.each(['min', 'max', 'allowedValues', 'decimal', 'exclusiveMax', 'exclusiveMin', 'regEx', 'blackbox'], function(k) {
|
||
Utility.deleteIfPresent(def, k);
|
||
});
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Adds implied keys.
|
||
* * If schema contains a key like "foo.$.bar" but not "foo", adds "foo".
|
||
* * If schema contains a key like "foo" with an array type, adds "foo.$".
|
||
* @param {Object} schema
|
||
* @returns {Object} modified schema
|
||
*/
|
||
var addImplicitKeys = function(schema) {
|
||
var arrayKeysToAdd = [], objectKeysToAdd = [], newKey, key, i, ln;
|
||
|
||
// Pass 1 (objects)
|
||
_.each(schema, function(def, existingKey) {
|
||
var pos = existingKey.indexOf(".");
|
||
while (pos !== -1) {
|
||
newKey = existingKey.substring(0, pos);
|
||
|
||
// It's an array item; nothing to add
|
||
if (newKey.substring(newKey.length - 2) === ".$") {
|
||
pos = -1;
|
||
}
|
||
// It's an array of objects; add it with type [Object] if not already in the schema
|
||
else if (existingKey.substring(pos, pos + 3) === ".$.") {
|
||
arrayKeysToAdd.push(newKey); // add later, since we are iterating over schema right now
|
||
pos = existingKey.indexOf(".", pos + 3); // skip over next dot, find the one after
|
||
}
|
||
// It's an object; add it with type Object if not already in the schema
|
||
else {
|
||
objectKeysToAdd.push(newKey); // add later, since we are iterating over schema right now
|
||
pos = existingKey.indexOf(".", pos + 1); // find next dot
|
||
}
|
||
}
|
||
});
|
||
|
||
for (i = 0, ln = arrayKeysToAdd.length; i < ln; i++) {
|
||
key = arrayKeysToAdd[i];
|
||
if (!(key in schema)) {
|
||
schema[key] = {type: [Object], optional: true};
|
||
}
|
||
}
|
||
|
||
for (i = 0, ln = objectKeysToAdd.length; i < ln; i++) {
|
||
key = objectKeysToAdd[i];
|
||
if (!(key in schema)) {
|
||
schema[key] = {type: Object, optional: true};
|
||
}
|
||
}
|
||
|
||
// Pass 2 (arrays)
|
||
adjustArrayFields(schema);
|
||
|
||
return schema;
|
||
};
|
||
|
||
var mergeSchemas = function(schemas) {
|
||
|
||
// Merge all provided schema definitions.
|
||
// This is effectively a shallow clone of each object, too,
|
||
// which is what we want since we are going to manipulate it.
|
||
var mergedSchema = {};
|
||
_.each(schemas, function(schema) {
|
||
|
||
// Create a temporary SS instance so that the internal object
|
||
// we use for merging/extending will be fully expanded
|
||
if (Match.test(schema, SimpleSchema)) {
|
||
schema = schema._schema;
|
||
} else {
|
||
schema = addImplicitKeys(expandSchema(schema));
|
||
}
|
||
|
||
// Loop through and extend each individual field
|
||
// definition. That way you can extend and overwrite
|
||
// base field definitions.
|
||
_.each(schema, function(def, field) {
|
||
mergedSchema[field] = mergedSchema[field] || {};
|
||
_.extend(mergedSchema[field], def);
|
||
});
|
||
|
||
});
|
||
|
||
// If we merged some schemas, do this again to make sure
|
||
// extended definitions are pushed into array item field
|
||
// definitions properly.
|
||
schemas.length && adjustArrayFields(mergedSchema);
|
||
|
||
return mergedSchema;
|
||
};
|
||
|
||
// Returns an object relating the keys in the list
|
||
// to their parent object.
|
||
var getObjectKeys = function(schema, schemaKeyList) {
|
||
var keyPrefix, remainingText, rKeys = {}, loopArray;
|
||
_.each(schema, function(definition, fieldName) {
|
||
if (definition.type === Object) {
|
||
//object
|
||
keyPrefix = fieldName + ".";
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
loopArray = [];
|
||
_.each(schemaKeyList, function(fieldName2) {
|
||
if (fieldName2.startsWith(keyPrefix)) {
|
||
remainingText = fieldName2.substring(keyPrefix.length);
|
||
if (remainingText.indexOf(".") === -1) {
|
||
loopArray.push(remainingText);
|
||
}
|
||
}
|
||
});
|
||
rKeys[keyPrefix] = loopArray;
|
||
});
|
||
return rKeys;
|
||
};
|
||
|
||
// returns an inflected version of fieldName to use as the label
|
||
var inflectedLabel = function(fieldName) {
|
||
var label = fieldName, lastPeriod = label.lastIndexOf(".");
|
||
if (lastPeriod !== -1) {
|
||
label = label.substring(lastPeriod + 1);
|
||
if (label === "$") {
|
||
var pcs = fieldName.split(".");
|
||
label = pcs[pcs.length - 2];
|
||
}
|
||
}
|
||
if (label === "_id") {
|
||
return "ID";
|
||
}
|
||
return humanize(label);
|
||
};
|
||
|
||
/**
|
||
* @method getAutoValues
|
||
* @private
|
||
* @param {MongoObject} mDoc
|
||
* @param {Boolean} [isModifier=false] - Is it a modifier doc?
|
||
* @param {Object} [extendedAutoValueContext] - Object that will be added to the context when calling each autoValue function
|
||
* @returns {undefined}
|
||
*
|
||
* Updates doc with automatic values from autoValue functions or default
|
||
* values from defaultValue. Modifies the referenced object in place.
|
||
*/
|
||
function getAutoValues(mDoc, isModifier, extendedAutoValueContext) {
|
||
var self = this;
|
||
var doneKeys = [];
|
||
|
||
//on the client we can add the userId if not already in the custom context
|
||
if (Meteor.isClient && extendedAutoValueContext.userId === void 0) {
|
||
extendedAutoValueContext.userId = (Meteor.userId && Meteor.userId()) || null;
|
||
}
|
||
|
||
function runAV(func) {
|
||
var affectedKey = this.key;
|
||
// If already called for this key, skip it
|
||
if (_.contains(doneKeys, affectedKey)) {
|
||
return;
|
||
}
|
||
var lastDot = affectedKey.lastIndexOf('.');
|
||
var fieldParentName = lastDot === -1 ? '' : affectedKey.slice(0, lastDot + 1);
|
||
var doUnset = false;
|
||
var autoValue = func.call(_.extend({
|
||
isSet: (this.value !== void 0),
|
||
unset: function() {
|
||
doUnset = true;
|
||
},
|
||
value: this.value,
|
||
operator: this.operator,
|
||
field: function(fName) {
|
||
var keyInfo = mDoc.getInfoForKey(fName) || {};
|
||
return {
|
||
isSet: (keyInfo.value !== void 0),
|
||
value: keyInfo.value,
|
||
operator: keyInfo.operator || null
|
||
};
|
||
},
|
||
siblingField: function(fName) {
|
||
var keyInfo = mDoc.getInfoForKey(fieldParentName + fName) || {};
|
||
return {
|
||
isSet: (keyInfo.value !== void 0),
|
||
value: keyInfo.value,
|
||
operator: keyInfo.operator || null
|
||
};
|
||
}
|
||
}, extendedAutoValueContext || {}), mDoc.getObject());
|
||
|
||
// Update tracking of which keys we've run autovalue for
|
||
doneKeys.push(affectedKey);
|
||
|
||
if (autoValue === void 0) {
|
||
if (doUnset) {
|
||
mDoc.removeValueForPosition(this.position);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// If the user's auto value is of the pseudo-modifier format, parse it
|
||
// into operator and value.
|
||
var op, newValue;
|
||
if (_.isObject(autoValue)) {
|
||
for (var key in autoValue) {
|
||
if (autoValue.hasOwnProperty(key) && key.substring(0, 1) === "$") {
|
||
op = key;
|
||
newValue = autoValue[key];
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Add $set for updates and upserts if necessary
|
||
if (!op && isModifier && this.position.slice(0, 1) !== '$') {
|
||
op = "$set";
|
||
newValue = autoValue;
|
||
}
|
||
|
||
// Update/change value
|
||
if (op) {
|
||
mDoc.removeValueForPosition(this.position);
|
||
mDoc.setValueForPosition(op + '[' + affectedKey + ']', newValue);
|
||
} else {
|
||
mDoc.setValueForPosition(this.position, autoValue);
|
||
}
|
||
}
|
||
|
||
_.each(self._autoValues, function(func, fieldName) {
|
||
var positionSuffix, key, keySuffix, positions;
|
||
|
||
// If we're under an array, run autovalue for all the properties of
|
||
// any objects that are present in the nearest ancestor array.
|
||
if (fieldName.indexOf("$") !== -1) {
|
||
var testField = fieldName.slice(0, fieldName.lastIndexOf("$") + 1);
|
||
keySuffix = fieldName.slice(testField.length + 1);
|
||
positionSuffix = MongoObject._keyToPosition(keySuffix, true);
|
||
keySuffix = '.' + keySuffix;
|
||
positions = mDoc.getPositionsForGenericKey(testField);
|
||
} else {
|
||
|
||
// See if anything in the object affects this key
|
||
positions = mDoc.getPositionsForGenericKey(fieldName);
|
||
|
||
// Run autovalue for properties that are set in the object
|
||
if (positions.length) {
|
||
key = fieldName;
|
||
keySuffix = '';
|
||
positionSuffix = '';
|
||
}
|
||
|
||
// Run autovalue for properties that are NOT set in the object
|
||
else {
|
||
key = fieldName;
|
||
keySuffix = '';
|
||
positionSuffix = '';
|
||
if (isModifier) {
|
||
positions = ["$set[" + fieldName + "]"];
|
||
} else {
|
||
positions = [MongoObject._keyToPosition(fieldName)];
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
_.each(positions, function(position) {
|
||
runAV.call({
|
||
key: (key || MongoObject._positionToKey(position)) + keySuffix,
|
||
value: mDoc.getValueForPosition(position + positionSuffix),
|
||
operator: Utility.extractOp(position),
|
||
position: position + positionSuffix
|
||
}, func);
|
||
});
|
||
});
|
||
}
|
||
|
||
//exported
|
||
SimpleSchema = function(schemas, options) {
|
||
var self = this;
|
||
var firstLevelSchemaKeys = [];
|
||
var fieldNameRoot;
|
||
options = options || {};
|
||
schemas = schemas || {};
|
||
|
||
if (!_.isArray(schemas)) {
|
||
schemas = [schemas];
|
||
}
|
||
|
||
// adjust and store a copy of the schema definitions
|
||
self._schema = mergeSchemas(schemas);
|
||
|
||
// store the list of defined keys for speedier checking
|
||
self._schemaKeys = [];
|
||
|
||
// store autoValue functions by key
|
||
self._autoValues = {};
|
||
|
||
// store the list of blackbox keys for passing to MongoObject constructor
|
||
self._blackboxKeys = [];
|
||
|
||
// a place to store custom validators for this instance
|
||
self._validators = [];
|
||
|
||
// a place to store custom error messages for this schema
|
||
self._messages = {};
|
||
|
||
self._depsMessages = new Tracker.Dependency();
|
||
self._depsLabels = {};
|
||
|
||
_.each(self._schema, function(definition, fieldName) {
|
||
// Validate the field definition
|
||
if (!Match.test(definition, schemaDefinition)) {
|
||
throw new Error('Invalid definition for ' + fieldName + ' field.');
|
||
}
|
||
|
||
fieldNameRoot = fieldName.split(".")[0];
|
||
|
||
self._schemaKeys.push(fieldName);
|
||
|
||
// We support defaultValue shortcut by converting it immediately into an
|
||
// autoValue.
|
||
if ('defaultValue' in definition) {
|
||
if ('autoValue' in definition) {
|
||
console.warn('SimpleSchema: Found both autoValue and defaultValue options for "' + fieldName + '". Ignoring defaultValue.');
|
||
} else {
|
||
if (fieldName.slice(-2) === ".$") {
|
||
throw new Error('An array item field (one that ends with ".$") cannot have defaultValue.');
|
||
}
|
||
self._autoValues[fieldName] = (function defineAutoValue(v) {
|
||
return function() {
|
||
if (this.operator === null && !this.isSet) {
|
||
return v;
|
||
}
|
||
};
|
||
})(definition.defaultValue);
|
||
}
|
||
}
|
||
|
||
if ('autoValue' in definition) {
|
||
if (fieldName.slice(-2) === ".$") {
|
||
throw new Error('An array item field (one that ends with ".$") cannot have autoValue.');
|
||
}
|
||
self._autoValues[fieldName] = definition.autoValue;
|
||
}
|
||
|
||
self._depsLabels[fieldName] = new Tracker.Dependency();
|
||
|
||
if (definition.blackbox === true) {
|
||
self._blackboxKeys.push(fieldName);
|
||
}
|
||
|
||
if (!_.contains(firstLevelSchemaKeys, fieldNameRoot)) {
|
||
firstLevelSchemaKeys.push(fieldNameRoot);
|
||
}
|
||
});
|
||
|
||
|
||
// Cache these lists
|
||
self._firstLevelSchemaKeys = firstLevelSchemaKeys;
|
||
self._objectKeys = getObjectKeys(self._schema, self._schemaKeys);
|
||
|
||
// We will store named validation contexts here
|
||
self._validationContexts = {};
|
||
};
|
||
|
||
// This allows other packages or users to extend the schema
|
||
// definition options that are supported.
|
||
SimpleSchema.extendOptions = function(options) {
|
||
_.extend(schemaDefinition, options);
|
||
};
|
||
|
||
// this domain regex matches all domains that have at least one .
|
||
// sadly IPv4 Adresses will be caught too but technically those are valid domains
|
||
// this expression is extracted from the original RFC 5322 mail expression
|
||
// a modification enforces that the tld consists only of characters
|
||
var RX_DOMAIN = '(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z](?:[a-z-]*[a-z])?';
|
||
// this domain regex matches everythign that could be a domain in intranet
|
||
// that means "localhost" is a valid domain
|
||
var RX_NAME_DOMAIN = '(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.|$))+';
|
||
// strict IPv4 expression which allows 0-255 per oktett
|
||
var RX_IPv4 = '(?:(?:[0-1]?\\d{1,2}|2[0-4]\\d|25[0-5])(?:\\.|$)){4}';
|
||
// strict IPv6 expression which allows (and validates) all shortcuts
|
||
var RX_IPv6 = '(?:(?:[\\dA-Fa-f]{1,4}(?::|$)){8}' // full adress
|
||
+ '|(?=(?:[^:\\s]|:[^:\\s])*::(?:[^:\\s]|:[^:\\s])*$)' // or min/max one '::'
|
||
+ '[\\dA-Fa-f]{0,4}(?:::?(?:[\\dA-Fa-f]{1,4}|$)){1,6})'; // and short adress
|
||
// this allows domains (also localhost etc) and ip adresses
|
||
var RX_WEAK_DOMAIN = '(?:' + [RX_NAME_DOMAIN,RX_IPv4,RX_IPv6].join('|') + ')';
|
||
|
||
SimpleSchema.RegEx = {
|
||
// We use the RegExp suggested by W3C in http://www.w3.org/TR/html5/forms.html#valid-e-mail-address
|
||
// This is probably the same logic used by most browsers when type=email, which is our goal. It is
|
||
// a very permissive expression. Some apps may wish to be more strict and can write their own RegExp.
|
||
Email: /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
|
||
|
||
Domain: new RegExp('^' + RX_DOMAIN + '$'),
|
||
WeakDomain: new RegExp('^' + RX_WEAK_DOMAIN + '$'),
|
||
|
||
IP: new RegExp('^(?:' + RX_IPv4 + '|' + RX_IPv6 + ')$'),
|
||
IPv4: new RegExp('^' + RX_IPv4 + '$'),
|
||
IPv6: new RegExp('^' + RX_IPv6 + '$'),
|
||
// URL RegEx from https://gist.github.com/dperini/729294
|
||
// http://mathiasbynens.be/demo/url-regex
|
||
Url: /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/i,
|
||
// unique id from the random package also used by minimongo
|
||
// character list: https://github.com/meteor/meteor/blob/release/0.8.0/packages/random/random.js#L88
|
||
// string length: https://github.com/meteor/meteor/blob/release/0.8.0/packages/random/random.js#L143
|
||
Id: /^[23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz]{17}$/,
|
||
// allows for a 5 digit zip code followed by a whitespace or dash and then 4 more digits
|
||
// matches 11111 and 11111-1111 and 11111 1111
|
||
ZipCode: /^\d{5}(?:[-\s]\d{4})?$/,
|
||
// taken from google's libphonenumber library
|
||
// https://github.com/googlei18n/libphonenumber/blob/master/javascript/i18n/phonenumbers/phonenumberutil.js
|
||
// reference the VALID_PHONE_NUMBER_PATTERN key
|
||
// allows for common phone number symbols including + () and -
|
||
Phone: /^[0-90-9٠-٩۰-۹]{2}$|^[++]*(?:[-x‐-―−ー--/ ()()[].\[\]/~⁓∼~*]*[0-90-9٠-٩۰-۹]){3,}[-x‐-―−ー--/ ()()[].\[\]/~⁓∼~0-90-9٠-٩۰-۹]*(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ \t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xx##~~]|int|anexo|int)[:\..]?[ \t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)?$/i
|
||
};
|
||
|
||
SimpleSchema._makeGeneric = function(name) {
|
||
if (typeof name !== "string") {
|
||
return null;
|
||
}
|
||
|
||
return name.replace(/\.[0-9]+(?=\.|$)/g, '.$');
|
||
};
|
||
|
||
SimpleSchema._depsGlobalMessages = new Tracker.Dependency();
|
||
|
||
// Inherit from Match.Where
|
||
// This allow SimpleSchema instance to be recognized as a Match.Where instance as well
|
||
// as a SimpleSchema instance
|
||
SimpleSchema.prototype = new Match.Where();
|
||
|
||
// If an object is an instance of Match.Where, Meteor built-in check API will look at
|
||
// the function named `condition` and will pass it the document to validate
|
||
SimpleSchema.prototype.condition = function(obj) {
|
||
var self = this;
|
||
|
||
//determine whether obj is a modifier
|
||
var isModifier, isNotModifier;
|
||
_.each(obj, function(val, key) {
|
||
if (key.substring(0, 1) === "$") {
|
||
isModifier = true;
|
||
} else {
|
||
isNotModifier = true;
|
||
}
|
||
});
|
||
|
||
if (isModifier && isNotModifier) {
|
||
throw new Match.Error("Object cannot contain modifier operators alongside other keys");
|
||
}
|
||
|
||
var ctx = self.newContext();
|
||
if (!ctx.validate(obj, {modifier: isModifier, filter: false, autoConvert: false})) {
|
||
var error = ctx.getErrorObject();
|
||
var matchError = new Match.Error(error.message);
|
||
matchError.invalidKeys = error.invalidKeys;
|
||
if (Meteor.isServer) {
|
||
matchError.sanitizedError = error.sanitizedError;
|
||
}
|
||
throw matchError;
|
||
}
|
||
|
||
return true;
|
||
};
|
||
|
||
function logInvalidKeysForContext(context, name) {
|
||
Meteor.startup(function() {
|
||
Tracker.autorun(function() {
|
||
if (!context.isValid()) {
|
||
console.log('SimpleSchema invalid keys for "' + name + '" context:', context.invalidKeys());
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
SimpleSchema.prototype.namedContext = function(name) {
|
||
var self = this;
|
||
if (typeof name !== "string") {
|
||
name = "default";
|
||
}
|
||
if (!self._validationContexts[name]) {
|
||
self._validationContexts[name] = new SimpleSchemaValidationContext(self);
|
||
|
||
// In debug mode, log all invalid key errors to the browser console
|
||
if (SimpleSchema.debug && Meteor.isClient) {
|
||
Tracker.nonreactive(function() {
|
||
logInvalidKeysForContext(self._validationContexts[name], name);
|
||
});
|
||
}
|
||
}
|
||
return self._validationContexts[name];
|
||
};
|
||
|
||
// Global custom validators
|
||
SimpleSchema._validators = [];
|
||
SimpleSchema.addValidator = function(func) {
|
||
SimpleSchema._validators.push(func);
|
||
};
|
||
|
||
// Instance custom validators
|
||
SimpleSchema.prototype.addValidator = function(func) {
|
||
this._validators.push(func);
|
||
};
|
||
|
||
/**
|
||
* @method SimpleSchema.prototype.pick
|
||
* @param {[fields]} The list of fields to pick to instantiate the subschema
|
||
* @returns {SimpleSchema} The subschema
|
||
*/
|
||
SimpleSchema.prototype.pick = function(/* arguments */) {
|
||
var self = this;
|
||
var args = _.toArray(arguments);
|
||
args.unshift(self._schema);
|
||
|
||
var newSchema = _.pick.apply(null, args);
|
||
return new SimpleSchema(newSchema);
|
||
};
|
||
|
||
SimpleSchema.prototype.omit = function() {
|
||
var self = this;
|
||
var args = _.toArray(arguments);
|
||
args.unshift(self._schema);
|
||
|
||
var newSchema = _.omit.apply(null, args);
|
||
return new SimpleSchema(newSchema);
|
||
};
|
||
|
||
|
||
/**
|
||
* @method SimpleSchema.prototype.clean
|
||
* @param {Object} doc - Document or modifier to clean. Referenced object will be modified in place.
|
||
* @param {Object} [options]
|
||
* @param {Boolean} [options.filter=true] - Do filtering?
|
||
* @param {Boolean} [options.autoConvert=true] - Do automatic type converting?
|
||
* @param {Boolean} [options.removeEmptyStrings=true] - Remove keys in normal object or $set where the value is an empty string?
|
||
* @param {Boolean} [options.trimStrings=true] - Trim string values?
|
||
* @param {Boolean} [options.getAutoValues=true] - Inject automatic and default values?
|
||
* @param {Boolean} [options.isModifier=false] - Is doc a modifier object?
|
||
* @param {Object} [options.extendAutoValueContext] - This object will be added to the `this` context of autoValue functions.
|
||
* @returns {Object} The modified doc.
|
||
*
|
||
* Cleans a document or modifier object. By default, will filter, automatically
|
||
* type convert where possible, and inject automatic/default values. Use the options
|
||
* to skip one or more of these.
|
||
*/
|
||
SimpleSchema.prototype.clean = function(doc, options) {
|
||
var self = this;
|
||
|
||
// By default, doc will be filtered and autoconverted
|
||
options = _.extend({
|
||
filter: true,
|
||
autoConvert: true,
|
||
removeEmptyStrings: true,
|
||
trimStrings: true,
|
||
getAutoValues: true,
|
||
isModifier: false,
|
||
extendAutoValueContext: {}
|
||
}, options || {});
|
||
|
||
// Convert $pushAll (deprecated) to $push with $each
|
||
if ("$pushAll" in doc) {
|
||
console.warn("SimpleSchema.clean: $pushAll is deprecated; converting to $push with $each");
|
||
doc.$push = doc.$push || {};
|
||
for (var field in doc.$pushAll) {
|
||
doc.$push[field] = doc.$push[field] || {};
|
||
doc.$push[field].$each = doc.$push[field].$each || [];
|
||
for (var i = 0, ln = doc.$pushAll[field].length; i < ln; i++) {
|
||
doc.$push[field].$each.push(doc.$pushAll[field][i]);
|
||
}
|
||
delete doc.$pushAll;
|
||
}
|
||
}
|
||
|
||
var mDoc = new MongoObject(doc, self._blackboxKeys);
|
||
|
||
// Clean loop
|
||
if (options.filter || options.autoConvert || options.removeEmptyStrings || options.trimStrings) {
|
||
mDoc.forEachNode(function() {
|
||
var gKey = this.genericKey, p, def, val;
|
||
if (gKey) {
|
||
def = self._schema[gKey];
|
||
val = this.value;
|
||
// Filter out props if necessary; any property is OK for $unset because we want to
|
||
// allow conversions to remove props that have been removed from the schema.
|
||
if (options.filter && this.operator !== "$unset" && !self.allowsKey(gKey)) {
|
||
// XXX Special handling for $each; maybe this could be made nicer
|
||
if (this.position.slice(-7) === "[$each]") {
|
||
mDoc.removeValueForPosition(this.position.slice(0, -7));
|
||
} else {
|
||
this.remove();
|
||
}
|
||
if (SimpleSchema.debug) {
|
||
console.info('SimpleSchema.clean: filtered out value that would have affected key "' + gKey + '", which is not allowed by the schema');
|
||
}
|
||
return; // no reason to do more
|
||
}
|
||
if (val !== void 0) {
|
||
// Autoconvert values if requested and if possible
|
||
var wasAutoConverted = false;
|
||
if (options.autoConvert && this.operator !== "$unset" && def) {
|
||
var newVal = typeconvert(val, def.type);
|
||
// trim strings
|
||
if (options.trimStrings && typeof newVal === "string") {
|
||
newVal = newVal.trim();
|
||
}
|
||
if (newVal !== void 0 && newVal !== val) {
|
||
// remove empty strings
|
||
if (options.removeEmptyStrings && (!this.operator || this.operator === "$set") && typeof newVal === "string" && !newVal.length) {
|
||
// For a document, we remove any fields that are being set to an empty string
|
||
newVal = void 0;
|
||
// For a modifier, we $unset any fields that are being set to an empty string
|
||
if (this.operator === "$set" && this.position.match(/\[.+?\]/g).length < 2) {
|
||
|
||
p = this.position.replace("$set", "$unset");
|
||
mDoc.setValueForPosition(p, "");
|
||
}
|
||
}
|
||
|
||
// Change value; if undefined, will remove it
|
||
SimpleSchema.debug && console.info('SimpleSchema.clean: autoconverted value ' + val + ' from ' + typeof val + ' to ' + typeof newVal + ' for ' + gKey);
|
||
this.updateValue(newVal);
|
||
wasAutoConverted = true;
|
||
}
|
||
}
|
||
if (!wasAutoConverted) {
|
||
// trim strings
|
||
if (options.trimStrings && typeof val === "string" && (!def || (def && def.trim !== false))) {
|
||
this.updateValue(val.trim());
|
||
}
|
||
// remove empty strings
|
||
if (options.removeEmptyStrings && (!this.operator || this.operator === "$set") && typeof val === "string" && !val.length) {
|
||
// For a document, we remove any fields that are being set to an empty string
|
||
this.remove();
|
||
// For a modifier, we $unset any fields that are being set to an empty string. But only if we're not already within an entire object that is being set.
|
||
if (this.operator === "$set" && this.position.match(/\[.+?\]/g).length < 2) {
|
||
p = this.position.replace("$set", "$unset");
|
||
mDoc.setValueForPosition(p, "");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}, {endPointsOnly: false});
|
||
}
|
||
|
||
// Set automatic values
|
||
options.getAutoValues && getAutoValues.call(self, mDoc, options.isModifier, options.extendAutoValueContext);
|
||
|
||
// Ensure we don't have any operators set to an empty object
|
||
// since MongoDB 2.6+ will throw errors.
|
||
if (options.isModifier) {
|
||
for (var op in doc) {
|
||
if (doc.hasOwnProperty(op) && _.isEmpty(doc[op])) {
|
||
delete doc[op];
|
||
}
|
||
}
|
||
}
|
||
|
||
return doc;
|
||
};
|
||
|
||
// Returns the entire schema object or just the definition for one key
|
||
// in the schema.
|
||
SimpleSchema.prototype.schema = function(key) {
|
||
var self = this;
|
||
// if not null or undefined (more specific)
|
||
if (key !== null && key !== void 0) {
|
||
return self._schema[SimpleSchema._makeGeneric(key)];
|
||
} else {
|
||
return self._schema;
|
||
}
|
||
};
|
||
|
||
// Returns the evaluated definition for one key in the schema
|
||
// key = non-generic key
|
||
// [propList] = props to include in the result, for performance
|
||
// [functionContext] = used for evaluating schema options that are functions
|
||
SimpleSchema.prototype.getDefinition = function(key, propList, functionContext) {
|
||
var self = this;
|
||
var defs = self.schema(key);
|
||
if (!defs) {
|
||
return;
|
||
}
|
||
|
||
if (_.isArray(propList)) {
|
||
defs = _.pick(defs, propList);
|
||
} else {
|
||
defs = _.clone(defs);
|
||
}
|
||
|
||
// For any options that support specifying a function,
|
||
// evaluate the functions.
|
||
_.each(['min', 'max', 'minCount', 'maxCount', 'allowedValues', 'optional', 'label'], function (prop) {
|
||
if (_.isFunction(defs[prop])) {
|
||
defs[prop] = defs[prop].call(functionContext || {});
|
||
}
|
||
});
|
||
|
||
// Inflect label if not defined
|
||
defs.label = defs.label || inflectedLabel(key);
|
||
|
||
return defs;
|
||
};
|
||
|
||
// Check if the key is a nested dot-syntax key inside of a blackbox object
|
||
SimpleSchema.prototype.keyIsInBlackBox = function(key) {
|
||
var self = this;
|
||
var parentPath = SimpleSchema._makeGeneric(key), lastDot, def;
|
||
|
||
// Iterate the dot-syntax hierarchy until we find a key in our schema
|
||
do {
|
||
lastDot = parentPath.lastIndexOf('.');
|
||
if (lastDot !== -1) {
|
||
parentPath = parentPath.slice(0, lastDot); // Remove last path component
|
||
def = self.getDefinition(parentPath);
|
||
}
|
||
} while (lastDot !== -1 && !def);
|
||
|
||
return !!(def && def.blackbox);
|
||
};
|
||
|
||
// Use to dynamically change the schema labels.
|
||
SimpleSchema.prototype.labels = function(labels) {
|
||
var self = this;
|
||
_.each(labels, function(label, fieldName) {
|
||
if (!_.isString(label) && !_.isFunction(label)) {
|
||
return;
|
||
}
|
||
|
||
if (!(fieldName in self._schema)) {
|
||
return;
|
||
}
|
||
|
||
self._schema[fieldName].label = label;
|
||
self._depsLabels[fieldName] && self._depsLabels[fieldName].changed();
|
||
});
|
||
};
|
||
|
||
// should be used to safely get a label as string
|
||
SimpleSchema.prototype.label = function(key) {
|
||
var self = this;
|
||
|
||
// Get all labels
|
||
if (key === null || key === void 0) {
|
||
var result = {};
|
||
_.each(self.schema(), function(def, fieldName) {
|
||
result[fieldName] = self.label(fieldName);
|
||
});
|
||
return result;
|
||
}
|
||
|
||
// Get label for one field
|
||
var def = self.getDefinition(key);
|
||
if (def) {
|
||
var genericKey = SimpleSchema._makeGeneric(key);
|
||
self._depsLabels[genericKey] && self._depsLabels[genericKey].depend();
|
||
return def.label;
|
||
}
|
||
|
||
return null;
|
||
};
|
||
|
||
// Global messages
|
||
|
||
SimpleSchema._globalMessages = {
|
||
required: "[label] is required",
|
||
minString: "[label] must be at least [min] characters",
|
||
maxString: "[label] cannot exceed [max] characters",
|
||
minNumber: "[label] must be at least [min]",
|
||
maxNumber: "[label] cannot exceed [max]",
|
||
minNumberExclusive: "[label] must be greater than [min]",
|
||
maxNumberExclusive: "[label] must be less than [max]",
|
||
minDate: "[label] must be on or after [min]",
|
||
maxDate: "[label] cannot be after [max]",
|
||
badDate: "[label] is not a valid date",
|
||
minCount: "You must specify at least [minCount] values",
|
||
maxCount: "You cannot specify more than [maxCount] values",
|
||
noDecimal: "[label] must be an integer",
|
||
notAllowed: "[value] is not an allowed value",
|
||
expectedString: "[label] must be a string",
|
||
expectedNumber: "[label] must be a number",
|
||
expectedBoolean: "[label] must be a boolean",
|
||
expectedArray: "[label] must be an array",
|
||
expectedObject: "[label] must be an object",
|
||
expectedConstructor: "[label] must be a [type]",
|
||
regEx: [
|
||
{msg: "[label] failed regular expression validation"},
|
||
{exp: SimpleSchema.RegEx.Email, msg: "[label] must be a valid e-mail address"},
|
||
{exp: SimpleSchema.RegEx.WeakEmail, msg: "[label] must be a valid e-mail address"},
|
||
{exp: SimpleSchema.RegEx.Domain, msg: "[label] must be a valid domain"},
|
||
{exp: SimpleSchema.RegEx.WeakDomain, msg: "[label] must be a valid domain"},
|
||
{exp: SimpleSchema.RegEx.IP, msg: "[label] must be a valid IPv4 or IPv6 address"},
|
||
{exp: SimpleSchema.RegEx.IPv4, msg: "[label] must be a valid IPv4 address"},
|
||
{exp: SimpleSchema.RegEx.IPv6, msg: "[label] must be a valid IPv6 address"},
|
||
{exp: SimpleSchema.RegEx.Url, msg: "[label] must be a valid URL"},
|
||
{exp: SimpleSchema.RegEx.Id, msg: "[label] must be a valid alphanumeric ID"}
|
||
],
|
||
keyNotInSchema: "[key] is not allowed by the schema"
|
||
};
|
||
|
||
SimpleSchema.messages = function(messages) {
|
||
_.extend(SimpleSchema._globalMessages, messages);
|
||
SimpleSchema._depsGlobalMessages.changed();
|
||
};
|
||
|
||
// Schema-specific messages
|
||
|
||
SimpleSchema.prototype.messages = function(messages) {
|
||
var self = this;
|
||
_.extend(self._messages, messages);
|
||
self._depsMessages.changed();
|
||
};
|
||
|
||
// Returns a string message for the given error type and key. Uses the
|
||
// def and value arguments to fill in placeholders in the error messages.
|
||
SimpleSchema.prototype.messageForError = function(type, key, def, value) {
|
||
var self = this;
|
||
|
||
// We proceed even if we can't get a definition because it might be a keyNotInSchema error
|
||
def = def || self.getDefinition(key, ['regEx', 'label', 'minCount', 'maxCount', 'min', 'max', 'type']) || {};
|
||
|
||
// Adjust for complex types, currently only regEx,
|
||
// where we might have regEx.1 meaning the second
|
||
// expression in the array.
|
||
var firstTypePeriod = type.indexOf("."), index = null;
|
||
if (firstTypePeriod !== -1) {
|
||
index = type.substring(firstTypePeriod + 1);
|
||
index = parseInt(index, 10);
|
||
type = type.substring(0, firstTypePeriod);
|
||
}
|
||
|
||
// Which regExp is it?
|
||
var regExpMatch;
|
||
if (type === "regEx") {
|
||
if (index !== null && index !== void 0 && !isNaN(index)) {
|
||
regExpMatch = def.regEx[index];
|
||
} else {
|
||
regExpMatch = def.regEx;
|
||
}
|
||
if (regExpMatch) {
|
||
regExpMatch = regExpMatch.toString();
|
||
}
|
||
}
|
||
|
||
// Prep some strings to be used when finding the correct message for this error
|
||
var typePlusKey = type + " " + key;
|
||
var genericKey = SimpleSchema._makeGeneric(key);
|
||
var typePlusGenKey = type + " " + genericKey;
|
||
|
||
// reactively update when message templates are changed
|
||
SimpleSchema._depsGlobalMessages.depend();
|
||
self._depsMessages.depend();
|
||
|
||
// Prep a function that finds the correct message for regEx errors
|
||
function findRegExError(message) {
|
||
if (type !== "regEx" || !_.isArray(message)) {
|
||
return message;
|
||
}
|
||
// Parse regEx messages, which are provided in a special object array format
|
||
// [{exp: RegExp, msg: "Foo"}]
|
||
// Where `exp` is optional
|
||
|
||
var msgObj;
|
||
// First see if there's one where exp matches this expression
|
||
if (regExpMatch) {
|
||
msgObj = _.find(message, function (o) {
|
||
return o.exp && o.exp.toString() === regExpMatch;
|
||
});
|
||
}
|
||
|
||
// If not, see if there's a default message defined
|
||
if (!msgObj) {
|
||
msgObj = _.findWhere(message, {exp: null});
|
||
if (!msgObj) {
|
||
msgObj = _.findWhere(message, {exp: void 0});
|
||
}
|
||
}
|
||
|
||
return msgObj ? msgObj.msg : null;
|
||
}
|
||
|
||
// Try finding the correct message to use at various levels, from most
|
||
// specific to least specific.
|
||
var message = self._messages[typePlusKey] || // (1) Use schema-specific message for specific key
|
||
self._messages[typePlusGenKey] || // (2) Use schema-specific message for generic key
|
||
self._messages[type]; // (3) Use schema-specific message for type
|
||
message = findRegExError(message);
|
||
|
||
if (!message) {
|
||
message = SimpleSchema._globalMessages[typePlusKey] || // (4) Use global message for specific key
|
||
SimpleSchema._globalMessages[typePlusGenKey] || // (5) Use global message for generic key
|
||
SimpleSchema._globalMessages[type]; // (6) Use global message for type
|
||
message = findRegExError(message);
|
||
}
|
||
|
||
if (!message) {
|
||
return "Unknown validation error";
|
||
}
|
||
|
||
// Now replace all placeholders in the message with the correct values
|
||
|
||
// [key]
|
||
message = message.replace("[key]", key);
|
||
|
||
// [label]
|
||
// The call to self.label() establishes a reactive dependency, too
|
||
message = message.replace("[label]", self.label(key));
|
||
|
||
// [minCount]
|
||
if (typeof def.minCount !== "undefined") {
|
||
message = message.replace("[minCount]", def.minCount);
|
||
}
|
||
|
||
// [maxCount]
|
||
if (typeof def.maxCount !== "undefined") {
|
||
message = message.replace("[maxCount]", def.maxCount);
|
||
}
|
||
|
||
// [value]
|
||
if (value !== void 0 && value !== null) {
|
||
message = message.replace("[value]", value.toString());
|
||
} else {
|
||
message = message.replace("[value]", 'null');
|
||
}
|
||
|
||
// [min] and [max]
|
||
var min = def.min;
|
||
var max = def.max;
|
||
if (def.type === Date || def.type === [Date]) {
|
||
if (typeof min !== "undefined") {
|
||
message = message.replace("[min]", Utility.dateToDateString(min));
|
||
}
|
||
if (typeof max !== "undefined") {
|
||
message = message.replace("[max]", Utility.dateToDateString(max));
|
||
}
|
||
} else {
|
||
if (typeof min !== "undefined") {
|
||
message = message.replace("[min]", min);
|
||
}
|
||
if (typeof max !== "undefined") {
|
||
message = message.replace("[max]", max);
|
||
}
|
||
}
|
||
|
||
// [type]
|
||
if (def.type instanceof Function) {
|
||
message = message.replace("[type]", def.type.name);
|
||
}
|
||
|
||
// Now return the message
|
||
return message;
|
||
};
|
||
|
||
// Returns true if key is explicitly allowed by the schema or implied
|
||
// by other explicitly allowed keys.
|
||
// The key string should have $ in place of any numeric array positions.
|
||
SimpleSchema.prototype.allowsKey = function(key) {
|
||
var self = this;
|
||
|
||
// Loop through all keys in the schema
|
||
return _.any(self._schemaKeys, function(schemaKey) {
|
||
|
||
// If the schema key is the test key, it's allowed.
|
||
if (schemaKey === key) {
|
||
return true;
|
||
}
|
||
|
||
// Black box handling
|
||
if (self.schema(schemaKey).blackbox === true) {
|
||
var kl = schemaKey.length;
|
||
var compare1 = key.slice(0, kl + 2);
|
||
var compare2 = compare1.slice(0, -1);
|
||
|
||
// If the test key is the black box key + ".$", then the test
|
||
// key is NOT allowed because black box keys are by definition
|
||
// only for objects, and not for arrays.
|
||
if (compare1 === schemaKey + '.$') {
|
||
return false;
|
||
}
|
||
|
||
// Otherwise
|
||
if (compare2 === schemaKey + '.') {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
});
|
||
};
|
||
|
||
SimpleSchema.prototype.newContext = function() {
|
||
return new SimpleSchemaValidationContext(this);
|
||
};
|
||
|
||
// Returns all the child keys for the object identified by the generic prefix,
|
||
// or all the top level keys if no prefix is supplied.
|
||
SimpleSchema.prototype.objectKeys = function(keyPrefix) {
|
||
var self = this;
|
||
if (!keyPrefix) {
|
||
return self._firstLevelSchemaKeys;
|
||
}
|
||
return self._objectKeys[keyPrefix + "."] || [];
|
||
};
|
||
|
||
SimpleSchema.prototype.validate = function (obj, options) {
|
||
if (Package.check && Package['audit-argument-checks']) {
|
||
// Call check but ignore the error to silence audit-argument-checks
|
||
try { check(obj); } catch (e) { /* ignore error */ }
|
||
}
|
||
|
||
var validationContext = this.newContext();
|
||
var isValid = validationContext.validate(obj, options);
|
||
|
||
if (isValid) return;
|
||
|
||
var errors = validationContext.invalidKeys().map(function (error) {
|
||
return {
|
||
name: error.name,
|
||
type: error.type,
|
||
details: {
|
||
value: error.value
|
||
}
|
||
};
|
||
});
|
||
|
||
// In order for the message at the top of the stack trace to be useful,
|
||
// we set it to the first validation error message.
|
||
var message = validationContext.keyErrorMessage(errors[0].name);
|
||
|
||
throw new Package['mdg:validation-error'].ValidationError(errors, message);
|
||
};
|
||
|
||
SimpleSchema.prototype.validator = function (options) {
|
||
var self = this;
|
||
options = options || {};
|
||
return function (obj) {
|
||
if (options.clean === true) self.clean(obj, options);
|
||
self.validate(obj);
|
||
};
|
||
};
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
}).call(this);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
(function(){
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// packages/aldeed_simple-schema/simple-schema-validation.js //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
/* global Utility */
|
||
/* global _ */
|
||
/* global SimpleSchema */
|
||
/* global MongoObject */
|
||
/* global doValidation1:true */
|
||
|
||
function doTypeChecks(def, keyValue, op) {
|
||
var expectedType = def.type;
|
||
|
||
// String checks
|
||
if (expectedType === String) {
|
||
if (typeof keyValue !== "string") {
|
||
return "expectedString";
|
||
} else if (def.max !== null && def.max < keyValue.length) {
|
||
return "maxString";
|
||
} else if (def.min !== null && def.min > keyValue.length) {
|
||
return "minString";
|
||
} else if (def.regEx instanceof RegExp && !def.regEx.test(keyValue)) {
|
||
return "regEx";
|
||
} else if (_.isArray(def.regEx)) {
|
||
var regExError;
|
||
_.every(def.regEx, function(re, i) {
|
||
if (!re.test(keyValue)) {
|
||
regExError = "regEx." + i;
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
if (regExError) {
|
||
return regExError;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Number checks
|
||
else if (expectedType === Number) {
|
||
if (typeof keyValue !== "number" || isNaN(keyValue)) {
|
||
return "expectedNumber";
|
||
} else if (op !== "$inc" && def.max !== null && (!!def.exclusiveMax ? def.max <= keyValue : def.max < keyValue)) {
|
||
return !!def.exclusiveMax ? "maxNumberExclusive" : "maxNumber";
|
||
} else if (op !== "$inc" && def.min !== null && (!!def.exclusiveMin ? def.min >= keyValue : def.min > keyValue)) {
|
||
return !!def.exclusiveMin ? "minNumberExclusive" : "minNumber";
|
||
} else if (!def.decimal && keyValue.toString().indexOf(".") > -1) {
|
||
return "noDecimal";
|
||
}
|
||
}
|
||
|
||
// Boolean checks
|
||
else if (expectedType === Boolean) {
|
||
if (typeof keyValue !== "boolean") {
|
||
return "expectedBoolean";
|
||
}
|
||
}
|
||
|
||
// Object checks
|
||
else if (expectedType === Object) {
|
||
if (!Utility.isBasicObject(keyValue)) {
|
||
return "expectedObject";
|
||
}
|
||
}
|
||
|
||
// Array checks
|
||
else if (expectedType === Array) {
|
||
if (!_.isArray(keyValue)) {
|
||
return "expectedArray";
|
||
} else if (def.minCount !== null && keyValue.length < def.minCount) {
|
||
return "minCount";
|
||
} else if (def.maxCount !== null && keyValue.length > def.maxCount) {
|
||
return "maxCount";
|
||
}
|
||
}
|
||
|
||
// Constructor function checks
|
||
else if (expectedType instanceof Function || Utility.safariBugFix(expectedType)) {
|
||
|
||
// Generic constructor checks
|
||
if (!(keyValue instanceof expectedType)) {
|
||
return "expectedConstructor";
|
||
}
|
||
|
||
// Date checks
|
||
else if (expectedType === Date) {
|
||
if (isNaN(keyValue.getTime())) {
|
||
return "badDate";
|
||
}
|
||
|
||
if (_.isDate(def.min) && def.min.getTime() > keyValue.getTime()) {
|
||
return "minDate";
|
||
} else if (_.isDate(def.max) && def.max.getTime() < keyValue.getTime()) {
|
||
return "maxDate";
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
doValidation1 = function doValidation1(obj, isModifier, isUpsert, keyToValidate, ss, extendedCustomContext) {
|
||
// First do some basic checks of the object, and throw errors if necessary
|
||
if (!_.isObject(obj)) {
|
||
throw new Error("The first argument of validate() or validateOne() must be an object");
|
||
}
|
||
|
||
if (!isModifier && Utility.looksLikeModifier(obj)) {
|
||
throw new Error("When the validation object contains mongo operators, you must set the modifier option to true");
|
||
}
|
||
|
||
var invalidKeys = [];
|
||
var mDoc; // for caching the MongoObject if necessary
|
||
|
||
// Validation function called for each affected key
|
||
function validate(val, affectedKey, affectedKeyGeneric, def, op, skipRequiredCheck, isInArrayItemObject, isInSubObject) {
|
||
|
||
// Get the schema for this key, marking invalid if there isn't one.
|
||
if (!def) {
|
||
invalidKeys.push(Utility.errorObject("keyNotInSchema", affectedKey, val, def, ss));
|
||
return;
|
||
}
|
||
|
||
// Check for missing required values. The general logic is this:
|
||
// * If the operator is $unset or $rename, it's invalid.
|
||
// * If the value is null, it's invalid.
|
||
// * If the value is undefined and one of the following are true, it's invalid:
|
||
// * We're validating a key of a sub-object.
|
||
// * We're validating a key of an object that is an array item.
|
||
// * We're validating a document (as opposed to a modifier).
|
||
// * We're validating a key under the $set operator in a modifier, and it's an upsert.
|
||
if (!skipRequiredCheck && !def.optional) {
|
||
if (
|
||
val === null ||
|
||
op === "$unset" ||
|
||
op === "$rename" ||
|
||
(val === void 0 && (isInArrayItemObject || isInSubObject || !op || op === "$set"))
|
||
) {
|
||
invalidKeys.push(Utility.errorObject("required", affectedKey, null, def, ss));
|
||
return;
|
||
}
|
||
}
|
||
|
||
// For $rename, make sure that the new name is allowed by the schema
|
||
if (op === "$rename" && typeof val === "string" && !ss.allowsKey(val)) {
|
||
invalidKeys.push(Utility.errorObject("keyNotInSchema", val, null, null, ss));
|
||
return;
|
||
}
|
||
|
||
// Value checks are not necessary for null or undefined values
|
||
// or for $unset or $rename values
|
||
if (op !== "$unset" && op !== "$rename" && Utility.isNotNullOrUndefined(val)) {
|
||
|
||
// Check that value is of the correct type
|
||
var typeError = doTypeChecks(def, val, op);
|
||
if (typeError) {
|
||
invalidKeys.push(Utility.errorObject(typeError, affectedKey, val, def, ss));
|
||
return;
|
||
}
|
||
|
||
// Check value against allowedValues array
|
||
if (def.allowedValues && !_.contains(def.allowedValues, val)) {
|
||
invalidKeys.push(Utility.errorObject("notAllowed", affectedKey, val, def, ss));
|
||
return;
|
||
}
|
||
|
||
}
|
||
|
||
// Perform custom validation
|
||
var lastDot = affectedKey.lastIndexOf('.');
|
||
var fieldParentName = lastDot === -1 ? '' : affectedKey.slice(0, lastDot + 1);
|
||
var validators = def.custom ? [def.custom] : [];
|
||
validators = validators.concat(ss._validators).concat(SimpleSchema._validators);
|
||
_.every(validators, function(validator) {
|
||
var errorType = validator.call(_.extend({
|
||
key: affectedKey,
|
||
genericKey: affectedKeyGeneric,
|
||
definition: def,
|
||
isSet: (val !== void 0),
|
||
value: val,
|
||
operator: op,
|
||
field: function(fName) {
|
||
mDoc = mDoc || new MongoObject(obj, ss._blackboxKeys); //create if necessary, cache for speed
|
||
var keyInfo = mDoc.getInfoForKey(fName) || {};
|
||
return {
|
||
isSet: (keyInfo.value !== void 0),
|
||
value: keyInfo.value,
|
||
operator: keyInfo.operator
|
||
};
|
||
},
|
||
siblingField: function(fName) {
|
||
mDoc = mDoc || new MongoObject(obj, ss._blackboxKeys); //create if necessary, cache for speed
|
||
var keyInfo = mDoc.getInfoForKey(fieldParentName + fName) || {};
|
||
return {
|
||
isSet: (keyInfo.value !== void 0),
|
||
value: keyInfo.value,
|
||
operator: keyInfo.operator
|
||
};
|
||
}
|
||
}, extendedCustomContext || {}));
|
||
if (typeof errorType === "string") {
|
||
invalidKeys.push(Utility.errorObject(errorType, affectedKey, val, def, ss));
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
}
|
||
|
||
// The recursive function
|
||
function checkObj(val, affectedKey, operator, setKeys, isInArrayItemObject, isInSubObject) {
|
||
var affectedKeyGeneric, def;
|
||
|
||
if (affectedKey) {
|
||
// When we hit a blackbox key, we don't progress any further
|
||
if (ss.keyIsInBlackBox(affectedKey)) {
|
||
return;
|
||
}
|
||
|
||
// Make a generic version of the affected key, and use that
|
||
// to get the schema for this key.
|
||
affectedKeyGeneric = SimpleSchema._makeGeneric(affectedKey);
|
||
def = ss.getDefinition(affectedKey);
|
||
|
||
// Perform validation for this key
|
||
if (!keyToValidate || keyToValidate === affectedKey || keyToValidate === affectedKeyGeneric) {
|
||
// We can skip the required check for keys that are ancestors
|
||
// of those in $set or $setOnInsert because they will be created
|
||
// by MongoDB while setting.
|
||
var skipRequiredCheck = _.some(setKeys, function(sk) {
|
||
return (sk.slice(0, affectedKey.length + 1) === affectedKey + ".");
|
||
});
|
||
validate(val, affectedKey, affectedKeyGeneric, def, operator, skipRequiredCheck, isInArrayItemObject, isInSubObject);
|
||
}
|
||
}
|
||
|
||
// Temporarily convert missing objects to empty objects
|
||
// so that the looping code will be called and required
|
||
// descendent keys can be validated.
|
||
if ((val === void 0 || val === null) && (!def || (def.type === Object && !def.optional))) {
|
||
val = {};
|
||
}
|
||
|
||
// Loop through arrays
|
||
if (_.isArray(val)) {
|
||
_.each(val, function(v, i) {
|
||
checkObj(v, affectedKey + '.' + i, operator, setKeys);
|
||
});
|
||
}
|
||
|
||
// Loop through object keys
|
||
else if (Utility.isBasicObject(val) && (!def || !def.blackbox)) {
|
||
|
||
// Get list of present keys
|
||
var presentKeys = _.keys(val);
|
||
|
||
// Check all present keys plus all keys defined by the schema.
|
||
// This allows us to detect extra keys not allowed by the schema plus
|
||
// any missing required keys, and to run any custom functions for other keys.
|
||
var keysToCheck = _.union(presentKeys, ss.objectKeys(affectedKeyGeneric));
|
||
|
||
// If this object is within an array, make sure we check for
|
||
// required as if it's not a modifier
|
||
isInArrayItemObject = (affectedKeyGeneric && affectedKeyGeneric.slice(-2) === ".$");
|
||
|
||
// Check all keys in the merged list
|
||
_.each(keysToCheck, function(key) {
|
||
checkObj(val[key], Utility.appendAffectedKey(affectedKey, key), operator, setKeys, isInArrayItemObject, true);
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
function checkModifier(mod) {
|
||
// Check for empty modifier
|
||
if (_.isEmpty(mod)) {
|
||
throw new Error("When the modifier option is true, validation object must have at least one operator");
|
||
}
|
||
|
||
// Get a list of all keys in $set and $setOnInsert combined, for use later
|
||
var setKeys = _.keys(mod.$set || {}).concat(_.keys(mod.$setOnInsert || {}));
|
||
|
||
// If this is an upsert, add all the $setOnInsert keys to $set;
|
||
// since we don't know whether it will be an insert or update, we'll
|
||
// validate upserts as if they will be an insert.
|
||
if ("$setOnInsert" in mod) {
|
||
if (isUpsert) {
|
||
mod.$set = mod.$set || {};
|
||
mod.$set = _.extend(mod.$set, mod.$setOnInsert);
|
||
}
|
||
delete mod.$setOnInsert;
|
||
}
|
||
|
||
// Loop through operators
|
||
_.each(mod, function (opObj, op) {
|
||
// If non-operators are mixed in, throw error
|
||
if (op.slice(0, 1) !== "$") {
|
||
throw new Error("When the modifier option is true, all validation object keys must be operators. Did you forget `$set`?");
|
||
}
|
||
if (Utility.shouldCheck(op)) {
|
||
// For an upsert, missing props would not be set if an insert is performed,
|
||
// so we add null keys to the modifier to force any "required" checks to fail
|
||
if (isUpsert && op === "$set") {
|
||
var presentKeys = _.keys(opObj);
|
||
_.each(ss.objectKeys(), function (schemaKey) {
|
||
if (!_.contains(presentKeys, schemaKey)) {
|
||
checkObj(void 0, schemaKey, op, setKeys);
|
||
}
|
||
});
|
||
}
|
||
_.each(opObj, function (v, k) {
|
||
if (op === "$push" || op === "$addToSet") {
|
||
if (Utility.isBasicObject(v) && "$each" in v) {
|
||
v = v.$each;
|
||
} else {
|
||
k = k + ".0";
|
||
}
|
||
}
|
||
checkObj(v, k, op, setKeys);
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// Kick off the validation
|
||
if (isModifier) {
|
||
checkModifier(obj);
|
||
} else {
|
||
checkObj(obj);
|
||
}
|
||
|
||
// Make sure there is only one error per fieldName
|
||
var addedFieldNames = [];
|
||
invalidKeys = _.filter(invalidKeys, function(errObj) {
|
||
if (!_.contains(addedFieldNames, errObj.name)) {
|
||
addedFieldNames.push(errObj.name);
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
|
||
return invalidKeys;
|
||
};
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
}).call(this);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
(function(){
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// packages/aldeed_simple-schema/simple-schema-validation-new.js //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
/* global Utility */
|
||
/* global _ */
|
||
/* global SimpleSchema */
|
||
/* global MongoObject */
|
||
/* global Meteor */
|
||
/* global Random */
|
||
/* global doValidation2:true */
|
||
|
||
function doTypeChecks(def, keyValue, op) {
|
||
var expectedType = def.type;
|
||
|
||
// String checks
|
||
if (expectedType === String) {
|
||
if (typeof keyValue !== "string") {
|
||
return "expectedString";
|
||
} else if (def.max !== null && def.max < keyValue.length) {
|
||
return "maxString";
|
||
} else if (def.min !== null && def.min > keyValue.length) {
|
||
return "minString";
|
||
} else if (def.regEx instanceof RegExp && !def.regEx.test(keyValue)) {
|
||
return "regEx";
|
||
} else if (_.isArray(def.regEx)) {
|
||
var regExError;
|
||
_.every(def.regEx, function(re, i) {
|
||
if (!re.test(keyValue)) {
|
||
regExError = "regEx." + i;
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
if (regExError) {
|
||
return regExError;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Number checks
|
||
else if (expectedType === Number) {
|
||
if (typeof keyValue !== "number" || isNaN(keyValue)) {
|
||
return "expectedNumber";
|
||
} else if (op !== "$inc" && def.max !== null && (!!def.exclusiveMax ? def.max <= keyValue : def.max < keyValue)) {
|
||
return !!def.exclusiveMax ? "maxNumberExclusive" : "maxNumber";
|
||
} else if (op !== "$inc" && def.min !== null && (!!def.exclusiveMin ? def.min >= keyValue : def.min > keyValue)) {
|
||
return !!def.exclusiveMin ? "minNumberExclusive" : "minNumber";
|
||
} else if (!def.decimal && keyValue.toString().indexOf(".") > -1) {
|
||
return "noDecimal";
|
||
}
|
||
}
|
||
|
||
// Boolean checks
|
||
else if (expectedType === Boolean) {
|
||
if (typeof keyValue !== "boolean") {
|
||
return "expectedBoolean";
|
||
}
|
||
}
|
||
|
||
// Object checks
|
||
else if (expectedType === Object) {
|
||
if (!Utility.isBasicObject(keyValue)) {
|
||
return "expectedObject";
|
||
}
|
||
}
|
||
|
||
// Array checks
|
||
else if (expectedType === Array) {
|
||
if (!_.isArray(keyValue)) {
|
||
return "expectedArray";
|
||
} else if (def.minCount !== null && keyValue.length < def.minCount) {
|
||
return "minCount";
|
||
} else if (def.maxCount !== null && keyValue.length > def.maxCount) {
|
||
return "maxCount";
|
||
}
|
||
}
|
||
|
||
// Constructor function checks
|
||
else if (expectedType instanceof Function || Utility.safariBugFix(expectedType)) {
|
||
|
||
// Generic constructor checks
|
||
if (!(keyValue instanceof expectedType)) {
|
||
return "expectedConstructor";
|
||
}
|
||
|
||
// Date checks
|
||
else if (expectedType === Date) {
|
||
if (isNaN(keyValue.getTime())) {
|
||
return "badDate";
|
||
}
|
||
|
||
if (_.isDate(def.min) && def.min.getTime() > keyValue.getTime()) {
|
||
return "minDate";
|
||
} else if (_.isDate(def.max) && def.max.getTime() < keyValue.getTime()) {
|
||
return "maxDate";
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
doValidation2 = function doValidation2(obj, isModifier, isUpsert, keyToValidate, ss, extendedCustomContext) {
|
||
|
||
// First do some basic checks of the object, and throw errors if necessary
|
||
if (!_.isObject(obj)) {
|
||
throw new Error("The first argument of validate() or validateOne() must be an object");
|
||
}
|
||
|
||
if (isModifier) {
|
||
if (_.isEmpty(obj)) {
|
||
throw new Error("When the modifier option is true, validation object must have at least one operator");
|
||
} else {
|
||
var allKeysAreOperators = _.every(obj, function(v, k) {
|
||
return (k.substring(0, 1) === "$");
|
||
});
|
||
if (!allKeysAreOperators) {
|
||
throw new Error("When the modifier option is true, all validation object keys must be operators");
|
||
}
|
||
|
||
// We use a LocalCollection to figure out what the resulting doc
|
||
// would be in a worst case scenario. Then we validate that doc
|
||
// so that we don't have to validate the modifier object directly.
|
||
obj = convertModifierToDoc(obj, ss.schema(), isUpsert);
|
||
}
|
||
} else if (Utility.looksLikeModifier(obj)) {
|
||
throw new Error("When the validation object contains mongo operators, you must set the modifier option to true");
|
||
}
|
||
|
||
var invalidKeys = [];
|
||
var mDoc; // for caching the MongoObject if necessary
|
||
|
||
// Validation function called for each affected key
|
||
function validate(val, affectedKey, affectedKeyGeneric, def, op, skipRequiredCheck, strictRequiredCheck) {
|
||
|
||
// Get the schema for this key, marking invalid if there isn't one.
|
||
if (!def) {
|
||
invalidKeys.push(Utility.errorObject("keyNotInSchema", affectedKey, val, def, ss));
|
||
return;
|
||
}
|
||
|
||
// Check for missing required values. The general logic is this:
|
||
// * If the operator is $unset or $rename, it's invalid.
|
||
// * If the value is null, it's invalid.
|
||
// * If the value is undefined and one of the following are true, it's invalid:
|
||
// * We're validating a key of a sub-object.
|
||
// * We're validating a key of an object that is an array item.
|
||
// * We're validating a document (as opposed to a modifier).
|
||
// * We're validating a key under the $set operator in a modifier, and it's an upsert.
|
||
if (!skipRequiredCheck && !def.optional) {
|
||
if (val === null || val === void 0) {
|
||
invalidKeys.push(Utility.errorObject("required", affectedKey, null, def, ss));
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Value checks are not necessary for null or undefined values
|
||
if (Utility.isNotNullOrUndefined(val)) {
|
||
|
||
// Check that value is of the correct type
|
||
var typeError = doTypeChecks(def, val, op);
|
||
if (typeError) {
|
||
invalidKeys.push(Utility.errorObject(typeError, affectedKey, val, def, ss));
|
||
return;
|
||
}
|
||
|
||
// Check value against allowedValues array
|
||
if (def.allowedValues && !_.contains(def.allowedValues, val)) {
|
||
invalidKeys.push(Utility.errorObject("notAllowed", affectedKey, val, def, ss));
|
||
return;
|
||
}
|
||
|
||
}
|
||
|
||
// Perform custom validation
|
||
var lastDot = affectedKey.lastIndexOf('.');
|
||
var fieldParentName = lastDot === -1 ? '' : affectedKey.slice(0, lastDot + 1);
|
||
var validators = def.custom ? [def.custom] : [];
|
||
validators = validators.concat(ss._validators).concat(SimpleSchema._validators);
|
||
_.every(validators, function(validator) {
|
||
var errorType = validator.call(_.extend({
|
||
key: affectedKey,
|
||
genericKey: affectedKeyGeneric,
|
||
definition: def,
|
||
isSet: (val !== void 0),
|
||
value: val,
|
||
operator: op,
|
||
field: function(fName) {
|
||
mDoc = mDoc || new MongoObject(obj, ss._blackboxKeys); //create if necessary, cache for speed
|
||
var keyInfo = mDoc.getInfoForKey(fName) || {};
|
||
return {
|
||
isSet: (keyInfo.value !== void 0),
|
||
value: keyInfo.value,
|
||
operator: keyInfo.operator
|
||
};
|
||
},
|
||
siblingField: function(fName) {
|
||
mDoc = mDoc || new MongoObject(obj, ss._blackboxKeys); //create if necessary, cache for speed
|
||
var keyInfo = mDoc.getInfoForKey(fieldParentName + fName) || {};
|
||
return {
|
||
isSet: (keyInfo.value !== void 0),
|
||
value: keyInfo.value,
|
||
operator: keyInfo.operator
|
||
};
|
||
}
|
||
}, extendedCustomContext || {}));
|
||
if (typeof errorType === "string") {
|
||
invalidKeys.push(Utility.errorObject(errorType, affectedKey, val, def, ss));
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
}
|
||
|
||
// The recursive function
|
||
function checkObj(val, affectedKey, skipRequiredCheck, strictRequiredCheck) {
|
||
var affectedKeyGeneric, def;
|
||
|
||
if (affectedKey) {
|
||
|
||
// When we hit a blackbox key, we don't progress any further
|
||
if (ss.keyIsInBlackBox(affectedKey)) {
|
||
return;
|
||
}
|
||
|
||
// Make a generic version of the affected key, and use that
|
||
// to get the schema for this key.
|
||
affectedKeyGeneric = SimpleSchema._makeGeneric(affectedKey);
|
||
def = ss.getDefinition(affectedKey);
|
||
|
||
// Perform validation for this key
|
||
if (!keyToValidate || keyToValidate === affectedKey || keyToValidate === affectedKeyGeneric) {
|
||
validate(val, affectedKey, affectedKeyGeneric, def, null, skipRequiredCheck, strictRequiredCheck);
|
||
}
|
||
}
|
||
|
||
// Temporarily convert missing objects to empty objects
|
||
// so that the looping code will be called and required
|
||
// descendent keys can be validated.
|
||
if ((val === void 0 || val === null) && (!def || (def.type === Object && !def.optional))) {
|
||
val = {};
|
||
}
|
||
|
||
// Loop through arrays
|
||
if (_.isArray(val)) {
|
||
_.each(val, function(v, i) {
|
||
checkObj(v, affectedKey + '.' + i);
|
||
});
|
||
}
|
||
|
||
// Loop through object keys
|
||
else if (Utility.isBasicObject(val) && (!def || !def.blackbox)) {
|
||
|
||
// Get list of present keys
|
||
var presentKeys = _.keys(val);
|
||
|
||
// Check all present keys plus all keys defined by the schema.
|
||
// This allows us to detect extra keys not allowed by the schema plus
|
||
// any missing required keys, and to run any custom functions for other keys.
|
||
var keysToCheck = _.union(presentKeys, ss._schemaKeys);
|
||
|
||
// If this object is within an array, make sure we check for
|
||
// required as if it's not a modifier
|
||
strictRequiredCheck = (affectedKeyGeneric && affectedKeyGeneric.slice(-2) === ".$");
|
||
|
||
// Check all keys in the merged list
|
||
_.each(keysToCheck, function(key) {
|
||
if (Utility.shouldCheck(key)) {
|
||
checkObj(val[key], Utility.appendAffectedKey(affectedKey, key), skipRequiredCheck, strictRequiredCheck);
|
||
}
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
// Kick off the validation
|
||
checkObj(obj);
|
||
|
||
// Make sure there is only one error per fieldName
|
||
var addedFieldNames = [];
|
||
invalidKeys = _.filter(invalidKeys, function(errObj) {
|
||
if (!_.contains(addedFieldNames, errObj.name)) {
|
||
addedFieldNames.push(errObj.name);
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
|
||
return invalidKeys;
|
||
};
|
||
|
||
function convertModifierToDoc(mod, schema, isUpsert) {
|
||
// Create unmanaged LocalCollection as scratchpad
|
||
var t = new Meteor.Collection(null);
|
||
|
||
// LocalCollections are in memory, and it seems
|
||
// that it's fine to use them synchronously on
|
||
// either client or server
|
||
var id;
|
||
if (isUpsert) {
|
||
// We assume upserts will be inserts (conservative
|
||
// validation of requiredness)
|
||
id = Random.id();
|
||
t.upsert({_id: id}, mod);
|
||
} else {
|
||
var mDoc = new MongoObject(mod);
|
||
// Create a ficticious existing document
|
||
var fakeDoc = new MongoObject({});
|
||
_.each(schema, function (def, fieldName) {
|
||
var setVal;
|
||
// Prefill doc with empty arrays to avoid the
|
||
// mongodb issue where it does not understand
|
||
// that numeric pieces should create arrays.
|
||
if (def.type === Array && mDoc.affectsGenericKey(fieldName)) {
|
||
setVal = [];
|
||
}
|
||
// Set dummy values for required fields because
|
||
// we assume any existing data would be valid.
|
||
else if (!def.optional) {
|
||
// TODO correct value type based on schema type
|
||
if (def.type === Boolean) {
|
||
setVal = true;
|
||
} else if (def.type === Number) {
|
||
setVal = def.min || 0;
|
||
} else if (def.type === Date) {
|
||
setVal = def.min || new Date();
|
||
} else if (def.type === Array) {
|
||
setVal = [];
|
||
} else if (def.type === Object) {
|
||
setVal = {};
|
||
} else {
|
||
setVal = "0";
|
||
}
|
||
}
|
||
|
||
if (setVal !== void 0) {
|
||
var key = fieldName.replace(/\.\$/g, ".0");
|
||
var pos = MongoObject._keyToPosition(key, false);
|
||
fakeDoc.setValueForPosition(pos, setVal);
|
||
}
|
||
});
|
||
fakeDoc = fakeDoc.getObject();
|
||
// Insert fake doc into local scratch collection
|
||
id = t.insert(fakeDoc);
|
||
// Now update it with the modifier
|
||
t.update(id, mod);
|
||
}
|
||
|
||
var doc = t.findOne(id);
|
||
// We're done with it
|
||
t.remove(id);
|
||
// Currently we don't validate _id unless it is
|
||
// explicitly added to the schema
|
||
if (!schema._id) {
|
||
delete doc._id;
|
||
}
|
||
return doc;
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
}).call(this);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
(function(){
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// packages/aldeed_simple-schema/simple-schema-context.js //
|
||
// //
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
//
|
||
/* global SimpleSchema */
|
||
/* global SimpleSchemaValidationContext:true */
|
||
/* global doValidation1 */
|
||
/* global doValidation2 */
|
||
|
||
function doValidation(obj, isModifier, isUpsert, keyToValidate, ss, extendedCustomContext) {
|
||
var useOld = true; //for now this can be manually changed to try the experimental method, which doesn't yet work properly
|
||
var func = useOld ? doValidation1 : doValidation2;
|
||
return func(obj, isModifier, isUpsert, keyToValidate, ss, extendedCustomContext);
|
||
}
|
||
|
||
/*
|
||
* PUBLIC API
|
||
*/
|
||
|
||
SimpleSchemaValidationContext = function SimpleSchemaValidationContext(ss) {
|
||
var self = this;
|
||
self._simpleSchema = ss;
|
||
self._schema = ss.schema();
|
||
self._schemaKeys = _.keys(self._schema);
|
||
self._invalidKeys = [];
|
||
//set up validation dependencies
|
||
self._deps = {};
|
||
self._depsAny = new Tracker.Dependency();
|
||
_.each(self._schemaKeys, function(name) {
|
||
self._deps[name] = new Tracker.Dependency();
|
||
});
|
||
};
|
||
|
||
//validates the object against the simple schema and sets a reactive array of error objects
|
||
SimpleSchemaValidationContext.prototype.validate = function simpleSchemaValidationContextValidate(doc, options) {
|
||
var self = this;
|
||
options = _.extend({
|
||
modifier: false,
|
||
upsert: false,
|
||
extendedCustomContext: {}
|
||
}, options || {});
|
||
|
||
//on the client we can add the userId if not already in the custom context
|
||
if (Meteor.isClient && options.extendedCustomContext.userId === void 0) {
|
||
options.extendedCustomContext.userId = (Meteor.userId && Meteor.userId()) || null;
|
||
}
|
||
|
||
var invalidKeys = doValidation(doc, options.modifier, options.upsert, null, self._simpleSchema, options.extendedCustomContext);
|
||
|
||
//now update self._invalidKeys and dependencies
|
||
|
||
//note any currently invalid keys so that we can mark them as changed
|
||
//due to new validation (they may be valid now, or invalid in a different way)
|
||
var removedKeys = _.pluck(self._invalidKeys, "name");
|
||
|
||
//update
|
||
self._invalidKeys = invalidKeys;
|
||
|
||
//add newly invalid keys to changedKeys
|
||
var addedKeys = _.pluck(self._invalidKeys, "name");
|
||
|
||
//mark all changed keys as changed
|
||
var changedKeys = _.union(addedKeys, removedKeys);
|
||
self._markKeysChanged(changedKeys);
|
||
|
||
// Return true if it was valid; otherwise, return false
|
||
return self._invalidKeys.length === 0;
|
||
};
|
||
|
||
//validates doc against self._schema for one key and sets a reactive array of error objects
|
||
SimpleSchemaValidationContext.prototype.validateOne = function simpleSchemaValidationContextValidateOne(doc, keyName, options) {
|
||
var self = this, i, ln, k;
|
||
options = _.extend({
|
||
modifier: false,
|
||
upsert: false,
|
||
extendedCustomContext: {}
|
||
}, options || {});
|
||
|
||
//on the client we can add the userId if not already in the custom context
|
||
if (Meteor.isClient && options.extendedCustomContext.userId === void 0) {
|
||
options.extendedCustomContext.userId = (Meteor.userId && Meteor.userId()) || null;
|
||
}
|
||
|
||
var invalidKeys = doValidation(doc, options.modifier, options.upsert, keyName, self._simpleSchema, options.extendedCustomContext);
|
||
|
||
//now update self._invalidKeys and dependencies
|
||
|
||
//remove objects from self._invalidKeys where name = keyName
|
||
var newInvalidKeys = [];
|
||
for (i = 0, ln = self._invalidKeys.length; i < ln; i++) {
|
||
k = self._invalidKeys[i];
|
||
if (k.name !== keyName) {
|
||
newInvalidKeys.push(k);
|
||
}
|
||
}
|
||
self._invalidKeys = newInvalidKeys;
|
||
|
||
//merge invalidKeys into self._invalidKeys
|
||
for (i = 0, ln = invalidKeys.length; i < ln; i++) {
|
||
k = invalidKeys[i];
|
||
self._invalidKeys.push(k);
|
||
}
|
||
|
||
//mark key as changed due to new validation (they may be valid now, or invalid in a different way)
|
||
self._markKeysChanged([keyName]);
|
||
|
||
// Return true if it was valid; otherwise, return false
|
||
return !self._keyIsInvalid(keyName);
|
||
};
|
||
|
||
//reset the invalidKeys array
|
||
SimpleSchemaValidationContext.prototype.resetValidation = function simpleSchemaValidationContextResetValidation() {
|
||
var self = this;
|
||
var removedKeys = _.pluck(self._invalidKeys, "name");
|
||
self._invalidKeys = [];
|
||
self._markKeysChanged(removedKeys);
|
||
};
|
||
|
||
SimpleSchemaValidationContext.prototype.isValid = function simpleSchemaValidationContextIsValid() {
|
||
var self = this;
|
||
self._depsAny.depend();
|
||
return !self._invalidKeys.length;
|
||
};
|
||
|
||
SimpleSchemaValidationContext.prototype.invalidKeys = function simpleSchemaValidationContextInvalidKeys() {
|
||
var self = this;
|
||
self._depsAny.depend();
|
||
return self._invalidKeys;
|
||
};
|
||
|
||
SimpleSchemaValidationContext.prototype.addInvalidKeys = function simpleSchemaValidationContextAddInvalidKeys(errors) {
|
||
var self = this;
|
||
|
||
if (!errors || !errors.length) {
|
||
return;
|
||
}
|
||
|
||
var changedKeys = [];
|
||
_.each(errors, function (errorObject) {
|
||
changedKeys.push(errorObject.name);
|
||
self._invalidKeys.push(errorObject);
|
||
});
|
||
|
||
self._markKeysChanged(changedKeys);
|
||
};
|
||
|
||
SimpleSchemaValidationContext.prototype._markKeysChanged = function simpleSchemaValidationContextMarkKeysChanged(keys) {
|
||
var self = this;
|
||
|
||
if (!keys || !keys.length) {
|
||
return;
|
||
}
|
||
|
||
_.each(keys, function(name) {
|
||
var genericName = SimpleSchema._makeGeneric(name);
|
||
if (genericName in self._deps) {
|
||
self._deps[genericName].changed();
|
||
}
|
||
});
|
||
self._depsAny.changed();
|
||
};
|
||
|
||
SimpleSchemaValidationContext.prototype._getInvalidKeyObject = function simpleSchemaValidationContextGetInvalidKeyObject(name, genericName) {
|
||
var self = this;
|
||
genericName = genericName || SimpleSchema._makeGeneric(name);
|
||
|
||
var errorObj = _.findWhere(self._invalidKeys, {name: name});
|
||
if (!errorObj) {
|
||
errorObj = _.findWhere(self._invalidKeys, {name: genericName});
|
||
}
|
||
return errorObj;
|
||
};
|
||
|
||
SimpleSchemaValidationContext.prototype._keyIsInvalid = function simpleSchemaValidationContextKeyIsInvalid(name, genericName) {
|
||
return !!this._getInvalidKeyObject(name, genericName);
|
||
};
|
||
|
||
// Like the internal one, but with deps
|
||
SimpleSchemaValidationContext.prototype.keyIsInvalid = function simpleSchemaValidationContextKeyIsInvalid(name) {
|
||
var self = this, genericName = SimpleSchema._makeGeneric(name);
|
||
self._deps[genericName] && self._deps[genericName].depend();
|
||
|
||
return self._keyIsInvalid(name, genericName);
|
||
};
|
||
|
||
SimpleSchemaValidationContext.prototype.keyErrorMessage = function simpleSchemaValidationContextKeyErrorMessage(name) {
|
||
var self = this, genericName = SimpleSchema._makeGeneric(name);
|
||
self._deps[genericName] && self._deps[genericName].depend();
|
||
|
||
var errorObj = self._getInvalidKeyObject(name, genericName);
|
||
if (!errorObj) {
|
||
return "";
|
||
}
|
||
|
||
return self._simpleSchema.messageForError(errorObj.type, errorObj.name, null, errorObj.value);
|
||
};
|
||
|
||
SimpleSchemaValidationContext.prototype.getErrorObject = function simpleSchemaValidationContextGetErrorObject() {
|
||
var self = this, message, invalidKeys = this._invalidKeys;
|
||
if (invalidKeys.length) {
|
||
message = self.keyErrorMessage(invalidKeys[0].name);
|
||
// We add `message` prop to the invalidKeys.
|
||
invalidKeys = _.map(invalidKeys, function (o) {
|
||
return _.extend({message: self.keyErrorMessage(o.name)}, o);
|
||
});
|
||
} else {
|
||
message = "Failed validation";
|
||
}
|
||
var error = new Error(message);
|
||
error.invalidKeys = invalidKeys;
|
||
// If on the server, we add a sanitized error, too, in case we're
|
||
// called from a method.
|
||
if (Meteor.isServer) {
|
||
error.sanitizedError = new Meteor.Error(400, message);
|
||
}
|
||
return error;
|
||
};
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
}).call(this);
|
||
|
||
|
||
/* Exports */
|
||
Package._define("aldeed:simple-schema", {
|
||
SimpleSchema: SimpleSchema,
|
||
MongoObject: MongoObject,
|
||
humanize: humanize
|
||
});
|
||
|
||
})();
|