copious-transitions
Version:
Framework for working with frameworks
524 lines (475 loc) • 24.7 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: lib/general_transition_engine.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: lib/general_transition_engine.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>
const AppLifeCycle = require("./general_lifecyle")
const fs_pr = require('fs/promises')
/**
* The General Transition Engine provides basic transitions for files and media while keeping the
* abstraction for managing and executing transtions which may be of varying complexity and asynchronicity.
*
* Ideally, the most advanced transition engine will operate distributed ATMs or Petri nets with the web interface
* taking part a an active input driver from among many input drivers.
*
*
* NOTE: all the base classes in /lib return the class and do not return an instance. Explore the applications and
* the reader will find that the descendant modules export instances. The classes provided by copious-transitions must
* be extended by an application.
*
* @memberof base
*/
class GeneralTransitionEngImpl extends AppLifeCycle {
//
constructor() {
super()
this.db = null
this.statics = null
this.dynamics = null
this._uploader_managers = {}
this.root_path = process.cwd()
//
this.trans_processor = false
this.user_processor = false
this.endpoint_service = false
this.web_sockets = false
}
/**
* At this level, this method just sets the configuration and the database referenc.
* @param {object} conf
* @param {object} db
*/
initialize(conf,db) {
this.conf = conf
this.db = db
}
/**
* This method sets back references to other lib component class instances.
* It also tends to the importation of crypto keys.
*
* @param {object} `statics_assets`
* @param {object} `dynamics_assets`
* @param {object} sessions
*/
install(statics_assets,dynamics_assets,sessions) {
this.sessions = sessions
this.statics = statics_assets
this.dynamics = dynamics_assets
this.statics.set_transition_engine(this)
this.dynamics.set_transition_engine(this)
//
dynamics_assets.import_keys(this.get_import_key_function())
}
/**
* This method sets back references to the contractual methods which provide the skeletal outline
* for handling client requests. The contractual class methods, especially those for transition processing,
* are generally the callers of the application's transition engine. But, the transition engine may call
* back to those classes if needed (depends on the application).
*
*
* @param {object} `trans_processor`
* @param {object} `user_processor`
* @param {object} `mime_processor`
*/
set_contractual_filters(trans_processor,user_processor,mime_processor) {
this.trans_processor = trans_processor
this.user_processor = user_processor
this.mime_processor = mime_processor
}
/**
* Accepts a reference to the application supplied web socket server manager and sets the `web_sockets` field to it.
*
* At times, the transition engine will use the web socket service to fire off messages to listening clients.
*
* @param {object} `web_sockets` - the reference to the application supplied web socket server manager.
*/
set_ws(web_sockets) {
this.web_sockets = web_sockets
}
/**
* This method returns false. It should be overridden in applications using crytpo key processing.
*
* @returns {string|boolean}
*/
get_import_key_function() {
return(false)
}
/**
* Calls the node.js Buffer.concat method. Some application may do something else.
* Returns a buffer.
*
* @param {Buffer} `blob_data`
* @returns {Buffer}
*/
chunks_to_data(blob_data) {
return Buffer.concat(blob_data)
}
/**
* A number of parameters are provided for applications needing more complexity than this method provides.
*
* As a default, this method calls node.js `mv`.
*
* @param {object} `file_descriptor`
* @param {string} `target_path`
* @param {object} `trans_obj`
* @param {Function} cb
* @returns {Number}
*/
async file_mover(file_descriptor,target_path,trans_obj,cb) {
file_descriptor.mv(target_path,cb)
return(Math.floor(Math.random()*10000)) // default random int
}
/**
* By default, this method calls the async writeFile method to store data.
* Some applications may need to route storage at this point.
*
* @param {object} `file_descriptor`
* @param {string} `target_path`
* @param {string} `writeable_data`
* @param {string} id
* @returns {string}
*/
async store_data(file_descriptor,target_path,writeable_data,id) {
await fs_pr.writeFile(target_path,writeable_data)
return id
}
/**
* This method is provided for those applications using persistent storage use meta descriptors of files.
*
* @param {object} `post_body`
* @param {Array} ids
*/
async update_meta_descriptors(post_body,ids) {}
/**
* Returns true if the application override is configured to use alternate storage to the default storage provided here.
*
* @returns {boolean}
*/
alt_store() { return false }
/**
* Replaces the functionality of `store_data` if alt_store returns true.
*
* In the application version of this method, the point is to return data that can be written into a
* file using the method `store_data`.
*
* @param {object} blob_data
*/
app_pack_data(blob_data) {}
// chunk_mover
// per file chunk mover
/**
*
* The map `_uploader_managers` stores a chunk manager per transition token.
* Elsewhere in this documentation, the transition object and its token is explained.
* One kind of transition is one that takes in data from the client, and its token
* will last as long as the data is being uploaded.
*
* The chunk manager has the purpose of return a collection of chunks in preparation for writing to storage.
* In some applications, the `chunk_mover` may return **false** if the chunks cannot be a complete file.
* The expectation is that the chunk mover will be called again after adding more chunks, until the data can be stored
* as a complete object.
*
* When the storage operation has been performed, the `store_data` method will return an identifier that identifies the data.
* This may be a hash of the data. Ultimately, this identifier will be returned by this method.
*
* This method is called by `chunks_complete`.
*
* @param {string} `token`
* @param {object} `file_descriptor`
* @param {string} `target_path`
* @param {Function} `cb`
* @returns {string|Function}
*/
async chunk_mover(token,file_descriptor,target_path,cb) {
//
let chunk_manager = this._uploader_managers[token]
let blob_data = false
if ( chunk_manager._chunks !== undefined ) { // ONE FILE -- one array of chunks
blob_data = chunk_manager._chunks
} else if ( chunk_manager._chunkers !== undefined ) { // MANY FILES -- map file names to arrays of chunks
blob_data = chunk_manager._chunkers[file_descriptor.name]
} else {
// COULD NOT PERFORM OP
return false
}
let id = false
if ( blob_data ) {
let writeable_data = this.alt_store() ? this.app_pack_data(blob_data) : this.chunks_to_data(blob_data)
id = await this.store_data(file_descriptor,target_path,writeable_data,id)
}
try {
if ( cb ) cb()
} catch (e) {}
//
return(id)
}
/**
* Initializes a chunk manager that will receive a number of files identified in the requet body of an uploader type
* of transition. The chunk manager will be keyed (remembered) by its transition token.
*
* The post body should have a field `file_list` which must be an array of file descriptors.
* Each descriptor will have at least one field `name`. The chunk manager will have a map, `_chunkers`,
* which maps the file name to an empty array. The empty array will later be filled with chunk data.
*
* If the post body does not have a `file_list` field, the chunk manager will keep just one array where all the chunks
* being uploaded will be placed for future storage in just one file.
*
* This method is often called by the application's session manager as a part of transition processing.
*
* @param {object} `post_body` - the POST request body from the client (or a message from the endpoint server)
* @param {string} `token` - the chunk manager is always identified by the transtion token
*/
files_coming_in_chunks(post_body,token) {
let chunk_manager = Object.assign({},post_body)
if ( post_body.file_list ) { // MULTIPLE FILES. for clients sending more than one file, a list is expected beyond the typical form field list
chunk_manager._chunkers = {}
for ( let file of post_body.file_list ) { // EACH of file list
if ( file.name !== undefined ) {
chunk_manager._chunkers[file.name] = [] // chunks // each file in the list has to have at least a name
}
}
} else {
chunk_manager._chunks = [] // ONE FILE only
}
this._uploader_managers[token] = chunk_manager // <-- transaction token gets a chunk manager
}
//
/**
*
* This method takes in the client's POST request body (or endpoint message) and the list of files associated with it.
* In some applications, the *files* map may be part of the request body. In other applications, the *files* map may be associated with a
* session token or session and might be cached between calls to the `upload_chunk`.
*
* The post body must have the `token` field with the value being the transition token required by the request.
*
* The previously prepared chunk manager will be accessed by mapping the token to the chunk manager in the
* `_uploader_managers` map.
*
* The files parameter must alwasy be a map even if there is only one file in `files`.
* If there is just one file, this method will check to see if uploading has been prepared to manage just one file.
* If it has been prepared to upload more, it is possible to have just one file if it is the last file still gathering chunks
* from the client. Complete files may have already been finalized. (This is why there are differen field names for the single
* and multiple case.)
*
* This method is often called by the application's session manager as a part of secondary transition processing.
*
*
* @param {object} `post_body`
* @param {string} `files`
* @returns {object} - `state_of_result`
*/
async upload_chunk(post_body,files) {
if ( !files || Object.keys(files).length === 0) {
let state_of_result = {
"state" : "failed",
"OK" : false
}
return state_of_result
}
//
let token = post_body.token
let f_keys = Object.keys(files)
let chunk_manager = this._uploader_managers[token]
if ( (f_keys.length === 1) && chunk_manager._chunks ) { // Handling one file
let file = files[f_keys[0]]
if ( file.data ) {
chunk_manager._chunks.push(file.data)
} else if ( typeof file === "string" ) {
chunk_manager._chunks.push(file)
}
} else { // Handling multiple files
// in a sequece of chunks one or more files may be identified in the post body
// the client will seize to send the smaller files while larger ones will be sent
// until post bodies indicate that there is no more data to come.
if ( chunk_manager._chunkers ) {
for ( let file_key in files ) {
let file = files[file_key]
let chunk_array = chunk_manager._chunkers[file_key]
if ( file && chunk_array ) {
if ( file.data ) {
chunk_array.push(file.data)
} else if ( typeof file === "string" ) {
chunk_array.push(file)
}
}
}
}
}
let state_of_result = {
"state" : "next",
"OK" : true
}
return state_of_result
}
/**
*
* The post body must have the `token` field with the value being the transition token required by the request.
*
* The previously prepared chunk manager will be accessed by mapping the token to the chunk manager in the
* `_uploader_managers` map.
*
* The post body must have one of the following fields:
*
* * `file_list` - an array of file descriptors
* * `file` - a single descriptor
*
* If the post body has the field `file_list`,which must be an array of file descriptors,
* then each descriptor will have at least one field `name`. The chunk manager will have a map, `_chunkers`,
* which maps the file name to the array of gathered chunks.
*
* If the post body as a `file` field instead of a `file_list` field, the chunk manager will look for the one array
* where all the chunks being uploaded have been gathered for storage in just one file.
*
* This method attempts to create a unique name for the file from the information that has been passed to it.
* Furthermore, it makes use of the transition class object's `file_entry_id`.
*
* This method is often called by the application's session manager as a part of transition finalization.
*
* @param {object} post_body
* @param {object} ttrans - the transition class object
* @returns {object} - `finalization_state` - this object has fields "state" equal to "stored" if successful, "OK" equal to **true**,
* and a list of ids, which are all the file ids that have been finally stored.
*/
async chunks_complete(post_body,ttrans) {
//
let ids = []
let token = post_body.token
if ( this._uploader_managers[token] !== undefined ) {
// the file_list is sent in the body.
// they may have fallen out from the upload process.
// But, it is expected that the client will send the list of all files
// in the completion post body.
if ( post_body.file_list ) { // MORE THAN ONE
for ( let uploaded_file of post_body.file_list ) {
let ext = uploaded_file.ext ? uploaded_file.ext : "media"
let file_differentiator = ttrans.file_entry_id("file")
let store_name = `${uploaded_file.name}${file_differentiator}.${ext}`
let dir = ttrans.directory()
//
let file_id = await this.chunk_mover(token,uploaded_file,dir + '/' + store_name,false)
if ( file_id !== false ) { // failed transaction will not return identifiers
ids.push(file_id)
}
}
} else { // JUST ONE
let uploaded_file = post_body.file
let ext = post_body.ext ? post_body.ext : "media"
let file_differentiator = ttrans.file_entry_id("file")
let store_name = `${uploaded_file.name}${file_differentiator}.${ext}`
let dir = ttrans.directory()
//
let file_id = await this.chunk_mover(token,uploaded_file,dir + '/' + store_name,false)
if ( file_id !== false ) { // failed transaction will not return identifiers
ids.push(file_id)
}
}
}
//
let finalization_state = {
"state" : "stored",
"OK" : "true",
"ids" : ids
}
return finalization_state
}
//
/**
* This method servers to upload a single file or multiple files in response one request, where
* all the files are complete upon arrival. (If chunking has occurred, it has been handled by standard HTTP mechanisms).
*
* The function of this handler is fairly common code stacks. Other methods in the class are useful in situations
* where data undergoes intermediate treatment during data gathering or when data is coming in messages from the endpoint server.
* Usually, this method is used when uploading a file from disk and passing it through standard forms.
*
* This method augments the placement of the file into an application directory by adding descriptors of the file into the DB
* (which is again defined by the application).
*
* @param {object} post_body
* @param {object} ttrans
* @param {Array} files
* @param {object} req
* @returns {object} - `finalization_state`
*/
async upload_file(post_body,ttrans,files,req) {
//
if ( !files || Object.keys(files).length === 0) {
let finalization_state = {
"state" : "failed",
"OK" : false
}
return finalization_state
}
//
let ukey = ttrans.primary_key()
let proto_file_name = post_body[ukey]
let file_name_base = ttrans.transform_file_name(proto_file_name)
let ext = post_body.file_type
//
let ids = []
for ( let file_key in files ) {
let uploaded_file = files[file_key]
let file_differentiator = ttrans.file_entry_id(file_key)
// mv is part of the express.js system
let store_name = `${file_name_base}${file_differentiator}.${ext}`
let dir = ttrans.directory()
let udata = {
'name' : proto_file_name,
'id-source' : ukey,
'id' : proto_file_name,
'pass' : '',
'dir' : dir,
'file' : store_name
}
ttrans.update_file_db_entry(udata)
let file_id = await this.file_mover(uploaded_file,dir + '/' + store_name,ttrans,((uudata,ureq) => {
return((err) => {
if ( err ) {
if ( this.sessions ) {
this.sessions.session_accrue_errors("upload",uudata,err,ureq)
}
} else {
this.db.store("upload",uudata)
}
});
})(udata,req),udata)
//
ids.push(file_id)
}
let finalization_state = {
"state" : "stored",
"OK" : "true",
"ids" : ids
}
return finalization_state
}
}
module.exports = GeneralTransitionEngImpl
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Namespaces</h3><ul><li><a href="Contractual.html">Contractual</a></li><li><a href="CopiousTransitions.html">CopiousTransitions</a></li><li><a href="DefaultDB.html">DefaultDB</a></li><li><a href="base.html">base</a></li><li><a href="field_validators.html">field_validators</a></li></ul><h3>Classes</h3><ul><li><a href="Contractual.LocalTObjectCache.html">LocalTObjectCache</a></li><li><a href="Contractual.MimeHandling.html">MimeHandling</a></li><li><a href="Contractual.TransitionHandling.html">TransitionHandling</a></li><li><a href="Contractual.UserHandling.html">UserHandling</a></li><li><a href="CopiousTransitions.CopiousTransitions.html">CopiousTransitions</a></li><li><a href="DefaultDB.CustomizationMethodsByApplication.html">CustomizationMethodsByApplication</a></li><li><a href="DefaultDB.FauxInMemStore.html">FauxInMemStore</a></li><li><a href="DefaultDB.FileMapper.html">FileMapper</a></li><li><a href="DefaultDB.FilesAndRelays.html">FilesAndRelays</a></li><li><a href="DefaultDB.FilesAndRelays_base.html">FilesAndRelays_base</a></li><li><a href="DefaultDB.LocalStaticDB.html">LocalStaticDB</a></li><li><a href="DefaultDB.LocalStorageLifeCycle.html">LocalStorageLifeCycle</a></li><li><a href="DefaultDB.LocalStorageSerialization.html">LocalStorageSerialization</a></li><li><a href="DefaultDB.PageableMemStoreElement.html">PageableMemStoreElement</a></li><li><a href="DefaultDB.PersistenceContracts.html">PersistenceContracts</a></li><li><a href="DefaultDB.RemoteMessaging.html">RemoteMessaging</a></li><li><a href="DefaultDB.StaticDBDefault.html">StaticDBDefault</a></li><li><a href="GeneralUserDBWrapperImpl.html">GeneralUserDBWrapperImpl</a></li><li><a href="SessionTokenManager.html">SessionTokenManager</a></li><li><a href="base.DBClass.html">DBClass</a></li><li><a href="base.EndpointManager.html">EndpointManager</a></li><li><a href="base.GeneralAppLifeCycle.html">GeneralAppLifeCycle</a></li><li><a href="base.GeneralAuth.html">GeneralAuth</a></li><li><a href="base.GeneralBusiness.html">GeneralBusiness</a></li><li><a href="base.GeneralDynamic.html">GeneralDynamic</a></li><li><a href="base.GeneralMiddleWare.html">GeneralMiddleWare</a></li><li><a href="base.GeneralStatic.html">GeneralStatic</a></li><li><a href="base.GeneralTransitionEngImpl.html">GeneralTransitionEngImpl</a></li><li><a href="base.SessionManager.html">SessionManager</a></li><li><a href="base.SessionManager_Lite.html">SessionManager_Lite</a></li><li><a href="base.TaggedTransition.html">TaggedTransition</a></li><li><a href="base.TokenTables.html">TokenTables</a></li><li><a href="base.UserMessageEndpoint.html">UserMessageEndpoint</a></li><li><a href="base.WebSocketManager.html">WebSocketManager</a></li><li><a href="field_validators.DataLookupField.html">DataLookupField</a></li><li><a href="field_validators.EmailField.html">EmailField</a></li><li><a href="field_validators.EmailVerifyField.html">EmailVerifyField</a></li><li><a href="field_validators.FieldTest.html">FieldTest</a></li><li><a href="field_validators.FieldValidatorTools.html">FieldValidatorTools</a></li><li><a href="field_validators.ForeignAuth.html">ForeignAuth</a></li><li><a href="field_validators.GeneralValidator.html">GeneralValidator</a></li><li><a href="field_validators.LengthyAlphabetField.html">LengthyAlphabetField</a></li><li><a href="field_validators.LengthyDigitalField.html">LengthyDigitalField</a></li><li><a href="field_validators.LengthyField.html">LengthyField</a></li><li><a href="field_validators.LengthyStringField.html">LengthyStringField</a></li><li><a href="field_validators.PasswordField.html">PasswordField</a></li><li><a href="field_validators.PasswordVerifyField.html">PasswordVerifyField</a></li><li><a href="field_validators.TypeCheckField.html">TypeCheckField</a></li></ul><h3>Global</h3><ul><li><a href="global.html#generate_password_block">generate_password_block</a></li><li><a href="global.html#load_configuration">load_configuration</a></li><li><a href="global.html#load_parameters">load_parameters</a></li><li><a href="global.html#module_top">module_top</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a> on Tue Oct 31 2023 17:32:59 GMT-0700 (Pacific Daylight Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>