UNPKG

base-domain

Version:

simple module to help build Domain-Driven Design

666 lines (549 loc) 15.9 kB
'use strict'; var Base, BaseModel, ModelProps, TypeInfo, Util, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; TypeInfo = require('./type-info'); Base = require('./base'); ModelProps = require('./model-props'); Util = require('../util'); /** Base model class of DDD pattern. @class BaseModel @extends Base @module base-domain */ BaseModel = (function(superClass) { extend(BaseModel, superClass); BaseModel.isEntity = false; /** Flag of the model's immutablity @static @property {Boolean} isImmutable */ BaseModel.isImmutable = false; /** key-value pair representing typeName - type use for definition of @properties for each extender @property TYPES @protected @final @static @type Object */ BaseModel.TYPES = TypeInfo.TYPES; /** key-value pair representing property's name - type of the model firstName : @TYPES.STRING lastName : @TYPES.STRING age : @TYPES.NUMBER registeredAt : @TYPES.DATE team : @TYPES.MODEL 'team' hobbies : @TYPES.MODEL 'hobby-list' info : @TYPES.ANY see type-info.coffee for full options. @property properties @abstract @static @protected @type Object */ BaseModel.properties = {}; /** extend @properties of Parent class @example class Parent extends BaseModel @properties: prop1: @TYPES.STRING class ChildModel extends ParentModel @properties: @withParentProps prop2: @TYPES.NUMBER ChildModel.properties # prop1 and prop2 @method withParentProps @protected @static @return {Object} */ BaseModel.withParentProps = function(properties) { var k, ref, v; if (properties == null) { properties = {}; } ref = this.properties; for (k in ref) { v = ref[k]; if (properties[k] == null) { properties[k] = v; } } return properties; }; /** @method enum @public @return {Object([key: String => Number])} */ BaseModel["enum"] = function(prop) { var ref, ref1; return (ref = this.properties) != null ? (ref1 = ref[prop]) != null ? ref1.numsByValue : void 0 : void 0; }; /** @method enum @public @return {Object} */ BaseModel.prototype["enum"] = function(prop) { return this.getModelProps().getEnumDic(prop); }; /** @method getModelProps @private @return {ModelProps} */ BaseModel.prototype.getModelProps = function() { if (this.root != null) { return this.facade.getModelProps(this.constructor.getName()); } else { return new ModelProps(this.constructor.getName(), this.constructor.properties, null); } }; /** @constructor @params {any} obj @params {RootInterface} root */ function BaseModel(obj, root) { BaseModel.__super__.constructor.call(this, root); if (obj) { this.set(obj); } } /** set value to prop @return {BaseModel} this */ BaseModel.prototype.set = function(prop, value) { var k, modelProps, subIdProp, submodelProp, v; if (typeof prop === 'object') { for (k in prop) { v = prop[k]; this.set(k, v); } return this; } this[prop] = value; modelProps = this.getModelProps(); if (modelProps.isEntity(prop)) { subIdProp = modelProps.getIdPropByEntityProp(prop); this[subIdProp] = value != null ? value.id : void 0; } else if (modelProps.isId(prop) && (value != null)) { this[prop] = value; submodelProp = modelProps.getEntityPropByIdProp(prop); if ((this[submodelProp] != null) && this[prop] !== this[submodelProp].id) { this[submodelProp] = void 0; } } else if (modelProps.isEnum(prop)) { this.setEnum(prop, value); } return this; }; /** set value to prop and create a new model @method $set @return {BaseModel} this */ BaseModel.prototype.$set = function(prop, value) { var props; if (typeof prop === 'object') { return this.copyWith(prop); } props = {}; props[prop] = value; return this.copyWith(props); }; /** set enum value @method setEnum @private @param {String} prop @param {String|Number} value */ BaseModel.prototype.setEnum = function(prop, value) { var enums, modelProps; if (value == null) { return; } modelProps = this.getModelProps(); enums = modelProps.getEnumDic(prop); if (typeof value === 'string' && (enums[value] != null)) { return this[prop] = enums[value]; } else if (typeof value === 'number' && (modelProps.getEnumValues(prop)[value] != null)) { return this[prop] = value; } return console.error("base-domain: Invalid value is passed to ENUM prop \"" + prop + "\" in model \"" + modelProps.modelName + "\".\nValue: \"" + value + "\"\nThe property was not set."); }; /** unset property @method unset @param {String} prop property name @return {BaseModel} this */ BaseModel.prototype.unset = function(prop) { var modelProps, subIdProp; this[prop] = void 0; modelProps = this.getModelProps(); if (modelProps.isEntity(prop)) { subIdProp = modelProps.getIdPropByEntityProp(prop); this[subIdProp] = void 0; } return this; }; /** unset property and create a new model @method $unset @param {String} prop property name @return {BaseModel} this */ BaseModel.prototype.$unset = function(prop) { var modelProps, props, subIdProp; props = {}; props[prop] = null; modelProps = this.getModelProps(); if (modelProps.isEntity(prop)) { subIdProp = modelProps.getIdPropByEntityProp(prop); props[subIdProp] = null; } return this.copyWith(props); }; /** inherit value of anotherModel @method inherit @param {BaseModel} anotherModel @return {BaseModel} this */ BaseModel.prototype.inherit = function(anotherModel) { var k, v; for (k in anotherModel) { if (!hasProp.call(anotherModel, k)) continue; v = anotherModel[k]; if (v != null) { this.set(k, v); } } return this; }; /** create plain object without relational entities descendants of Entity are removed, but not descendants of BaseModel descendants of Entity in descendants of BaseModel are removed ( = recursive) @method toPlainObject @return {Object} plainObject */ BaseModel.prototype.toPlainObject = function() { var modelProps, plainObject, prop, ref, value; plainObject = {}; modelProps = this.getModelProps(); ref = this; for (prop in ref) { if (!hasProp.call(ref, prop)) continue; value = ref[prop]; if (modelProps.isEntity(prop) || modelProps.isOmitted(prop)) { continue; } if (typeof (value != null ? value.toPlainObject : void 0) === 'function') { plainObject[prop] = value.toPlainObject(); } else { plainObject[prop] = value; } } return plainObject; }; /** check equality @method equals @param {BaseModel} model @return {Boolean} */ BaseModel.prototype.equals = function(model) { return (model != null) && this.constructor === model.constructor; }; /** clone the model as a plain object @method plainClone @public @return {Object} */ BaseModel.prototype.plainClone = function() { var modelProps, plainObject, prop, ref, value; plainObject = {}; modelProps = this.getModelProps(); ref = this; for (prop in ref) { if (!hasProp.call(ref, prop)) continue; value = ref[prop]; if (modelProps.isModel && value instanceof BaseModel) { plainObject[prop] = value.plainClone(); } else { plainObject[prop] = Util.clone(value); } } return plainObject; }; /** create clone @method clone @public @return {BaseModel} */ BaseModel.prototype.clone = function() { var modelProps, plainObject; plainObject = this.plainClone(); modelProps = this.getModelProps(); return this.facade.createModel(modelProps.modelName, plainObject); }; /** shallow copy the model with props @method copyWith @return {BaseModel} */ BaseModel.prototype.copyWith = function(props) { var entity, entityProp, i, len, modelProps, obj, prop, ref, ref1, subId, subIdProp, value; if (props == null) { props = {}; } modelProps = this.getModelProps(); obj = {}; ref = this; for (prop in ref) { if (!hasProp.call(ref, prop)) continue; value = ref[prop]; obj[prop] = value; } for (prop in props) { if (!hasProp.call(props, prop)) continue; value = props[prop]; if (value != null) { obj[prop] = value; } else { delete obj[prop]; } } ref1 = modelProps.getEntityProps(); for (i = 0, len = ref1.length; i < len; i++) { entityProp = ref1[i]; entity = obj[entityProp]; subIdProp = modelProps.getIdPropByEntityProp(entityProp); subId = obj[subIdProp]; if ((entity != null) && entity.id !== subId) { obj[subIdProp] = entity.id; } } modelProps = this.getModelProps(); return this.facade.createModel(modelProps.modelName, obj); }; /** Get diff prop values @method getDiff @public @param {any} plainObj @param {Object} [options] @param {Array(String)} [options.ignores] prop names to skip checking diff @return {Object} */ BaseModel.prototype.getDiff = function(plainObj, options) { if (plainObj == null) { plainObj = {}; } if (options == null) { options = {}; } return this.getDiffProps(plainObj, options).reduce(function(obj, prop) { obj[prop] = plainObj[prop]; return obj; }, {}); }; /** Get diff props @method diff @public @param {any} plainObj @param {Object} [options] @param {Array(String)} [options.ignores] prop names to skip checking diff @return {Array(String)} */ BaseModel.prototype.getDiffProps = function(plainObj, options) { var diffProps, entityProp, i, ignores, j, len, len1, modelProps, prop, propsToCheck, ref, thatEntityValue, thatEnumValue, thatISOValue, thatValue, thisEntityValue, thisISOValue, thisValue; if (plainObj == null) { plainObj = {}; } if (options == null) { options = {}; } if ((plainObj == null) || typeof plainObj !== 'object') { return Object.keys(this); } diffProps = []; modelProps = this.getModelProps(); ignores = {}; if (Array.isArray(options.ignores)) { ref = options.ignores; for (i = 0, len = ref.length; i < len; i++) { prop = ref[i]; ignores[prop] = true; } } propsToCheck = modelProps.getAllProps().filter(function(prop) { return !ignores[prop] && !modelProps.isEntity(prop); }); for (j = 0, len1 = propsToCheck.length; j < len1; j++) { prop = propsToCheck[j]; thisValue = this[prop]; thatValue = plainObj[prop]; if (thisValue == null) { if (thatValue == null) { continue; } } if ((thatValue == null) || (thisValue == null)) { diffProps.push(prop); continue; } if (thisValue === thatValue) { continue; } if (modelProps.isEntity(prop) && (thisValue[prop] != null) && (thatValue == null)) { continue; } if (modelProps.isId(prop)) { entityProp = modelProps.getEntityPropByIdProp(prop); if (thisValue !== thatValue) { diffProps.push(prop, entityProp); continue; } thisEntityValue = this[entityProp]; thatEntityValue = plainObj[entityProp]; if (thisEntityValue == null) { if (thatEntityValue != null) { diffProps.push(entityProp); } continue; } else if (typeof thisEntityValue.isDifferentFrom === 'function') { if (thisEntityValue.isDifferentFrom(thatEntityValue)) { diffProps.push(entityProp); } continue; } else { diffProps.push(entityProp); } } else if (modelProps.isDate(prop)) { thisISOValue = typeof thisValue.toISOString === 'function' ? thisValue.toISOString() : thisValue; thatISOValue = typeof thatValue.toISOString === 'function' ? thatValue.toISOString() : thatValue; if (thisISOValue === thatISOValue) { continue; } } else if (modelProps.isEnum(prop)) { thatEnumValue = typeof thatValue === 'string' ? this["enum"](prop)[thatValue] : thatValue; if (thisValue === thatEnumValue) { continue; } } else if (typeof thisValue.isDifferentFrom === 'function') { if (!thisValue.isDifferentFrom(thatValue)) { continue; } } else { if (Util.deepEqual(thisValue, thatValue)) { continue; } } diffProps.push(prop); } return diffProps; }; /** Get difference props @method diff @public @param {any} plainObj @return {Array(String)} */ BaseModel.prototype.isDifferentFrom = function(val) { return this.getDiffProps(val).length > 0; }; /** freeze the model */ BaseModel.prototype.freeze = function() { if (!this.constructor.isImmutable) { throw this.error('FreezeMutableModel', 'Cannot freeze mutable model.'); } return Object.freeze(this); }; /** include all relational models if not set @method include @param {Object} [options] @param {Boolean} [options.async=true] get async values @param {Array(String)} [options.props] include only given props @return {Promise(BaseModel)} self */ BaseModel.prototype.include = function(options) { var Includer; if (options == null) { options = {}; } Includer = require('./includer'); return new Includer(this, options).include().then((function(_this) { return function() { return _this; }; })(this)); }; /** include all relational models and returns new model @method $include @param {Object} [options] @param {Boolean} [options.async=true] get async values @param {Array(String)} [options.props] include only given props @return {Promise(BaseModel)} new model */ BaseModel.prototype.$include = function(options) { var Includer, createNew; if (options == null) { options = {}; } Includer = require('./includer'); return new Includer(this, options).include(createNew = true); }; /** Check if all subentities are included. @method included @return {Boolean} */ BaseModel.prototype.included = function(recursive) { var entityProp, i, j, len, len1, modelProp, modelProps, ref, ref1, subIdProp; if (recursive == null) { recursive = false; } modelProps = this.getModelProps(); ref = modelProps.getEntityProps(); for (i = 0, len = ref.length; i < len; i++) { entityProp = ref[i]; subIdProp = modelProps.getIdPropByEntityProp(entityProp); if ((this[subIdProp] != null) && (this[entityProp] == null)) { return false; } } if (!recursive) { return true; } ref1 = modelProps.models; for (j = 0, len1 = ref1.length; j < len1; j++) { modelProp = ref1[j]; if ((this[modelProp] != null) && !this[modelProp].included()) { return false; } } return true; }; return BaseModel; })(Base); module.exports = BaseModel;