/* documentation verified by mpv on 2018-03-18 */
/* global UB, App */
const _ = require('lodash')
const iso8601ParseAsDate = require('./LocalDataStore').iso8601ParseAsDate
/**
* @module UBDomain
* @memberOf module:@unitybase/cs-shared
*/
/**
* Database connection config (w/o credential)
*
* @typedef {object} DBConnectionConfig
* @property {string} name
* @property {string} dialect
* @property {boolean} isDefault
* @property {Array<string>} supportLang
* @property {string} advSettings database specific settings
* @property {string} [lobParameters] Oracle only - additional parameters for LOB (CLOB, BLOB) columns. Example: `STORE AS BASICFILE (TABLESPACE blob_tbs)`
* @property {string} [indexTablespace] tablespace(Oracle, Postgres)/filegroup(SQLServer) name for storing newly added indexes
*/
/**
* @classdesc
* Domain object model (metadata) - in-memory representation of all *.meta files included in the application config.
* Developer should never create {@link UBDomain} class directly, but instead use a:
* - {@link module:@unitybase/ub#App.domainInfo App.domainInfo} property inside server-side methods
* - {@link module:@unitybase/base#SyncConnection SyncConnection.getDomainInfo} method inside CLI scripts
* - `UBConnection.domain` property inside a browser
* @example
// server-side example
const UB = require('@unitybase/ub')
const App = UB.App
let ubaAuditPresent = App.domainInfo.has('uba_audit')
* @param {object} domainInfo getDomainInfo UB server method result
* @param {object} domainInfo.domain raw entities collection
* @param {object} domainInfo.entityMethods entities methods access rights for current user
* @param {object} domainInfo.models information about domain models
* @param {object} domainInfo.i18n entities localization to current user language
* @class
*/
function UBDomain (domainInfo) {
const me = this
const entityCodes = Object.keys(domainInfo.domain)
const isV4API = (typeof domainInfo.entityMethods === 'undefined')
/**
* Map with keys is entity name, value is UBEntity
*
* @member {Object<string, UBEntity>}
*/
this.entities = {}
/**
* Connection collection (extended domain only)
*
* @member {Array<DBConnectionConfig>}
*/
this.connections = domainInfo.connections
/**
* Default connection (extended domain only)
*
* @member {DBConnectionConfig}
*/
this.defaultConnection = undefined
if (this.connections) {
this.connections.forEach((conn) => {
if (conn.isDefault) this.defaultConnection = conn
})
}
for (let i = 0, L = entityCodes.length; i < L; i++) {
const entityCode = entityCodes[i]
const entity = domainInfo.domain[entityCode]
// entity attributes locale can come either as array
// "attributes": [{"name": "attrCode", ...}, ..]
// or as object
// "attributes": {"attrCode": {...}, ..]
// to be merged correctly transformation to object required
if (entity.i18n && entity.i18n.attributes && Array.isArray(entity.i18n.attributes)) {
const attrs = entity.i18n.attributes
const newAttrs = {}
for (let k = 0, lL = attrs.length; k < lL; k++) {
const attr = attrs[k]
const attrName = attr.name
if (!attrName) throw new Error('Invalid localization JSON for entity ' + entityCode)
delete attr.name
newAttrs[attrName] = attr
}
entity.i18n.attributes = newAttrs
}
if (isV4API) {
me.entities[entityCode] = new UBEntity(
entity,
entity.entityMethods || {},
entity.i18n,
entityCode,
me
)
} else {
me.entities[entityCode] = new UBEntity(
entity,
domainInfo.entityMethods[entityCode] || {},
domainInfo.i18n[entityCode],
entityCode,
me
)
}
}
/**
* Array of models, sorted by the order of loading
*
* @member {Array<UBModel>}
*/
this.orderedModels = []
/**
* Models collection
*
* @member {Object<string, UBModel>}
*/
this.models = {}
const modelCodes = Object.keys(domainInfo.models)
modelCodes.forEach(function (modelCode) {
const m = domainInfo.models[modelCode]
me.models[modelCode] = new UBModel(m, modelCode)
me.orderedModels.push(me.models[modelCode])
})
me.orderedModels.sort((a, b) => a.order - b.order)
/**
* Array of vendor models names
*
* @type {Array<string>}
*/
this.vendorModels = domainInfo.vendorModels ? domainInfo.vendorModels.split(':') : []
/**
* Array of customer models names
*
* @type {Array<string>}
*/
this.customerModels = domainInfo.customerModels ? domainInfo.customerModels.split(':') : []
}
/**
* Check all provided entity methods are accessible via ELS.
*
* If entity does not exist in domain or at last one of provided methods is not accessible - return false
*
* @param {string} entityCode
* @param {string|Array} methodNames
* @returns {boolean}
*/
UBDomain.prototype.isEntityMethodsAccessible = function (entityCode, methodNames) {
const entity = this.entities[entityCode]
if (!entity) return false
return Array.isArray(methodNames) ? entity.haveAccessToMethods(methodNames) : entity.haveAccessToMethod(methodNames)
}
/**
* Get entity by code
*
* @param {string} entityCode
* @param {boolean} [raiseErrorIfNotExists=true] If `true`(default) and entity does not exists throw error
* @returns {UBEntity}
*/
UBDomain.prototype.get = function (entityCode, raiseErrorIfNotExists) {
const result = this.entities[entityCode]
if ((raiseErrorIfNotExists !== false) && !result) {
throw new Error(`Entity with code '${entityCode}' does not exist or not accessible`)
}
return result
}
/**
* Check entity present in domain & user has access right for at least one entity method
*
* @param {string} entityCode
* @returns {boolean}
*/
UBDomain.prototype.has = function (entityCode) {
return !!this.entities[entityCode]
}
/**
* Callback for UBDomain.forEach iterator
*
* @callback domainEntitiesIteratorCallback
* @param {UBEntity} entity
* @param {string} entityCode
* @param {Object<string, UBEntity>} entities
*/
/**
* Iterates over domain entities and invokes `callBack` for each entity.
* The iteratee is invoked with three arguments: (UBEntity, entityName, UBDomain.entities)
*
* @param {domainEntitiesIteratorCallback} cb
* @returns {object}
*/
UBDomain.prototype.eachEntity = function (cb) {
return _.forEach(this.entities, cb)
}
/**
* Filter entities by properties
*
* @example
// sessionCachedEntities contains all entities with property cacheType === Session
var sessionCachedEntities = domain.filterEntities({cacheType: 'Session'});
* @param {object|Function} predicate Either a function passed to lodash filter or object
* @returns {Array<UBEntity>}
*/
UBDomain.prototype.filterEntities = function (predicate) {
if (_.isFunction(predicate)) {
return _.filter(this.entities, predicate)
} else {
return _.filter(this.entities, function (item) {
let res = true
for (const prop in predicate) {
if (Object.prototype.hasOwnProperty.call(predicate, prop)) {
res = res && (item[prop] === predicate[prop])
}
}
return res
})
}
}
/**
* Possible types of the attributes
*
* @readonly
* @enum
*/
UBDomain.ubDataTypes = {
/** Small string. _MSSQL: NVARCHAR, ORACLE: NVARCHAR2, POSTGRE: VARCHAR_ */
String: 'String',
/** 32-bite Integer. MSSQL: INT, ORACLE: INTEGER, POSTGRE: INTEGER */
Int: 'Int',
/** 64-bite Integer. MSSQL: BIGINT, ORACLE: NUMBER(19), POSTGRE: BIGINT */
BigInt: 'BigInt',
/** Double. MSSQL: FLOAT, ORACLE: NUMBER(19, 4), POSTGRE: NUMERIC(19, 4) */
Float: 'Float',
/** Currency. MSSQL: FLOAT, ORACLE: NUMBER(19, 2), POSTGRE: NUMERIC(19, 2) */
Currency: 'Currency',
/** Boolean. MSSQL: TINYINT, ORACLE: NUMBER(1), POSTGRE: SMALLINT */
Boolean: 'Boolean',
/** Date + Time in UTC (GMT+0) timezone. MSSQL: DATETIME, OARCLE: DATE, POSTGRE: TIMESTAMP WITH TIME ZONE */
DateTime: 'DateTime',
/** Long strint. MSSQL: NVARCHAR(MAX), ORACLE: CLOB, POSTGRE: TEXT */
Text: 'Text',
/** Alias for BigInt */
ID: 'ID',
/** Reference to enother entity. BigInt */
Entity: 'Entity',
/** Store a JSON with information about Document place in blob store */
Document: 'Document',
Many: 'Many',
/** Seconds since UNIX epoch, Int64. MSSQL: BIGINT, ORACLE: NUMBER(19), POSTGRE: BIGINT */
TimeLog: 'TimeLog',
/** Enumertion (see ubm_enum) */
Enum: 'Enum',
/** Bynary data. MSSQL: VARBINARY(MAX), ORACLE: BLOB, POSTGRE: BYTEA */
BLOB: 'BLOB',
/** Date (without time) in UTC (GMT+0) */
Date: 'Date',
/** Json stored in database. Postgres: JSONB, _MSSQL: NVARCHAR(4000), ORACLE: NVARCHAR2(2000) */
Json: 'Json'
}
UBDomain.prototype.ubDataTypes = UBDomain.ubDataTypes
UBDomain.FLOATING_SCALE_PRECISION = UBDomain.prototype.FLOATING_SCALE_PRECISION = 6
/**
* Types of expressions in attribute mapping
*
* @readonly
* @protected
* @enum
*/
UBDomain.ExpressionType = {
Field: 'Field',
Expression: 'Expression'
}
/**
* UnityBase base mixins
*
* @readonly
* @private
* @enum
*/
UBDomain.ubMixins = {
dataHistory: 'dataHistory',
mStorage: 'mStorage',
unity: 'unity',
treePath: 'treePath'
}
/**
* Service attribute names
*
* @readonly
* @enum
*/
UBDomain.ubServiceFields = {
dateFrom: 'mi_datefrom',
dateTo: 'mi_dateto'
}
/**
* Entity dataSource types
*
* @enum {string}
* @readonly
*/
UBDomain.EntityDataSourceType = {
Normal: 'Normal',
External: 'External',
System: 'System',
Virtual: 'Virtual'
}
/**
* @enum
*/
UBDomain.EntityCacheTypes = {
None: 'None',
Entity: 'Entity',
Session: 'Session',
SessionEntity: 'SessionEntity'
}
/**
* Priority to apply a mapping of a attributes/entities to the physical tables depending on connection dialect
*
* @enum
* @protected
*/
UBDomain.dialectsPriority = {
MSSQL2012: ['MSSQL2012', 'MSSQL', 'AnsiSQL'],
MSSQL2008: ['MSSQL2008', 'MSSQL', 'AnsiSQL'],
MSSQL: ['MSSQL', 'AnsiSQL'],
Oracle11: ['Oracle11', 'Oracle', 'AnsiSQL'],
Oracle10: ['Oracle10', 'Oracle', 'AnsiSQL'],
Oracle9: ['Oracle9', 'Oracle', 'AnsiSQL'],
Oracle: ['Oracle', 'AnsiSQL'],
PostgreSQL: ['PostgreSQL', 'AnsiSQL'],
AnsiSQL: ['AnsiSQL'],
Firebird: ['Firebird', 'AnsiSQL'],
SQLite3: ['SQLite3', 'AnsiSQL']
}
/**
* Return physical type by UBDataType
*
* @param {string} dataType
* @returns {string}
*/
UBDomain.getPhysicalDataType = function (dataType) {
const ubDataTypes = UBDomain.ubDataTypes
const typeMap = {}
if (!this.physicalTypeMap) {
typeMap[ubDataTypes.Int] = 'int'
typeMap[ubDataTypes.Entity] = 'int'
typeMap[ubDataTypes.ID] = 'int'
typeMap[ubDataTypes.BigInt] = 'int'
typeMap[ubDataTypes.String] = 'string'
typeMap[ubDataTypes.Text] = 'string'
typeMap[ubDataTypes.Enum] = 'string'
typeMap[ubDataTypes.Float] = 'float'
typeMap[ubDataTypes.Currency] = 'float'
typeMap[ubDataTypes.Boolean] = 'boolean'
typeMap[ubDataTypes.Date] = 'date'
typeMap[ubDataTypes.DateTime] = 'date'
this.physicalTypeMap = typeMap
}
return this.physicalTypeMap[dataType] || 'auto'
}
/**
* Function used as replacer for JSON.stringify inside toJSON methods
*
* @private
* @param {string} k
* @param {*} v
* @returns {*}
*/
UBDomain.jsonReplacer = function (k, v) {
// eslint-disable-next-line no-proto
const jr = this.__proto__.forJSONReplacer
if (jr) {
// skip props marked as null inside forJSONReplacer collection
if (Object.prototype.hasOwnProperty.call(jr, k) && jr[k] === null) return undefined
// skip boolean props mach forJSONReplacer boolean values
if (typeof v === 'boolean' && v === jr[k]) return undefined
}
// skip '', 0
if (typeof v !== 'boolean' && !v) return undefined
// skip empty customSettings
if (k === 'customSettings' && !Object.keys(v).length) return undefined
return v
}
/**
* Model (logical group of entities).
* Instantiated in {@link UBDomain#models UBDomain.models} and {@link UBDomain#orderedModels UBDomain.orderedModels}
*
* @class
* @param {object} cfg
* @param {string} cfg.path
* @param {boolean} cfg.needInit
* @param {boolean} cfg.needLocalize
* @param {number} cfg.order
* @param {string} cfg.version
* @param {string} cfg.description
* @param {string} [cfg.moduleName]
* @param {string} [cfg.moduleSuffix]
* @param {string} [cfg.clientRequirePath] if passed are used instead of moduleName + moduleSuffix
* @param {string} [cfg.realPublicPath]
* @param {string} modelCode
*/
function UBModel (cfg, modelCode) {
/**
* Model name as specified in application config
*
* @type {string}
*/
this.name = modelCode
this.path = cfg.path
if (cfg.needInit) {
/**
* `initModel.js` script is available in the public folder (should be injected by client)
*
* @type {boolean}
*/
this.needInit = cfg.needInit
}
if (cfg.needLocalize) {
/**
* `locale-Lang.js` script is available in the public folder (should be injected by client)
*
* @type {boolean}
*/
this.needLocalize = cfg.needLocalize
}
/**
* An order of model initialization (as it is provided in server domain config)
*
* @type {number}
*/
this.order = cfg.order
/**
* Module name for `require`
*
* @type {string}
*/
this.moduleName = cfg.moduleName
// if (cfg.moduleSuffix && cfg.moduleName) {
// this.moduleName = this.moduleName + '/' + cfg.moduleSuffix
// }
/**
* The path for retrieve a model public accessible files (using clientRequire endpoint)
*
* @type {string}
*/
this.clientRequirePath = (cfg.moduleSuffix && cfg.moduleName && cfg.moduleName.startsWith('@'))
? (this.moduleName + '/' + cfg.moduleSuffix)
: (this.path)
if (cfg.realPublicPath) {
/**
* Server-side domain only - full path to the model public folder (if any) including trailer `/`
*
* @type {string}
*/
this.realPublicPath = cfg.realPublicPath
}
if (cfg.realPath) {
/**
* Server-side domain only - the full path to model folder
*
* @type {string}
*/
this.realPath = cfg.realPath
}
/**
* Model version as specified in `version` key of model package.json
* If package.json not found version is empty.
*
* Introduced in ub server@5.4.2
*
* @type {string}
*/
this.version = cfg.version
/**
* Model description as specified in `description` key of model package.json
* If package.json not found description is empty.
*
* Introduced in ub server@5.24.21
*
* @type {string}
*/
this.description = cfg.description || ''
}
UBModel.prototype.needInit = false
UBModel.prototype.needLocalize = false
UBModel.prototype.realPublicPath = ''
/**
* Collection of attributes
*
* @class
*/
function UBEntityAttributes () {}
/**
* Return a JSON representation of all attributes
* WITHOUT properties which have default values
*
* @returns {object}
*/
UBEntityAttributes.prototype.asPlainJSON = function () {
return JSON.parse(JSON.stringify(this, UBDomain.jsonReplacer))
}
/**
* @class
* @param {object} mapping
*/
function UBEntityMapping (mapping) {
/**
* @type {string}
*/
this.selectName = mapping.selectName || ''
/** @type {string} */
this.execName = mapping.execName || this.selectName
/** @type {string} */
this.pkGenerator = mapping.pkGenerator
}
/**
* Entity metadata
*
* @class
* @param {object} entityInfo
* @param {object} entityMethods
* @param {object} i18n
* @param {string} entityCode
* @param {UBDomain} domain
*/
function UBEntity (entityInfo, entityMethods, i18n, entityCode, domain) {
const me = this
let mixinInfo, i18nMixin
if (i18n && ((typeof process === 'undefined') || !process.isServer)) { // merge i18n only on client side
// compatibility: in case entity.captionSingular is not defined in lang file - use captionSingular = caption
if (i18n && !i18n.captionSingular && i18n.caption) i18n.captionSingular = i18n.caption
_.merge(entityInfo, i18n)
// verify entity is valid after merging i18n: at last all attributes have dataType
_.forEach(entityInfo.attributes, (attrDef, attrKey) => {
if (!attrDef.dataType) {
const eMsg = `Invalid i18n for entity "${entityCode}" - attribute "${attrKey}" not exist in meta or it's dataType is empty`
throw new Error(eMsg)
}
})
}
/**
* Non-enumerable (to prevent JSON.stringify circular ref) read only domain
*
* @property {UBDomain} domain
* @readonly
*/
Object.defineProperty(this, 'domain', { enumerable: false, value: domain })
/**
* @type {string}
* @readonly
*/
this.code = entityCode
/**
* Name of model where entity is defined (in case entity is overridden - see overridesBy)
*
* @type {string}
* @readonly
*/
this.modelName = entityInfo.modelName
/**
* CSV model names where entity is overridden
*
* @type {string}
* @readonly
*/
this.overriddenBy = entityInfo.overriddenBy
/**
* Entity name
*
* @type {string}
* @readonly
*/
this.name = entityInfo.name
if (entityInfo.caption) this.caption = entityInfo.caption
if (entityInfo.captionSingular) this.captionSingular = entityInfo.captionSingular
if (entityInfo.description) this.description = entityInfo.description
if (entityInfo.documentation) this.documentation = entityInfo.documentation
if (entityInfo.descriptionAttribute) this.descriptionAttribute = entityInfo.descriptionAttribute
if (entityInfo.cacheType) this.cacheType = entityInfo.cacheType
if (entityInfo.dsType) this.dsType = entityInfo.dsType
if (entityInfo.isUnity) this.isUnity = true
if (entityInfo.isManyManyRef) this.isManyManyRef = true
if (entityInfo.isFTSDataTable) this.isFTSDataTable = true
/**
* Internal short alias
*
* @type {string}
* @readonly
*/
this.sqlAlias = entityInfo.sqlAlias
/**
* Data source connection name
*
* @type {string}
* @readonly
*/
this.connectionName = entityInfo.connectionName
/**
* Reference to connection definition (for extended domain only)
*
* @type {DBConnectionConfig}
* @readonly
*/
this.connectionConfig = (this.connectionName && this.domain && this.domain.connections) ? _.find(this.domain.connections, { name: this.connectionName }) : undefined
/**
* Optional mapping of entity to physical data (for extended domain info only).
* Calculated from a entity mapping collection in accordance with application connection configuration
*
* @type {UBEntityMapping}
* @readonly
*/
this.mapping = undefined
if (entityInfo.mapping) {
const mappingKeys = Object.keys(entityInfo.mapping)
mappingKeys.forEach(key => {
if (!UBDomain.dialectsPriority[key]) throw new Error(`Invalid dialect ${key} in ${this.code} mapping`)
})
if (mappingKeys.length) {
const me = this
const dialectPriority = UBDomain.dialectsPriority[this.connectionConfig.dialect]
_.forEach(dialectPriority, function (dialect) {
if (entityInfo.mapping[dialect]) {
me.mapping = new UBEntityMapping(entityInfo.mapping[dialect])
return false
}
})
}
}
/**
* Optional dbKeys (for extended domain info)
*
* @type {object}
*/
this.dbKeys = entityInfo.dbKeys && Object.keys(entityInfo.dbKeys).length ? entityInfo.dbKeys : undefined
/**
* Optional dbExtensions (for extended domain info)
*
* @type {object}
*/
this.dbExtensions = entityInfo.dbExtensions && Object.keys(entityInfo.dbExtensions).length ? entityInfo.dbExtensions : undefined
/**
* Entity attributes collection
*
* @type {Object<string, UBEntityAttribute>}
*/
this.attributes = new UBEntityAttributes()
/**
* Slice of attributes with type `Document`
*
* @type {Array<UBEntityAttribute>}
*/
this.blobAttributes = []
const attributesIsArray = Array.isArray(entityInfo.attributes)
_.forEach(entityInfo.attributes, (attributeInfo, attributeCode) => {
if (attributesIsArray) attributeCode = attributeInfo.code || attributeInfo.name
const attr = new UBEntityAttribute(attributeInfo, attributeCode, me)
// record history mixin set a dateTo automatically, so let's allow blank mi_dateTo on UI
// but for DDL generator mi_dateTo must be not null, so change only for browser side
if ((attr.code === 'mi_dateTo') && (typeof window !== 'undefined')) {
attr.allowNull = true
}
me.attributes[attributeCode] = attr
if (attr.dataType === UBDomain.ubDataTypes.Document) {
this.blobAttributes.push(attr)
}
})
const mixinNames = Object.keys(entityInfo.mixins || {})
/**
* Collection of entity mixins
*
* @type {Object<string, UBEntityMixin>}
*/
this.mixins = {}
mixinNames.forEach(function (mixinCode) {
mixinInfo = entityInfo.mixins[mixinCode]
i18nMixin = (i18n && i18n.mixins ? i18n.mixins[mixinCode] : null)
switch (mixinCode) {
case 'mStorage':
me.mixins[mixinCode] = new UBEntityStoreMixin(mixinInfo, i18nMixin, mixinCode)
break
case 'dataHistory':
me.mixins[mixinCode] = new UBEntityHistoryMixin(mixinInfo, i18nMixin, mixinCode)
break
case 'aclRls':
me.mixins[mixinCode] = new UBEntityAclRlsMixin(mixinInfo, i18nMixin, mixinCode)
break
case 'fts':
me.mixins[mixinCode] = new UBEntityFtsMixin(mixinInfo, i18nMixin, mixinCode)
break
case 'als':
me.mixins[mixinCode] = new UBEntityAlsMixin(mixinInfo, i18nMixin, mixinCode)
break
default:
me.mixins[mixinCode] = new UBEntityMixin(mixinInfo, i18nMixin, mixinCode)
}
})
/**
* Entity methods, allowed for current logged-in user in format {method1: 1, method2: 1}. 1 mean method is allowed
* Empty for server-side domain - use `entity.haveAccessToMethod` to check method is accessible for user
*
* @type {Object<string, number>}
* @readonly
*/
this.entityMethods = entityMethods || {}
/**
* Private settings (for extended domain info)
*
* @property {object} privateSettings
*/
if (entityInfo.privateSettings) this.privateSettings = entityInfo.privateSettings
/**
* Custom settings
*
* @property {object} customSettings
*/
this.customSettings = entityInfo.customSettings || {}
}
// default UBEntity props - used by JSON.stringify replacer to produce entity JSON representation
UBEntity.prototype.forJSONReplacer = {
code: null,
modelName: null,
cacheType: 'None',
isFTSDataTable: null,
isManyManyRef: null,
blobAttributes: null,
entityMethods: null
}
/**
* Entity caption in plural
*
* @type {string}
*/
UBEntity.prototype.caption = ''
/**
* Entity caption in singular
*
* @type {string}
*/
UBEntity.prototype.captionSingular = ''
/**
* Entity description
*
* @type {string}
*/
UBEntity.prototype.description = ''
/**
* Documentation
*
* @type {string}
*/
UBEntity.prototype.documentation = ''
/**
* Name of attribute witch used as a display value in lookup
*
* @type {string}
*/
UBEntity.prototype.descriptionAttribute = ''
/**
* Indicate how entity content is cached on the client side.
*
* @type {UBDomain.EntityCacheTypes}
* @readonly
*/
UBEntity.prototype.cacheType = 'None'
/**
*
* @type {UBDomain.EntityDataSourceType}
*/
UBEntity.prototype.dsType = 'Normal'
/**
* Indicate this entity is a UNITY for someone
*
* @type {boolean}
*/
UBEntity.prototype.isUnity = false
/**
* Indicate this entity is a many-to-many storage for attributes of type "Many"
*
* @type {boolean}
*/
UBEntity.prototype.isManyManyRef = false
/**
* This is a Full Text Search entity
*
* @type {boolean}
*/
UBEntity.prototype.isFTSDataTable = false
/**
* Return an entity caption to display on UI
*
* @returns {string}
*/
UBEntity.prototype.getEntityCaption = function () {
return this.caption || this.description
}
/**
* Get entity attribute by code. Return `undefined` if attribute is not found
*
* @param {string} attributeCode
* @param {boolean} [simpleOnly=false] If `false`(default) - parse complex attributes like `attr1.attr2.attr3`
* @returns {UBEntityAttribute}
*/
UBEntity.prototype.attr = function (attributeCode, simpleOnly) {
let res = this.attributes[attributeCode]
if (!res && !simpleOnly) {
res = this.getEntityAttribute(attributeCode)
}
return res
}
/**
* Get entity attribute by code. Throw error if attribute is not found
*
* @param {string} attributeCode
* @returns {UBEntityAttribute}
*/
UBEntity.prototype.getAttribute = function (attributeCode) {
const attr = this.attributes[attributeCode]
if (!attr) {
throw new Error(`Attribute ${this.code}.${attributeCode} doesn't exist`)
}
return attr
}
/**
* @callback entityAttributesIteratorCallback
* @param {UBEntityAttribute} attribute
* @param {string} [attributeName]
* @param {UBEntityAttributes} [attributes]
*/
/**
* Iterates over entity attributes.
* The iteratee is invoked with three arguments: (UBEntityAttribute, attributeName, UBEntityAttributes)
*
* @param {entityAttributesIteratorCallback} callBack
* @returns {object}
*/
UBEntity.prototype.eachAttribute = function (callBack) {
return _.forEach(this.attributes, callBack)
}
/**
* Get entity mixin by code. Returns `undefined` if mixin is not found
*
* @param {string} mixinCode
* @returns {UBEntityMixin}
*/
UBEntity.prototype.mixin = function (mixinCode) {
return this.mixins[mixinCode]
}
/**
* Checks if entity has enabled mixin with specified code
*
* @param {string} mixinCode
* @returns {boolean}
*/
UBEntity.prototype.hasMixin = function (mixinCode) {
const mixin = this.mixins[mixinCode]
if (mixinCode === 'audit') {
return !mixin || (!!mixin && mixin.enabled)
}
return (!!mixin && mixin.enabled)
}
/**
* Checks if entity has mixin. Throw if mixin does not exist or not enabled
*
* @param {string} mixinCode
*/
UBEntity.prototype.checkMixin = function (mixinCode) {
if (!this.hasMixin(mixinCode)) {
throw new Error('Entity ' + this.code + ' does not have mixin ' + mixinCode)
}
}
const STD_MIXINS_ATTRIBUTES = [
'mi_owner', 'mi_createDate', 'mi_createUser', 'mi_modifyDate', 'mi_modifyUser',
'mi_deleteDate', 'mi_deleteUser',
'mi_data_id', 'mi_dateFrom', 'mi_dateTo',
'mi_unityEntity', 'mi_treePath'
]
/**
* Return a JSON representation entity WITHOUT properties which have default values
* Result is very close to meta file.
*
* **WARNING** use carefully inside server thread - method is slow
*
* @param {boolean} [attributesAsArray=true]
* @param {boolean} [removeAttrsAddedByMixin=true]
* @returns {any}
*/
UBEntity.prototype.asPlainJSON = function (attributesAsArray = true, removeAttrsAddedByMixin = true) {
const entityJSON = JSON.parse(JSON.stringify(this, UBDomain.jsonReplacer))
if (removeAttrsAddedByMixin && entityJSON.dsType !== 'Virtual') {
_.forEach(entityJSON.attributes, (attrVal, attrCode) => {
if (attrCode.startsWith('mi_') && STD_MIXINS_ATTRIBUTES.includes(attrCode)) {
delete entityJSON.attributes[attrCode]
}
})
}
if (!attributesAsArray) return entityJSON
// transform {ID: {}, } -> [{name: 'ID',..},..]
const newAttributes = []
for (const attrName in entityJSON.attributes) {
const attr = Object.assign({ name: attrName }, entityJSON.attributes[attrName]) // move name to the first position in JSON
if (attr.mapping) {
if (!Array.isArray(attr.mapping)) {
const newMappings = []
for (const dialectName in attr.mapping) {
// noinspection JSUnfilteredForInLoop
const oldDialect = attr.mapping[dialectName]
const newDialect = Object.assign({ name: dialectName }, oldDialect)
newMappings.push(newDialect)
}
attr.mapping = newMappings
}
}
newAttributes.push(attr)
}
entityJSON.attributes = newAttributes
return entityJSON
}
// noinspection JSDeprecatedSymbols
/**
* Checks if current user has access to a specified entity method
*
* @param {string} methodCode
* @returns {boolean}
*/
UBEntity.prototype.haveAccessToMethod = function (methodCode) {
return ((typeof App !== 'undefined') && App.els) // server side
? App.els(this.code, methodCode)
: this.entityMethods[methodCode] === 1
}
/**
* Filters attributes by properties
*
* @param {object|Function} predicate
* @returns {Array<UBEntityAttribute>}
* @example
// return all attributes where property dataType equal Document
domain.get('uba_user').filterAttribute({dataType: 'Document'});
*/
UBEntity.prototype.filterAttribute = function (predicate) {
if (_.isFunction(predicate)) {
return _.filter(this.attributes, predicate)
} else {
return _.filter(this.attributes, function (item) {
let res = true
for (const prop in predicate) {
if (Object.prototype.hasOwnProperty.call(predicate, prop)) {
res = res && (item[prop] === predicate[prop])
}
}
return res
})
}
}
/**
* Checks if current user has access to at last one of specified methods
*
* @param {Array<string>} methodsCodes
* @returns {boolean}
*/
UBEntity.prototype.haveAccessToAnyMethods = function (methodsCodes) {
const me = this
const fMethods = methodsCodes || []
let result = false
fMethods.forEach(function (methodCode) {
if (UB.isServer && process.isServer) {
result = result || App.els(me.code, methodCode)
} else {
result = result || me.entityMethods[methodCode] === 1
}
})
return result
}
/**
* Checks if current user has access to ALL the specified methods
*
* @param {Array<string>} methods Method names
* @returns {boolean}
*/
UBEntity.prototype.haveAccessToMethods = function (methods) {
const me = this
let result = true
const fMethods = methods || []
fMethods.forEach(function (methodCode) {
if (UB.isServer && process.isServer) {
result = result && App.els(me.code, methodCode)
} else {
result = result && (me.entityMethods[methodCode] === 1)
}
})
return result
}
// noinspection JSUnusedLocalSymbols
/**
* Add entity level method. Client can call such methods remotely. Also such methods are the subjects of ELS.
*
* Property named `methodName` with a type `function` should be added to the entity namespace.
* Such functions accept single parameter of type {@link ubMethodParams}
*
* Don't add methods what do not called from client using {@UBEntity#addMethod}!
*
* **Warning:** do not call UBEntity.addMethod from inside function or conditions.
* This code evaluated during thread initialization and each thread must add method in the same manner.
*
* @example
*
//consider entity with code `my_entity` exists. Inside my_entity.js file):
var me = my_entity;
me.entity.addMethod('externalMethod');
// @param {ubMethodParams} ctx <- here must be JSDoc comment format
me.externalMethod = function (ctx) {
let params = ctx.mParams
let a = params.a || 1
let b = params.b || 1
params.multiplyResult = a*b
}
// now from client side you can call
$App.connection.query({entity: 'my_entity', method: 'externalMethod', a: 10, b:20}).then(function(result){
console.log(' 10 * 20 = ', result.multiplyResult); // will put to log "10 * 20 = 200"
})
* @param {string} methodName
*/
UBEntity.prototype.addMethod = function (methodName) {
throw new Error('UBEntity.addMethod implemented only in HTTP worker thread')
}
/**
* Convert UnityBase server dateTime response to Date object
*
* @private
* @param {*} value
* @returns {Date|null}
*/
function iso8601Parse (value) {
return value ? new Date(value) : null
}
/**
* Convert UnityBase server Boolean response to Boolean (0 = false & 1 = true)
*
* @private
* @param {*} v Value to convert
* @returns {boolean|null}
*/
function booleanParse (v) {
if (typeof v === 'boolean') {
return v
}
if ((v === undefined || v === null || v === '')) {
return null
}
return (v === 1) || (v === '1')
}
/**
* Convert UnityBase server Json response to Object, Return null in case of empty string
*
* @private
* @param {*} v Value to convert
* @returns {*|null}
*/
function jsonParse (v) {
if (typeof v === 'object') {
return v
}
return v ? JSON.parse(v) : null
}
/**
* Convert UnityBase server Enum response to String, Return null in case of empty string
*
* @private
* @param {*} v Value to convert
* @returns {string|null}
*/
function enumParse (v) {
return (typeof v === 'number')
? String(v)
: v || null
}
/**
* Return array of conversion rules for raw server response data
*
* @param {Array<string>} fieldList
* @returns {Array<{index: number, convertFn: Function}>}
*/
UBEntity.prototype.getConvertRules = function (fieldList) {
const me = this
const rules = []
const types = UBDomain.ubDataTypes
fieldList.forEach(function (fieldName, index) {
const attribute = me.attr(fieldName)
if (attribute) {
if (attribute.dataType === types.DateTime) {
rules.push({
index,
convertFn: iso8601Parse
})
} else if (attribute.dataType === types.Date) {
rules.push({
index,
convertFn: iso8601ParseAsDate
})
} else if (attribute.dataType === types.Boolean) {
rules.push({
index,
convertFn: booleanParse
})
} else if (attribute.dataType === types.Json) {
rules.push({
index,
convertFn: jsonParse
})
} else if (attribute.dataType === types.Enum) {
rules.push({
index,
convertFn: enumParse
})
}
}
})
return rules
}
/**
* Returns description attribute name (`descriptionAttribute` metadata property)
* If `descriptionAttribute` is empty - fallback to attribute with code `caption`
*
* @param {boolean} [raiseErrorIfNotExists=true] If `true`(default) and description attribute does not exist throw error,
* if `false` - return `undefined`
* @returns {string|undefined}
*/
UBEntity.prototype.getDescriptionAttribute = function (raiseErrorIfNotExists) {
let result = this.descriptionAttribute || 'caption'
if (!this.attr(result)) {
if ((raiseErrorIfNotExists !== false)) {
throw new Error(`Missing description attribute for entity '${this.code}'`)
} else {
result = undefined
}
}
return result
}
/**
* Returns information about attribute and attribute entity. Understand complex attributes like `firmID.firmType.code`
*
* @example
UB.connection.domain.get('cdn_country').getEntityAttributeInfo('mi_modifyUser.name')
// {entity: 'uba_user', attribute: 'name', parentAttribute: {code: mi_modifyUser, dataType: 'Entity', ....}}
* @param {string} attributeName
* @param {number} [depth=0] If 0 - last, -1 - before last, > 0 - first. Default 0.
* - `0` means last attribute in chain (code from above)
* - `-1` - before last (firmType from above)
* - `>0` - first (firmID from above)
* @returns {{ entity: string, attribute: UBEntityAttribute, parentAttribute: UBEntityAttribute, attributeCode: string }|undefined}
* Either attribute information or undefined if chain not points to attribute
*/
UBEntity.prototype.getEntityAttributeInfo = function (attributeName, depth) {
let currentEntity = this
let currentEntityCode = this.code
/** @type {UBEntityAttribute} */
let parentAttribute = null
/** @type {UBEntityAttribute} */
let attribute = null
let attributeNameParts = []
let partsCount
let currentPart
if (attributeName.indexOf('.') === -1) {
currentPart = attributeName
partsCount = 1
} else {
attributeNameParts = attributeName.split('.')
partsCount = attributeNameParts.length
currentPart = attributeNameParts[0]
}
if (depth && depth > 0) {
return {
entity: currentEntityCode,
attribute: currentEntity.attr(currentPart),
parentAttribute: null,
attributeCode: currentPart
}
}
if (depth === -1) partsCount -= 1 // request for before tail attribute
let currentLevel = 0
while (currentEntity && currentLevel < partsCount) {
if (currentPart.indexOf('@') > -1) {
const complexAttr = currentPart.split('@')
currentEntityCode = complexAttr[1]
currentEntity = this.domain.get(currentEntityCode) // real entity is text after @ parentID.code@org_department
currentPart = complexAttr[0]
}
parentAttribute = attribute
attribute = currentEntity.attributes[currentPart]
if (!attribute) return undefined // attribute not exists in currentEntity
if (attribute.dataType === 'Enum') { // stop on enums. Prev code will check for name also: && attributeName === 'name'
break
} else if ((attribute.dataType === UBDomain.ubDataTypes.Json) &&
(currentLevel + 1 < partsCount)) { // request to the JSON key `attrOfTypeJson.foo`
parentAttribute = attribute
attribute = undefined
break
} else {
currentLevel += 1
if (currentLevel < partsCount) {
currentPart = attributeNameParts[currentLevel]
currentEntityCode = attribute.associatedEntity
currentEntity = attribute.getAssociatedEntity()
}
}
}
return { entity: currentEntityCode, attribute, parentAttribute, attributeCode: currentPart }
}
/**
* Returns entity attribute. Understand complex attributes like `firmID.firmType.code`
*
* @param {string} attributeName
* @param {number} [depth=0] Current recursion depth
* - `0` means last attribute in chain (code from above)
* - `-1` - before last (firmType from above)
* - `>0` - first (firmID from above)
* @returns {UBEntityAttribute}
*/
UBEntity.prototype.getEntityAttribute = function (attributeName, depth) {
const info = this.getEntityAttributeInfo(attributeName, depth)
return info ? info.attribute : undefined
}
/**
* Returns array of entity attribute`s names
*
* @param {object|Function} [predicate] See {@link UBEntity.filterAttribute}. If empty - will return all names
* @returns {Array<string>}
*/
UBEntity.prototype.getAttributeNames = function (predicate) {
const attributes = []
if (predicate) {
_.forEach(this.filterAttribute(predicate), function (attr) {
attributes.push(attr.code)
})
return attributes
} else {
return Object.keys(this.attributes)
}
}
/**
* For each attribute of type `Entity` from `fieldList` add entity code to result (duplicates are removed)
*
* @param {Array<string>} [fieldList] If empty - all entity attributes will be used
* @returns {Array<string>}
*/
UBEntity.prototype.getEntityRequirements = function (fieldList) {
const result = []
fieldList = fieldList || this.getAttributeNames()
for (let i = 0, L = fieldList.length; i < L; ++i) {
const attr = this.getEntityAttribute(fieldList[i])
if (attr && (attr.dataType === UBDomain.ubDataTypes.Entity) &&
(result.indexOf(attr.associatedEntity) === -1)) {
result.push(attr.associatedEntity)
}
}
return result
}
/**
* Checks entity has attribute(s) and throw error if not
*
* @param {string|Array<string>} attributeNames
* @param {string} contextMessage
*/
UBEntity.prototype.checkAttributeExist = function (attributeNames, contextMessage) {
const me = this
attributeNames = !Array.isArray(attributeNames) ? [attributeNames] : attributeNames
attributeNames.forEach(function (fieldName) {
if (!me.getEntityAttributeInfo(fieldName)) {
throw new Error(contextMessage + (contextMessage ? ' ' : '') +
'The entity "' + me.code + '" does not have attribute "' + fieldName + '"')
}
})
}
/**
* Return entity description
*
* @returns {string}
*/
UBEntity.prototype.getEntityDescription = function () {
return this.description || this.caption
}
/**
* Returns an array of UBEntityAttribute with "Entity" data type and pointing to this entity
* (associatedEntity === this entity) and such relation should be visible in the UI "Details" menu.
*
* Excluded attributes are:
* - `mi_*` attributes
* - attributes with customSetting.hiddenInDetails === true
* - all attributes for entities user do not have access to the `select` method
*
* @returns {Array<UBEntityAttribute>}
*/
UBEntity.prototype.getDetailsForUI = function () {
const IS_ATTR_FROM_MIXIN = /^ID|mi_/
const result = []
for (const e of Object.values(this.domain.entities)) {
if (e.name === this.name) continue // Do not allow details for same entity
if (!e.haveAccessToMethod('select')) continue // Must have permission on "select" method
if (e.isManyManyRef) continue // Do NOT generate details for the "Many" ref keeping tables
for (const attr of Object.values(e.attributes)) {
if (attr.dataType !== 'Entity') continue // Ignore all attributes other than simple associations (for example "Many")
if (attr.associatedEntity !== this.name) continue // Associations to the current entity only, not others
if (IS_ATTR_FROM_MIXIN.test(attr.name)) continue // Skip mixin attributes
result.push(attr)
}
}
return result
}
/**
* @class
* @param {object} mapping
*/
function UBEntityAttributeMapping (mapping) {
/**
* @type {UBDomain.ExpressionType}
*/
this.expressionType = mapping.expressionType
/** @type {string} */
this.expression = mapping.expression
}
/**
* Entity attribute
*
* @param {object} attributeInfo
* @param {string} attributeCode
* @param {UBEntity} entity
* @class
*/
function UBEntityAttribute (attributeInfo, attributeCode, entity) {
// i18n already merged by entity constructor
/**
* @type {string}
* @readonly
*/
this.code = attributeCode
/**
* @type {string}
* @readonly
*/
this.name = attributeInfo.name
/**
* Non-enumerable (to prevent JSON.stringify circular ref) read only entity reference
*
* @property {UBEntity} entity
* @readonly
*/
Object.defineProperty(this, 'entity', { enumerable: false, value: entity })
/**
* Data type
*
* @type {UBDomain.ubDataTypes}
* @readonly
*/
this.dataType = attributeInfo.dataType || 'String'
/**
* Name of entity referenced by the attribute (for attributes of type `Many` - entity name from the AssociationManyData)
*
* @type {string}
* @readonly
*/
this.associatedEntity = attributeInfo.associatedEntity
/**
* @type {string}
* @readonly
*/
this.associationAttr = attributeInfo.associationAttr
/**
* @type {string}
* @readonly
*/
this.caption = attributeInfo.caption || ''
/**
* @type {string}
* @readonly
*/
this.description = attributeInfo.description || ''
/**
* @type {string}
* @readonly
*/
this.documentation = attributeInfo.documentation || ''
/**
* @type {number}
* @readonly
*/
this.size = attributeInfo.size || 0
/**
* Attribute value can be empty or null
*
* @type {boolean}
* @readonly
*/
this.allowNull = (attributeInfo.allowNull !== false)
/**
* Allow order by clause by this attribute
*
* @type {boolean}
* @readonly
*/
this.allowSort = (attributeInfo.allowSort !== false)
/**
* @type {boolean}
* @readonly
*/
this.isUnique = (attributeInfo.isUnique === true)
/**
* @type {string}
* @readonly
*/
this.defaultValue = attributeInfo.defaultValue
/**
* Edition allowed (not verified by server side)
*
* @type {boolean}
* @readonly
*/
this.readOnly = (attributeInfo.readOnly === true)
/**
* @type {boolean}
* @readonly
*/
this.isMultiLang = (attributeInfo.isMultiLang === true)
/**
* For attributes of type Entity enable cascade deletion on application server level (not on database level)
*
* @type {boolean}
* @readonly
*/
this.cascadeDelete = (attributeInfo.cascadeDelete === true)
/**
* For attributes of type Enum - code of enumeration group from `ubm_enum.eGroup`
*
* @property {string} enumGroup
* @readonly
*/
this.enumGroup = attributeInfo.enumGroup
/**
* @type {object}
* @readonly
*/
this.customSettings = attributeInfo.customSettings || {}
/**
* For attributes of type Many - name of the many-to-many table. UB create system entity with this name and generate table during DDL generation
*
* @type {string}
* @readonly
*/
this.associationManyData = attributeInfo.associationManyData
/**
* For attributes of type Document - name of BLOB store from application `storeConfig`. If empty - default store will be used
*
* @type {string}
* @readonly
*/
this.storeName = attributeInfo.storeName
/**
* For attributes of type Entity. If `false` - bypass foreign key generation by DDL generator
*
* @type {boolean}
*/
this.generateFK = attributeInfo.generateFK !== false
/**
* If `true` - the client should show this attribute in auto-build forms and in '*' select fields
*
* @type {boolean}
*/
this.defaultView = attributeInfo.defaultView !== false
/**
* Optional mapping of attribute to physical data (for extended domain info only).
* Calculated from an entity mapping collection in accordance with application connection configuration
*
* @type {UBEntityAttributeMapping}
* @readonly
*/
this.mapping = undefined
if (attributeInfo.mapping) {
const me = this
const mappingKeys = Object.keys(attributeInfo.mapping)
mappingKeys.forEach(function (key) {
if (!UBDomain.dialectsPriority[key]) throw new Error(`Invalid dialect ${key} in ${entity.code}.${me.code} mapping`)
})
if (mappingKeys.length) {
const dialectsPriority = UBDomain.dialectsPriority[this.entity.connectionConfig.dialect]
_.forEach(dialectsPriority, function (dialect) {
if (attributeInfo.mapping[dialect]) {
me.mapping = new UBEntityAttributeMapping(attributeInfo.mapping[dialect])
return false // break loop
}
})
}
}
/**
* @property {string} physicalDataType
* @readonly
*/
this.physicalDataType = UBDomain.getPhysicalDataType(this.dataType || 'String')
/**
* Index of type CATALOGUE exists for attribute
*
* @property {boolean} hasCatalogueIndex
* @readonly
*/
this.hasCatalogueIndex = attributeInfo.hasCatalogueIndex === true
/**
* Private settings (for extended domain info)
*
* @property {object} privateSettings
* @readonly
*/
if (attributeInfo.privateSettings) this.privateSettings = attributeInfo.privateSettings
}
// default UBEntityAttribute props - used by JSON.stringify replacer to produce entity JSON representation
UBEntityAttribute.prototype.forJSONReplacer = {
code: null,
name: null,
allowNull: true,
allowSort: true,
isUnique: false,
readOnly: false,
isMultiLang: false,
cascadeDelete: false,
generateFK: true,
defaultView: true,
physicalDataType: null,
hasCatalogueIndex: null
}
/**
* Return associated entity or `null` if type of attribute !==`Entity`
*
* @returns {UBEntity}
*/
UBEntityAttribute.prototype.getAssociatedEntity = function () {
return this.associatedEntity ? this.entity.domain.get(this.associatedEntity) : null
}
/**
* Return a JSON representation of attribute
* WITHOUT properties which have default values
*
* @returns {object}
*/
UBEntityAttribute.prototype.asPlainJSON = function () {
return JSON.parse(JSON.stringify(this, UBDomain.jsonReplacer))
}
/**
* Contains all properties defined in mixin section of an entity meta file
*
* @class
* @protected
* @param {object} mixinInfo
* @param {object} i18n
* @param {string} mixinCode
*/
function UBEntityMixin (mixinInfo, i18n, mixinCode) {
/**
* Mixin code
*
* @type {string}
*/
this.code = mixinCode
_.assign(this, mixinInfo)
if (i18n) {
_.assign(this, i18n)
}
}
UBEntityMixin.prototype.forJSONReplacer = {
enabled: true,
code: null
}
/**
* Is mixin enabled
*
* @type {boolean}
*/
UBEntityMixin.prototype.enabled = true
/**
* Mixin for persisting entity to a database
*
* @class
* @augments UBEntityMixin
* @param {object} mixinInfo
* @param {object} i18n
* @param {string} mixinCode
*/
function UBEntityStoreMixin (mixinInfo, i18n, mixinCode) {
UBEntityMixin.apply(this, arguments)
}
UBEntityStoreMixin.prototype = Object.create(UBEntityMixin.prototype)
UBEntityStoreMixin.prototype.constructor = UBEntityStoreMixin
// defaults
/**
* Is `simpleAudit` enabled
*
* @type {boolean}
*/
UBEntityStoreMixin.prototype.simpleAudit = false
/**
* Use a soft delete
*
* @type {boolean}
*/
UBEntityStoreMixin.prototype.safeDelete = false
/**
* Historical data storage mixin
*
* @class
* @augments UBEntityMixin
* @param {object} mixinInfo
* @param {object} i18n
* @param {string} mixinCode
* @constructor
*/
function UBEntityHistoryMixin (mixinInfo, i18n, mixinCode) {
UBEntityMixin.apply(this, arguments)
}
UBEntityHistoryMixin.prototype = Object.create(UBEntityMixin.prototype)
UBEntityHistoryMixin.prototype.constructor = UBEntityHistoryMixin
/**
* A history storage strategy
*
* @type {string}
*/
UBEntityHistoryMixin.prototype.historyType = 'common'
/**
* Access control list Row Level Security mixin
*
* @class
* @augments UBEntityMixin
* @param {object} mixinInfo
* @param {object} i18n
* @param {string} mixinCode
*/
function UBEntityAclRlsMixin (mixinInfo, i18n, mixinCode) {
UBEntityMixin.apply(this, arguments)
}
UBEntityAclRlsMixin.prototype = Object.create(UBEntityMixin.prototype)
UBEntityAclRlsMixin.prototype.constructor = UBEntityAclRlsMixin
/**
* Full text search mixin
*
* @class
* @augments UBEntityMixin
* @param {object} mixinInfo
* @param {object} i18n
* @param {string} mixinCode
*/
function UBEntityFtsMixin (mixinInfo, i18n, mixinCode) {
UBEntityMixin.apply(this, arguments)
}
UBEntityFtsMixin.prototype = Object.create(UBEntityMixin.prototype)
UBEntityFtsMixin.prototype.constructor = UBEntityFtsMixin
/**
* scope
*
* @type {string}
*/
UBEntityFtsMixin.prototype.scope = 'connection' // sConnection
/**
* Data provider type
*
* @type {string}
*/
UBEntityFtsMixin.prototype.dataProvider = 'mixin'// dcMixin
/**
* Attribute level security mixin
*
* @param {object} mixinInfo
* @param {object} i18n
* @param {string} mixinCode
* @constructor
* @augments UBEntityMixin
*/
function UBEntityAlsMixin (mixinInfo, i18n, mixinCode) {
UBEntityMixin.apply(this, arguments)
}
UBEntityAlsMixin.prototype = Object.create(UBEntityMixin.prototype)
UBEntityAlsMixin.prototype.constructor = UBEntityAlsMixin
/**
* Is optimistic
*
* @type {boolean}
*/
UBEntityAlsMixin.prototype.alsOptimistic = true
UBDomain.UBEntity = UBEntity
UBDomain.UBModel = UBModel
UBDomain.UBEntity.UBEntityAttribute = UBEntityAttribute
module.exports = UBDomain