UNPKG

speck-entity

Version:

Domain entities with reactive validation

266 lines (212 loc) 9.02 kB
'use strict'; 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; }; }(); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 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; }; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var _require = require('lodash'), clone = _require.clone, get = _require.get, isNil = _require.isNil, isFunction = _require.isFunction, noop = _require.noop; function applyAfterSetHook(field, data, afterSetHook) { var newData = afterSetHook(data, field); (typeof newData === 'undefined' ? 'undefined' : _typeof(newData)) === 'object' && _extends(data, newData); } function createGetterAndSetter(instance, field) { var schemaField = instance.schema[field]; var afterSet = get(schemaField, 'hooks.afterSet'); var afterSetHook = typeof afterSet === 'function' ? applyAfterSetHook : noop; return { set: function set(newValue) { if (instance.data[field] !== newValue) { instance.data[field] = newValue; afterSetHook(field, instance.data, afterSet); return instance._validate(); } }, get: function get() { return instance.data[field]; }, enumerable: true }; } var Speck = function () { function Speck() { var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, Speck); Object.defineProperty(this, 'schema', { value: this.constructor.SCHEMA, enumerable: false }); Object.defineProperty(this, 'contexts', { value: this.constructor.CONTEXTS, enumerable: false }); Object.defineProperty(this, 'childrenEntities', { value: Object.keys(this.constructor.SCHEMA).filter(this.__fieldHasType.bind(this)), enumerable: false }); for (var _len = arguments.length, dependencies = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { dependencies[_key - 1] = arguments[_key]; } Object.defineProperty(this, 'data', { value: this._mergeDefault.apply(this, [data].concat(dependencies)), enumerable: false }); this._validate(); } _createClass(Speck, [{ key: '__fieldHasType', value: function __fieldHasType(field) { return !!this.constructor.SCHEMA[field].type; } }, { key: '__initFieldValue', value: function __initFieldValue(field, data) { var hasValue = !isNil(data[field]); if (hasValue) return data[field]; var defaultValue = this.schema[field].defaultValue; var isFunction = defaultValue instanceof Function; return isFunction ? defaultValue : clone(this.schema[field].defaultValue); } }, { key: '_mergeDefault', value: function _mergeDefault(data) { var newData = {}; var field = void 0; for (var _len2 = arguments.length, dependencies = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { dependencies[_key2 - 1] = arguments[_key2]; } for (field in this.schema) { newData[field] = this.__initFieldValue(field, data); if (this.schema[field].type || this.schema[field].builder) { newData[field] = this.applyEntityConstructor.apply(this, [this.schema[field], newData[field]].concat(dependencies)); } Object.defineProperty(this, field, createGetterAndSetter(this, field)); } return newData; } }, { key: '_fetchChild', value: function _fetchChild(fieldValue) { if (Array.isArray(fieldValue)) { return fieldValue.map(this._fetchChild); } if (fieldValue && typeof fieldValue.toJSON === 'function') { return fieldValue.toJSON(); } return fieldValue; } }, { key: '__validateField', value: function __validateField(field) { var validator = typeof this.schema[field] === 'function' ? this.schema[field] : this.schema[field].validator; return validator(this.data, field, this.constructor.name + 'Entity'); } }, { key: '_validate', value: function _validate() { this.errors = {}; for (var field in this.schema) { var error = this.__validateField(field); if (error) { this.errors[field] = { errors: [error.message || error] }; } } this.valid = Object.keys(this.errors).length === 0; if (!this.valid) { return this.errors; } } }, { key: '_includeChildErrors', value: function _includeChildErrors(field, errors, entity, index) { if (!entity.valid) { if (errors[field] === undefined) { errors[field] = {}; } errors[field][index] = entity.getErrors(); } return errors; } }, { key: '_getChildrenErrors', value: function _getChildrenErrors(errors, field) { var children = Array.isArray(this[field]) ? this[field] : [this[field]]; return children.reduce(this._includeChildErrors.bind(this, field), errors); } }, { key: 'applyEntityConstructor', value: function applyEntityConstructor(field, data) { var Type = field.type; if (isFunction(field.builder)) { for (var _len3 = arguments.length, dependencies = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) { dependencies[_key3 - 2] = arguments[_key3]; } return field.builder.apply(field, [data, Type].concat(dependencies)); } if (!data) return; if (Array.isArray(data)) { return data.map(function (instance) { return new Type(instance); }); } return new Type(data); } }, { key: 'toJSON', value: function toJSON() { var _this = this; var rawData = Object.keys(this.data).reduce(function (data, field) { return _extends(data, _defineProperty({}, field, _this._fetchChild(_this.data[field]))); }, {}); return JSON.parse(JSON.stringify(rawData)); } }, { key: 'getErrors', value: function getErrors() { var errors = _extends({}, this._validate()); return this.childrenEntities.reduce(this._getChildrenErrors.bind(this), errors); } }, { key: 'validateContext', value: function validateContext(context) { var _this2 = this; if (!get(this.contexts, context)) return this.errors; var validation = function validation() { return true; }; if (this.contexts[context].exclude && Object.keys(this.contexts[context].exclude).length > 0) { validation = function validation(error) { return _this2.contexts[context].exclude.find(function (exclude) { return exclude === error; }) === undefined; }; } if (this.contexts[context].include && Object.keys(this.contexts[context].include).length > 0) { validation = function validation(error) { return _this2.contexts[context].include.find(function (include) { return include === error; }) !== undefined; }; } var contextErrors = _extends({}, this.errors); if (this.contexts[context].fields) { Object.keys(this.contexts[context].fields).forEach(function (field) { var result = _this2.contexts[context].fields[field](_this2, field, _this2.constructor.name); result && (contextErrors[field] = { errors: result }); }); } var errors = Object.keys(contextErrors).filter(validation); var contextValidation = errors.reduce(function (newError, errorField) { newError[errorField] = contextErrors[errorField]; return newError; }, {}); return _extends({ valid: !Object.keys(contextValidation).length }, contextValidation); } }]); return Speck; }(); module.exports = Speck;