UNPKG

forerunnerdb

Version:

A NoSQL document store database for browsers and Node.js.

703 lines (582 loc) 20.1 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: Mixin.Triggers.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: Mixin.Triggers.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>"use strict"; var Overload = require('./Overload'); /** * Provides trigger functionality methods. * @mixin */ var Triggers = { /** * Add a trigger by id, type and phase. * @name addTrigger * @method Triggers.addTrigger * @param {String} id The id of the trigger. This must be unique to the type and * phase of the trigger. Only one trigger may be added with this id per type and * phase. * @param {Constants} type The type of operation to apply the trigger to. See * Mixin.Constants for constants to use. * @param {Constants} phase The phase of an operation to fire the trigger on. See * Mixin.Constants for constants to use. * @param {Triggers.addTriggerCallback} method The method to call when the trigger is fired. * @returns {boolean} True if the trigger was added successfully, false if not. */ addTrigger: function (id, type, phase, method) { var self = this, triggerIndex; // Check if the trigger already exists triggerIndex = self._triggerIndexOf(id, type, phase); if (triggerIndex === -1) { self.triggerStack = {}; // The trigger does not exist, create it self._trigger = self._trigger || {}; self._trigger[type] = self._trigger[type] || {}; self._trigger[type][phase] = self._trigger[type][phase] || []; self._trigger[type][phase].push({ id: id, method: method, enabled: true }); return true; } return false; }, /** * Removes a trigger by id, type and phase. * @name removeTrigger * @method Triggers.removeTrigger * @param {String} id The id of the trigger to remove. * @param {Number} type The type of operation to remove the trigger from. See * Mixin.Constants for constants to use. * @param {Number} phase The phase of the operation to remove the trigger from. * See Mixin.Constants for constants to use. * @returns {boolean} True if removed successfully, false if not. */ removeTrigger: function (id, type, phase) { var self = this, triggerIndex; // Check if the trigger already exists triggerIndex = self._triggerIndexOf(id, type, phase); if (triggerIndex > -1) { // The trigger exists, remove it self._trigger[type][phase].splice(triggerIndex, 1); } return false; }, /** * Tells the current instance to fire or ignore all triggers whether they * are enabled or not. * @param {Boolean} val Set to true to ignore triggers or false to not * ignore them. * @returns {*} */ ignoreTriggers: function (val) { if (val !== undefined) { this._ignoreTriggers = val; return this; } return this._ignoreTriggers; }, /** * Generates triggers that fire in the after phase for all CRUD ops * that automatically transform data back and forth and keep both * import and export collections in sync with each other. * @param {String} id The unique id for this link IO. * @param {Object} ioData The settings for the link IO. */ addLinkIO: function (id, ioData) { var self = this, matchAll, exportData, importData, exportTriggerMethod, importTriggerMethod, exportTo, importFrom, allTypes, i; // Store the linkIO self._linkIO = self._linkIO || {}; self._linkIO[id] = ioData; exportData = ioData['export']; importData = ioData['import']; if (exportData) { exportTo = self.db().collection(exportData.to); } if (importData) { importFrom = self.db().collection(importData.from); } allTypes = [ self.TYPE_INSERT, self.TYPE_UPDATE, self.TYPE_REMOVE ]; matchAll = function (data, callback) { // Match all callback(false, true); }; if (exportData) { // Check for export match method if (!exportData.match) { // No match method found, use the match all method exportData.match = matchAll; } // Check for export types if (!exportData.types) { exportData.types = allTypes; } exportTriggerMethod = function (operation, oldDoc, newDoc) { // Check if we should execute against this data exportData.match(newDoc, function (err, doExport) { if (!err &amp;&amp; doExport) { // Get data to upsert (if any) exportData.data(newDoc, operation.type, function (err, data, callback) { if (!err &amp;&amp; data) { // Disable all currently enabled triggers so that we // don't go into a trigger loop exportTo.ignoreTriggers(true); if (operation.type !== 'remove') { // Do upsert exportTo.upsert(data, callback); } else { // Do remove exportTo.remove(data, callback); } // Re-enable the previous triggers exportTo.ignoreTriggers(false); } }); } }); }; } if (importData) { // Check for import match method if (!importData.match) { // No match method found, use the match all method importData.match = matchAll; } // Check for import types if (!importData.types) { importData.types = allTypes; } importTriggerMethod = function (operation, oldDoc, newDoc) { // Check if we should execute against this data importData.match(newDoc, function (err, doExport) { if (!err &amp;&amp; doExport) { // Get data to upsert (if any) importData.data(newDoc, operation.type, function (err, data, callback) { if (!err &amp;&amp; data) { // Disable all currently enabled triggers so that we // don't go into a trigger loop exportTo.ignoreTriggers(true); if (operation.type !== 'remove') { // Do upsert self.upsert(data, callback); } else { // Do remove self.remove(data, callback); } // Re-enable the previous triggers exportTo.ignoreTriggers(false); } }); } }); }; } if (exportData) { for (i = 0; i &lt; exportData.types.length; i++) { self.addTrigger(id + 'export' + exportData.types[i], exportData.types[i], self.PHASE_AFTER, exportTriggerMethod); } } if (importData) { for (i = 0; i &lt; importData.types.length; i++) { importFrom.addTrigger(id + 'import' + importData.types[i], importData.types[i], self.PHASE_AFTER, importTriggerMethod); } } }, /** * Removes a previously added link IO via it's ID. * @param {String} id The id of the link IO to remove. * @returns {boolean} True if successful, false if the link IO * was not found. */ removeLinkIO: function (id) { var self = this, linkIO = self._linkIO[id], exportData, importData, importFrom, i; if (linkIO) { exportData = linkIO['export']; importData = linkIO['import']; if (exportData) { for (i = 0; i &lt; exportData.types.length; i++) { self.removeTrigger(id + 'export' + exportData.types[i], exportData.types[i], self.db.PHASE_AFTER); } } if (importData) { importFrom = self.db().collection(importData.from); for (i = 0; i &lt; importData.types.length; i++) { importFrom.removeTrigger(id + 'import' + importData.types[i], importData.types[i], self.db.PHASE_AFTER); } } delete self._linkIO[id]; return true; } return false; }, enableTrigger: new Overload({ 'string': function (id) { // Alter all triggers of this type var self = this, types = self._trigger, phases, triggers, result = false, i, k, j; if (types) { for (j in types) { if (types.hasOwnProperty(j)) { phases = types[j]; if (phases) { for (i in phases) { if (phases.hasOwnProperty(i)) { triggers = phases[i]; // Loop triggers and set enabled flag for (k = 0; k &lt; triggers.length; k++) { if (triggers[k].id === id) { triggers[k].enabled = true; result = true; } } } } } } } } return result; }, 'number': function (type) { // Alter all triggers of this type var self = this, phases = self._trigger[type], triggers, result = false, i, k; if (phases) { for (i in phases) { if (phases.hasOwnProperty(i)) { triggers = phases[i]; // Loop triggers and set to enabled for (k = 0; k &lt; triggers.length; k++) { triggers[k].enabled = true; result = true; } } } } return result; }, 'number, number': function (type, phase) { // Alter all triggers of this type and phase var self = this, phases = self._trigger[type], triggers, result = false, k; if (phases) { triggers = phases[phase]; if (triggers) { // Loop triggers and set to enabled for (k = 0; k &lt; triggers.length; k++) { triggers[k].enabled = true; result = true; } } } return result; }, 'string, number, number': function (id, type, phase) { // Check if the trigger already exists var self = this, triggerIndex = self._triggerIndexOf(id, type, phase); if (triggerIndex > -1) { // Update the trigger self._trigger[type][phase][triggerIndex].enabled = true; return true; } return false; } }), disableTrigger: new Overload({ 'string': function (id) { // Alter all triggers of this type var self = this, types = self._trigger, phases, triggers, result = false, i, k, j; if (types) { for (j in types) { if (types.hasOwnProperty(j)) { phases = types[j]; if (phases) { for (i in phases) { if (phases.hasOwnProperty(i)) { triggers = phases[i]; // Loop triggers and set enabled flag for (k = 0; k &lt; triggers.length; k++) { if (triggers[k].id === id) { triggers[k].enabled = false; result = true; } } } } } } } } return result; }, 'number': function (type) { // Alter all triggers of this type var self = this, phases = self._trigger[type], triggers, result = false, i, k; if (phases) { for (i in phases) { if (phases.hasOwnProperty(i)) { triggers = phases[i]; // Loop triggers and set to disabled for (k = 0; k &lt; triggers.length; k++) { triggers[k].enabled = false; result = true; } } } } return result; }, 'number, number': function (type, phase) { // Alter all triggers of this type and phase var self = this, phases = self._trigger[type], triggers, result = false, k; if (phases) { triggers = phases[phase]; if (triggers) { // Loop triggers and set to disabled for (k = 0; k &lt; triggers.length; k++) { triggers[k].enabled = false; result = true; } } } return result; }, 'string, number, number': function (id, type, phase) { // Check if the trigger already exists var self = this, triggerIndex = self._triggerIndexOf(id, type, phase); if (triggerIndex > -1) { // Update the trigger self._trigger[type][phase][triggerIndex].enabled = false; return true; } return false; } }), /** * Checks if a trigger will fire based on the type and phase provided. * @param {Number} type The type of operation. See Mixin.Constants for * constants to use. * @param {Number} phase The phase of the operation. See Mixin.Constants * for constants to use. * @returns {Boolean} True if the trigger will fire, false otherwise. */ willTrigger: function (type, phase) { if (!this._ignoreTriggers &amp;&amp; this._trigger &amp;&amp; this._trigger[type] &amp;&amp; this._trigger[type][phase] &amp;&amp; this._trigger[type][phase].length) { // Check if a trigger in this array is enabled var arr = this._trigger[type][phase], i; for (i = 0; i &lt; arr.length; i++) { if (arr[i].enabled) { return true; } } } return false; }, /** * Processes trigger actions based on the operation, type and phase. * @param {Object} operation Operation data to pass to the trigger. * @param {Number} type The type of operation. See Mixin.Constants for * constants to use. * @param {Number} phase The phase of the operation. See Mixin.Constants * for constants to use. * @param {Object} oldDoc The document snapshot before operations are * carried out against the data. * @param {Object} newDoc The document snapshot after operations are * carried out against the data. * @returns {boolean} */ processTrigger: function (operation, type, phase, oldDoc, newDoc) { var self = this, triggerArr, triggerIndex, triggerCount, triggerItem, response, typeName, phaseName; if (!self._ignoreTriggers &amp;&amp; self._trigger &amp;&amp; self._trigger[type] &amp;&amp; self._trigger[type][phase]) { triggerArr = self._trigger[type][phase]; triggerCount = triggerArr.length; for (triggerIndex = 0; triggerIndex &lt; triggerCount; triggerIndex++) { triggerItem = triggerArr[triggerIndex]; // Check if the trigger is enabled if (triggerItem.enabled) { if (self.debug()) { switch (type) { case this.TYPE_INSERT: typeName = 'insert'; break; case this.TYPE_UPDATE: typeName = 'update'; break; case this.TYPE_REMOVE: typeName = 'remove'; break; default: typeName = ''; break; } switch (phase) { case this.PHASE_BEFORE: phaseName = 'before'; break; case this.PHASE_AFTER: phaseName = 'after'; break; default: phaseName = ''; break; } console.log('Triggers: Processing trigger "' + triggerItem.id + '" for ' + typeName + ' in phase "' + phaseName + '"'); } // Check if the trigger is already in the stack, if it is, // don't fire it again (this is so we avoid infinite loops // where a trigger triggers another trigger which calls this // one and so on) if (self.triggerStack &amp;&amp; self.triggerStack[type] &amp;&amp; self.triggerStack[type][phase] &amp;&amp; self.triggerStack[type][phase][triggerItem.id]) { // The trigger is already in the stack, do not fire the trigger again if (self.debug()) { console.log('Triggers: Will not run trigger "' + triggerItem.id + '" for ' + typeName + ' in phase "' + phaseName + '" as it is already in the stack!'); } continue; } // Add the trigger to the stack so we don't go down an endless // trigger loop self.triggerStack = self.triggerStack || {}; self.triggerStack[type] = {}; self.triggerStack[type][phase] = {}; self.triggerStack[type][phase][triggerItem.id] = true; // Run the trigger's method and store the response response = triggerItem.method.call(self, operation, oldDoc, newDoc); // Remove the trigger from the stack self.triggerStack = self.triggerStack || {}; self.triggerStack[type] = {}; self.triggerStack[type][phase] = {}; self.triggerStack[type][phase][triggerItem.id] = false; // Check the response for a non-expected result (anything other than // [undefined, true or false] is considered a throwable error) if (response === false) { // The trigger wants us to cancel operations return false; } if (response !== undefined &amp;&amp; response !== true &amp;&amp; response !== false) { // Trigger responded with error, throw the error throw('ForerunnerDB.Mixin.Triggers: Trigger error: ' + response); } } } // Triggers all ran without issue, return a success (true) return true; } }, /** * Returns the index of a trigger by id based on type and phase. * @param {String} id The id of the trigger to find the index of. * @param {Number} type The type of operation. See Mixin.Constants for * constants to use. * @param {Number} phase The phase of the operation. See Mixin.Constants * for constants to use. * @returns {Number} * @private */ _triggerIndexOf: function (id, type, phase) { var self = this, triggerArr, triggerCount, triggerIndex; if (self._trigger &amp;&amp; self._trigger[type] &amp;&amp; self._trigger[type][phase]) { triggerArr = self._trigger[type][phase]; triggerCount = triggerArr.length; for (triggerIndex = 0; triggerIndex &lt; triggerCount; triggerIndex++) { if (triggerArr[triggerIndex].id === id) { return triggerIndex; } } } return -1; } }; /** * When called in a before phase the newDoc object can be directly altered * to modify the data in it before the operation is carried out. * @callback Triggers.addTriggerCallback * @param {Object} operation The details about the operation. * @param {Object} oldDoc The document before the operation. * @param {Object} newDoc The document after the operation. */ module.exports = Triggers;</code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="ActiveBucket.html">ActiveBucket</a></li><li><a href="Angular.html">Angular</a></li><li><a href="AutoBind.html">AutoBind</a></li><li><a href="Collection.html">Collection</a></li><li><a href="CollectionGroup.html">CollectionGroup</a></li><li><a href="Condition.html">Condition</a></li><li><a href="Core.html">Core</a></li><li><a href="Db.html">Db</a></li><li><a href="Document.html">Document</a></li><li><a href="Grid.html">Grid</a></li><li><a href="Highchart.html">Highchart</a></li><li><a href="Index2d.html">Index2d</a></li><li><a href="IndexBinaryTree.html">IndexBinaryTree</a></li><li><a href="IndexHashMap.html">IndexHashMap</a></li><li><a href="Infinilist.html">Infinilist</a></li><li><a href="KeyValueStore.html">KeyValueStore</a></li><li><a href="Metrics.html">Metrics</a></li><li><a href="MyModule.html">MyModule</a></li><li><a href="NodeApiClient.html">NodeApiClient</a></li><li><a href="NodeApiServer.html">NodeApiServer</a></li><li><a href="NodeRAS.html">NodeRAS</a></li><li><a href="Odm.html">Odm</a></li><li><a href="OldView.html">OldView</a></li><li><a href="Operation.html">Operation</a></li><li><a href="Overload.html">Overload</a></li><li><a href="Overview.html">Overview</a></li><li><a href="Overview_init.html">init</a></li><li><a href="Path.html">Path</a></li><li><a href="Persist.html">Persist</a></li><li><a href="Procedure.html">Procedure</a></li><li><a href="ReactorIO.html">ReactorIO</a></li><li><a href="Section.html">Section</a></li><li><a href="Serialiser.html">Serialiser</a></li><li><a href="Shared.overload.html">overload</a></li><li><a href="View.html">View</a></li></ul><h3>Mixins</h3><ul><li><a href="ChainReactor.html">ChainReactor</a></li><li><a href="Common.html">Common</a></li><li><a href="Constants.html">Constants</a></li><li><a href="Events.html">Events</a></li><li><a href="Matching.html">Matching</a></li><li><a href="Shared.html">Shared</a></li><li><a href="Sorting.html">Sorting</a></li><li><a href="Tags.html">Tags</a></li><li><a href="Triggers.html">Triggers</a></li><li><a href="Updating.html">Updating</a></li></ul><h3><a href="global.html">Global</a></h3> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Thu Mar 01 2018 11:34:22 GMT+0000 (GMT) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>