UNPKG

shared-table-types

Version:

This module is a library for utlitizing an outline for managing processes that share tables each requiring a selection of a communication option.

441 lines (394 loc) 14.1 kB
const {ServeMessageEndpoint} = require('message-relay-services') const crypto = require('crypto') const DEFAULT_AGE_WINDOW = 10000 // This is an endpoint server with communication from the parent also /** * ProcJSTableManager * * This class provides a fuax DB interface for parents and children. * There is no message forwarding for communication between the classes. * */ class ProcJSTableManager extends ServeMessageEndpoint { // constructor(conf) { super(conf) // this._in_mem_table = {} this._in_mem_verify_table = {} this._time_stamp_queue = [] this.messenger_path = "mem-table" // this.conf = conf this.timer_list = [] // this.messenger = false if ( typeof process.send === 'function' ) { this.subscribe_to_parent_commands(conf) } // this._te = new TextEncoder() // this.init_timers() } /** * do_hash * @param {string} msg * @returns */ do_hash(msg) { let buf = this._te.encode(msg) let hh = crypto.hash('sha256',buf) return hh } /** * init_timers */ init_timers() { let conf = this.conf if ( conf.aging !== undefined ) { let self = this let window_size = conf.age_window !== undefined ? parseInt(conf.age_window) : DEFAULT_AGE_WINDOW this.timer_list.push(setInterval(() => { self.expel_aged_entries(self,window_size) },parseInt(conf.aging))) } } /** * shutdown_timers */ shutdown_timers() { for ( let timer of this.timer_list ) { clearInterval(timer) } } /** * subscribe_to_parent_commands * * --only if-- a launching process (parent) is detected (in nodejs by virtue of a process.send command) * * @param {object} conf */ subscribe_to_parent_commands(conf) { let parent_subscription_conf = conf.parent_command_conf if ( parent_subscription_conf && parent_subscription_conf.parent_com ) { if ( parent_subscription_conf.class_choice ) { let ClientOfParent = require(parent_subscription_conf.parent_com)[parent_subscription_conf.class_choice] this.messenger = new ClientOfParent(parent_subscription_conf.parent_conf) } else { let ClientOfParent = require(parent_subscription_conf.parent_com) this.messenger = new ClientOfParent(parent_subscription_conf.parent_conf) } // let HandlerFactory = require(parent_subscription_conf.parent_response_handler) let handler_supplier = new HandlerFactory(parent_subscription_conf.handler_conf) let parent_topic_list = parent_subscription_conf.accepted_topics // for ( let topic_def of parent_topic_list ) { let topic = topic_def.topic let path = "proc-control" let message = {} let handler = handler_supplier.get_supplier_action(topic,path,this) this.messenger.subscribe(topic,path,message,handler) } } } /** * unsubscribe_to_parent_commands * * --only if-- a launching process (parent) is detected (in nodejs by virtue of a process.send command) * * @param {object} conf */ unsubscribe_to_parent_commands() { // if ( this.messenger ) { let conf = this.conf let parent_subscription_conf = conf.parent_command_conf let parent_topic_list = parent_subscription_conf.accepted_topics // for ( let topic_def of parent_topic_list ) { let topic = topic_def.topic let path = "proc-control" this.messenger.unsubscribe(topic,path) } } } // /** * id_augmentation * * @param {object} msg_obj */ id_augmentation(msg_obj) { // hash the object ... let user_id = msg_obj._user_dir_key ? msg_obj[msg_obj._user_dir_key] : msg_obj._id if ( (user_id === undefined) && (msg_obj._id !== undefined) ) { user_id = msg_obj._id } msg_obj._id = user_id } // alter the data object for user consumption... such as removing secrets or control codes // /** * application_data_update * * * @param {object} msg_obj * @param {object} data * @returns */ async application_data_update(msg_obj,data) { return(data) } /** * expel_aged_entries * * * @param {object} caller - this... in a different context * @param {timer} interval - the lower bound on being able to stay */ async expel_aged_entries(caller,interval) { // let n = caller._time_stamp_queue.length if ( n > 0 ) { let del_time = Date.now() del_time -= interval // while ( n > 0 ) { n--; let entry = caller._time_stamp_queue[n] if ( entry.time < del_time ) { caller._time_stamp_queue.splice(n,1) let hash = entry.hash delete caller._in_mem_table[hash] await caller.app_publish_on_path('D$shared',this.messenger_path,entry) // await } } } } /** * delete * * @param {object} msg_obj * @returns boolean */ async delete(msg_obj) { // let hash = msg_obj.hash let val_obj = this._in_mem_table[hash] delete this._in_mem_table[hash] let time = val_obj.time let index = this._time_stamp_queue.findIndex((value) => { return ( value.time === time ) && (value.hash === hash ) }) // this._time_stamp_queue.splice(index,1) // return true } /** * create_entry_type * * @param {object} msg_obj * @returns boolean */ async create_entry_type(msg_obj) { let hash = msg_obj.hash let val_obj = this._in_mem_table[hash] if ( val_obj !== undefined ) return false // let v = msg_obj.v let stored = { "hash" : hash, "v" : v, "time" : Date.now() } // this._in_mem_table[hash] = v let link_hash = msg_obj.link_hash this._in_mem_verify_table[link_hash] = hash this._time_stamp_queue.push(stored) //console.log(`storing ${v} at ${hash} ${this._time_stamp_queue.length}`) return true } /** * update_entry_type * * @param {object} msg_obj * @returns boolean */ async update_entry_type(msg_obj) { let hash = msg_obj.hash let val_obj = this._in_mem_table[hash] if ( val_obj === undefined ) { this.create_entry_type(msg_obj) } else { this._in_mem_table[hash] = msg_obj.v } return true } /** * load_data * * @param {object} msg_obj * @returns boolean */ async load_data(msg_obj) { let hash = msg_obj.hash let v = this._in_mem_table[hash] //console.log(`getting ${v} at ${hash}`) if ( v !== undefined ) { return v } else { return false } } /** * * @param {string} link_hash * @param {object} value * @returns */ is_id_hash(link_hash,value) { // example: link_hash, ccwid // let hash = this._in_mem_verify_table[link_hash] if ( hash !== undefined ) { if ( hash in this._in_mem_table ) { if ( this._in_mem_table[hash] === value ) { return true } } } // return false } // async app_message_handler(msg_obj) { let op = msg_obj._tx_op let result = "ERR" // this.id_augmentation(msg_obj) // switch ( op ) { case 'S' : { msg_obj.link_hash = msg_obj._id let status = await this.create_entry_type(msg_obj) if ( status ) { result = "OK" this.app_publish_on_path('C$shared',this.messenger_path,msg_obj) } return({ "status" : result, "hash" : msg_obj.link_hash, "explain" : "set", "when" : Date.now() }) } case 'H' : { let value_str = msg_obj.v // "hash" : session key, "v" : value if ( typeof value_str !== 'string' ) { value_str = JSON.stringify(value_str) } // msg_obj.link_hash = this.do_hash(`${msg_obj.hash}-${value_str}`) let status = await this.create_entry_type(msg_obj) if ( status ) { result = "OK" this.app_publish_on_path('C$shared',this.messenger_path,msg_obj) } return({ "status" : result, "hash" : msg_obj.link_hash, "explain" : "set", "when" : Date.now() }) } case 'M' : { let status = await this.update_entry_type(msg_obj) if ( status ) { result = "OK" this.app_publish_on_path('M$shared',this.messenger_path,msg_obj) } return({ "status" : result, "explain" : "mod", "when" : Date.now() }) } case 'G' : { // get user information let data = await this.load_data(msg_obj) // as a string not altered if ( data === false ) { data = "" } else { result = "OK"; data = await this.application_data_update(msg_obj,data) // alter the data object for user consumption... such as removing secrets or control codes } return({ "status" : result, "data" : data, "explain" : "get", "when" : Date.now() }) } case 'D' : { // delete asset from everywhere if all ref counts to zero. (unpinned) let status = await this.delete(msg_obj) if ( status ) { result = "OK" this.app_publish_on_path('D$shared',this.messenger_path,msg_obj) } return({ "status" : result, "explain" : "del", "when" : Date.now() }) } default: { // or send 'OP' // let action = msg_obj._user_op if ( action === "inv_hash" ) { // "link_hash" : link_hash, "v" : value let truth = this.is_id_hash(msg_obj.link_hash,msg_obj.v) if ( truth ) { return({ "status" : "OK", "explain" : "inv_hash", "when" : Date.now() }) } } result = "ERR" // } } // return({ "status" : result, "explain" : `${op} performed`, "when" : Date.now(), "_tracking" : msg_obj._tracking }) } // app_publication_pre_fan_response // -- /** * Runs before writing publications. * * This runs if `app_can_block_and_respond`. A true value returned means that the application has blocked the publication. * * Gives the application the chance to refuse a publication or to pass it based on criterea. * * The application may take care of any operation it requires for publication prior to publishing * with the intention of returning false to pass this on to publication. (One might imagine a situation * where the application will read a measurement from sensor and publish the message if the sensor has actually changed. The application version of this * method would return false indicating that the update publication will not be blocked. The method can be used to write the new value onto * the message object.) * * The pulication method, `send_to_all` awaits the return of this method. * * @param {string} topic * @param {object} msg_obj * @returns {boolean} True if this appliction will return without publishing to subscribers. False if going on to publication. */ async app_publication_pre_fan_response(topic,msg_obj) { //console.log("Descendent must implement app_publication_pre_fan_response") return false } // app_subscription_handler // -- runs post fanout of publication /** * * Applications override this method if `app_handles_subscriptions` has been set via configuration. * This method is called after publication of a topic message to subscribers. * This method gives applications the chance to handle internal publication or to make database updates (say) * or any other action required to manage the pub/sub process. * * * @param {string} topic * @param {object} msg_obj */ app_subscription_handler(topic,msg_obj) { //console.log("Descendent must implement app_subscription_handler") } /** * This method is always called by the `add_to_topic` method. * The application has the chance to perform any operation it needs to mark the beginning of a subscription * and to set aside whatever resources or needed to manage the subscription. * * @param {string} topic * @param {string} client_name * @param {object} relayer */ app_post_start_subscription(topic,client_name,relayer) { //console.log("Descendent must implement app_post_start_subscription") } release_and_exit() { this.shutdown_timers() this.closeAll() } } module.exports = ProcJSTableManager