const http = require('http')
const csShared = require('@unitybase/cs-shared')
const LocalDataStore = csShared.LocalDataStore
const UBSession = csShared.UBSession
const UBDomain = csShared.UBDomain
const CryptoJS = require('@unitybase/cryptojs/core')
const { ServerRepository } = require('./ServerRepository')
CryptoJS.MD5 = require('@unitybase/cryptojs/md5')
// regular expression for URLs server not require authorization.
const NON_AUTH_URLS_RE = /(\/|^)(models|auth|getAppInfo|downloads)(\/|\?|$)/
const bracketsRe = /\[.*]/
/* global nsha256 */
/**
* @typedef ubRequest
* @type {object}
* @property {string} entity
* @property {string} method
* @property {Array<string>} [fieldList]
* @property {Object<string, *>} [execParams]
*/
/**
* @classdesc
* Synchronous server-side connection to the UnityBase instance. To be used only inside UnityBase.
* For Node.js & browser use asynchronous UBConnection class from @unitybase/ub-pub package.
*
* The most used method is {@link SyncConnection#query SyncConnection.query} - an authorized request to `ubql` endpoint.
*
* @example
const SyncConnection = require('@unitybase/base').SyncConnection
const conn = new SyncConnection({URL: 'http://localhost:888'})
conn.onRequestAuthParams = function(){ return {authSchema: 'UB', login: 'admin', password: 'admin'} }
const domain = conn.getDomainInfo();
if (domain.has('my_entity')){
..
}
* @class
* @param {object} options Connection parameters. See {@link module:http http.request} for details
*/
function SyncConnection (options) {
const me = this
const client = http.request(options)
let /** @type {UBDomain} */
_domain
let ubSession = null
const lookupCache = {}
const userDataDefault = { lang: 'en' }
let appInfo = {}
/**
* Internal instance of HTTP client
* @type {ClientRequest}
* @protected
* @readonly
*/
this.clientRequest = client
let appName = client.options.path
let servicePath = client.options.path
if (servicePath.charAt(servicePath.length - 1) !== '/') servicePath = servicePath + '/' // normalize path
if (appName.length !== 1) {
if (appName.charAt(0) === '/') appName = appName.slice(1, 100) // remove leading / from app name
if (appName.charAt(appName.length - 1) === '/') appName = appName.slice(0, appName.length - 1) // remove / from end of app name
}
/**
* Root path to all application-level method
* @type {string}
* @readonly
*/
this.servicePath = servicePath
/**
* Name of UnityBase application
* @type {string}
* @readonly
*/
this.appName = appName
/**
* Callback for resolving user credential.
* Take a {@link SyncConnection} as a parameter, must return authorization parameters object:
*
* {authSchema: authType, login: login, password: password, [apiKey: ]}
*
* For an internal usage (requests from a localhost or other systems, etc.) and in case `authShema == 'UB'` it is possible to pass a
* `apiKey` instead of a password. apiKey is actually a `uba_user.uPasswordHashHexa` content
*
* @type {Function}
*/
this.onRequestAuthParams = null
/**
* @deprecated Do not use this property due to memory overuse - see http://forum.ub.softline.kiev.ua/viewtopic.php?f=12&t=85
* @private
*/
appInfo = this.get('getAppInfo') // non-auth request
const v = appInfo.serverVersion.split('.')
/**
* Server support UBQL v2 (value instead of values)
* @property {boolean} UBQLv2
* @readonly
*/
this.UBQLv2 = ((v[0] >= 'v5') && (v[1] >= 10))
/**
* Return information about how application is configured as returned by `getAppInfo` endpoint
* @returns {object}
*/
this.getAppInfo = function () {
return appInfo
}
/**
* Retrieve application domain information.
* @param {boolean} [isExtended=false] For member of admin group can return a additional domain information,
* such as mappings, connection details, indexes, realPath for models etc.
* @returns {UBDomain}
*/
this.getDomainInfo = function (isExtended = false) {
if (this._cachedDomainIsExtended !== isExtended) {
_domain = null
}
if (!_domain) {
// authorize connection to get a valid user name
if (this.authNeed) this.authorize(false)
const domainData = this.get('getDomainInfo', {
v: 4,
userName: this.userLogin(),
extended: isExtended || undefined
})
_domain = new UBDomain(domainData)
this._cachedDomainIsExtended = isExtended
}
return _domain
}
/**
* Endpoint name for query (`runList` before 1.12, `ubql` after 1.12)
* @type {string}
*/
this.queryMethod = appInfo.serverVersion.startsWith('1.9.') || appInfo.serverVersion.startsWith('1.10.') ? 'runList' : 'ubql'
/**
* Is server require content encryption
* @type {boolean}
* @readonly
*/
this.encryptContent = appInfo.encryptContent || false
/**
* `base64` encoded server certificate used for cryptographic operation
* @type {boolean}
* @readonly
*/
this.serverCertificate = appInfo.serverCertificate || ''
/**
* Lifetime (in second) of session encryption
* @type {number}
* @readonly
*/
this.sessionKeyLifeTime = appInfo.sessionKeyLifeTime || 0
/**
* Possible server authentication method
* @type {Array.<string>}
* @readonly
*/
this.authMethods = appInfo.authMethods
/**
* Is UnityBase server require authorization
* @type {boolean}
* @readonly
*/
this.authNeed = me.authMethods && (me.authMethods.length > 0)
// noinspection JSUnusedGlobalSymbols
/**
* AdminUI settings
* @type {object}
*/
this.appConfig = appInfo.adminUI
/**
* @param {boolean} isRepeat
* @private
* @returns {UBSession}
*/
this.authorize = function (isRepeat) {
let resp, serverNonce, secretWord, pwdHash
if (!ubSession || isRepeat) {
ubSession = null
if (!this.onRequestAuthParams) {
throw new Error('set SyncConnection.onRequestAuthParams function to perform authorized requests')
}
const authParams = this.onRequestAuthParams(this)
if (authParams.authSchema === 'UBIP') {
if (isRepeat) {
throw new Error('UBIP authentication must not return false on the prev.step')
}
resp = this.xhr({ endpoint: 'auth?AUTHTYPE=UBIP', headers: { Authorization: authParams.authSchema + ' ' + authParams.login } })
ubSession = new UBSession(resp, '', authParams.authSchema)
} else if (authParams.authSchema === 'ROOT') {
if (isRepeat) {
throw new Error('ROOT authentication must not return false on the prev.step')
}
resp = this.xhr({ endpoint: 'auth?AUTHTYPE=ROOT', headers: { Authorization: authParams.authSchema + ' ' + process.rootOTP() } })
ubSession = new UBSession(resp, '', authParams.authSchema)
} else {
resp = this.get('auth', {
AUTHTYPE: authParams.authSchema || 'UB',
userName: authParams.login
})
const clientNonce = Math.trunc(1e17 * Math.random()).toString(16)
const request2 = {
AUTHTYPE: authParams.authSchema || 'UB',
userName: authParams.login
}
const request2body = {
clientNonce
}
if (resp.connectionID) {
request2.connectionID = resp.connectionID
}
if (resp.realm) { // LDAP
serverNonce = resp.nonce
if (!serverNonce) {
throw new Error('invalid LDAP auth response')
}
request2body.password = Buffer.from(authParams.password).toString('base64')
secretWord = request2body.password // todo - very unsecured to be used only over SSL!!
} else {
serverNonce = resp.result
if (!serverNonce) {
throw new Error('invalid auth response')
}
if (authParams.apiKey) {
pwdHash = authParams.apiKey
} else {
pwdHash = nsha256('salt' + authParams.password)
}
request2body.password = nsha256(appName.toLowerCase() + serverNonce + clientNonce + authParams.login + pwdHash).toString()
secretWord = pwdHash
}
resp = this.post('auth', request2body, request2)
ubSession = new UBSession(resp, secretWord, authParams.authSchema)
}
}
return ubSession
}
/**
* Check is current connection already perform authentication request
* @returns {boolean}
*/
this.isAuthorized = function () {
return Boolean(ubSession)
}
/**
* Return current user logon or 'anonymous' in case not logged in
* @returns {string}
*/
this.userLogin = function () {
return this.isAuthorized() ? ubSession.logonname : 'anonymous'
}
/**
* Return current user language or 'en' in case not logged in
* @returns {string}
*/
this.userLang = function () {
return this.userData('lang')
}
/**
* Return custom data for logged in user, or {lang: 'en'} in case not logged in
*
* If key is provided - return only key part of user data
*
* @example
$App.connection.userData('lang')
// or the same but dedicated alias
$App.connection.userLang()
* @param {string} [key] Optional key
* @returns {*}
*/
this.userData = function (key) {
const uData = this.isAuthorized() ? ubSession.userData : userDataDefault
return key ? uData[key] : uData
}
/**
* Lookup value in entity using a Condition
* @example
// create condition using Repository
const myID = conn.lookup('ubm_enum', 'ID',
conn.Repository('ubm_enum').where('eGroup', '=', 'UBA_RULETYPE').where('code', '=', 'A').ubql().whereList
)
// or pass condition directly
const adminID = conn.lookup('uba_user', 'ID', {
expression: 'name', condition: 'equal', value: 'admin'}
})
* @param {string} aEntity - entity to lookup
* @param {string} lookupAttribute - attribute to lookup
* @param {string|object} aCondition - lookup condition. String in case of custom expression,
* or whereListItem {expression: condition: value: },
* or whereList {condition1: {expression: condition: value: }, condition2: {}, ....}
* @param {boolean} [doNotUseCache=false]
* @returns {*} `lookupAttribute` value of first result row or null if not found.
*/
this.lookup = function (aEntity, lookupAttribute, aCondition, doNotUseCache) {
const me = this
const cKey = aEntity + JSON.stringify(aCondition) + lookupAttribute
let request
if (!doNotUseCache && lookupCache.hasOwnProperty(cKey)) {
return lookupCache[cKey] // found in cache
} else {
request = this.Repository(aEntity).attrs(lookupAttribute).limit(1).ubql()
if (typeof aCondition === 'string') {
if (!bracketsRe.test(aCondition)) {
aCondition = `[${aCondition}]`
}
request.whereList = { lookup: { expression: aCondition, condition: 'custom' } }
} else if (aCondition.expression && (typeof aCondition.expression === 'string')) {
if (!bracketsRe.test(aCondition.expression)) {
aCondition.expression = `[${aCondition.expression}]`
}
request.whereList = { lookup: aCondition }
} else {
request.whereList = aCondition
}
const resData = me.query(request).resultData.data
if ((resData.length === 1) && (resData[0][0] != null)) { // `!= null` is equal to (not null && not undefined)
if (!doNotUseCache) {
lookupCache[cKey] = resData[0][0]
}
return resData[0][0]
} else {
return null
}
}
}
}
/**
* Perform authorized UBQL request.
* Can take one QB Query or an array of UB Query and execute it at once.
* @param {object|Array<object>} ubq
* @returns {object|Array}
*/
SyncConnection.prototype.query = function (ubq) {
if (Array.isArray(ubq)) {
return this.xhr({ endpoint: this.queryMethod, data: ubq })
} else {
return this.xhr({ endpoint: this.queryMethod, data: [ubq] })[0]
}
}
/**
* HTTP request to UB server. In case of success response return body parsed to {object} or {ArrayBuffer} depends on Content-Type response header
*
* @example
conn.xhr({
endpoint: 'runSQL',
URLParams: {CONNECTION: 'dba'},
data: 'DROP SCHEMA IF EXISTS ub_autotest CASCADE; DROP USER IF EXISTS ub_autotest;'
})
* @param {object} options
* @param {string} options.endpoint
* @param {string} [options.UBMethod] This parameter is **DEPRECATED**. Use `options.endpoint` instead
* @param {string} [options.HTTPMethod='POST']
* @param {object} [options.headers] Optional request headers in format {headerName: headerValue, ...}
* @param {boolean} [options.simpleTextResult=false] do not parse response and return it as is even if response content type is JSON
* @param {*} [options.URLParams] Optional parameters added to URL using http.buildURL
* @param {ArrayBuffer|object|string} [options.data] Optional body
* @param {string} [options.responseType] see <a href="https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType">responseType</a>.
* Currently only `arraybuffer` supported.
* @returns {ArrayBuffer|object|string|Array<object>}
*/
SyncConnection.prototype.xhr = function (options) {
const me = this
const req = this.clientRequest
let resp
let result = {}
let path = this.servicePath + (options.endpoint || options.UBMethod)
if (options.URLParams) {
path = http.buildURL(path, options.URLParams)
}
if (me.authNeed && !NON_AUTH_URLS_RE.test(path)) { // request need authentication
const session = me.authorize(me._inRelogin)
req.setHeader('Authorization', session.authHeader())
}
req.setMethod(options.HTTPMethod || 'POST') // must be after auth request!
req.setPath(path)
if (options.headers) {
for (const key in options.headers) {
req.setHeader(key, options.headers[key])
}
}
if (options.data) {
if (options.data.toString() === '[object ArrayBuffer]') {
req.setHeader('Content-Type', 'application/octet-stream')
} else {
req.setHeader('Content-Type', 'application/json;charset=utf-8')
}
resp = req.end(options.data)
} else {
resp = req.end()
}
const status = resp.statusCode
if (status >= 200 && status < 300) {
if (options.responseType === 'arraybuffer') {
result = resp.read('bin')
} else if (((resp.headers['content-type'] || '').indexOf('json') >= 0) && !options.simpleTextResult) {
const txtRes = resp.read()
result = txtRes ? JSON.parse(txtRes) : null
} else {
result = resp.read() // return string reads as UTF-8
}
} else if (status === 401 && me.isAuthorized()) { // re-login
if (me._inRelogin) {
me._inRelogin = false
throw new Error('invalid user name or password')
}
me._inRelogin = true
console.debug('Session expire - repeat auth request')
try {
result = me.xhr(options)
} finally {
me._inRelogin = false
}
} else {
if ((status >= 400) && ((resp.headers['content-type'] || '').indexOf('json') >= 0)) { // server report error and body is JSON
const respObj = JSON.parse(resp.read())
if (respObj.errMsg) {
throw new Error('Server error: "' + respObj.errMsg)
} else {
throw new Error('HTTP communication error: "' + status + ': ' + http.STATUS_CODES[status] + '" during request to: ' + path)
}
} else {
throw new Error('HTTP communication error: "' + status + ': ' + http.STATUS_CODES[status] + '" during request to: ' + path)
}
}
return result
}
/**
* Perform get request to `endpoint` with optional URLParams.
* @param {string} endpoint
* @param {*} [URLParams]
* @returns {ArrayBuffer|object|string}
*/
SyncConnection.prototype.get = function (endpoint, URLParams) {
const params = {
endpoint,
HTTPMethod: 'GET'
}
if (URLParams) { params.URLParams = URLParams }
return this.xhr(params)
}
/**
* Shortcut method to perform authorized `POST` request to application we connected
* @param {string} endpoint
* @param {ArrayBuffer|object|string} data
* @param {object} [URLParams]
* @returns {ArrayBuffer|object|string|Array<object>}
*/
SyncConnection.prototype.post = function (endpoint, data, URLParams) {
if (URLParams) {
return this.xhr({ endpoint, data, URLParams })
} else {
return this.xhr({ endpoint, data })
}
}
/**
* Shortcut method to perform authorized `POST` request to `ubql` endpoint
* @private
* @deprecated Since UB 1.11 use SyncConnection.query
* @param {Array<ubRequest>} runListData
* @returns {object}
*/
SyncConnection.prototype.runList = function (runListData) {
return this.xhr({ endpoint: this.queryMethod, data: runListData })
}
/**
* Send request to any endpoint. For entity-level method execution (`ubql` endpoint) better to use {@link SyncConnection#query SyncConnection.query}
* @returns {*} body of HTTP request result. If !simpleTextResult and response type is json - then parsed to object
*/
SyncConnection.prototype.runCustom = function (endpoint, aBody, aURLParams, simpleTextResult, aHTTPMethod) {
return this.xhr({ HTTPMethod: aHTTPMethod || 'POST', endpoint: endpoint, URLParams: aURLParams, data: aBody, simpleTextResult: simpleTextResult })
// throw new Error ('Use one of runList/run/post/xhr SyncConnection methods');
}
/**
* Shortcut method to perform authorized `POST` request to `ubql` endpoint.
* Can take one ubRequest and wrap it to array
* @deprecated Since UB 1.11 use SyncConnection.query
* @param {ubRequest} request
* @returns {object}
*/
SyncConnection.prototype.run = function (request) {
return this.xhr({ endpoint: this.queryMethod, data: [request] })[0]
}
/**
* Logout from server if logged in
*/
SyncConnection.prototype.logout = function () {
if (this.isAuthorized()) {
try {
this.post('logout', '')
} catch (e) {
}
}
}
/**
* Saves a file content as a potential value of the specified entity instance attribute to the TEMP store.
*
* Call this function before entity insert of update. Result of this function is what shall be assigned to the
* attribute value, to "execParams".
* @param {string} entity Entity name
* @param {string} attribute Entity attribute name
* @param {number} id ID of the record
* @param {ArrayBuffer|string} data File content
* @param {string} origName
* @param {string} [fileName] If not specified, origName will be used.
* @param {string} dataEncoding Specify `data` parameter encoding. Either omit for binary data
* or set to `base64` for base64 encoded data
* @returns {string}
*
* @example
const myObj = conn.Repository(entityName)
.attrs('ID', 'mi_modifyDate')
.where('code', '=', code)
.selectSingle()
const {ID, mi_modifyDate} = myObj
const data = fs.readFileSync(fileName, {encoding: 'bin'})
const tempStoreResult = conn.setDocument(entityName, 'configuration', ID, data, fn)
conn.query({
entity: entityName,
method: 'update',
execParams: {ID, configuration: tempStoreResult, mi_modifyDate}
})
*/
SyncConnection.prototype.setDocument = function (entity, attribute, id, data, origName, fileName, dataEncoding) {
const urlParams = {
entity,
attribute,
id,
origName: origName || fileName,
filename: fileName || origName
}
if (dataEncoding) urlParams.encoding = dataEncoding
const setDocumentResponse = this.xhr({
HTTPMethod: 'POST',
endpoint: 'setDocument',
data,
URLParams: urlParams
})
return JSON.stringify(setDocumentResponse.result)
}
const ALLOWED_GET_DOCUMENT_PARAMS = ['entity', 'attribute', 'ID', 'id', 'isDirty', 'forceMime', 'fileName', 'store', 'revision']
/**
* Retrieve content of `document` type attribute field from server
* @example
//Retrieve content of document as string using GET
let frmContent = conn.getDocument({
entity:'ubm_form',
attribute: 'formDef',
ID: 100000232003
})
console.log(typeof frmContent)
//The same, but using POST for bypass cache
let frmContent = conn.getDocument({
entity:'ubm_form',
attribute: 'formDef',
ID: 100000232003
}, {
bypassCache: true
})
console.log(typeof frmContent) // string
//Retrieve content of document as ArrayBuffer and bypass cache
let frmContent = conn.getDocument({
entity:'ubm_form',
attribute: 'formDef',
ID: 100000232003
}, {
bypassCache: true, resultIsBinary: true
})
console.log('Result is', typeof frmContent, 'of length' , frmContent.byteLength, 'bytes'); //output: Result is object of length 2741 bytes
*
* @param {object} params
* @param {string} params.entity Code of entity to retrieve from
* @param {string} params.attribute `document` type attribute code
* @param {number} params.id Instance ID
* @param {string} [params.forceMime] If passed and server support transformation from source MIME type to `forceMime` server perform transformation and return documenRt representation in the passed MIME
* @param {number} [params.revision] Optional revision of the documnet (if supported by server-side store configuration). Default is current revision.
* @param {string} [params.fileName] ????
* @param {boolean} [params.isDirty=false] Optional ability to retrieve document in **dirty** state
* @param {string} [params.store] ????
*
* @param {object} [options] Additional request options
* @param {boolean} [options.resultIsBinary=false] if true - return document content as arrayBuffer
* @param {boolean} [options.bypassCache] HTTP POST verb will be used instead of GET for bypass browser cache
* @returns {ArrayBuffer|string} Document content (either ArrayBuffer in case options.resultIsBinary===true or text/json)
*/
SyncConnection.prototype.getDocument = function (params, options) {
const opt = Object.assign({}, options)
const reqParams = {
endpoint: 'getDocument',
HTTPMethod: opt.bypassCache ? 'POST' : 'GET'
}
if (options && options.resultIsBinary) {
reqParams.responseType = 'arraybuffer'
}
if (opt.bypassCache) {
reqParams.data = Object.assign({}, params)
Object.keys(reqParams.data).forEach(function (key) {
if (ALLOWED_GET_DOCUMENT_PARAMS.indexOf(key) === -1) {
delete reqParams.data[key]
}
})
} else {
reqParams.URLParams = params
}
return this.xhr(reqParams)
}
/**
* Execute insert method by add method: 'insert' to `ubq` query (if req.method not already set)
*
* If `ubq.fieldList` contain only `ID` return inserted ID, else return array of attribute values passed to `fieldList`.
* If no field list passed at all - return response.resultData (null usually).
*
* @example
const testRole = conn.insert({
entity: 'uba_role',
fieldList: ['ID', 'mi_modifyDate'],
execParams: {
name: 'testRole1',
allowedAppMethods: 'runList'
}
})
console.log(testRole) //[3000000000200,"2014-10-21T11:56:37Z"]
const testRoleID = conn.insert({
entity: 'uba_role',
fieldList: ['ID'],
execParams: {
name: 'testRole1',
allowedAppMethods: 'runList'
}
})
console.log(testRoleID) //3000000000200
*
* @param {ubRequest} ubq
* @returns {*}
*/
SyncConnection.prototype.insert = function (ubq) {
const req = ubq
req.method = req.method || 'insert'
const res = this.query(req)
if (req.fieldList) {
return ((req.fieldList.length === 1) && (req.fieldList[0] === 'ID')) ? res.resultData.data[0][0] : res.resultData.data[0]
} else {
return res.resultData
}
}
/**
* Run UBQL command with `insert` method
*
* In case `fieldList` is passed - result will contains new values for attributes specified in `fieldList` as Object, otherwise - null
*
* In opposite to `insert` method values in result are PARSED based on Domain (as in AsyncConnection) - so values
* for boolean attributes is true/false, date is typeof Date etc.
*
* @param {ubRequest} ubq
* @param {Object<string, string>} [fieldAliases] Optional object to change attribute names during transform array to object. Keys are original names, values - new names
* @returns {object}
*
* @example
const newRole = conn.insertAsObject({
entity: 'uba_role',
fieldList: ['ID', 'name', 'allowedAppMethods', 'mi_modifyDate'],
execParams: {
name: 'testRole61',
allowedAppMethods: 'runList'
}
}, {mi_modifyDate: 'modifiedAt'})
console.log(newRole) // {ID: 332462911062017, name: 'testRole1', allowedAppMethods: 'runList', mi_modifyDate: 2020-12-21T15:45:01.000Z}
console.log(newRole.modifiedAt instanceof Date) //true
*/
SyncConnection.prototype.insertAsObject = function (ubq, fieldAliases) {
const req = ubq
req.method = req.method || 'insert'
const serverResp = this.query(req)
const res = LocalDataStore.convertResponseDataToJsTypes(this.getDomainInfo(), serverResp)
return (res.resultData && res.resultData.data && res.resultData.data.length)
? LocalDataStore.selectResultToArrayOfObjects(res, fieldAliases)[0]
: null
}
/**
* Execute addnew method: add method 'addnew' to `ubq` query (if req.method not already set)
*
* If `ubq.fieldList` contain only `ID` return generated ID, otherwise return array of attribute values passed to `fieldList`.
* If no field list passed at all - return response.resultData (null usually).
*
* @example
const testRole = conn.addNew({
entity: 'uba_role',
fieldList: ['ID', 'name', 'allowedAppMethods'],
execParams: {
name: 'testRole1',
allowedAppMethods: 'runList'
}
})
console.log(testRole) //[3000000000200,"testRole1","runList"]
const testRoleID = conn.addNew({
entity: 'uba_role',
fieldList: ['ID']
})
console.log(testRoleID) //3000000000200
*
* @param {ubRequest} ubq
* @returns {*}
*/
SyncConnection.prototype.addNew = function (ubq) {
const req = ubq
req.method = req.method || 'addnew'
const res = this.query(req)
if (!req.fieldList) {
return res.resultData
} else if (req.fieldList.length === 1 && req.fieldList[0] === 'ID') {
return res.resultData.data[0][0]
} else {
return res.resultData.data[0]
}
}
/**
* Run UBQL command with `addnew` method
*
* In case `fieldList` is passed - result will contains new values for attributes specified in `fieldList` as Object, otherwise - null
*
* In opposite to `addNew` method values in result are PARSED based on Domain (as in AsyncConnection) - so values
* for boolean attributes is true/false, date is typeof Date etc.
*
* @param {ubRequest} ubq
* @param {Object<string, string>} [fieldAliases] Optional object to change attribute names during transform array to object. Keys are original names, values - new names
* @returns {object}
*
* @example
const newRole = conn.addNewAsObject({
entity: 'uba_role',
fieldList: ['ID', 'name', 'allowedAppMethods'],
execParams: {
name: 'testRole61',
allowedAppMethods: 'runList'
}
})
console.log(newRole) // {ID: 332462911062017, name: 'testRole1', allowedAppMethods: 'runList'}
*/
SyncConnection.prototype.addNewAsObject = function (ubq, fieldAliases) {
const req = ubq
req.method = req.method || 'addnew'
const serverResp = this.query(req)
const res = LocalDataStore.convertResponseDataToJsTypes(this.getDomainInfo(), serverResp)
return (res.resultData && res.resultData.data && res.resultData.data.length)
? LocalDataStore.selectResultToArrayOfObjects(res, fieldAliases)[0]
: null
}
/**
* Execute update method (adds method: 'update' if `req.method` is not already set)
* @param {UBQL} ubq
* @returns {object}
*/
SyncConnection.prototype.update = function (ubq) {
const req = ubq
req.method = req.method || 'update'
const res = this.query(req)
return res.resultData
}
/**
* Run UBQL command with `update` method
*
* In case `fieldList` is passed - result will contain new values for attributes specified in `fieldList` as Object, otherwise - null
*
* In opposite to `update` method values in result are PARSED based on Domain (as in AsyncConnection) - so values
* for boolean attributes is true/false, date is typeof Date etc.
*
* @param {ubRequest} ubq
* @param {Object<string, string>} [fieldAliases] Optional object to change attribute names during transform array to object. Keys are original names, values - new names
* @returns {object}
*
* @example
const newRole = conn.updateAsObject({
entity: 'uba_role',
fieldList: ['ID', 'name', 'allowedAppMethods', 'mi_modifyDate'],
execParams: {
ID: 123,
name: 'testRole61'
}
}, {mi_modifyDate: 'modifiedAt'})
console.log(newRole) // {ID: 332462911062017, name: 'testRole1', allowedAppMethods: 'runList', mi_modifyDate: 2020-12-21T15:45:01.000Z}
console.log(newRole.modifiedAt instanceof Date) //true
*/
SyncConnection.prototype.updateAsObject = function (ubq, fieldAliases) {
const req = ubq
req.method = req.method || 'update'
const serverResp = this.query(req)
const res = LocalDataStore.convertResponseDataToJsTypes(this.getDomainInfo(), serverResp)
return (res.resultData && res.resultData.data && res.resultData.data.length)
? LocalDataStore.selectResultToArrayOfObjects(res, fieldAliases)[0]
: null
}
/**
* Create a new instance of repository
* @param {string|object} entityCodeOrUBQL The name of the Entity for which the Repository is being created or UBQL
* @returns {ServerRepository}
*/
SyncConnection.prototype.Repository = function (entityCodeOrUBQL) {
if (typeof entityCodeOrUBQL === 'string') {
return new ServerRepository(this, entityCodeOrUBQL)
} else {
return new ServerRepository(this, '').fromUbql(entityCodeOrUBQL)
}
}
module.exports = SyncConnection