adhara
Version:
foundation for any kind of website: microframework
361 lines (338 loc) • 16.5 kB
JavaScript
class DataInterface extends StorageSelector.select(){
constructor(){
super();
this.config = Adhara.app._diConfig;
this.request_queue = {}; // for NON-get requests
this.rem_que = {};
this.db_table = this.select("default", this.config.http_cache_table);
this.initDependencies();
}
// local helpers ...
makeServiceCall(url, method_name, data, controller){
data = Adhara.app.requestMiddleWare(url, method_name, data);
return new Promise(function (resolve, reject) {
// make API call and fetch data OR fetch from client storage, pass on_success and on_failure to the thing
controller[method_name] ({
url, data, crossDomain: true, xhrFields: { "withCredentials": true },
beforeSend(xhr){
xhr.withCredentials = true;
},
success(response, xhr){
resolve({response, xhr});
},
failure (error, xhr){
reject({error, xhr});
}
});
});
}
handle_bulk_config(query_type, entity_configs, data, opts){
if(['get', 'get_list'].indexOf(query_type) < 0) return;
let num_reqs = entity_configs.length,
result = new Array(num_reqs),
entry_count = 0;
let caller_viewable = opts.caller_view && (opts.caller_view instanceof AdharaView
|| (opts.caller_view.hasOwnProperty('handleDataChange')
&& typeof opts.caller_view.handleDataChange === 'function'));
for(let i=0; i<num_reqs; i++){
let dummy_entity_config = Object.assign({}, entity_configs[i]);
dummy_entity_config.processor = (function IIFE(scoped_i){
return {
success: function(query_type, entity_config, response, xhr){
let processed_data = processor_helper.get_basic_processed_data(query_type, entity_config, response, xhr);
result[scoped_i] = processed_data;
entry_count++;
if(caller_viewable) {
if(entry_count === num_reqs) {
opts.caller_view.handleDataChange(result);
}
} else {
Adhara.configUtils.getViewInstance(entity_config).handleDataChange(processed_data);
}
},
error: function(query_type, entity_config, error, xhr){
let processed_data = processor_helper.get_basic_processed_data(query_type, entity_config, error, xhr);
result[scoped_i] = processed_data;
entry_count++;
if(caller_viewable) {
if(entry_count === num_reqs){
opts.caller_view.handleDataChange(result);
}
} else {
Adhara.configUtils.getViewInstance(entity_config).handleDataError(processed_data);
}
}
};
})(i);
this.enqueue(query_type, dummy_entity_config, data, opts);
}
}
handle_batch_enqueue(entity_config){
let batch_result_map = {};
let data_config = Adhara.configUtils.getDataConfig(entity_config);
let batch_calls = data_config.batch_data_override;
function check_fills(obj){
for(let k in obj){
if(obj.hasOwnProperty(k)){
if(typeof obj[k] === 'undefined'){
return false;
}
}
}
return true;
}
function publishToView(final_batch_data){
Adhara.configUtils.getViewInstance(entity_config).handleBatchData(final_batch_data);
}
let batch_processor = { // custom processor for batching requests
success: function(query_type, entity_config, response, xhr){
let blob = Adhara.configUtils.getBlobClass(entity_config);
let data_config = Adhara.configUtils.getDataConfig(entity_config);
let processed_data;
if(query_type==="get_list"){
processed_data = [];
for(let datum of response){
processed_data.push(new blob(datum));
}
}else{
processed_data = new blob(response);
}
batch_result_map[data_config.identifier] = {success:processed_data};
if(check_fills(batch_result_map)) publishToView(batch_result_map);
processor_helper.on_success_common(query_type, entity_config, response, xhr);
},
error: function(query_type, entity_config, error, xhr){
let data_config = Adhara.configUtils.getDataConfig(entity_config);
batch_result_map[data_config.identifier] = {error, response_code:xhr.status};
if(check_fills(batch_result_map)) publishToView(batch_result_map);
}
};
for(let batch_call of batch_calls){
batch_result_map[batch_call.identifier] = undefined;
//TODO add stuff to the cline db after query abd before querying teh ckiebt DV befire quertubg!
//add stuff to the client db after query and before querying the client DB
let dummy_entity_config = {
data_config: {
url: batch_call.url,
_url: batch_call.url,
identifier: batch_call.identifier,
default_query_type: batch_call.query_type || 'get',
allowed_query_types: ["get", "get_list"],
reuse: batch_call.reuse,
blob: batch_call.blob
},
view: Adhara.configUtils.getViewInstance(entity_config),
processor: batch_processor,
controller: Adhara.configUtils.getController(entity_config)
};
this.enqueue(undefined, dummy_entity_config, undefined);
}
}
isValidStorageData(stored_data){
return stored_data.hasOwnProperty("response");
}
isMethodAllowed(query_type, http_method, data_config) {
return (
Adhara.app.allowedHttpMethods.indexOf(http_method) !== -1
|| ( data_config.allowed_query_types && data_config.allowed_query_types.indexOf(query_type) !== -1 )
);
}
isValidDataConfig(data_config){
return (data_config instanceof Object
&& data_config.allowed_query_types instanceof Array
&& typeof data_config.url === "string"
&& data_config.allowed_query_types.length > 0)
|| (data_config instanceof Object
&& data_config.batch_data_override instanceof Array);
}
isValidProcessor(processor){
return (processor instanceof Object
&& processor.success instanceof Function
&& processor.error instanceof Function);
}
isValidEntityConfig(entity_config){
if(entity_config instanceof Object && this.isValidDataConfig(entity_config.data_config)){
if (entity_config.data_config.batch_data_override){
return true;
}
else if(entity_config.data_config.allowed_query_types.indexOf('get') > -1
|| entity_config.data_config.allowed_query_types.indexOf('get_list') > -1 ){
if(this.isValidProcessor(entity_config.processor)){
return true;
} else if( !(AdharaView.isPrototypeOf(entity_config.view)) ) {
return false;
}
}
return true;
}
return false;
}
getHTTPMethod(query_type){
return query_type==="get_list"?"get":query_type;
}
getUniqueUrlForData(url, http_method, data){
return url+ ( data?("?"+Object.keys(data).sort().map(key=> key+"="+data[key]).join("&")):"" );
}
remember(url, response, reuse, _url){
let expires = (reuse.timeout || this.config.reuse_timeout || 5*60*1000 ) + Date.now(); //5 minutes is the default timeout
let _ = {
expires,
url: _url || url
};
if(reuse.scope === "in_page"){
_.page_name = Adhara.router.getCurrentPageName();
}
this.rem_que[url] = response; // hold response till dbPromise resolves
return this.db_table.store({ url, response, _ }).then(()=> {
delete this.rem_que[url];
return true;
});
}
recall(data_url) {
return new Promise((resolve, reject) => {
if(this.rem_que[data_url]){
resolve(this.rem_que[data_url]);
} else {
this.db_table.retrieve(data_url).then((data)=>{
if(!data){
reject({message: "No such key stored", code:404});
} else if (isNaN(data._.expires) || data._.expires < (new Date()).getTime()){
reject({message: "data has expired", code:404});
}
else {
this.isValidStorageData(data) ? resolve(data.response) : reject({message:`Invalid data ${data.response}`, code:500});
}
}, e => {
if(e.code === 8){
reject({message: "No such key stored", code:404});
} else {
reject({message: e || "Unknown Error", code:500});
}
});//.catch(reject);
}
});
}
remove(data_url) {
return this.db_table.remove(data_url);
}
signalViewSuccess(query_type, entity_config, response_json, xhr){
Adhara.configUtils
.getProcessor(entity_config)
.success(query_type, entity_config, response_json, xhr, true);
}
signalViewFailure(query_type, entity_config, error, xhr){
Adhara.configUtils
.getProcessor(entity_config)
.error(query_type, entity_config, error, xhr);
}
// rest of the public methods and local glue logic goes here ...
enqueue(query_type, entity_config, data, options){
// AS OF NOW options can contain additional parameter 'caller_view'. Which ensures the caller view does get the result.
if(!this.isValidEntityConfig(entity_config)){
throw new Error(`Entity config passed to enqueue is invalid: ${JSON.stringify(entity_config, null, 4)}`);
}
let data_config = Adhara.configUtils.getDataConfig(entity_config);
if(data_config.hasOwnProperty("batch_data_override")){ // Map of url -> data
this.handle_batch_enqueue(entity_config);
return;
}
if(entity_config instanceof Array){ // Array of app configs -> array of responses
// batch call type 2
this.handle_bulk_config(query_type, entity_config, data, options);
return;
}
// if it is not a get or get_list it will make it wait in a queue
if(!(this.request_queue[data_config.url] instanceof Array)){
this.request_queue[data_config.url] = [];
}
if(['get', 'get_list'].indexOf(query_type) < 0) {
if(options && options.consider_for_queueing !== false){
options.consider_for_queueing = false; // would prevent recursive queueing of the same URL
this.request_queue[data_config.url].push( {entry_time : performance.now(), arg : [query_type, entity_config, data, options]} );
if(this.request_queue[data_config.url].length > 1) { // not the first entry, taken up later hence
return;
}
}
}
//=========================================================================================================//
// LOGISTICS END HERE, CORE ENQUEUE IS BELOW THIS COMMENT
//=========================================================================================================//
if(!query_type) query_type = data_config.default_query_type;
let http_method = this.getHTTPMethod(query_type);
if(!this.isMethodAllowed(query_type, http_method, data_config)) { // either globally off or off due to links configured for particular data configuration
let failure_message = {message: "Unauthorized Request"};
this.signalViewFailure(query_type, entity_config, failure_message, 405);
return;
}
let reuse = data_config.reuse || {};
if( ( reuse.enable !== false && this.config.default_reuse !== false ) && ['get', 'get_list'].indexOf(http_method) !== -1 ){
let unique_url = this.getUniqueUrlForData(data_config.url, http_method, data);
let msc = ()=>{
//initiating call to Backend Service, and registering listeners for success and failure
this.makeServiceCall(data_config.url, http_method, data, Adhara.configUtils.getController(entity_config)).then(response_object => {
let response = Adhara.app.responseMiddleWare(entity_config, true, response_object.response, response_object.xhr);
this.signalViewSuccess(
query_type, entity_config,
response,
response_object.xhr
);
this.remember(unique_url, response, reuse, data_config._url);
}, error_response_object => {
this.signalViewFailure(
query_type, entity_config,
Adhara.app.responseMiddleWare(entity_config, false, error_response_object.error, error_response_object.xhr),
error_response_object.xhr
);
});
};
this.recall(unique_url)
.then(
response => {
if(!(reuse instanceof Function) || reuse(response)){
let xhr = new XMLHttpRequest();
this.signalViewSuccess(query_type, entity_config, response, xhr);
}else{
msc();
}
},
err => {
msc();
});
} else {
this.makeServiceCall(data_config.url, http_method, data, Adhara.configUtils.getController(entity_config)).then(response_object => {
this.signalViewSuccess(
query_type, entity_config,
Adhara.app.responseMiddleWare(entity_config, true, response_object.response, response_object.xhr),
response_object.xhr
);
}, error_response_object => {
this.signalViewFailure(
query_type, entity_config,
Adhara.app.responseMiddleWare(entity_config, false, error_response_object.error, error_response_object.xhr),
error_response_object.xhr
);
}).then(()=>{
this.request_queue[data_config.url].shift();
if(this.request_queue[data_config.url].length > 0){
if(performance.now() - this.request_queue[data_config.url][0]['entry_time'] <= 30000){
this.enqueue(...(this.request_queue[data_config.url][0]['arg']));
}
else {
this.request_queue[data_config.url] = [];
}
}
});
}
}
cleanUp(current_page_name){
this.db_table.removeMultiple((url, response) => {
return ( response._.expires <= Date.now()
|| ( response._.page_name && response._.page_name !== current_page_name ) );
});
}
initDependencies(){
Adhara.router.onRoute("DIPageChangeListener", ()=>{
this.cleanUp(Adhara.router.getCurrentPageName());
});
}
}