const LocalDataStore = require('@unitybase/cs-shared').LocalDataStore
const ClientRepository = require('./ClientRepository')
const _ = require('lodash')
/**
* Format for data, used to initialize LocalRepository
*
* @typedef {object} TubLocalData
* @property {Array<Array>} data
* @property {Array<string>} fields
* @property {number} [rowCount] Optional. If not set, will be calculated from data
*/
/**
* @classdesc
* A CustomRepository descendant to be used with local data
*
* Usually created using UB.LocalRepository fabric function
* @example
const UB = require('@unitybase/ub-pub')
const localData = {data: [[1, 'Jon'], [2, 'Bob']], fields: ['ID', 'name'], rowCount: 2}
await UB.LocalRepository(localData, 'uba_user').attrs('ID', 'name').selectAsArray()
// {"resultData":{"data":[[1,"Jon"],[2,"Bob"]],"fields":["ID","name"]},"total":2}
await UB.LocalRepository(localData, 'uba_user').attrs('ID').selectAsArrayOfValues()
// [1, 2]
await UB.LocalRepository(localData, 'uba_user').attrs('name').where('ID', '=', 2).selectScalar()
// "Bob"
* @class LocalRepository
* @augments ClientRepository
*/
class LocalRepository extends ClientRepository {
/**
* Do not create directly - use UB.LocalRepository instead
*
* @example
const UB = require('@unitybase/ub-pub')
const localData = {data: [[1, 'Jon'], [2, 'Bob']], fields: ['ID', 'name']}
await UB.LocalRepository(localData, 'uba_user').attrs('ID', 'name').selectAsArray()
* @example
const UB = require('@unitybase/ub-pub')
const localData = {data: [[1, 'Jon', '#maxdate'], [2, 'Bob', new Date(2023, 2, 15]], fields: ['ID', 'name', 'mi_deleteDate']}
await UB.LocalRepository(localData, 'uba_user').attrs('ID', 'name').selectAsArray()
* @example
const UB = require('@unitybase/ub-pub')
const localData = [{ID: 1, name: 'Jon'}, {ID: 2, name: 'Bob'}]
await UB.LocalRepository(localData, 'uba_user').attrs('ID', 'name').selectAsArray()
* @override
* @private
* @param {Array|TubLocalData} source
* @param {string} entityName
* @param {UBConnection} [conn]
*/
constructor (source, entityName, conn) {
super(conn, entityName)
let localData
if (Array.isArray(source)) {
if (source.length === 0) {
localData = { data: [], fields: ['ID'], rowCount: 0 }
} else {
const fields = Object.keys(source[0])
localData = {
fields,
data: source.map(row => fields.map(field => row[field])),
rowCount: source.length
}
// Automatically add mi_deleteDate field if it is not present in the fields, and set it to "#maxdate",
// but only for entities which have mi_deleteDate attribute.
if (!fields.includes('mi_deleteDate') && this.connection) {
const entityInfo = this.connection.domain.get(entityName, false)
if (entityInfo && ('mi_deleteDate' in entityInfo.attributes)) {
fields.push('mi_deleteDate')
const maxDate = new Date(9999, 11, 31)
for (const row of localData.data) {
row.push(maxDate)
}
}
}
// Automatically add ID field if it is not present in the fields
if (!fields.includes('ID') && this.connection) {
fields.push('ID')
for (const [index, row] of localData.data.entries()) {
row.push(index)
}
}
}
} else {
// "source" is TubLocalData (not array of objects)
localData = source
if (Array.isArray(localData.data)) {
// Replace "#maxdate" with for mi_deleteDate attributes with 9999-12-31, which means "not deleted"
const deleteDateAttributeIndexes = localData.fields
.map((f, i) => f === 'mi_deleteDate' || f.endsWith('.mi_deleteDate') ? i : -1)
.filter(i => i !== -1)
if (deleteDateAttributeIndexes.length > 0) {
const maxDate = new Date(9999, 11, 31)
for (const dataRow of localData.data) {
for (const attributeIndex of deleteDateAttributeIndexes) {
if (dataRow[attributeIndex] === '#maxdate') {
dataRow[attributeIndex] = maxDate
}
}
}
}
if (localData.rowCount === undefined) {
localData.rowCount = localData.data.length
}
}
}
/**
* @private
* @property {TubCachedData} _localData
*/
Object.defineProperty(this, '_localData', { enumerable: false, writable: false, value: localData })
}
/**
* @returns {Promise<{resultData: TubCachedData, total: number}>}
*/
selectAsArray () {
const _ubql = this.ubql()
// assign fields based on ubql fieldList for empty data. This fixes the using LocalRepository initialized from an empty array
const localDataNormalized = this._localData.rowCount === 0 ? { ...this._localData, fields: _ubql.fieldList } : this._localData
const filtered = LocalDataStore.doFilterAndSort(localDataNormalized, _ubql)
// transform a result according to passed fieldList (if needed)
const rd = filtered.resultData // ref
if (
!_ubql.fieldList.length ||
(_ubql.fieldList.length === 1 && _ubql.fieldList[0] === '*') ||
!rd.data.length ||
_.isEqual(_ubql.fieldList, rd.fields)
) {
// Repository attributes list is equal to localData fields list - no additional transformation required
return Promise.resolve(filtered)
} else {
const localFl = rd.fields
const idxMap = _ubql.fieldList.map(f => localFl.indexOf(f)) // map of requested fieldList to localData field list
const dataAsInFieldList = rd.data.map(r => {
return idxMap.map(i => r[i])
})
return Promise.resolve({
resultData: { data: dataAsInFieldList, fields: _ubql.fieldList, rowCount: dataAsInFieldList.length },
total: rd.total
})
}
}
/**
* @override
* @returns {LocalRepository}
*/
clone () {
const cloned = super.clone()
// copy _localData by hand since it is private property
Object.defineProperty(cloned, '_localData', { enumerable: false, writable: false, value: this._localData })
return cloned
}
}
module.exports = LocalRepository