const EventEmitter = require('events').EventEmitter
const UBDomain = require('@unitybase/cs-shared').UBDomain
const ServerRepository = require('@unitybase/base').ServerRepository
const repositoryFabric = ServerRepository.fabric // for backward compatibility with UB 1.7
const App = require('./modules/App')
const Session = require('./modules/Session')
const format = require('@unitybase/base').format
const blobStores = require('@unitybase/blob-stores')
const TubDataStore = require('./TubDataStore')
const Errors = require('./modules/ubErrors')
// TODO - this hack is required for register UB.getWSNotifier. Must be rewrites
const ws = require('./modules/web-sockets')
const mI18n = require('./modules/i18n')
const modelLoader = require('./modules/modelLoader')
const mStorage = require('./mixins/mStorage')
//if (typeof global['UB'] !== 'undefined') throw new Error('@unitybase/ub already required')
/**
* @module @unitybase/ub
*/
let UB = module.exports = {
/**
* If we are in UnityBase server scripting (both -f or server thread) this property is true, if in browser - undefined or false.
* Use it for check execution context in scripts, shared between client & server.
* To check we are in server thread use process.isServer.
* @readonly
*/
isServer: true,
/**
* Server-side Abort exception. To be used in server-side logic in case of HANDLED
* exception. This errors logged using "Error" log level to prevent unnecessary
* EXC log entries.
* @type {UBAbort}
*/
UBAbort: Errors.UBAbort,
/**
* Server-side Security exception. Throwing of such exception will trigger
* `Session.securityViolation` event
* @type {ESecurityException}
*/
ESecurityException: Errors.ESecurityException,
ns: ns,
format: format,
/**
* Create new instance of {@link ServerRepository}
*
* @param {String} entityName
* @param {Object} [cfg]
* @param {SyncConnection} [connection] Pass in case of remote UnityBase server connection.
* @returns {ServerRepository}
*/
Repository: function (entityName, cfg, connection) {
connection = connection || global.conn
return repositoryFabric(entityName, connection)
},
getWSNotifier: ws.getWSNotifier,
/**
* Information about the logged in user
* @type {Session}
*/
Session: Session,
/**
* Construct new data store
* @param {string} entityCode
* @return {TubDataStore}
* @constructor
*/
DataStore: function (entityCode) {
return new TubDataStore(entityCode)
},
/**
* Translate message specified language using data, prepared by `UB.i18nExtend`
* To add model-depended values in your model use {@link module:@unitybase/ub#i18nExtend UB.i18nExtend}
* @param {String} msg Message to translate
* @param {String} [lang] language to translate to. if not passed - current user session language used, or default application language if not logged in
*/
i18n: function i18n (msg, lang) {
lang = lang || Session.userLang || App.defaultLang
let res = mI18n.lookup(lang, msg)
return res || msg
},
/**
* Merge localizationObject to UB.i18n
* @example
const UB = require('@unitybase/ub')
UB.i18nExtend({
"en": {yourMessage: "yourTranslationToEng", ...},
"uk": {yourMessage: "yourTranslationToUk", ...},
....
})
// if logged in user language is `en` will output "yourTranslationToEng"
console.log(UB.i18n(yourMessage))
// will output "yourTranslationToUk"
console.log(UB.i18n(yourMessage, 'uk'))
* @param {Object} localizationObject
*/
i18nExtend: mI18n.extend,
/**
* For UB < 4.2 compatibility - require all non - entity js
* in specified folder add it's subfolder (one level depth),
* exclude `modules` and `node_modules` subfolder's.
* From 'public' subfolder only cs*.js are required.
* To be called in model index.js as such:
* const modelLoader = require('@unitybase/ub).modelLoader
*
* For new models we recommend to require non-entity modules manually
* @param {String} folderPath
* @param {Boolean} isFromPublicFolder
* @param {number} depth Current recursion depth
*/
loadLegacyModules: modelLoader.loadLegacyModules,
/**
* Application instance
* @type {App}
*/
App: App,
start: start,
mixins: {
mStorage: mStorage
}
}
/**
* Initialize UnityBase application:
* - create namespaces (global objects) for all `*.meta` files from domain
* - require all packages specified in config `application.domain.models`
* - emit {@link event:domainIsLoaded App.domainIsLoaded} event
* - register build-in UnityBase {@link module:@unitybase/ub.module:endpoints endpoints}
*/
function start () {
normalizeEnums()
initializeDomain()
/**
* @deprecated Use `const UB = require('@unitybase/ub')`
*/
global.UB = UB
/**
* @deprecated Use `const App = require('@unitybase/ub').App`
*/
global.App = App
// for each model:
// - load all entities modules
// - require a model itself
let orderedModels = App.domainInfo.orderedModels
orderedModels.forEach((model) => {
if (model.realPath && (model.name !== 'UB')) { // UB already loaded by UB.js
modelLoader.loadEntitiesModules(model.realPath)
require(model.realPath)
}
})
App.emit('domainIsLoaded')
blobStores.initBLOBStores(App, Session)
// ENDPOINTS
const {clientRequireEp, modelsEp, getAppInfoEp, getDomainInfoEp, staticEp, runSQLEp, restEp, allLocalesEp} = require('./modules/endpoints')
App.registerEndpoint('getAppInfo', getAppInfoEp, false)
App.registerEndpoint('models', modelsEp, false)
App.registerEndpoint('clientRequire', clientRequireEp, false)
App.registerEndpoint('getDomainInfo', getDomainInfoEp, true)
App.registerEndpoint('getDocument', blobStores.getDocumentEndpoint, true)
App.registerEndpoint('setDocument', blobStores.setDocumentEndpoint, true)
App.registerEndpoint('runSQL', runSQLEp, true)
App.registerEndpoint('rest', restEp, true)
App.registerEndpoint('allLocales', allLocalesEp, false)
App.registerEndpoint('statics', staticEp, false, true)
}
// normalize ENUMS TubCacheType = {Entity: 1, SessionEntity: 2} => {Entity: 'Entity', SessionEntity: 'SessionEntity'}
function normalizeEnums () {
let enums = ['TubftsScope', 'TubCacheType', 'TubEntityDataSourceType', 'TubEntityDataSourceType',
'TubAttrDataType', 'TubSQLExpressionType', 'TubSQLDialect', 'TubSQLDriver']
enums.forEach(eN => {
let e = global[eN]
if (!e) return
let vals = Object.keys(e)
vals.forEach(k => { e[k] = k })
})
}
// domain initialization
function initializeDomain () {
const {addEntityMethod} = process.binding('ub_app')
const {getDomainInfo} = process.binding('ub_app')
// create scope for all domain objects
const tempDomain = new UBDomain(JSON.parse(getDomainInfo(true)))
tempDomain.eachEntity(entity => {
let e = global[entity.code] = { }
Object.defineProperty(e, 'entity', {
writable: false,
enumerable: true,
value: entity
})
EventEmitter.call(e)
Object.assign(e, EventEmitter.prototype)
e.entity.addMethod = (function (entityCode) {
return function (methodCode) {
addEntityMethod(entityCode, methodCode)
}
})(entity.code)
})
// TODO - validate domain
// 1) if (FAttr.dataType = adtDocument) and (FAttr.storeName <> '') and
// ubLog.Add.Log(sllError, 'DomainLoad: Error loading entity "%". A storeName="%" specified in attribute "%" not in defined in application.blobStore config)',
// 2) fLog.Log(sllInfo, 'Check blob store "%" folder "%": folder exists', [fAppConfig.blobStores[i].name, fAppConfig.blobStores[i].path]);
}
/**
* Creates namespaces to be used for scoping variables and classes so that they are not global.
*
* UB.ns('DOC.Report');
* DOC.Report.myReport = function() { ... };
*
* @deprecated Try to avoid namespaces - instead create a modules and use require()
* @param {String} namespacePath
* @return {Object} The namespace object.
*/
function ns (namespacePath) {
let root = global
let parts = namespacePath.split('.')
for (let j = 0, subLn = parts.length; j < subLn; j++) {
let part = parts[j]
if (!root[part]) {
root[part] = {}
}
root = root[part]
}
return root
}
/**
* @type Session
* @deprecated Use `const Session = require('@unitybase/ub').Session`
*/
global.Session = Session
// legacy 1.12
require('./modules/RLS')
module.exports = UB