UNPKG

schema-object

Version:

Enforce schema on JavaScript objects, including type, transformation, and validation. Supports extends, sub-schemas, and arrays.

1,122 lines (903 loc) 54.5 kB
'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _extendableBuiltin(cls) { function ExtendableBuiltin() { var instance = Reflect.construct(cls, Array.from(arguments)); Object.setPrototypeOf(instance, Object.getPrototypeOf(this)); return instance; } ExtendableBuiltin.prototype = Object.create(cls.prototype, { constructor: { value: cls, enumerable: false, writable: true, configurable: true } }); if (Object.setPrototypeOf) { Object.setPrototypeOf(ExtendableBuiltin, cls); } else { ExtendableBuiltin.__proto__ = cls; } return ExtendableBuiltin; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } (function (_) { 'use strict'; var _isProxySupported = typeof Proxy !== 'undefined' && Proxy.toString().indexOf('proxies not supported on this platform') === -1; // Use require conditionally, otherwise assume global dependencies. if (typeof require !== 'undefined') { _ = require('lodash'); if (!global._babelPolyfill) { // Necessary to do this instead of runtime transformer for browser compatibility. require('babel-polyfill'); } // Patch the harmony-era (pre-ES6) Proxy object to be up-to-date with the ES6 spec. // Without the --harmony and --harmony_proxies flags, options strict: false and dotNotation: true will fail with exception. if (_isProxySupported === true) { require('harmony-reflect'); } } else { _ = window._; } // If reflection is being used, our traps will hide internal properties. // If reflection is not being used, Symbol will hide internal properties. var _privateKey = _isProxySupported === true ? '_private' : Symbol('_private'); // Reserved fields, map to internal property. var _reservedFields = ['super']; // Is a number (ignores type). function isNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // Used to get real index name. function getIndex(index) { if (this[_privateKey]._options.keysIgnoreCase && typeof index === 'string') { var indexLowerCase = index.toLowerCase(); for (var key in this[_privateKey]._schema) { if (typeof key === 'string' && key.toLowerCase() === indexLowerCase) { return key; } } } return index; } // Used to fetch current values. function getter(value, properties) { // Most calculations happen within the typecast and the value passed is typically the value we want to use. // Typically, the getter just returns the value. // Modifications to the value within the getter are not written to the object. // Getter can transform value after typecast. if (properties.getter) { value = properties.getter.call(this[_privateKey]._root, value); } return value; } // Used to write value to object. function writeValue(value, fieldSchema) { // onBeforeValueSet allows you to cancel the operation. // It doesn't work like transform and others that allow you to modify the value because all typecast has already happened. // For use-cases where you need to modify the value, you can set a new value in the handler and return false. if (this[_privateKey]._options.onBeforeValueSet) { if (this[_privateKey]._options.onBeforeValueSet.call(this, value, fieldSchema.name) === false) { return; } } // Alias simply copies the value without actually writing it to alias index. // Because the value isn't actually set on the alias index, onValueSet isn't fired. if (fieldSchema.type === 'alias') { this[fieldSchema.index] = value; return; } // Write the value to the inner object. this[_privateKey]._obj[fieldSchema.name] = value; // onValueSet notifies you after a value has been written. if (this[_privateKey]._options.onValueSet) { this[_privateKey]._options.onValueSet.call(this, value, fieldSchema.name); } } // Represents an error encountered when trying to set a value. var SetterError = function SetterError(errorMessage, setValue, originalValue, fieldSchema) { _classCallCheck(this, SetterError); this.errorMessage = errorMessage; this.setValue = setValue; this.originalValue = originalValue; this.fieldSchema = fieldSchema; }; // Returns typecasted value if possible. If rejected, originalValue is returned. function typecast(value, originalValue, properties) { var options = this[_privateKey]._options; // Allow transform to manipulate raw properties. if (properties.transform) { value = properties.transform.call(this[_privateKey]._root, value, originalValue, properties); } // Allow null to be preserved. if (value === null && options.preserveNull) { return null; } // Property types are always normalized as lowercase strings despite shorthand definitions being available. switch (properties.type) { case 'string': // Reject if object or array. if (_.isObject(value) || _.isArray(value)) { throw new SetterError('String type cannot typecast Object or Array types.', value, originalValue, properties); } // If index is being set with null or undefined, set value and end. if (value === undefined || value === null) { return undefined; } // Typecast to String. value = value + ''; // If stringTransform function is defined, use. // This is used before we do validation checks (except to be sure we have a string at all). if (properties.stringTransform) { value = properties.stringTransform.call(this[_privateKey]._root, value, originalValue, properties); } // If clip property & maxLength properties are set, the string should be clipped. // This is basically a shortcut property that could be done with stringTransform. if (properties.clip !== undefined && properties.maxLength !== undefined) { value = value.substr(0, properties.maxLength); } // If enum is being used, be sure the value is within definition. if (_.isArray(properties.enum) && properties.enum.indexOf(value) === -1) { throw new SetterError('String does not exist in enum list.', value, originalValue, properties); } // If minLength is defined, check to be sure the string is > minLength. if (properties.minLength !== undefined && value.length < properties.minLength) { throw new SetterError('String length too short to meet minLength requirement.', value, originalValue, properties); } // If maxLength is defined, check to be sure the string is < maxLength. if (properties.maxLength !== undefined && value.length > properties.maxLength) { throw new SetterError('String length too long to meet maxLength requirement.', value, originalValue, properties); } // If regex is defined, check to be sure the string matches the regex pattern. if (properties.regex && !properties.regex.test(value)) { throw new SetterError('String does not match regular expression pattern.', value, originalValue, properties); } return value; case 'number': // If index is being set with null, undefined, or empty string: clear value. if (value === undefined || value === null || value === '') { return undefined; } // Set values for boolean. if (_.isBoolean(value)) { value = value ? 1 : 0; } // Remove comma from strings. if (typeof value === 'string') { value = value.replace(/,/g, ''); } // Reject if array, object, or not numeric. if (_.isArray(value) || _.isObject(value) || !isNumeric(value)) { throw new SetterError('Number type cannot typecast Array or Object types.', value, originalValue, properties); } // Typecast to number. value = value * 1; // Transformation after typecasting but before validation and filters. if (properties.numberTransform) { value = properties.numberTransform.call(this[_privateKey]._root, value, originalValue, properties); } if (properties.min !== undefined && value < properties.min) { throw new SetterError('Number is too small to meet min requirement.', value, originalValue, properties); } if (properties.max !== undefined && value > properties.max) { throw new SetterError('Number is too big to meet max requirement.', value, originalValue, properties); } return value; case 'boolean': // If index is being set with null, undefined, or empty string: clear value. if (value === undefined || value === null || value === '') { return undefined; } // If is String and is 'false', convert to Boolean. if (value === 'false') { return false; } // If is Number, <0 is true and >0 is false. if (isNumeric(value)) { return value * 1 > 0 ? true : false; } // Use Javascript to eval and return boolean. value = value ? true : false; // Transformation after typecasting but before validation and filters. if (properties.booleanTransform) { value = properties.booleanTransform.call(this[_privateKey]._root, value, originalValue, properties); } return value; case 'array': // If it's an object, typecast to an array and return array. if (_.isObject(value)) { value = _.toArray(value); } // Reject if not array. if (!_.isArray(value)) { throw new SetterError('Array type cannot typecast non-Array types.', value, originalValue, properties); } // Arrays are never set directly. // Instead, the values are copied over to the existing SchemaArray instance. // The SchemaArray is initialized immediately and will always exist. originalValue.length = 0; _.each(value, function (arrayValue) { originalValue.push(arrayValue); }); return originalValue; case 'object': // If it's not an Object, reject. if (!_.isObject(value)) { throw new SetterError('Object type cannot typecast non-Object types.', value, originalValue, properties); } // If object is schema object and an entirely new object was passed, clear values and set. // This preserves the object instance. if (properties.objectType) { // The object will usually exist because it's initialized immediately for deep access within SchemaObjects. // However, in the case of Array elements, it will not exist. var schemaObject = void 0; if (originalValue !== undefined) { // Clear existing values. schemaObject = originalValue; schemaObject.clear(); } else { // The SchemaObject doesn't exist yet. Let's initialize a new one. // This is used for Array types. schemaObject = new properties.objectType({}, this[_privateKey]._root); } // Copy value to SchemaObject and set value to SchemaObject. for (var key in value) { schemaObject[key] = value[key]; } value = schemaObject; } // Otherwise, it's OK. return value; case 'date': // If index is being set with null, undefined, or empty string: clear value. if (value === undefined || value === null || value === '') { return undefined; } // Reject if object, array or boolean. if (!_.isDate(value) && !_.isString(value) && !_.isNumber(value)) { throw new SetterError('Date type cannot typecast Array or Object types.', value, originalValue, properties); } // Attempt to parse string value with Date.parse (which returns number of milliseconds). if (_.isString(value)) { value = Date.parse(value); } // If is timestamp, convert to Date. if (isNumeric(value)) { value = new Date((value + '').length > 10 ? value : value * 1000); } // If the date couldn't be parsed, do not modify index. if (value == 'Invalid Date' || !_.isDate(value)) { throw new SetterError('Could not parse date.', value, originalValue, properties); } // Transformation after typecasting but before validation and filters. if (properties.dateTransform) { value = properties.dateTransform.call(this[_privateKey]._root, value, originalValue, properties); } return value; default: // 'any' return value; } } // Properties can be passed in multiple forms (an object, just a type, etc). // Normalize to a standard format. function normalizeProperties(properties, name) { // Allow for shorthand type declaration: // Check to see if the user passed in a raw type of a properties hash. if (properties) { // Raw type passed. // index: Type is translated to index: {type: Type} // Properties hash created. if (properties.type === undefined) { properties = { type: properties }; // Properties hash passed. // Copy properties hash before modifying. // Users can pass in their own custom types to the schema and we don't want to write to that object. // Especially since properties.name contains the index of our field and copying that will break functionality. } else { properties = _.cloneDeep(properties); } } // Type may be an object with properties. // If "type.type" exists, we'll assume it's meant to be properties. // This means that shorthand objects can't use the "type" index. // If "type" is necessary, they must be wrapped in a SchemaObject. if (_.isObject(properties.type) && properties.type.type !== undefined) { _.each(properties.type, function (value, key) { if (properties[key] === undefined) { properties[key] = value; } }); properties.type = properties.type.type; } // Null or undefined should be flexible and allow any value. if (properties.type === null || properties.type === undefined) { properties.type = 'any'; // Convert object representation of type to lowercase string. // String is converted to 'string', Number to 'number', etc. // Do not convert the initialized SchemaObjectInstance to a string! // Check for a shorthand declaration of schema by key length. } else if (_.isString(properties.type.name) && properties.type.name !== 'SchemaObjectInstance' && Object.keys(properties.type).length === 0) { properties.type = properties.type.name; } if (_.isString(properties.type)) { properties.type = properties.type.toLowerCase(); } // index: [Type] or index: [] is translated to index: {type: Array, arrayType: Type} if (_.isArray(properties.type)) { if (_.size(properties.type)) { // Properties will be normalized when array is initialized. properties.arrayType = properties.type[0]; } properties.type = 'array'; } // index: {} or index: SchemaObject is translated to index: {type: Object, objectType: Type} if (!_.isString(properties.type)) { if (_.isFunction(properties.type)) { properties.objectType = properties.type; properties.type = 'object'; } else if (_.isObject(properties.type)) { // When an empty object is passed, no schema is enforced. if (_.size(properties.type)) { // Options should be inherited by sub-SchemaObjects, except toObject. var options = _.clone(this[_privateKey]._options); delete options.toObject; // When we're creating a nested schema automatically, it should always inherit the root "this". options.inheritRootThis = true; // Initialize the SchemaObject sub-schema automatically. properties.objectType = new SchemaObject(properties.type, options); } // Regardless of if we created a sub-schema or not, the field is indexed as an object. properties.type = 'object'; } } // Set name if passed on properties. // It's used to show what field an error what generated on. if (name) { properties.name = name; } return properties; } // Add field to schema and initializes getter and setter for the field. function addToSchema(index, properties) { this[_privateKey]._schema[index] = normalizeProperties.call(this, properties, index); defineGetter.call(this[_privateKey]._getset, index, this[_privateKey]._schema[index]); defineSetter.call(this[_privateKey]._getset, index, this[_privateKey]._schema[index]); } // Defines getter for specific field. function defineGetter(index, properties) { var _this = this; // If the field type is an alias, we retrieve the value through the alias's index. var indexOrAliasIndex = properties.type === 'alias' ? properties.index : index; this.__defineGetter__(index, function () { // If accessing object or array, lazy initialize if not set. if (!_this[_privateKey]._obj[indexOrAliasIndex] && (properties.type === 'object' || properties.type === 'array')) { // Initialize object. if (properties.type === 'object') { if (properties.default !== undefined) { writeValue.call(_this[_privateKey]._this, _.isFunction(properties.default) ? properties.default.call(_this) : properties.default, properties); } else { writeValue.call(_this[_privateKey]._this, properties.objectType ? new properties.objectType({}, _this[_privateKey]._root) : {}, properties); } // Native arrays are not used so that Array class can be extended with custom behaviors. } else if (properties.type === 'array') { writeValue.call(_this[_privateKey]._this, new SchemaArray(_this, properties), properties); } } try { return getter.call(_this, _this[_privateKey]._obj[indexOrAliasIndex], properties); } catch (error) { // This typically happens when the default value isn't valid -- log error. _this[_privateKey]._errors.push(error); } }); } // Defines setter for specific field. function defineSetter(index, properties) { var _this2 = this; this.__defineSetter__(index, function (value) { // Don't proceed if readOnly is true. if (properties.readOnly) { return; } try { // this[_privateKey]._this[index] is used instead of this[_privateKey]._obj[index] to route through the public interface. writeValue.call(_this2[_privateKey]._this, typecast.call(_this2, value, _this2[_privateKey]._this[index], properties), properties); } catch (error) { // Setter failed to validate value -- log error. _this2[_privateKey]._errors.push(error); } }); } // Reset field to default value. function clearField(index, properties) { // Aliased fields reflect values on other fields and do not need to be cleared. if (properties.isAlias === true) { return; } // In case of object & array, they must be initialized immediately. if (properties.type === 'object') { this[properties.name].clear(); // Native arrays are never used so that toArray can be globally supported. // Additionally, other properties such as unique rely on passing through SchemaObject. } else if (properties.type === 'array') { this[properties.name].length = 0; // Other field types can simply have their value set to undefined. } else { writeValue.call(this[_privateKey]._this, undefined, properties); } } // Represents a basic array with typecasted values. var SchemaArray = function (_extendableBuiltin2) { _inherits(SchemaArray, _extendableBuiltin2); function SchemaArray(self, properties) { _classCallCheck(this, SchemaArray); // Store all internals. var _this3 = _possibleConstructorReturn(this, (SchemaArray.__proto__ || Object.getPrototypeOf(SchemaArray)).call(this)); var _private = _this3[_privateKey] = {}; // Store reference to self. _private._self = self; // Store properties (arrayType, unique, etc). _private._properties = properties; // Normalize our own properties. if (properties.arrayType) { properties.arrayType = normalizeProperties.call(self, properties.arrayType); } return _this3; } _createClass(SchemaArray, [{ key: 'push', value: function push() { var _this4 = this; // Values are passed through the typecast before being allowed onto the array if arrayType is set. // In the case of rejection, the typecast returns undefined, which is not appended to the array. var values = void 0; for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } if (this[_privateKey]._properties.arrayType) { values = [].map.call(args, function (value) { return typecast.call(_this4[_privateKey]._self, value, undefined, _this4[_privateKey]._properties.arrayType); }, this); } else { values = args; } // Enforce filter. if (this[_privateKey]._properties.filter) { values = _.filter(values, function (value) { return _this4[_privateKey]._properties.filter.call(_this4, value); }); } // Enforce uniqueness. if (this[_privateKey]._properties.unique) { values = _.difference(values, _.toArray(this)); } return Array.prototype.push.apply(this, values); } }, { key: 'concat', value: function concat() { // Return new instance of SchemaArray. var schemaArray = new SchemaArray(this[_privateKey]._self, this[_privateKey]._properties); // Create primitive array with all elements. var array = this.toArray(); for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } for (var i in args) { if (args[i].toArray) { args[i] = args[i].toArray(); } array = array.concat(args[i]); } // Push each value in individually to typecast. for (var _i in array) { schemaArray.push(array[_i]); } return schemaArray; } }, { key: 'toArray', value: function toArray() { // Create new Array to hold elements. var array = []; // Loop through each element, clone if necessary. _.each(this, function (element) { // Call toObject() method if defined (this allows us to return primitive objects instead of SchemaObjects). if (_.isObject(element) && _.isFunction(element.toObject)) { element = element.toObject(); // If is non-SchemaType object, shallow clone so that properties modification don't have an affect on the original object. } else if (_.isObject(element)) { element = _.clone(element); } array.push(element); }); return array; } }, { key: 'toJSON', value: function toJSON() { return this.toArray(); } // Used to detect instance of SchemaArray internally. }, { key: '_isSchemaArray', value: function _isSchemaArray() { return true; } }]); return SchemaArray; }(_extendableBuiltin(Array)); // Represents an object FACTORY with typed indexes. var SchemaObject = function SchemaObject(schema) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, SchemaObject); // Create object for options if doesn't exist and merge with defaults. options = _.extend({ // By default, allow only values in the schema to be set. // When this is false, setting new fields will dynamically add the field to the schema as type "any". strict: true, // Allow fields to be set via dotNotation; obj['user.name'] = 'Scott'; -> obj: { user: 'Scott' } dotNotation: false, // Do not set undefined values to keys within toObject(). // This is the default because MongoDB will convert undefined to null and overwrite existing values. // If this is true, toObject() will output undefined for unset primitives and empty arrays/objects for those types. // If this is false, toObject() will not output any keys for unset primitives, arrays, and objects. setUndefined: false, // If this is set to true, null will NOT be converted to undefined automatically. // In many cases, when people use null, they actually want to unset a value. // There are rare cases where preserving the null is important. // Set to true if you are one of those rare cases. preserveNull: false, // Allow "profileURL" to be set with "profileUrl" when set to false keysIgnoreCase: false, // Inherit root object "this" context from parent SchemaObject. inheritRootThis: false }, options); // Some of the options require reflection. if (_isProxySupported === false) { if (!options.strict) { throw new Error('[schema-object] Turning strict mode off requires --harmony flag.'); } if (options.dotNotation) { throw new Error('[schema-object] Dot notation support requires --harmony flag.'); } if (options.keysIgnoreCase) { throw new Error('[schema-object] Keys ignore case support requires --harmony flag.'); } } // Used at minimum to hold default constructor. if (!options.constructors) { options.constructors = {}; } // Default constructor can be overridden. if (!options.constructors.default) { // By default, populate runtime values as provided to this instance of object. options.constructors.default = function (values) { this.populate(values); }; } // Create SchemaObject factory. var SO = SchemaObjectInstanceFactory(schema, options); // Add custom constructors. _.each(options.constructors, function (method, key) { SO[key] = function () { // Initialize new SO. var obj = new SO(); // Expose default constructor to populate defaults. obj[_privateKey]._reservedFields.super = function () { options.constructors.default.apply(obj, arguments); }; // Call custom constructor. method.apply(obj, arguments);; // Cleanup and return SO. delete obj[_privateKey]._reservedFields.super; return obj; }; }); return SO; }; // Represents an object INSTANCE factory with typed indexes. function SchemaObjectInstanceFactory(schema, options) { // Represents an actual instance of an object. var SchemaObjectInstance = function () { _createClass(SchemaObjectInstance, null, [{ key: 'extend', // Extend instance factory. value: function extend(extendSchema) { var extendOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // Extend requires reflection. if (_isProxySupported === false) { throw new Error('[schema-object] Extending object requires --harmony flag.'); } // Merge schema and options together. var mergedSchema = _.merge({}, schema, extendSchema); var mergedOptions = _.merge({}, options, extendOptions); // Allow method and constructor to call `this.super()`. var methodHomes = ['methods', 'constructors']; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { var _loop = function _loop() { var methodHome = _step.value; // Ensure object containing methods exists on both provided and original options. if (_.size(options[methodHome]) && _.size(extendOptions[methodHome])) { // Loop through each method in the original options. // It's not necessary to bind `this.super()` for options that didn't already exist. _.each(options[methodHome], function (method, name) { // The original option may exist, but was it extended? if (extendOptions[methodHome][name]) { // Extend method by creating a binding that takes the `this` context given and adds `self`. // `self` is a reference to the original method, also bound to the correct `this`. mergedOptions[methodHome][name] = function () { var _this5 = this, _arguments = arguments; this[_privateKey]._reservedFields.super = function () { return method.apply(_this5, _arguments); }; var ret = extendOptions[methodHome][name].apply(this, arguments); delete this[_privateKey]._reservedFields.super; return ret; }; } }); } }; for (var _iterator = methodHomes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { _loop(); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return new SchemaObject(mergedSchema, mergedOptions); } // Construct new instance pre-populated with values. }]); function SchemaObjectInstance(values, _root) { var _this6 = this; _classCallCheck(this, SchemaObjectInstance); // Object used to store internals. var _private = this[_privateKey] = {}; // _private._root = options.inheritRootThis ? _root || this : this; // Object with getters and setters bound. _private._getset = this; // Public version of ourselves. // Overwritten with proxy if available. _private._this = this; // Object used to store raw values. var obj = _private._obj = {}; // Schema as defined by constructor. _private._schema = schema; // Errors, retrieved with getErrors(). _private._errors = []; // Options need to be accessible. Shared across ALL INSTANCES. _private._options = options; // Reserved keys for storing internal properties accessible from outside. _private._reservedFields = {}; // Normalize schema properties to allow for shorthand declarations. _.each(schema, function (properties, index) { schema[index] = normalizeProperties.call(_this6, properties, index); }); // Define getters/typecasts based off of schema. _.each(schema, function (properties, index) { // Use getter / typecast to intercept and re-route, transform, etc. defineGetter.call(_private._getset, index, properties); defineSetter.call(_private._getset, index, properties); }); // Proxy used as interface to object allows to intercept all access. // Without Proxy we must register individual getter/typecasts to put any logic in place. // With Proxy, we still use the individual getter/typecasts, but also catch values that aren't in the schema. if (_isProxySupported === true) { (function () { var proxy = _this6[_privateKey]._this = new Proxy(_this6, { // Ensure only public keys are shown. ownKeys: function ownKeys(target) { return Object.keys(_this6.toObject()); }, // Return keys to iterate. enumerate: function enumerate(target) { return Object.keys(_this6[_privateKey]._this)[Symbol.iterator](); }, // Check to see if key exists. has: function has(target, key) { return !!_private._getset[key]; }, // Ensure correct prototype is returned. getPrototypeOf: function getPrototypeOf() { return _private._getset; }, // Ensure readOnly fields are not writeable. getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, key) { return { value: proxy[key], writeable: !schema[key] || schema[key].readOnly !== true, enumerable: true, configurable: true }; }, // Intercept all get calls. get: function get(target, name, receiver) { // First check to see if it's a reserved field. if (_reservedFields.includes(name)) { return _this6[_privateKey]._reservedFields[name]; } // Support dot notation via lodash. if (options.dotNotation && name.indexOf('.') !== -1) { return _.get(_this6[_privateKey]._this, name); } // Use registered getter without hitting the proxy to avoid creating an infinite loop. return _this6[name]; }, // Intercept all set calls. set: function set(target, name, value, receiver) { // Support dot notation via lodash. if (options.dotNotation && name.indexOf('.') !== -1) { return _.set(_this6[_privateKey]._this, name, value); } // Find real keyname if case sensitivity is off. if (options.keysIgnoreCase && !schema[name]) { name = getIndex.call(_this6, name); } if (!schema[name]) { if (options.strict) { // Strict mode means we don't want to deal with anything not in the schema. // TODO: SetterError here. return true; } else { // Add index to schema dynamically when value is set. // This is necessary for toObject to see the field. addToSchema.call(_this6, name, { type: 'any' }); } } // This hits the registered setter but bypasses the proxy to avoid an infinite loop. _this6[name] = value; // Necessary for Node v6.0. Prevents error: 'set' on proxy: trap returned falsish for property 'string'". return true; }, // Intercept all delete calls. deleteProperty: function deleteProperty(target, property) { _this6[property] = undefined; return true; } }); })(); } // Populate schema defaults into object. _.each(schema, function (properties, index) { if (properties.default !== undefined) { // Temporarily ensure readOnly is turned off to prevent the set from failing. var readOnly = properties.readOnly; properties.readOnly = false; _this6[index] = _.isFunction(properties.default) ? properties.default.call(_this6) : properties.default; properties.readOnly = readOnly; } }); // Call default constructor. _private._options.constructors.default.call(this, values); // May return actual object instance or Proxy, depending on harmony support. return _private._this; } // Populate values. _createClass(SchemaObjectInstance, [{ key: 'populate', value: function populate(values) { for (var key in values) { this[_privateKey]._this[key] = values[key]; } } // Clone and return SchemaObject. }, { key: 'clone', value: function clone() { return new SchemaObjectInstance(this.toObject(), this[_privateKey]._root); } // Return object without getter/typecasts, extra properties, etc. }, { key: 'toObject', value: function toObject() { var _this7 = this; var options = this[_privateKey]._options; var getObj = {}; // Populate all properties in schema. _.each(this[_privateKey]._schema, function (properties, index) { // Do not write values to object that are marked as invisible. if (properties.invisible) { return; } // Fetch value through the public interface. var value = _this7[_privateKey]._this[index]; // Do not write undefined values to the object because of strange behavior when using with MongoDB. // MongoDB will convert undefined to null and overwrite existing values in that field. if (value === undefined && options.setUndefined !== true) { return; } // Clone objects so they can't be modified by reference. if (_.isObject(value)) { if (value._isSchemaObject) { value = value.toObject(); } else if (value._isSchemaArray) { value = value.toArray(); } else if (_.isArray(value)) { value = value.splice(0); } else if (_.isDate(value)) { // https://github.com/documentcloud/underscore/pull/863 // _.clone doesn't work on Date object. getObj[index] = new Date(value.getTime()); } else { value = _.clone(value); } // Don't write empty objects or arrays. if (!_.isDate(value) && !options.setUndefined && !_.size(value)) { return; } } // Write to object. getObj[index] = value; }); // If options contains toObject, pass through before returning final object. if (_.isFunction(options.toObject)) { getObj = options.toObject.call(this, getObj); } return getObj; } // toJSON is an interface used by JSON.stringify. // Return the raw object if called. }, { key: 'toJSON', value: function toJSON() { return this.toObject(); } // Clear all values. }, { key: 'clear', value: function clear() { var _this8 = this; _.each(this[_privateKey]._schema, function (properties, index) { clearField.call(_this8[_privateKey]._this, index, properties); }); } // Get all errors. }, { key: 'getErrors', value: function getErrors() { var _this9 = this; var errors = []; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = this[_privateKey]._errors[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var error = _step2.value; error = _.cloneDeep(error); error.schemaObject = this; errors.push(error); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); }