adhara
Version:
foundation for any kind of website: microframework
356 lines (336 loc) • 18 kB
JavaScript
function initDataInterface(){
function DataInterface(){
let self = this;
self.config = Adhara.app.DIConfig;
let request_queue = {}; // for NON-get requests
// local helpers ...
function makeServiceCall(url, method_name, data){
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
RestAPI[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});
}
});
});
}
function 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);
self.enqueue(query_type, dummy_entity_config, data, opts);
}
}
function 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};
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
};
self.enqueue(undefined, dummy_entity_config, undefined);
}
}
function isValidStorageData(stored_data){
return stored_data.hasOwnProperty("response");
}
function 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 )
);
}
function 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);
}
function isValidProcessor(processor){
return (processor instanceof Object
&& processor.success instanceof Function
&& processor.error instanceof Function);
}
function isValidEntityConfig(entity_config){
if(entity_config instanceof Object && 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(isValidProcessor(entity_config.processor)){
return true;
} else if( !(AdharaView.isPrototypeOf(entity_config.view)) ) {
return false;
}
}
return true;
}
return false;
}
// namespaced storage and views related methods as per usage ...
let storage_m = { // methods to talk to storage service
rem_que : {},
getUniqueUrlForData : function(url, http_method, data){
return url+ ( data?("?"+Object.keys(data).sort().map(key=> key+"="+data[key]).join("&")):"" );
},
remember : function(data_url, response, resource_timeout){
storage_m.rem_que[data_url] = response; // hold response till dbPromise resolves
return self.dbPromise.then(db => {
const tx = db.transaction(self.config.url_storage, 'readwrite');
tx.objectStore(self.config.url_storage).put({
url: data_url,
response: response,
useby : (isNaN(resource_timeout) ? 30*60*1000 : resource_timeout) + (new Date()).getTime()
});
return tx.complete;
}).then(()=> {
delete storage_m.rem_que[data_url];
return true;
});
},
recall : function (data_url) {
return new Promise((resolve, reject) => {
if(storage_m.rem_que[data_url]){
resolve(storage_m.rem_que[data_url]);
} else {
self.dbPromise.then(db => {
return db.transaction(self.config.url_storage).objectStore(self.config.url_storage).get(data_url);
}).then((data)=>{
if(!data){
reject({message: "No such key stored", code:404});
} else if (isNaN(data.useby) || data.useby < (new Date()).getTime()){
reject({message: "use by period has expired", code:404});
}
else {
isValidStorageData(data) ? resolve(data.response) : reject({message:`Invalid data ${data.response}`, code:500});
}
});//.catch(reject);
}
});
},
remove : function (data_url) {
return self.dbPromise.then(db => {
return db.transaction(self.config.url_storage, 'readwrite').objectStore(self.config.url_storage).delete(data_url);
});
}
};
this.Storage = storage_m;
let views_m = { // methods to talk to views service
signalSuccess : function(query_type, entity_config, response_json, xhr){
Adhara.configUtils
.getProcessor(entity_config)
.success(query_type, entity_config, response_json, xhr, true);
},
signalFailure : function(query_type, entity_config, error, xhr){
Adhara.configUtils
.getProcessor(entity_config)
.error(query_type, entity_config, error, xhr);
}
};
this.getHTTPMethod = function(query_type){
return query_type==="get_list"?"get":query_type;
};
// rest of the public methods and local glue logic goes here ...
this.enqueue = function(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(!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
handle_batch_enqueue(entity_config);
return;
}
if(entity_config instanceof Array){ // Array of app configs -> array of responses
// batch call type 2
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(!(request_queue[data_config.url] instanceof Array)){
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
request_queue[data_config.url].push( {entry_time : performance.now(), arg : [query_type, entity_config, data, options]} );
if(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(!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"};
views_m.signalFailure(query_type, entity_config, failure_message, 405);
return;
}
let reuse = data_config['reuse'], resource_timeout;
if(reuse instanceof Function){
reuse = reuse();
}
if(typeof reuse === "number"){
resource_timeout = reuse;
reuse = true;
}
if((reuse === true || ( typeof reuse === "undefined" && self.config.default_reuse === true ))
&& ['get', 'get_list'].indexOf(http_method) !== -1 ){
let unique_url = storage_m.getUniqueUrlForData(data_config.url, http_method, data);
storage_m.recall(unique_url)
.then(
response => {
views_m.signalSuccess(query_type, entity_config, response, 200);
},
err => {
//initiating call to Backend Service, and registering listeners for success and failure
makeServiceCall(data_config.url, http_method, data).then(response_object => {
let response = Adhara.app.responseMiddleWare(entity_config, true, response_object.response, response_object.xhr);
views_m.signalSuccess(
query_type, entity_config,
response,
response_object.xhr
);
storage_m.remember(unique_url, response, resource_timeout);
}, response_object => {
views_m.signalFailure(
query_type, entity_config,
Adhara.app.responseMiddleWare(entity_config, false, response_object.error, response_object.xhr),
response_object.xhr
);
});
});
} else {
makeServiceCall(data_config.url, http_method, data).then(response_object => {
views_m.signalSuccess(
query_type, entity_config,
Adhara.app.responseMiddleWare(entity_config, true, response_object.response, response_object.xhr),
response_object.xhr
);
}, response_object => {
views_m.signalFailure(
query_type, entity_config,
Adhara.app.responseMiddleWare(entity_config, false, response_object.error, response_object.xhr),
response_object.xhr
);
}).then(()=>{
request_queue[data_config.url].shift();
if(request_queue[data_config.url].length > 0){
if(performance.now() - request_queue[data_config.url][0]['entry_time'] <= 30000){
self.enqueue(...(request_queue[data_config.url][0]['arg']));
}
else {
request_queue[data_config.url] = [];
}
}
});
}
};
// initiate DB
initPersister(self); // initializes the persister and gives access to the DB promise
// turn the engine on
}
return new DataInterface();
}
let Controller = {
control(method, entity_config, data, options){
return Adhara.dataInterface.enqueue(method, entity_config, data, options);
}
};