const csShared = require('@unitybase/cs-shared')
const LocalDataStore = csShared.LocalDataStore
const CustomRepository = csShared.CustomRepository
/**
* @classdesc
* Extend CustomRepository to be used for client-side data retrieve
* Implements:
* - {@link class:ClientRepository#select select} method for retrieve `array of object` representation of server entity
* - {@link class:ClientRepository#selectAsArray selectAsArray} method for retrieve `array of array` representation of server entity
* - {@link class:ClientRepository#selectAsStore selectAsStore} method for retrieve {UB.ux.data.UBStore} (applicable only for Ext-based client types)
*
* Usually created using <a href='../server-v5/ServerRepository.html'>UB.Repository</a> fabric function
*
* @example
const store = UB.Repository('my_entity').attrs(['ID', 'code'])
.where('code', 'includes', ['1', '2', '3']) // code in ('1', '2', '3')
.where('name', 'contains', 'Homer'). // name like '%homer%'
//(birthday >= '2012-01-01') AND (birthday <= '2012-01-02')
.where('birthday', 'geq', new Date()).where('birthday', 'leq', new Date() + 10)
.where('[age] -10', '>=', {age: 15}, 'byAge') // (age + 10 >= 15)
.where('', 'match', 'myvalue') // for condition match expression not need
.logic('(byStrfType OR bySrfKindID)AND(dasdsa)')
.select().then(function(response){
// here response is in [{ID: 10, code: 'value1'}, .... {}] format
})
* @class ClientRepository
* @augments CustomRepository
*/
class ClientRepository extends CustomRepository {
/**
* @param {UBConnection} connection
* @param {String} entityName name of Entity we create for
*/
constructor (connection, entityName) {
super(entityName)
/**
* @property {UBConnection} connection
* @private
*/
Object.defineProperty(this, 'connection', { enumerable: false, writable: false, value: connection })
/**
* Raw result of method execution. Available after Promise of select* method execution is resolved.
* Can be used to get additional response parameters such as `resultLock` or `resultAls`.
* ** Client repository only **
*
* @example
// get lock information together with `select` execution
let repo = UB.Repository('tst_document').attrs('ID').misc({lockType: 'None'}).where('ID', '=', 332729226657819)
let data = await repo.selectSingle()
let lockInfo = repo.rawResult.resultLock
* @type {object|undefined}
*/
this.rawResult = undefined
}
/**
* @override
*/
_getDomain () {
return this.connection ? this.connection.domain : null
}
/**
* For cached entities check all attributes from where/order is in fieldList and adds missed. Fix for [#107]
*
* @private
*/
addAttrsForCachedEntity () {
if (!this.connection || !this.connection.domain || this.__misc.__mip_disablecache === true) return
/** @type {UBEntity} */
const e = this.connection.domain.get(this.entityName, false)
if (!e || !e.cacheType || (e.cacheType === 'None')) return
const addAttrIfNotAdded = (expr) => {
if (!expr) return
let exprNb = ''
if (expr[0] === '[') {
exprNb = expr.slice(1, -1) // remove []
}
if (!this.fieldList.includes(expr) && (!exprNb || !this.fieldList.includes(exprNb))) {
this.attrs(exprNb || expr)
}
}
for (const wn in this.whereList) {
const clause = this.whereList[wn]
if ((clause.condition !== 'subquery') && (clause.condition !== 'custom')) {
addAttrIfNotAdded(clause.expression)
}
}
for (const orderItem of this.orderList) {
addAttrIfNotAdded(orderItem.expression)
}
}
/**
* Asynchronously run request, constructed by Repository. Return promise, resolved to `array of object`
* representation of response.
*
* @example
UB.Repository('ubm_navshortcut').attrs(['ID', 'code'])
.where('code', 'in', ['uba_user', 'uba_auditTrail'])
.selectAsObj().then(function(store) {
console.log(store)
// output is [{"ID":3000000000004,"code":"uba_user"},{"ID":3000000000039,"code":"ubs_audit"}]
})
// Optionally can rename attributes in the resulting object:
UB.Repository('investment')
.attrs(['ID', 'product', 'product.name', 'product.provider.name'])
.selectAsObject({
'product.name': 'productName',
'product.provider.name': 'productProviderName'
}).then(function(result){
console.log(result);
// output [{"ID": 1, "productName": "My product", "productProviderName": "My provider"}, ...]
})
* @param {Object<string, string>} [fieldAliases] Optional object to change attribute
* names during transform array to object
* @returns {Promise<Array<object>>}
*/
selectAsObject (fieldAliases) {
return this.selectAsArray().then(resp => {
return LocalDataStore.selectResultToArrayOfObjects(resp, fieldAliases)
})
}
/**
* Asynchronously run request, constructed by Repository. Return promise, resolved
* to `array of array` representation of response.
* Actual data is placed to `resultData` response property.
*
* @example
UB.Repository('ubm_navshortcut').attrs(['ID', 'code'])
.where('code', 'in', ['uba_user', 'ubs_audit'])
.select().then(UB.logDebug);
// output is
//{"resultData":{"data":[
// [3000000000004,"uba_user"],[3000000000039,"ubs_audit"]
//],"fields":["ID","code"]},"total":2}
// Response MAY (but may not even for the same request) contain other variables,
// returned by server in case data retrieved not from cache, but resultData is always present
// since uba_user have `unity` mixin it ID property point us to parent (`uba_subject` in this case)
UB.Repository('uba_user').attrs(['ID', 'name', 'ID.name'])
.selectAsArray().then(UB.logDebug);
// {"entity":"uba_user","fieldList":["ID","name","ID.name"],"method":"select",
// "resultData":{"fields":["ID","name","ID.name"],
// "data":[[10,"admin","admin"]]},"total":1}
* @returns {Promise<{entity: string, fieldList: string, method: string, resultData: TubCachedData}>}
*/
selectAsArray () {
this.addAttrsForCachedEntity()
return this.connection.select(this.ubql()).then(resp => {
this.rawResult = resp
return resp
})
}
/**
* For repository with ONE attribute returns a flat array of attribute values
*
* @example
const usersIDs = await UB.Repository('uba_user').attrs('ID').limit(100).selectAsArrayOfValues()
// usersIDs is array of IDs [1, 2, 3, 4]
* @returns {Promise<Array<string|number>>}
*/
selectAsArrayOfValues () {
return this.selectAsArray().then(resp => {
return resp.resultData.data.map(r => r[0])
})
}
/**
* For core module (without Ext) - do the same as {ClientRepository.selectAsObj}
*
* For ExtJS based client (actual implementation in {UB.ux.data.UBStore}) - create store based on request, constructed by Repository.
* Return promise resolved to loaded {UB.ux.data.UBStore} instance
*
* @example
UB.Repository('ubm_navshortcut').attrs(['ID', 'code'])
.where('code', 'in', ['uba_user', 'ubs_audit'])
.selectAsStore().then(function(store){
console.log(store.getTotalCount()); // here store is UB.ux.data.UBStore instance
})
* @param {object} [storeConfig] optional config passed to store constructor
* @returns {Promise<Array<object>>}
*/
selectAsStore (storeConfig) {
return this.selectAsObject(storeConfig)
}
/**
* Alias to {@link class:ClientRepository#selectAsObject selectAsObject}
* @param {object} [fieldAliases]
* @returns {Promise<Array<object>>}
*/
select (fieldAliases) {
return this.selectAsObject(fieldAliases)
}
/**
* Select a single row. If ubql result is empty - return {undefined}.
*
* **WARNING** method do not check repository contains the single row and always return
* a first row from result
*
* @param {Object<string, string>} [fieldAliases] Optional object to change attribute names
* during transform array to object. See {@link class:ClientRepository#selectAsObject selectAsObject}
* @returns {Promise<object|undefined>} Promise, resolved to {Object|undefined}
*/
selectSingle (fieldAliases) {
return this.selectAsObject(fieldAliases).then((rows) => {
if (rows.length > 1) console.error(this.CONSTANTS.selectSingleMoreThanOneRow)
return rows[0]
})
}
/**
* Perform select and return a value of the first attribute from the first row
*
* **WARNING** method do not check repository contains the single row
*
* @returns {Promise<number|string|undefined>} Promise, resolved to {Number|String|undefined}
*/
selectScalar () {
return this.selectAsArray().then((result) => {
const L = result.resultData.data.length
if (L) {
if (L > 1) console.error(this.CONSTANTS.selectScalarMoreThanOneRow)
return result.resultData.data[0][0]
} else {
return undefined
}
})
}
/**
* Select a single row by ID. If ubql result is empty - return {undefined}.
*
* @param {number} ID Row identifier
* @param {Object<string, string>} [fieldAliases] Optional object to change attribute names
* during transform array to object. See {@link class:ClientRepository#selectAsObject selectAsObject}
* @returns {Promise<object|undefined>} Promise, resolved to {Object|undefined}
*/
selectById (ID, fieldAliases) {
return this.where('[ID]', '=', ID).selectSingle(fieldAliases)
}
/**
* Asynchronously run request, constructed by Repository. Return promise, resolved
* to columnar (each attribute values is a separate array) representation.
*
* Useful for data analysis \ graph
*
* @example
await UB.Repository('req_request')
.attrs('department.name', 'COUNT([ID])')
.groupBy('department.name').select({'department.name': 'department', 'COUNT([ID])': 'requestCount'})
// {
// 'department': ["Electricity of Kiev", "Water utility of Kiev"],
// 'requestCount': [5, 44]
// }
* @param {object} [fieldAliases] Optional object to change attribute names to column name.
* Object key is attribute name (as it in .attr) and value is preferred column name
* @returns {Promise<{}>} Promise of object with keys = column name and value - array of attribute values
*/
selectColumunar (fieldAliases = {}) {
return this.selectAsArray().then(resp => {
const { fields, data } = resp.resultData
const rArr = resp.resultData.fields.map(f => []) // array of empty arrays
const FC = fields.length
for (let i = 0, L = data.length; i < L; i++) {
for (let j = 0; j < FC; j++) {
rArr[j].push(data[i][j])
}
}
const res = {}
for (let j = 0; j < FC; j++) {
const rfn = fieldAliases[fields[j]] || fields[j]
res[rfn] = rArr[j]
}
return res
})
}
}
module.exports = ClientRepository