forerunnerdb
Version:
A NoSQL document store database for browsers and Node.js.
703 lines (582 loc) • 20.1 kB
HTML
<!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 && doExport) {
// Get data to upsert (if any)
exportData.data(newDoc, operation.type, function (err, data, callback) {
if (!err && 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 && doExport) {
// Get data to upsert (if any)
importData.data(newDoc, operation.type, function (err, data, callback) {
if (!err && 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 < exportData.types.length; i++) {
self.addTrigger(id + 'export' + exportData.types[i], exportData.types[i], self.PHASE_AFTER, exportTriggerMethod);
}
}
if (importData) {
for (i = 0; i < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 && this._trigger && this._trigger[type] && this._trigger[type][phase] && 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 < 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 && self._trigger && self._trigger[type] && self._trigger[type][phase]) {
triggerArr = self._trigger[type][phase];
triggerCount = triggerArr.length;
for (triggerIndex = 0; triggerIndex < 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 && self.triggerStack[type] && self.triggerStack[type][phase] && 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 && response !== true && 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 && self._trigger[type] && self._trigger[type][phase]) {
triggerArr = self._trigger[type][phase];
triggerCount = triggerArr.length;
for (triggerIndex = 0; triggerIndex < 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>