Data layer for accessing UnityBase server from Browser or NodeJS
NodeJS example
global.XMLHttpRequest = require('xhr2')
const UB = require('@unitybase/ub-pub')
const HOST = process.env.UB_HOST || 'http://localhost:8881'
const USER = process.env.UB_USER || 'admin'
const PWD = process.env.UB_PWD || 'admin'
async function call_ub () {
const conn = await UB.connect({
host: HOST,
onCredentialRequired: function (conn, isRepeat) {
if (isRepeat) {
throw new UB.UBAbortError('invalid credential')
} else {
return Promise.resolve({authSchema: 'UB', login: USER, password: PWD})
}
},
onAuthorizationFail: function (reason) {
console.error(reason)
}
})
console.log(`
Hello, ${conn.userLogin()}!
We know that you are ${JSON.stringify(conn.userData(), null, ' ')}
`)
conn.get('stat').then(function (statResp) {
console.log('Current server statistics:', statResp.data)
})
const items = await conn.Repository('ubm_navshortcut').attrs(['ID', 'code', 'caption'])
.limit(2)
.selectAsObject()
.then(function (data) {
console.log('First 2 adminUI shortcuts:')
console.log(JSON.stringify(data, null, '\t'))
})
console.table(items)
}
try {
call_ub()
} catch (e) {
console.error(e)
}
The same code as above will work in browser (just comment first line where XMLHttpRequest is required).
Connecting to UB server
The main entry point is connect method.
const UB = require('@unitybase/ub-pub')
const conn = UB.connect({
host: 'https://myserver.com',
onCredentialRequired: function(conn, isRepeat){
if (isRepeat){
throw new UB.UBAbortError('invalid credential')
} else {
return Promise.resolve({authSchema: 'UB', login: 'myuser', password: 'mypassword'})
}
},
onAuthorizationFail: function(reason){
alert(reason)
}
})
After connecting UBConnection class cares about reconnect, data cashing, request buffering and proper data serialization.
Session
Connection contains the information about currently logged-in user. The application logic on the server side can add any custom properties required for application when user is already logged-in. Such properties are available on the client in connection.userData()}
console.log(`
Hello, ${conn.userLogin()}!
We know that you are ${JSON.stringify(conn.userData())}
`)
Domain
connection.domain contains information about the application domain - the list of models, entities, entities' attributes and methods. Domain is already localized to the language of logged-in user.
This information should be used by the client application during building the UI. For example:
let usersEntity = conn.domain.get('uba_user')
// localized caption of entity uba_user
console.log(usersEntity.caption)
// localized
console.log(`Input control for user name
should be of type ${usersEntity.attributes.name.dataType}
and with label ${usersEntity.attributes.name.caption}
`)
console.log(`Control for selecting the user from the list
should use ${usersEntity.getDescriptionAttribute()}
as a list content attribute`)
console.log(`Currently logged-in user
${u.haveAccessToMethod('update') ? 'can' : 'can not'} edit uba_user`)
Querying data
In most cases client retrieves data from the server using UBQL (UnityBase Query Language) JSON.
connection.Repository fabric function is a helper for building UBQL JSON
conn.Repository('my_entity').attrs(['ID', 'code'])
.attrs('attrOfEntityType.caption') // JOIN to other table
.where('code', 'in', ['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') // FTS query
.selectAsObject().then(function(response){
// here response is in [{ID: 10, code: 'value1'}, .... {}] format
})
See Repository method documentation in ClientRepository
Buffering
Several UI controls can simultaneously send queries using one connection. In this case several queries that come in the same 20ms period of time will be buffered and sent to the server as a single HTTP request to reduce network bandwidth and latency.
This happens automatically - just write the code as usual and let the connection care about network performance. Run the code below in console and look into the Network - you will see the single HTTP request
Promise.all([
conn.Repository('uba_user').attrs('ID').selectAsArray(),
conn.Repository('uba_group').attrs('ID').selectAsObject()
]).then(UB.logDebug)
Caching
Server-side developer can decide that some of the entities are changed infrequently and contain small amount of data. In this case such entities are marked as cached. The repository is aware of such entities by using the information from Domain, and can return data without sending HTTP request over the wire. Internally the repository uses LocalDataStorage to filter and sort data locally.
Test it from the console:
// first call to cached entity will get data from server
UB.Repository('ubm_enum').attrs(['ID', 'code'])
.where('code', 'startsWith', 'I').selectAsObject()
.then(UB.logDebug)
// second - filter data locally, even if filter condition is changed
UB.Repository('ubm_enum').attrs(['ID', 'code'])
.where('code', 'startsWith', 'UPD').selectAsObject()
.then(UB.logDebug)
Promisified XHR
As a side effect @unitybase/ub-pub module contains "Promisified" API for HTTP request:
- xhr: An asynchronous HTTP request. Returns a {Promise} object
- get: simplified
xhr
for GET - post: simplified
xhr
for POST
So you do not need axios etc. Just use a UB.xhr
, UB.get
or UB.post
:
const UB = require('@unitybase/ub-pub')
UB.get('https://unitybase.info').then(resp => console.log(resp.data))
Submodules
Members
# appConfig deprecated static
Use connection.appConfig instead
# ClientRepository : ClientRepository static
# connection : UBConnection static
After call to UB.connect this property will point to the active connection
# CryptoJS static
CryptoJS instance (included modules are enc-base64, sha256, md5)
# formatter : formatByPattern static
Locale based Date and Number formatters, See details in @unitybase/cs-shared/formatByPattern
const d = new Date(2020, 04, 23, 13, 14)
UB.formatter.formatDate(d, 'date') // without 3rd lang parameter - will be formatted for user default lang (for uk - 23.05.2020)
UB.formatter.formatDate('2020-05-23', 'date', 'uk') // 23.05.2020
UB.formatter.formatDate(d, 'date', 'en') // 05/23/2020
UB.formatter.formatDate(d, 'dateTime', 'uk') // 23.05.2020 13:14
UB.formatter.formatDate(d, 'date', 'en') // 05/23/2020, 1:14 PM
const n = 2305.1
UB.formatter.formatNumber(n, 'sum', 'en') // 2,305.10
UB.formatter.formatNumber('2305.1', 'sum', 'en') // 2,305.10
UB.formatter.formatNumber(n, 'sum') // without 3rd lang parameter - will be formatted for user default lang (for uk "2 305,10")
# i18n static
Return locale-specific resource from its identifier. localeString
must be:
- either previously defined dy call to i18nExtend
- or be a combination of entity and attribute names so that
UB.i18n('uba_user')
orUB.i18n('uba_role.description')
would be resolved to localized entity caption or entity attribute caption - description/documentation/captionSingular of entity/attribute can be localized using hashtag:
- #description - an entity description
- #documentation - an entity documentation
- #captionSingular - if entity.captionSingular is defined - entity.captionSingular, else - entity.caption See samples below
//Localized string can be formatted either by position args:
UB.i18nExtend({
greeting: 'Hello {0}, welcome to {1:i18n}',
Kiev: 'Kyiv city'
})
UB.i18n('greeting', 'Mark', 'Kiev') // Hello Mark, welcome to Kyiv city
// in sample above :i18n modifier is added to the second format args, so `Kiev` is also translated
//Or by named args:
UB.i18nExtend({
namedGreeting: 'Hello {name}, welcome to {place}'
})
UB.i18n('namedGreeting', {name: 'Mark', place: 'Kiev'}) // Hello Mark, welcome to Kiev
//Localization itself can be an object:
UB.i18nExtend({
loginPage: { welcome: 'Welcome to our app', user: 'Dear {user}'}
})
UB.i18n('loginPage.welcome') // Welcome to our app
UB.i18n('loginPage.user', {user: 'Pol}) // Dear Pol
UB.i18n('loginPage') // return object {welcome: "Welcome to our app", user: "Dear {user}"}
UB.i18n('uba_user') // -> "Users" (caption from uba_use.meta)
UB.i18n('uba_user.firstName') // -> "First Name" (caption of uba_user.firstName attribute)
UB.i18n('uba_user.name#description') // "User login in lower case" (description for uba_user.name attribute)
UB.i18n('uba_audit#documentation') // "All changes to UBA..." ( documentation for uba_audit entity )
UB.i18n('uba_audit#captionSingular') // "Security Audit" ( fallback to caption because uba_audit.captionSingular is not defined in meta)
# iso8601ParseAsDate static
Convert UnityBase server date response to Date object.
date response
is a day with 00 time (2015-07-17T00:00Z), to get a real date we must add current timezone shift
# LDS_KEYS static
localDataStorage keys used by @unitybase-ub-pub (in case of browser environment)
# LocalDataStore : LocalDataStore static
Helper class for manipulation with data, stored locally in (TubCachedData format)
# MD5 static
Calculate MD5 checksum
# SHA256 static
Calculate SHA256 checksum
# truncTimeToUtcNull static
Convert a local DateTime to Date with zero time in UTC0 timezone as expected by UB server for Date attributes
# UBAbortError : UBAbortError static
Quiet exception. Global error handler does not show this exception for user. Use it for silently reject promise.
# UBCache : UBCache static
Client side cache
# UBError : UBError static
Client-side exception. Such exceptions will not be showed as unknown error in UB.showErrorWindow
# UBNativeMessage : UBNativeMessage static
Class for communicate with native messages plugin content script
.
Methods
# apply (objectTo: object, objectsFrom: object) → object static
Copies all the properties of one or several objectsFrom to the specified objectTo. Non-simple type copied by reference!
Return:
returns objectTo
# base64FromAny (data: File) → Promise.<string> static
Fast async transformation of data to base64 string
Return:
resolved to data converted to base64 string
Arguments:
data
: File| ArrayBuffer| string| Blob| Array
# base64toArrayBuffer (base64: string) → ArrayBuffer static
Convert base64 encoded string to decoded array buffer
Arguments:
base64
: string
# booleanParse (v: *) → boolean | null static
Convert UnityBase server Boolean response (0 or 1) to JS Boolean (false or true)
Arguments:
v
: *Value to convert
# connect (cfg: object) → Promise.<UBConnection> static
Create authorized connection to UnityBase server.
For a browser clients in case value of silenceKerberosLogin
localStorage key is 'true' and 'Negotiate'
authorization method is enabled for application will try to authenticate user using Kerberos/NTLM method.
Preferred locale tip: to define connection preferredLocale parameter call
localStorage.setItem(UB.LDS_KEYS.PREFERRED_LOCALE, 'uk')
before call to UBConnection.connect
Arguments:
cfg
: objecthost
: stringServer host
path
: stringAPI path - the same as in Server config
httpServer.path
onCredentialRequired
: authParamsCallbackCallback for requesting a user credentials. See authParamsCallback description for details
onRequest2fa
: request2faCallbackCallback for requesting second factor (if needed). See request2faCallback description for details
allowSessionPersistent
: booleanFor a non-SPA browser client allow to persist a Session in the local storage between reloading of pages. In case user logged out by server side this type persistent not work and UBConnection will call onCredentialRequired handler, so user will be prompted for credentials
onAuthorizationFail
: functionCallback for authorization failure. See event:authorizationFail event. Should handle all errors inside!
onAuthorized
: functionCallback for authorization success. See event:authorized event. On this stage
connection.domain
is still not exists, so do not use Repository inside.onNeedChangePassword
: functionCallback for a password expiration. See event:passwordExpired event
onGotApplicationConfig
: functionCalled just after application configuration retrieved from server. Accept one parameter - connection: UBConnection Usually on this stage application inject some scripts required for authentication (locales, cryptography etc). Should return a promise then done
onGotApplicationDomain
: functionCalled after server returns domainInfo and
connection.domain
is initialized
const UB = require('@unitybase/ub-pub')
let conn = UB.connect({
host: window.location.origin,
path: window.location.pathname,
onCredentialRequired: function(conn, isRepeat){
if (isRepeat){
throw new UB.UBAbortError('invalid credential')
} else {
return Promise.resolve({authSchema: 'UB', login: 'admin', password: 'admin'})
}
},
onAuthorizationFail: function(reason){
alert(reason)
}
})
conn.then(function(conn){
conn.get('stat').then(function(statResp){
document.getElementById('ubstat').innerText = JSON.stringify(statResp.data, null, '\t')
})
conn.Repository('ubm_navshortcut').attrs(['ID', 'code', 'caption']).selectAsArray().then(function(data){
let tmpl = _.template(document.getElementById('repo-template').innerHTML);
let result = tmpl(data.resultData);
// document.getElementById('ubnav').innerText = JSON.stringify(data.resultData);
document.getElementById('ubnav').innerHTML = result;
})
})
# file2Uint8Array (file: File) → Promise.<Uint8Array> static
Fast async transformation of file to Uint8Array
Return:
resolved to file content as Uint8Array
Arguments:
file
: File
let f = document.getElementById('inputOfTypeFile').files[0]
ui8Arr = await UB.file2Uint8Array(f)
# format (stringToFormat: string, values: *) → string static
Allows to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
Return:
The formatted string.
Arguments:
stringToFormat
: stringThe string to be formatted.
values
: *The values to replace tokens
{0}
,{1}
, etc in order.
var s = UB.format('{1}/ext-lang-{0}.js', 'en', 'locale');
// s now contains the string: ''locale/ext-lang-en.js''
# get (url: string, configopt: object) → Promise.<XHRResponse> static
Shortcut for UB.xhr to perform a GET
request
Arguments:
// GET http://my-api.com?param=val
const resp = await UB.get('http://my-api.com', {params: {param: 'val'}})
console.log(resp.data)
# i18nExtend (localizationObject: object) static
Merge localizationObject to UB.i18n. Usually called form modelFolder/locale/lang-*.js
scripts
Arguments:
localizationObject
: object
# inject (url: string, charsetopt: string) → Promise static
Inject external script or css to DOM and return a promise to be resolved when script is loaded.
if script successfully loaded using inject it will not be loaded anymore with repeatable calls to UB.inject.
//Load script.js:
UB.inject('jslibs/script.js')
//Load several script at once and error handling:
Promise.all([UB.inject('jslibs/script.js'), UB.inject('script2.js')])
.catch(function(err){
console.log('Oh! error occurred: ' + err)
})
//Load one script and then load other
UB.inject('jslibs/js_beautify.js').then(function(){
console.log('first script loaded. Continue to load second')
return UB.inject('jslibs/js_beautify1.js')
})
//Load a couple of resources:
Promise.all([UB.inject('css/first.css'), UB.inject('css/second.css')])
# iso8601Parse (value: Date) → Date static
Convert UnityBase server dateTime response (ISO8601 string) to Date object
# LocalRepository (localData: Array | TubLocalData, entityName: string) → LocalRepository static
Create a new instance of a repository based on a local data
Arguments:
localData
: Array| TubLocalDataentityName
: string
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('name').where('ID', '=', 2).selectScalar() // "Bob"
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()
# logError (msg: *) static
Log error message to console (if console available)
Arguments:
msg
: *
# logWarn (msg: *) static
Log warning message to console (if console available)
Arguments:
msg
: *
# ns (namespacePath: string) → object static deprecated
Try to avoid namespaces - instead create a module and use require()
Creates namespaces to be used for scoping variables and classes so that they are not global.
Return:
The namespace object.
Arguments:
namespacePath
: string
UB.ns('DOC.Report')
DOC.Report.myReport = function() { ... }
# post (url: string, data: *, configopt: object) → Promise.<XHRResponse> static
Shortcut for UB.xhr to perform a POST
request
Arguments:
// POST http://my-api.com?param1=12¶m2=someVal with body contains a stringified object
const resp = await UB.post('http://my-api.com', {this: 'is', body: 'of' request}, {params: {param1: 12, param2: 'someVal'}})
console.log(resp.data)
# Repository (entityCodeOrUBQL: string) → ClientRepository static
Create a new instance of repository for a current connection. To be used after connection is created.
# setErrorReporter (errorReportedFunction: function) static
Set an error reporter callback for unhandled errors (including unhandled promise rejections).
Callback signature function({errMsg, errCode, entityCode, detail})
errMsg
is already translated using UB.i18n
This callback also called inside UBPub.showErrorWindow
Arguments:
errorReportedFunction
: function
# showErrorWindow (errMsg: string | object | Error | UBError, errCodeopt: string, entityCodeopt: string, detailopt: string) static
Default error reported handler. Will translate error message using i18n.
For a UI other then adminUI developer can call UB.setErrorReporter
to set his own error reporter
Arguments:
const UB = require('@unitybase/ub-pub')
const vm = new Vue({
...
methods: {
showError: function(errMsg, errCode, entityCode, detail) {
this.$message({
showClose: true,
message: errMsg,
type: 'error'
})
}
...
})
UB.setErrorReporter(vm.showError.bind(vm))
# xhr (requestConfig: object) → Promise.<XHRResponse> static
An asynchronous HTTP request. Returns a Promise, what resolves to the XHRResponse object:
Arguments:
requestConfig
: objecturl
: stringAbsolute or relative URL of the resource that is being requested
method
: stringHTTP method (e.g. 'GET', 'POST', etc). Default is GET
params
: Object.<(string | object)>Map of strings or objects which will be turned to
?key1=value1&key2=value2
after the url. If the value is not a string, it will be JSONified. Keys and values are URL encoded inside a function.data
: string | objectData to be sent as the request message data
headers
: objectMap of strings or functions which return strings representing HTTP headers to send to the server. If the return value of a function is null, the header will not be sent. Merged with UB.xhrDefaults.headers
transformRequest
: function | Array.data, function())> Transform function or an array of such functions. The transform function takes the http request body and headers and returns its transformed (typically serialized) version.
transformResponse
: function | Array.data, function())> Transform function or an array of such functions. The transform function takes the http response body and headers and returns its transformed (typically deserialized) version.
timeout
: number | Promisetimeout in milliseconds, or {Promise} that should abort the request when resolved. Default to {UB.xhrDefaults.timeout}
withCredentials
: booleanwhether to to set the
withCredentials
flag on the XHR object. See requests with credentials for more information.responseType
: stringsee responseType.
onProgress
: functionXHR onProgress callback, see ProgressEvent for details. To be user instead obsolete Q Promise.progress()
Object describing the request to be made and how it should be processed. The object has the following properties:
//Get some data from server:
const resp = await UB.xhr({url: 'getAppInfo'})
console.log('app info: %o', resp.data)
//The same, but in more short form via `UB.get` shorthand:
const resp = await UB.get('getAppInfo')
console.log('app info: %o', resp.data)
//Run POST method:
UB.post('ubql', [
{entity: 'uba_user', method: 'select', fieldList: ['*']}
]).then(function(resp) {
console.log('success!')
}, function(resp) {
console.log('request failed with status' + resp.status);
})
//retrieve binary data as ArrayBuffer
const resp = await UB.get('downloads/cert/ACSK(old).cer', {responseType: 'arraybuffer'})
console.log('Got ArrayBuffer of %d byte length', resp.data.byteLength);
# addBrowserUnhandledRejectionHandler () inner
Intercept all unhandled errors including Promise unhandled rejections. Errors will be parsed and passed to UB.showErrorWindow {@see setErrorReporter setErrorReporter}