Comparing version 13.0.0-11 to 13.0.0-12
@@ -8,6 +8,11 @@ /** | ||
var rttc = require('rttc'); | ||
var flaverr = require('./private/flaverr'); | ||
var helpConfigureMachineInstance = require('./private/help-configure-machine-instance'); | ||
var buildInspectFn = require('./private/build-inspect-fn'); | ||
var verifyExitDefinition = require('./private/verify-exit-definition'); | ||
/** | ||
* `Machine.build()` | ||
* | ||
* Construct a machine instance from the provided definition. | ||
@@ -24,7 +29,34 @@ * | ||
// If nothing was provided, build a no-op machine. | ||
// βββ ββββββββββββββββββ ββββββββββββββ βββ | ||
// βββ ββββββββββββββββββββββββββββββββββ ββββ | ||
// βββ βββββββββ βββββββββββββββββ βββββββ | ||
// ββββ ββββββββββ βββββββββββββββββ βββββ | ||
// βββββββ βββββββββββ βββββββββ βββ | ||
// βββββ βββββββββββ βββββββββ βββ | ||
// | ||
// βββββββ βββββββ βββββββ βββ βββββββββββββ βββββββββββββββ | ||
// ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ | ||
// βββββββββββββββββββ ββββββ βββββββββ βββββββββ βββ βββ | ||
// βββββββ βββββββββββ βββββββ ββββββββββ βββββββββ βββ βββ | ||
// βββ βββ ββββββββββββ βββββββ βββββββββββββββββββββββββββ | ||
// βββ βββ βββ βββββββ βββββ ββββββββββ βββββββββββββββ | ||
// | ||
// ββββ ββββ ββββββ ββββββββββ ββββββββββ βββββββββββ βββββββ ββββββββββββββββ | ||
// βββββ ββββββββββββββββββββββββ βββββββββββ βββββββββββ ββββββββββββββββββββββββ | ||
// ββββββββββββββββββββββ βββββββββββββββββ βββββββββ βββ βββββββββ ββββββ | ||
// ββββββββββββββββββββββ βββββββββββββββββββββββββββ βββ βββββββββ ββββββ | ||
// βββ βββ ββββββ ββββββββββββββ βββββββββ ββββββββββββββ βββββββββββββββββββ | ||
// βββ ββββββ βββ ββββββββββ βββββββββ βββββββββββββ βββββββ βββββββββββ | ||
// | ||
// If nothing was provided, build a no-op machine, return that, and bail. | ||
if (!machineDefinition) { | ||
return Machine.buildNoopMachine(); | ||
return Machine.build({ | ||
identity: '_noop', | ||
fn: function (inputs,exits) { return exits.success(); } | ||
}); | ||
} | ||
// --β’ | ||
// If a function was provided, it could mean one of two different things: | ||
@@ -56,19 +88,16 @@ // (1) the user passed in an anonymous function and wants a machine created for it automatically. | ||
var err = new Error(); | ||
// Ensure `machineDefinition` is valid | ||
if (!_.isObject(machineDefinition) || !machineDefinition.fn) { | ||
err.code = 'MACHINE_DEFINITION_INVALID'; | ||
err.message = util.format( | ||
'Failed to build the specified Node Machine definition.\n'+ | ||
'Machines should be defined as a dictionary (aka plain JavaScript object)\n'+ | ||
'with at least one property: `fn`. (See http:// node-machine.org/spec/machine)\n'+ | ||
'\n'+ | ||
'The specified machine definition is invalid:\n'+ | ||
'------------------------------------------------------\n'+ | ||
'%s\n'+ | ||
'------------------------------------------------------', | ||
util.inspect(machineDefinition, false, null)); | ||
throw err; | ||
} | ||
throw flaverr('MACHINE_DEFINITION_INVALID', new Error( | ||
'Failed to build the specified Node Machine definition.\n'+ | ||
'Machines should be defined as a dictionary (aka plain JavaScript object)\n'+ | ||
'with at least one property: `fn`. (See http:// node-machine.org/spec/machine)\n'+ | ||
'\n'+ | ||
'The specified machine definition is invalid:\n'+ | ||
'------------------------------------------------------\n'+ | ||
''+util.inspect(machineDefinition, false, null)+'\n'+ | ||
'------------------------------------------------------' | ||
)); | ||
}// --β’ | ||
@@ -134,16 +163,9 @@ machineDefinition.inputs = machineDefinition.inputs || {}; | ||
// Build an error message | ||
var e = new Error(); | ||
e.code = 'MACHINE_INPUT_INVALID'; | ||
e.message = e.message + '\n' + util.format( | ||
'Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\n'+ | ||
'The following `input` properties are not valid: \n%s', | ||
'β’ ' + _errors.join(',\nβ’ ')); | ||
// If there are issues w/ one or more machine inputs, then throw an error. | ||
if(_errors.length > 0) { | ||
throw flaverr({ code: 'MACHINE_INPUT_INVALID' }, new Error('Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\nThe following `input` properties are not valid: \nβ’ ' + _errors.join(',\nβ’ '))); | ||
}//--β’ | ||
if(_errors.length) { | ||
throw e; | ||
} | ||
} | ||
}//</if provided machine def has inputs >- | ||
// Resolve `like` and `itemOf` directives for each input and exit: | ||
@@ -158,20 +180,6 @@ _.each(machineDefinition.inputs, function(inputDef, inputId) { | ||
if (inputDef.like === inputId) { | ||
throw (function(){ | ||
var err = new Error( | ||
'Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\n'+ | ||
'The machine\'s `'+inputId+'` input has a circular `like` directive (references itself).' | ||
); | ||
err.code = 'MACHINE_INPUT_INVALID'; | ||
return err; | ||
})(); | ||
throw flaverr('MACHINE_INPUT_INVALID', new Error('Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\nThe machine\'s `'+inputId+'` input has a circular `like` directive (references itself).')); | ||
} | ||
if (inputDef.itemOf === inputId) { | ||
throw (function(){ | ||
var err = new Error( | ||
'Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\n'+ | ||
'The machine\'s `'+inputId+'` input has a circular `itemOf` directive (references itself).' | ||
); | ||
err.code = 'MACHINE_INPUT_INVALID'; | ||
return err; | ||
})(); | ||
throw flaverr('MACHINE_INPUT_INVALID', new Error('Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\nThe machine\'s `'+inputId+'` input has a circular `itemOf` directive (references itself).')); | ||
} | ||
@@ -197,6 +205,7 @@ | ||
// input defs in a subsequent release) | ||
validateExitDef(inputDef, machineDefinition); | ||
verifyExitDefinition(inputDef, machineDefinition); | ||
} | ||
} | ||
}); | ||
}//</if example is not explicitly defined> | ||
});//</_.each() :: input definition> | ||
_.each(machineDefinition.exits, function(exitDef, exitId) { | ||
@@ -221,17 +230,12 @@ // Set `_id` on each input | ||
// If `example` is not explicitly defined... | ||
// If `example` is not explicitly defined, then this exit def might still have `like`/`itemOf`/`getExample`. | ||
// So check that, if provided, they make sense. | ||
if (_.isUndefined(exitDef.example)) { | ||
// Then this exit def might still have `like`/`itemOf`/`getExample`. | ||
// So check that, if provided, they make sense. | ||
validateExitDef(exitDef, machineDefinition); | ||
verifyExitDefinition(exitDef, machineDefinition); | ||
} | ||
// Prevent building if `getExample` is not a valid function | ||
if (exitDef.getExample && !_.isFunction(exitDef.getExample)) { | ||
var e = new Error( | ||
'Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\n'+ | ||
'The machine\'s `'+exitId+'` exit has an invalid `getExample` function (should be a function, not a '+rttc.getDisplayType(exitDef.getExample)+').' | ||
); | ||
e.code = 'MACHINE_EXIT_INVALID'; | ||
throw e; | ||
} | ||
throw flaverr('MACHINE_EXIT_INVALID', new Error('Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\nThe machine\'s `'+exitId+'` exit has an invalid `getExample` function (should be a function, not a '+rttc.getDisplayType(exitDef.getExample)+').')); | ||
}//--β’ | ||
@@ -242,20 +246,22 @@ // (Note that `like`, `itemOf`, and `getExample` resolution is still pretty generic here-- | ||
}); | ||
});//</_.each() :: exit definition> | ||
// If the catchall exit is defined and not equal to "error", fail with a deprecation error. | ||
if ( | ||
// Be nice and allow "catchAllExit" (with the "All" capitalized) | ||
(machineDefinition.catchAllExit && machineDefinition.catchAllExit !== 'error') || | ||
(machineDefinition.catchallExit && machineDefinition.catchallExit !== 'error') | ||
) { | ||
err = new Error(util.format( | ||
// If the catchall exit is defined, fail with a deprecation error. | ||
// > Note that play nice and allow "catchAllExit" (with the "All" capitalized) | ||
var isCatchallExitDefined = | ||
!_.isUndefined(machineDefinition.catchAllExit) || | ||
!_.isUndefined(machineDefinition.catchallExit); | ||
if (isCatchallExitDefined) { | ||
throw flaverr({ | ||
code: 'CATCHALL_EXIT_SETTING_DEPRECATED', | ||
machine: machineDefinition.identity | ||
}, new Error( | ||
'Failed to instantiate machine "' + machineDefinition.friendlyName + '" from the specified machine definition.\n'+ | ||
'The `defaultExit` setting (%s) was deprecated in machine@7.0.0. The default exit should always be `success`.', | ||
machineDefinition.catchAllExit||machineDefinition.catchallExit)); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'CATCHALL_EXIT_SETTING_DEPRECATED'; | ||
throw err; | ||
} | ||
'The `defaultExit` setting ('+(machineDefinition.catchAllExit||machineDefinition.catchallExit)+') was deprecated '+ | ||
'in machine@7.0.0. The default exit should always be `success`.' | ||
)); | ||
}//--β’ | ||
@@ -265,10 +271,11 @@ | ||
if (machineDefinition.defaultExit && machineDefinition.defaultExit !== 'success') { | ||
err = new Error(util.format( | ||
throw flaverr({ | ||
code: 'DEFAULT_EXIT_SETTING_DEPRECATED', | ||
machine: machineDefinition.identity | ||
}, new Error( | ||
'Failed to instantiate machine from the specified machine definition.\n'+ | ||
'The `defaultExit` setting (%s) was deprecated in machine@7.0.0. The default exit should always be `success`.', | ||
machineDefinition.defaultExit)); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'DEFAULT_EXIT_SETTING_DEPRECATED'; | ||
throw err; | ||
} | ||
'The `defaultExit` setting ('+machineDefinition.defaultExit+') was deprecated in machine@7.0.0. '+ | ||
'The default exit should always be `success`.' | ||
)); | ||
}//--β’ | ||
@@ -325,20 +332,55 @@ // Ensure "error" exists in the machine def. | ||
else { | ||
err = new Error( | ||
throw flaverr({ | ||
code: 'INVALID_SIDE_EFFECTS', | ||
machine: machineDefinition.identity | ||
}, new Error( | ||
'Failed to instantiate machine from the specified machine definition.\n'+ | ||
'If `sideEffects` is provided, it must be `cacheable`, `idempotent`, or `` (empty string).' | ||
); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'INVALID_SIDE_EFFECTS'; | ||
throw err; | ||
)); | ||
} | ||
//>-β’ | ||
// Define a callable machine function | ||
// βββββββ βββββββββββββββββββββββ βββββββββββ | ||
// ββββββββββββββββββββββββββββββββ βββββββββββ | ||
// βββ βββββββββ ββββββ βββββββββ βββββββββ | ||
// βββ βββββββββ ββββββ βββββββββββββββββββ | ||
// βββββββββββββββββββ ββββββ ββββββββββββββ | ||
// βββββββ βββββββββββ ββββββ βββββββββββββ | ||
// | ||
// Any time this function or one of its proxy methods is called, | ||
// a new machine instance is returned. | ||
// βββββββ ββββββ βββ βββ ββββββ βββββββ βββ ββββββββ | ||
// βββββββββββββββββββ βββ βββββββββββββββββββ ββββββββ | ||
// βββ βββββββββββ βββ βββββββββββββββββββ ββββββ | ||
// βββ βββββββββββ βββ βββββββββββββββββββ ββββββ | ||
// βββββββββββ ββββββββββββββββββββββ βββββββββββββββββββββββββββ | ||
// ββββββββββ ββββββββββββββββββββββ ββββββββββ ββββββββββββββββ | ||
// | ||
// βββ ββββββββββ ββββββ βββββββ βββββββ βββββββββββββββ | ||
// βββ βββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
// βββ ββ βββββββββββββββββββββββββββββββββββββββββ ββββββββ | ||
// βββββββββββββββββββββββββββββββββ βββββββ ββββββ ββββββββ | ||
// βββββββββββββ ββββββ ββββββ βββ βββββββββββ βββ | ||
// ββββββββ βββ ββββββ ββββββ βββ βββββββββββ βββ | ||
// | ||
// Define a new callable machine function: | ||
// | ||
// > Any time this function or one of its proxy methods is called, | ||
// > a new live machine instance is returned. | ||
/** | ||
* @optional {Dictionary?} argins | ||
* @optional {Dictionary?|Function?} cbs | ||
* @optional {Dictionary?} envToSet | ||
* | ||
* @return {LiveMachine} | ||
*/ | ||
var _callableMachineWrapper = function _callableMachineWrapper (){ | ||
var machineInstance = new Machine(machineDefinition); | ||
machineInstance.configure.apply(machineInstance, Array.prototype.slice.call(arguments)); | ||
// Configure provided argins, callbacks, and/or envToSet. | ||
helpConfigureMachineInstance(machineInstance, arguments[0], arguments[1], arguments[2]); | ||
// If multiple arguments were provided, go ahead and exec() the machine instance | ||
@@ -367,3 +409,3 @@ if (arguments.length > 1) { | ||
var machineInstance = new Machine(machineDefinition); | ||
return machineInstance.setEnvironment.apply(machineInstance, Array.prototype.slice.call(arguments)); | ||
return machineInstance.setEnv.apply(machineInstance, Array.prototype.slice.call(arguments)); | ||
}; | ||
@@ -403,3 +445,3 @@ _callableMachineWrapper.configure = function (){ | ||
// Last but not least, inject an `.inspect` method to provide usage info | ||
_callableMachineWrapper.inspect = require('./build-inspect-fn')(_callableMachineWrapper); | ||
_callableMachineWrapper.inspect = buildInspectFn(_callableMachineWrapper); | ||
@@ -421,131 +463,1 @@ // Also, throw these in there for backwards compatibility: | ||
/** | ||
* Validate the `like`, `itemOf`, `getExample`, or explicit exemplar | ||
* in the specified exit definition. | ||
* | ||
* @param {Dictionary} defToResolve | ||
* @param {Dictionary} machineDefinition | ||
*/ | ||
function validateExitDef(defToResolve, machineDefinition){ | ||
// If an explicit example was provided, use that. | ||
if (!_.isUndefined(defToResolve.example)) { | ||
return; | ||
} | ||
var err; | ||
var referencedInput; | ||
// If `like` was provided, use the example of the specified input. | ||
if (!_.isUndefined(defToResolve.like)) { | ||
referencedInput = machineDefinition.inputs[defToResolve.like]; | ||
// If specified input does not exist, this is an error. | ||
if (!referencedInput) { | ||
err = new Error(util.format( | ||
'Failed to instantiate machine ("%s") from the specified machine definition.\n'+ | ||
'`like` should refer to a known machine input, but there is no input named `%s`.', | ||
machineDefinition.friendlyName, | ||
defToResolve.like)); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'UNRECOGNIZED_INPUT'; | ||
throw new Error(err); | ||
} | ||
// Also ensure that any input referenced via `like` has an explicit example. | ||
// (i.e. it can't have `like` or `itemOf` itself) | ||
if (_.isUndefined(referencedInput.example)) { | ||
// However, if the referenced input definition has `isExemplar: true`, then tolerate | ||
// its absense of an example and assume the exemplar is either '*' or '==='. (See above | ||
// for more explanation on why we do this, and to find the code that actually does it.) | ||
if (referencedInput.isExemplar === true) { return; } | ||
throw (function (){ | ||
var err = new Error(util.format( | ||
'Failed to instantiate machine ("%s") from the specified machine definition.\n'+ | ||
'`like` should refer to a machine input with an explicit example (i.e. it can\'t use `like` or `itemOf` too!) The referenced input (named `%s`) does not have an explicit example.', | ||
machineDefinition.friendlyName, | ||
defToResolve.like)); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'UNRECOGNIZED_INPUT'; | ||
return err; | ||
})(); | ||
} | ||
return; | ||
}//</if `like` is specified> | ||
// If `itemOf` was provided, return the pattern of the array example from the specified input. | ||
else if (!_.isUndefined(defToResolve.itemOf)) { | ||
referencedInput = machineDefinition.inputs[defToResolve.itemOf]; | ||
// If specified input does not exist, this is an error. | ||
if (!referencedInput) { | ||
err = new Error(util.format( | ||
'Failed to instantiate machine ("%s") from the specified machine definition.\n'+ | ||
'`itemOf` should refer to a known machine input, but there is no input named `%s`.', | ||
machineDefinition.friendlyName, | ||
defToResolve.itemOf)); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'UNRECOGNIZED_INPUT'; | ||
throw new Error(err); | ||
} | ||
// Ensure that any input referenced via `itemOf` has an explicit example. | ||
// (i.e. it can't have `like` or `itemOf` itself) | ||
if (_.isUndefined(referencedInput.example)) { | ||
throw (function (){ | ||
var err = new Error(util.format( | ||
'Failed to instantiate machine ("%s") from the specified machine definition.\n'+ | ||
'`itemOf` should refer to a machine input with an explicit example (i.e. it can\'t use `like` or `itemOf` too!) The referenced input (named `%s`) does not have an explicit example.', | ||
machineDefinition.friendlyName, | ||
defToResolve.itemOf)); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'UNRECOGNIZED_INPUT'; | ||
return err; | ||
})(); | ||
} | ||
// If specified input example is not an array, this is an error. | ||
if (!_.isArray(referencedInput.example)) { | ||
err = new Error(util.format( | ||
'Failed to instantiate machine ("%s") from the specified machine definition.\n'+ | ||
'`itemOf` should refer to a machine input with a patterned array example, but the example of input `%s` is:', | ||
machineDefinition.friendlyName, | ||
defToResolve.itemOf, | ||
referencedInput.example )); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'INPUT_NOT_ARRAY'; | ||
throw new Error(err); | ||
} | ||
// If specified input example does not have a pattern, this is an error. | ||
if (referencedInput.length < 1) { | ||
err = new Error(util.format( | ||
'Failed to instantiate machine ("%s") from the specified machine definition.\n'+ | ||
'`itemOf` should refer to a machine input with a patterned array example, but the example of input `%s` is `[]`. To indicate an array with JSON-compatible contents, use `[\'*\']`.', | ||
machineDefinition.friendlyName, | ||
defToResolve.itemOf )); | ||
err.machine = machineDefinition.identity; | ||
err.code = 'INPUT_NOT_ARRAY'; | ||
throw new Error(err); | ||
} | ||
return; | ||
}//</if `itemOf` is specified> | ||
} |
@@ -7,7 +7,13 @@ /** | ||
var _ = require('lodash'); | ||
var flaverr = require('./private/flaverr'); | ||
var helpConfigureMachineInstance = require('./private/help-configure-machine-instance'); | ||
var buildInspectFn = require('./private/build-inspect-fn'); | ||
/** | ||
* Construct a configurable/usable Machine instance. | ||
* `Machine` | ||
* | ||
* Construct a configurable/usable live machine (`Machine`) instance. | ||
* > This is what you get when you `require('machine')`. | ||
* | ||
* ---------------------------------------------------------------------------------------- | ||
@@ -19,18 +25,7 @@ * Note that the API for this constructor is private, and it should not be called | ||
* | ||
* @private | ||
* | ||
* @optional {Dictionary} machineDefinition | ||
* @optional {Dictionary?} machineDefinition | ||
* β’ defaults to an anonymous "noop" machine definition which, when | ||
* executed, does nothing beyond calling its success exit. | ||
* @constructor {Machine} | ||
* | ||
* @static Machine.build() | ||
* @static Machine.pack() | ||
* | ||
* @public Machine.prototype.configure() | ||
* @public Machine.prototype.cache() | ||
* @public Machine.prototype.exec() | ||
* @public Machine.prototype.error() | ||
* @public Machine.prototype.warn() | ||
* | ||
* @constructs {LiveMachine} | ||
*/ | ||
@@ -70,15 +65,17 @@ | ||
// If maxRecursion is exceeded, sub-out the real machine instance with a fake one that | ||
// always fails. Default max recursive call depth to 250 | ||
if (recursiveMachineInstance._recursiveDepth > (machineDefinition.maxRecursion||250)) { | ||
// always fails. Default max recursive call depth to `DEFAULT_MAX_RECURSION`. | ||
var DEFAULT_MAX_RECURSION = 250; | ||
if (recursiveMachineInstance._recursiveDepth > (machineDefinition.maxRecursion||DEFAULT_MAX_RECURSION)) { | ||
recursiveMachineInstance = self.constructor.build(function (inputs, exits){ | ||
var err = new Error( util.format( | ||
'This machine called itself too many times--exceeding the maximum recursive depth (%d). '+ | ||
'There is probably an issue in the machine\'s implementation (might be missing a base case, etc.) '+ | ||
'If you are the implementor of this machine, and you\'re sure there are no problems, you can configure '+ | ||
'the maximum number of recursive calls for this machine using `maxRecursion` (a top-level property in '+ | ||
'your machine definition). The default is 250.', | ||
(machineDefinition.maxRecursion||250) | ||
) ); | ||
err.code = 'E_MAX_RECURSION'; | ||
return exits.error( err ); | ||
return exits.error( | ||
flaverr('E_MAX_RECURSION', new Error( | ||
'This machine called itself too many times--exceeding the maximum recursive depth ('+(machineDefinition.maxRecursion||DEFAULT_MAX_RECURSION)+'). '+ | ||
'There is probably an issue in the machine\'s implementation (might be missing a base case, etc.) '+ | ||
'If you are the implementor of this machine, and you\'re sure there are no problems, you can configure '+ | ||
'the maximum number of recursive calls for this machine using `maxRecursion` (a top-level property in '+ | ||
'your machine definition). The default is '+DEFAULT_MAX_RECURSION+'.' | ||
)) | ||
); | ||
}); | ||
@@ -236,2 +233,3 @@ } | ||
// TODO: probably deprecate this | ||
var elaborationPhrase = (function _determineElaborationPhrase(){ | ||
@@ -250,65 +248,135 @@ var phrase = (machine.description||machine.friendlyName) ? ' ('+(machine.description||machine.friendlyName)+')' : ''; | ||
} | ||
}//</top-lvl module / Machine constructor> | ||
// Static methods | ||
// // ======================================================================================== | ||
// // ======================================================================================== | ||
// // ======================================================================================== | ||
// | ||
// For future reference, the experimental userland type-safety-customization flags: | ||
// | ||
// this._runTimeTypeCheck = false; | ||
// this._inputCoercion = false; | ||
// this._exitCoercion = false; | ||
// this._unsafeMode = true; | ||
// | ||
// // ======================================================================================== | ||
// // ======================================================================================== | ||
// // ======================================================================================== | ||
// βββββββββββββββββ ββββββ ββββββββββββ βββββββ | ||
// βββββββββββββββββββββββββββββββββββββββββββββ | ||
// ββββββββ βββ ββββββββ βββ ββββββ | ||
// ββββββββ βββ ββββββββ βββ ββββββ | ||
// ββββββββ βββ βββ βββ βββ βββββββββββ | ||
// ββββββββ βββ βββ βββ βββ βββ βββββββ | ||
// | ||
// ββββ ββββββββββββββββββββββββ βββ βββββββ βββββββ ββββββββ | ||
// βββββ βββββββββββββββββββββββββ βββββββββββββββββββββββββββββββ | ||
// βββββββββββββββββ βββ βββββββββββ ββββββ ββββββββββββββ | ||
// βββββββββββββββββ βββ βββββββββββ ββββββ ββββββββββββββ | ||
// βββ βββ βββββββββββ βββ βββ βββββββββββββββββββββββββββββββ | ||
// βββ βββββββββββ βββ βββ βββ βββββββ βββββββ ββββββββ | ||
// | ||
// ββββ¦ β¦ββ β¦ β¦βββ ββ¦ββββββ¦ββ¦ β¦βββββ¦ββββ | ||
// β βββ ββ β©ββ ββ βββββ£ β β ββ£β β βββββ | ||
// β© βββββββ©βββ©βββ β© β©βββ β© β© β©βββββ©ββββ | ||
// ββ ββββββ ββ¬ββ¬ β¬βββ ββ¬ββββββββ¬ β¬β¬ββββββ ββββββββββββββ¬ββ¬βββ¬ β¬βββββ¬βββββ¬ββ ββ | ||
// ββββ β ββββ β βββ€ββ€ ββββββ€β βββ€ββββββ€ β β βββββββ β ββ¬ββ ββ β β βββ¬β ββββ | ||
// ββ ββββββ β΄ β΄ β΄βββ β΄ β΄β΄ β΄ββββ΄ β΄β΄ββββββ ββββββββββββ β΄ β΄ββββββββ β΄ ββββ΄ββ ββ | ||
// Public (static) methods available on the Machine constructor: | ||
Machine.build = require('./Machine.build'); | ||
Machine.pack = require('./Machine.pack'); | ||
Machine.buildNoopMachine = require('./Machine.buildNoopMachine'); | ||
Machine.buildHaltMachine = require('./Machine.buildHaltMachine'); | ||
Machine.getMethodName = require('./Machine.getMethodName'); | ||
// Aliases | ||
Machine.load = Machine.build; | ||
Machine.require = Machine.build; | ||
Machine.machine = Machine.build; | ||
// Prototypal methods | ||
// ββββ¦βββββββ¦βββββββββ¦ββββββ¦β ββ¦ββββββ¦ββ¦ β¦βββββ¦ββββ | ||
// β βββ β¦ββ β β ββ£ β β ββ£ ββ βββββ£ β β ββ£β β βββββ | ||
// β© β©βββββ β© ββββββ β© βββββ©β β© β©βββ β© β© β©βββββ©ββββ | ||
// ββ ββββββ ββ¬ββ¬ β¬βββ ββ¬ββββββββ¬ β¬β¬ββββββ ββββββββββββββ¬ββ¬βββ¬ β¬βββββ¬βββββ¬ββ ββ | ||
// ββββ β ββββ β βββ€ββ€ ββββββ€β βββ€ββββββ€ β β βββββββ β ββ¬ββ ββ β β βββ¬β ββββ | ||
// ββ ββββββ β΄ β΄ β΄βββ β΄ β΄β΄ β΄ββββ΄ β΄β΄ββββββ ββββββββββββ β΄ β΄ββββββββ β΄ ββββ΄ββ ββ | ||
// Protected (static) methods available on the Machine constructor: | ||
/** | ||
* .inspect() | ||
* | ||
* When the Machine constructor is inspected (e.g. `util.inspect()` / `console.log()`), | ||
* pretty print the current version of node-machine, with license information and a link | ||
* to the documentation. | ||
* | ||
* @return {String} | ||
* @protected | ||
*/ | ||
Machine.inspect = function () { | ||
return util.format( | ||
'-----------------------------------------\n'+ | ||
' machine\n'+ | ||
' v%s\n'+ | ||
' \n'+ | ||
' β’ License : %s\n'+ | ||
' β’ Docs : %s\n'+ | ||
'-----------------------------------------\n', | ||
require('../package.json').version, | ||
require('../package.json').license, | ||
require('../package.json').docs && require('../package.json').docs.url | ||
); | ||
}; | ||
// βββββββ ββββββββββββββββββββ ββββββ ββββ βββ βββββββββββββββ | ||
// ββββββββ βββββββββββββββββββββββββββββββββ βββββββββββββββββββ | ||
// βββββββββ βββββββββββ βββ ββββββββββββββ ββββββ ββββββ | ||
// βββββββββββββββββββββ βββ βββββββββββββββββββββ ββββββ | ||
// ββββββ ββββββββββββββ βββ βββ ββββββ ββββββββββββββββββββββ | ||
// ββββββ βββββββββββββ βββ βββ ββββββ βββββ βββββββββββββββ | ||
// | ||
// ββββ ββββββββββββββββββββββββ βββ βββββββ βββββββ ββββββββ | ||
// βββββ βββββββββββββββββββββββββ βββββββββββββββββββββββββββββββ | ||
// βββββββββββββββββ βββ βββββββββββ ββββββ ββββββββββββββ | ||
// βββββββββββββββββ βββ βββββββββββ ββββββ ββββββββββββββ | ||
// βββ βββ βββββββββββ βββ βββ βββββββββββββββββββββββββββββββ | ||
// βββ βββββββββββ βββ βββ βββ βββββββ βββββββ ββββββββ | ||
// | ||
// ββββ¦ β¦ββ β¦ β¦βββ ββ¦ββββββ¦ββ¦ β¦βββββ¦ββββ | ||
// β βββ ββ β©ββ ββ βββββ£ β β ββ£β β βββββ | ||
// β© βββββββ©βββ©βββ β© β©βββ β© β© β©βββββ©ββββ | ||
// ββ ββββββ β¬ β¬β¬ β¬βββ ββ¬ββββββββ¬ β¬β¬ββββββ β¬ββββββββ¬ββββββββββββββββ ββ | ||
// ββββ β ββββ β βββββββ€ ββββββ€β βββ€ββββββ€ βββββββ β βββ€ββββ ββ€ βββ ββββ | ||
// ββ ββββββ β΄βββ΄ ββ βββ β΄ β΄β΄ β΄ββββ΄ β΄β΄ββββββ β΄ββββββ β΄ β΄ β΄ββββββββββββ ββ | ||
// Public methods on live machine instances: | ||
Machine.prototype.exec = require('./Machine.prototype.exec'); | ||
Machine.prototype.execSync = require('./Machine.prototype.execSync'); | ||
Machine.prototype.demuxSync = require('./Machine.prototype.demuxSync'); | ||
Machine.prototype.configure = require('./Machine.prototype.configure'); | ||
Machine.prototype.cache = require('./Machine.prototype.cache'); | ||
Machine.prototype.warn = require('./Machine.prototype.warn'); | ||
Machine.prototype.log = require('./Machine.prototype.warn'); | ||
Machine.prototype.error = require('./Machine.prototype.error'); | ||
/** | ||
* @param {Boolean} flag | ||
* @chainable | ||
*/ | ||
Machine.prototype.rttc = function(flag) { | ||
this._runTimeTypeCheck = flag; | ||
return this; | ||
}; | ||
/** | ||
* @param {Boolean} flag | ||
* setEnv() | ||
* | ||
* Fold the specified habitat variables into the `env` that will | ||
* be passed in as the third argument to the machine `fn`. | ||
* | ||
* @required {Dictionary} envToSet | ||
* @chainable | ||
*/ | ||
Machine.prototype.unsafe = function(flag) { | ||
this._unsafeMode = flag; | ||
if (flag === true) { | ||
this._runTimeTypeCheck = false; | ||
this._inputCoercion = false; | ||
this._exitCoercion = false; | ||
Machine.prototype.setEnv = function (envToSet) { | ||
if (!_.isObject(envToSet) || _.isArray(envToSet) || _.isFunction(envToSet)) { | ||
throw flaverr('E_USAGE', new Error('Invalid usage: `.setEnv()` expects a dictionary of habitat variables. But instead got: '+util.inspect(envToSet, {depth: null}))); | ||
} | ||
return this; | ||
}; | ||
/** | ||
* @param {Boolean} flag | ||
* @chainable | ||
*/ | ||
Machine.prototype.inputCoercion = function(flag) { | ||
this._inputCoercion = flag; | ||
return this; | ||
}; | ||
// Call helper. | ||
helpConfigureMachineInstance(this, undefined, undefined, envToSet); | ||
/** | ||
* @param {Boolean} flag | ||
* @chainable | ||
*/ | ||
Machine.prototype.exitCoercion = function(flag) { | ||
this._exitCoercion = flag; | ||
return this; | ||
@@ -318,111 +386,93 @@ }; | ||
/** | ||
* Set the new onInvoke handler function. | ||
* @param {[type]} invokeEventHandlerFn [description] | ||
* .configure() | ||
* | ||
* Configure a live machine instance with argins, callbacks, and/or habitat variables. | ||
* (This just uses the private `helpConfigureMachineInstance()` under the covers.) | ||
* | ||
* @required {Dictionary?} argins | ||
* @required {Dictionary?|Function?} cbs | ||
* @required {Dictionary?} envToSet | ||
* | ||
* @returns {LiveMachine} | ||
* @chainable | ||
*/ | ||
Machine.prototype.onInvoke = function (invokeEventHandlerFn) { | ||
this._onInvoke = invokeEventHandlerFn; | ||
return this; | ||
}; | ||
Machine.prototype.configure = function (argins, cbs, envToSet) { | ||
// Call helper | ||
helpConfigureMachineInstance(this, argins, cbs, envToSet); | ||
/** | ||
* Configure runtime input values. | ||
* | ||
* @param {Dictionary} configuredInputVals | ||
* @chainable | ||
*/ | ||
Machine.prototype.setInputs = function (configuredInputVals) { | ||
_.extend(this._configuredInputs, configuredInputVals); | ||
// Return self (to make this chainable). | ||
return this; | ||
}; | ||
/** | ||
* @param {Dictionary} callbacks | ||
* @chainable | ||
*/ | ||
Machine.prototype.setExits = function (callbacks) { | ||
// Handle callback function | ||
if (_.isFunction(callbacks)) { | ||
this._configuredExits.success = function (result){ | ||
return callbacks(null, result); | ||
}; | ||
this._configuredExits.error = function (err){ | ||
return callbacks(err); | ||
}; | ||
} | ||
else if (!_.isObject(callbacks)) { | ||
throw new Error('Machine must be configured with either (A) a single, traditional Node.js-style callback function, or (B) a dictionary of exit callbacks. But instead, got:\n'+util.inspect(callbacks,{depth: null})); | ||
} | ||
// Handle exits dictionary | ||
else { | ||
// Make sure only declared exits are configured. | ||
var undeclaredExits = _.difference(_.keys(callbacks), _.keys(this.exits)); | ||
if (undeclaredExits.length > 0) { | ||
throw new Error('One or more callbacks were configured for exits that are not recognized by this machine: `' + undeclaredExits.join(', ') + '`.'); | ||
} | ||
_.extend(this._configuredExits, callbacks); | ||
} | ||
return this; | ||
}; | ||
// ββββ¦βββββββ¦βββββββββ¦ββββββ¦β ββ¦ββββββ¦ββ¦ β¦βββββ¦ββββ | ||
// β βββ β¦ββ β β ββ£ β β ββ£ ββ βββββ£ β β ββ£β β βββββ | ||
// β© β©βββββ β© ββββββ β© βββββ©β β© β©βββ β© β© β©βββββ©ββββ | ||
// ββ ββββββ β¬ β¬β¬ β¬βββ ββ¬ββββββββ¬ β¬β¬ββββββ β¬ββββββββ¬ββββββββββββββββ ββ | ||
// ββββ β ββββ β βββββββ€ ββββββ€β βββ€ββββββ€ βββββββ β βββ€ββββ ββ€ βββ ββββ | ||
// ββ ββββββ β΄βββ΄ ββ βββ β΄ β΄β΄ β΄ββββ΄ β΄β΄ββββββ β΄ββββββ β΄ β΄ β΄ββββββββββββ ββ | ||
// Protected methods on live machine instances: | ||
// | ||
// > These should not be called in userland code! | ||
/** | ||
* setEnv() | ||
* Machine.prototype._warn() | ||
* | ||
* Set `env` (third argument to machine fn) | ||
* Trigger a warning on this machine. | ||
* > Use configured `onWarn` function, or by default, use `console.error`. | ||
* | ||
* @param {Dictionary} configuredEnv | ||
* @chainable | ||
* @private | ||
* | ||
* TODO: change this to `_warn()` to make it less tempting to mess w/ in userland | ||
*/ | ||
Machine.prototype.setEnv = function (configuredEnv) { | ||
_.extend(this._configuredEnvironment, configuredEnv); | ||
Machine.prototype.warn = function Machine_prototype_warn () { | ||
(this.onWarn||function _defaultWarnHandler(/*...*/){ | ||
console.error.apply(console, Array.prototype.slice.call(arguments)); | ||
}).apply(this, Array.prototype.slice.call(arguments)); | ||
return this; | ||
}; | ||
// Alias `setEnvironment()` => `setEnv()` | ||
Machine.prototype.setEnvironment = Machine.prototype.setEnv; | ||
/** | ||
* Pretty print the current version of node-machine, with license information | ||
* and a link to the documentation. | ||
* Machine.prototype.log() | ||
* | ||
* @return {String} | ||
* Trigger a log on this machine. | ||
* > Use configured `onLog` function, or by default, use `console.log()`. | ||
* | ||
* @chainable | ||
* @private | ||
* | ||
* TODO: change this to `_log()` to make it less tempting to mess w/ in userland | ||
*/ | ||
Machine.inspect = function () { | ||
return util.format( | ||
'-----------------------------------------\n'+ | ||
' machine\n'+ | ||
' v%s\n'+ | ||
' \n'+ | ||
' β’ License : %s\n'+ | ||
' β’ Docs : %s\n'+ | ||
'-----------------------------------------\n', | ||
require('../package.json').version, | ||
require('../package.json').license, | ||
require('../package.json').docs && require('../package.json').docs.url | ||
); | ||
Machine.prototype.log = function Machine_prototype_log () { | ||
(this.onLog||function _defaultLogHandler(/*...*/){ | ||
console.log.apply(console, Array.prototype.slice.call(arguments)); | ||
}).apply(this, Array.prototype.slice.call(arguments)); | ||
return this; | ||
}; | ||
/** | ||
* Expose `getMethodName()` | ||
* .inspect() | ||
* | ||
* When a live machine instance is inspected (e.g. `util.inspect()` / `console.log()`), | ||
* pretty print some basic info about the machine's usage. | ||
* | ||
* @return {String} | ||
*/ | ||
Machine.getMethodName = require('./get-method-name'); | ||
/** | ||
* @return {String} | ||
*/ | ||
Machine.prototype.inspect = function () { | ||
var _inspect = require('./build-inspect-fn')(this); | ||
var _inspect = buildInspectFn(this); | ||
return _inspect(); | ||
@@ -433,5 +483,19 @@ }; | ||
/** | ||
* @type {Machine.constructor} | ||
*/ | ||
// βββββββββββ ββββββββββ βββββββ ββββββββββββββββ βββββββ ββββ βββ | ||
// βββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββ βββ | ||
// ββββββ ββββββ βββββββββββ βββββββββββββββββ βββ βββββββββ βββ | ||
// ββββββ ββββββ βββββββ βββ βββββββββββββββββ βββ βββββββββββββ | ||
// ββββββββββββ ββββββ βββββββββββββββββββββββββ ββββββββββββ ββββββ | ||
// βββββββββββ ββββββ βββββββ ββββββββββββββββ βββββββ βββ βββββ | ||
// | ||
// ββββ ββββ βββββββ βββββββ βββ ββββββ ββββββββ βββββββββββ ββββββββββ βββββββ βββββββ βββββββββββββββββ | ||
// βββββ βββββββββββββββββββββββββ ββββββ ββββββββ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
// ββββββββββββββ ββββββ ββββββ ββββββ ββββββ ββββββ ββββββ βββββββββββ βββββββββββ βββ ββββββββ | ||
// ββββββββββββββ ββββββ ββββββ ββββββ ββββββ ββββββ ββββββ βββββββ βββ βββββββββββ βββ ββββββββ | ||
// βββ βββ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββ ββββββββββββ βββ βββ ββββββββ | ||
// βββ βββ βββββββ βββββββ βββββββ ββββββββββββββββββββββββββββββ ββββββ βββββββ βββ βββ βββ ββββββββ | ||
// | ||
// Expose the Machine constructor. | ||
module.exports = Machine; |
@@ -9,6 +9,7 @@ /** | ||
var includeAll = require('include-all'); | ||
var flaverr = require('./private/flaverr'); | ||
/** | ||
* Machine.pack() | ||
* `Machine.pack()` | ||
* | ||
@@ -21,13 +22,19 @@ * Load modules in the specified directory and expose them as | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* @required {Dictionary} dir | ||
* The absolute path to the location of the modules to load & pack. | ||
* (If a relative path is specified, it will be resolved relative from the `pkg`) | ||
* @required {Dictionary?|String} options | ||
* Either the absolute path to the location of the modules to load & pack (see `dir` below) | ||
* -OR- a dictionary of options: | ||
* | ||
* @optional {Dictionary} pkg | ||
* The package dictionary (i.e. what package.json exports). | ||
* Will be used for refining the directory to load modules from. | ||
* If `pkg` is not specified, all `.js` files in `dir` will be loaded | ||
* (with the exception of `index.js`, which is reserved.) | ||
* @property {String} dir | ||
* The absolute path to the location of the modules to load & pack. | ||
* (If a relative path is specified, it will be resolved relative from the `pkg`) | ||
* | ||
* @property {Dictionary} pkg | ||
* The package dictionary (i.e. what package.json exports). | ||
* Will be used for refining the directory to load modules from. | ||
* If `pkg` is not specified, all `.js` files in `dir` will be loaded | ||
* (with the exception of `index.js`, which is reserved.) | ||
* | ||
* | ||
* @returns {Dictionary} | ||
* A dictionary of packed modules with camel-cased keys, and functions as values. | ||
*/ | ||
@@ -96,15 +103,12 @@ | ||
catch (e) { | ||
var err = new Error('Failed to instantiate hydrated machinepack using the provided `pkg`.'); | ||
err.code = 'E_INVALID_OPTION'; | ||
err.message = util.format( | ||
'Failed to instantiate hydrated machinepack using the provided `pkg`.\n'+ | ||
'`pkg` should be a dictionary with the following properties:\n'+ | ||
' β’ machinepack.machines\n β’ machinepack\n\n'+ | ||
'But the actual `pkg` option provided was:\n'+ | ||
'------------------------------------------------------\n'+ | ||
'%s\n'+ | ||
'------------------------------------------------------\n', | ||
util.inspect(options.pkg, false, null)); | ||
throw err; | ||
throw flaverr('E_INVALID_OPTION', new Error( | ||
'Failed to instantiate hydrated machinepack using the provided `pkg`.\n'+ | ||
'`pkg` should be a dictionary with a `machinepack` property (also a dictionary, '+ | ||
'with its own array of strings called `machines`).\n'+ | ||
'But the actual `pkg` option provided was:\n'+ | ||
'------------------------------------------------------\n'+ | ||
''+util.inspect(options.pkg, false, null)+'\n'+ | ||
'------------------------------------------------------\n'+ | ||
'Raw error details:\n'+e.stack) | ||
); | ||
} | ||
@@ -111,0 +115,0 @@ |
@@ -6,6 +6,8 @@ /** | ||
var _ = require('lodash'); | ||
var flaverr = require('./private/flaverr'); | ||
/** | ||
* `Machine.prototype.cache()` | ||
* | ||
* Provide cache settings. | ||
@@ -16,3 +18,3 @@ * @param {Dictionary} cacheSettings | ||
module.exports = function Machine_prototype_cache (cacheSettings) { | ||
module.exports = function cache(cacheSettings) { | ||
@@ -22,7 +24,3 @@ // Caching is only allowed on machines which explicitly identify themselves as cacheable. | ||
if (!this.cacheable) { | ||
throw (function (){ | ||
var _err = new Error('Cannot use `.cache()` with `'+this.identity+'` machine because it does not consider itself cacheable (i.e. `cacheable:true`)'); | ||
_err.code = 'E_USAGE'; | ||
return _err; | ||
})(); | ||
throw flaverr('E_USAGE', new Error('Cannot use `.cache()` with `'+this.identity+'` machine because it does not consider itself cacheable (i.e. `cacheable:true`)')); | ||
} | ||
@@ -29,0 +27,0 @@ |
@@ -10,7 +10,19 @@ /** | ||
/** | ||
* Warning: this function is experimental and its usage may change! | ||
* `Machine.prototype.demuxSync()` | ||
* | ||
* @param {String} exitName -- optional, defaults to success. The name of the exit traversed for which this function will return `true` (otherwise it returns false) | ||
* Run this machine's `fn`, then return `true` if it worked. Otherwise, return `false`. | ||
* | ||
* | ||
* @optional {String} exitName | ||
* The name of the exit to expect. (i.e. for which this function will return `true`) | ||
* Defaults to "success". | ||
* | ||
* @returns {Boolean} `true` if machine triggers the desired exit, `false` otherwise. | ||
* @throws {E_USAGE} If `demuxSync()` cannot be used | ||
* @throws {E_USAGE} If `demuxSync()` cannot be used (i.e. because this machine does not declare `sync: true`) | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
* > Warning: this function is experimental and its usage may change! | ||
* > | ||
* > (To check for current usage in other core utilities / experimental tooling, | ||
* > see https://github.com/search?utf8=%E2%9C%93&q=demuxSync+user%3Abalderdashy+user%3Asailsjs+user%3Anode-machine+user%3Atreelinehq+user%3Amikermcneil&type=Code&ref=searchresults) | ||
*/ | ||
@@ -23,4 +35,19 @@ module.exports = function demuxSync(exitName){ | ||
catch (e) { | ||
if (e.code === 'E_USAGE') { | ||
throw e; | ||
switch (e.exit) { | ||
case 'error': | ||
case undefined: | ||
switch (e.code) { | ||
case 'E_USAGE': throw e; | ||
default: | ||
// If `code` is not explicitly recognized as any kind of internal error | ||
// about generic usage (userland code's fault) or a malformed definition | ||
// (machine impl code's fault), then we can just assume this is some | ||
// miscellaneous runtime error that was thrown from the machine's error | ||
// exit. | ||
} | ||
break; | ||
default: | ||
// Otherwise, this machine exited through some miscellaneous exit, | ||
// so we don't have to negotiate the error any further. | ||
} | ||
@@ -27,0 +54,0 @@ } |
@@ -6,14 +6,10 @@ /** | ||
var util = require('util'); | ||
var Debug = require('debug'); | ||
var _ = require('lodash'); | ||
var rttc = require('rttc'); | ||
var switchback = require('switchback'); | ||
var hashArgins = require('./hash-argins'); | ||
var interceptExitCallbacks = require('./intercept-exit-callbacks'); | ||
var validateConfiguredInputValues = require('./validate-configured-input-values'); | ||
var buildLamdaMachine = require('./build-lamda-machine'); | ||
var flaverr = require('./private/flaverr'); | ||
var helpExecMachineInstance = require('./private/help-exec-machine-instance'); | ||
var helpConfigureMachineInstance = require('./private/help-configure-machine-instance'); | ||
/** | ||
* .exec() | ||
* `Machine.prototype.exec()` | ||
* | ||
@@ -30,10 +26,15 @@ * Run this machine's `fn` and trigger the appropriate exit callback. | ||
* | ||
* @param? {Dictionary|Function} done | ||
* @optional {Dictionary?|Function?} done | ||
* An optional callback function or dictionary of exit-handling callback functions. | ||
* If provided, this callback (or set of callbacks) will be folded onto any existing | ||
* exit-handling callbacks which were already attached with `.setExits()`. | ||
* exit-handling callbacks which were already attached with `.configure()`. | ||
* | ||
* @optional {Dictionary?} envToSet | ||
* An optional dictionary of additional habitat variables to merge in on top of | ||
* whatever else is there before invoking the machine. (Will be passed in to the | ||
* machine's `fn` as the `env` argument.) | ||
* | ||
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
*/ | ||
module.exports = function Machine_prototype_exec (done) { | ||
module.exports = function exec(done, envToSet) { | ||
@@ -44,3 +45,5 @@ var self = this; | ||
// (better to terminate the process than trigger a callback twice) | ||
if (self._started) { throw new Error('Cannot call `.exec()` twice on the same live machine (machine `'+_.camelCase(self.identity)+'` already started running). To run a machine multiple times, please configure it each time to get a new instance.'); } | ||
if (self._started) { | ||
throw flaverr('E_USAGE', new Error('Invalid usage: Cannot call `.exec()` twice on the same live machine (machine `'+_.camelCase(self.identity)+'` already started running). To run a machine multiple times, configure it each time to obtain a new live machine instance.')); | ||
} | ||
@@ -50,15 +53,10 @@ // Set the _started spinlock so that this live machine cannot be `exec()`d again. | ||
// Create an error instance so that we have a clean stack trace leading back to the | ||
// calling machine. We'll use this below when we build forwarding callbacks for | ||
// miscellaneous exits, to give them better output messages. | ||
var cleanError = new Error(); | ||
// Lop off the first line of the stack trace, which we'll replace | ||
// with a better message below. | ||
cleanError.stack = cleanError.stack.split('\n').slice(1).join('\n'); | ||
// If `envToSet` was provided, validate it, then merge it in. | ||
if (!_.isUndefined(envToSet)) { | ||
if (!_.isObject(envToSet) || _.isArray(envToSet) || _.isFunction(envToSet)) { | ||
throw flaverr('E_USAGE', new Error('Invalid usage: If a second argument is provided to `.exec()`, it is expected to be a dictionary of habitat variables. But instead got: '+util.inspect(envToSet, {depth: null}))); | ||
} | ||
// If duration-tracking is enabled, track current timestamp | ||
// as a JavaScript Date instance in `_execBeginTimestamp`. | ||
if (self._doTrackDuration){ | ||
self._execBeginTimestamp = new Date(); | ||
helpConfigureMachineInstance(self, undefined, undefined, envToSet); | ||
} | ||
@@ -69,14 +67,19 @@ | ||
if (done !== undefined) { | ||
// Do a quick sanity check to make sure it _at least looks like_ a callback function or dictionary of callback functions. | ||
// Do a quick sanity check to make sure it _at least looks like_ a callback function | ||
// or dictionary of callback functions. | ||
if (_.isArray(done) || (!_.isFunction(done) && !_.isObject(done))) { | ||
throw new Error('Invalid usage: If something is passed in to `.exec()`, it must either be:\n'+ | ||
' (1) a standard Node callback function, or\n'+ | ||
' (2) a dictionary of per-exit callback functions\n'+ | ||
'\n'+ | ||
'But instead, got:\n'+ | ||
util.inspect({depth: null})+ | ||
''); | ||
throw flaverr('E_USAGE', new Error('Invalid usage: `.exec()` must be called with either:\n'+ | ||
' (1) a standard Node callback function, or\n'+ | ||
' (2) a dictionary of per-exit callback functions\n'+ | ||
'\n'+ | ||
'But instead, got:\n'+ | ||
util.inspect({depth: null})+ | ||
'')); | ||
} | ||
this.setExits(done); | ||
// --β’ | ||
// Configure provided callbacks. | ||
helpConfigureMachineInstance(self, undefined, done, undefined); | ||
}//</if :: `done` was provided> | ||
@@ -93,5 +96,3 @@ | ||
if (_.isUndefined(self._configuredExits.error)) { | ||
var err_noErrorCallbackConfigured = new Error('Invalid usage: Cannot execute machine (`'+self.identity+'`) without providing any catchall error handling (e.g. an `error` callback).'); | ||
err_noErrorCallbackConfigured.code = 'E_NO_ERROR_CALLBACK_CONFIGURED'; | ||
throw err_noErrorCallbackConfigured; | ||
throw flaverr('E_NO_ERROR_CALLBACK_CONFIGURED', new Error('Invalid usage: Cannot execute machine (`'+self.identity+'`) without providing any catchall error handling (e.g. an `error` callback).')); | ||
} | ||
@@ -104,699 +105,12 @@ // Also, as a sanity check, make sure it's a valid callback function and not something else crazy. | ||
// Log debug messages, if relevant. | ||
(function _writeDebugLogMsgs (){ | ||
var DEBUG_LOG_LINE_LEN = 45; | ||
var identity = self.identity; | ||
var paddedIdentity = _.padRight(_.trunc('machine-log:'+identity, {length: DEBUG_LOG_LINE_LEN, omission: ''}), DEBUG_LOG_LINE_LEN); | ||
// Now finish things off by calling the `helpExecMachineInstance()` helper. | ||
// This takes care of intercepting callbacks and forwarding exits, plus supporting | ||
// timeouts, caching, coercion, and more. | ||
helpExecMachineInstance(self); | ||
Debug('machine:'+self.identity+':exec')(''); | ||
// Debug(paddedIdentity)(' -< '+(self.friendlyName||'')); | ||
Debug(paddedIdentity)(' β’- '+(self.friendlyName||'')); | ||
})();//</just logged debug message> | ||
// -- | ||
// This method is NOT chainable. | ||
return; | ||
// This local variable (`potentiallyCoercedArgins`) is used below to hold a dictionary | ||
// of argins (runtime input values) that have _potentially_ been coerced to match the expectations | ||
// of the input definitions of this machine. | ||
// | ||
// See `rttc.validate()` for more information about this form of coercion; and about how | ||
// *loose validation* works in general. | ||
var potentiallyCoercedArgins; | ||
// βββ βββ ββββββ βββ ββββββββββ ββββββ βββββββββββββββββ | ||
// βββ ββββββββββββββ ββββββββββββββββββββββββββββββββββββ | ||
// βββ ββββββββββββββ ββββββ βββββββββββ βββ ββββββ | ||
// ββββ βββββββββββββββ ββββββ βββββββββββ βββ ββββββ | ||
// βββββββ βββ βββββββββββββββββββββββββ βββ βββ ββββββββ | ||
// βββββ βββ βββββββββββββββββββββ βββ βββ βββ ββββββββ | ||
// | ||
// βββ βββββββ βββββββ βββββββββββββββ βββββββββββββββ | ||
// βββ βββββββββββββββββββββββββββββββββββββββββββββββββ | ||
// βββββββββ βββ βββ βββββββββ βββββββββββ ββββββ | ||
// βββββββββ βββ βββ βββββββββ βββββββββββ ββββββ | ||
// βββββββ ββββββββββββββββββββββββββββ βββββββββββββββββββ | ||
// βββββββ βββββββ βββββββ βββββββββββ βββ βββββββββββββββ | ||
// | ||
// ββββββ βββββββ βββββββ βββββββ βββββββββββ | ||
// ββββββββββββββββββββββββ ββββββββ βββββββββββ | ||
// βββββββββββββββββββ βββββββββββββ βββββββββββ | ||
// βββββββββββββββββββ ββββββββββββββββββββββββ | ||
// βββ ββββββ ββββββββββββββββββ ββββββββββββββ | ||
// βββ ββββββ βββ βββββββ ββββββ βββββββββββββ | ||
// | ||
// If `unsafeMode` is disabled... | ||
if (!self._unsafeMode) { | ||
// Perform loose validation on our argins (runtime input values). | ||
// This also generates potentially coerced values, which we may or may not actually use | ||
// (see below for more on that.) | ||
var looseValidationReport = validateConfiguredInputValues(self); | ||
potentiallyCoercedArgins = looseValidationReport.values; | ||
var errors = looseValidationReport.errors; | ||
// If there are (still) `e.errors` (meaning one or more argins were invalid), then... | ||
if (errors.length > 0) { | ||
// If runtime type checking is enabled, then... | ||
if(self._runTimeTypeCheck) { | ||
// Build an appropriate runtime validation error. | ||
var err_machineRuntimeValidation = (function _buildMachineRuntimeValidationErr() { | ||
var bulletPrefixedErrors = _.map(errors, function (rttcValidationErr){ return ' β’ '+rttcValidationErr.message; }); | ||
var prettyPrintedValidationErrorsStr = bulletPrefixedErrors.join('\n'); | ||
var errMsg = 'Could not run `'+self.identity+'` due to '+errors.length+' '+ | ||
'validation error'+(errors.length>1?'s':'')+':\n'+prettyPrintedValidationErrorsStr; | ||
var err_machineRuntimeValidation = new Error(errMsg); | ||
err_machineRuntimeValidation.code = 'E_MACHINE_RUNTIME_VALIDATION'; | ||
err_machineRuntimeValidation.machineInstance = self; | ||
err_machineRuntimeValidation.errors = errors; | ||
return err_machineRuntimeValidation; | ||
})();//</self-calling function :: built a E_MACHINE_RUNTIME_VALIDATON error> | ||
// -- | ||
// Build a switchback from the configured exits. | ||
// Fourth argument (`true`) means that the switchback will be built to run **synchronously.** | ||
var sb = switchback(self._configuredExits, undefined, undefined, true); | ||
// Trigger the callback with an error. | ||
sb(err_machineRuntimeValidation); | ||
return self; | ||
}//</if :: self._runTimeTypeCheck> | ||
}//<if :: any argins (runtime input values) are invalid> | ||
}//</if :: NOT in "unsafe" mode> | ||
// --β’ | ||
// If the `_inputCoercion` flag is enabled, configure this live machine instance | ||
// with the newly coerced argins. | ||
if (self._inputCoercion) { | ||
self.setInputs(potentiallyCoercedArgins); | ||
} | ||
// ββββββ βββββββ βββββββ βββ βββ βββ | ||
// βββββββββββββββββββββββββββ ββββ ββββ | ||
// βββββββββββββββββββββββββββ βββββββ | ||
// βββββββββββββββ βββββββ βββ βββββ | ||
// βββ ββββββ βββ βββββββββββ | ||
// βββ ββββββ βββ βββββββββββ | ||
// | ||
// βββββββ ββββββββββββββββ ββββββ βββ ββββββ ββββββββββββββββββββββββββ βββββββ | ||
// βββββββββββββββββββββββββββββββββββ ββββββ βββββββββββββββββββββββββββββββββββ | ||
// βββ βββββββββ ββββββ βββββββββββ ββββββ βββ ββββββββ βββ βββ βββ | ||
// βββ βββββββββ ββββββ βββββββββββ ββββββ βββ ββββββββ βββ βββ βββ | ||
// βββββββββββββββββββ βββ βββββββββββββββββββββββ ββββββββ βββ βββββββββ | ||
// βββββββ βββββββββββ βββ βββ βββββββ βββββββββββ ββββββββ βββ βββββββ | ||
// | ||
// βββββββββ βββββββ ββββββ βββββββ βββββββ βββββββ βββββββββββ | ||
// ββββββββββββββββββ ββββββββββββββββββββββββ ββββββββ βββββββββββ | ||
// βββ βββ βββ βββββββββββββββββββ βββββββββββββ βββββββββββ | ||
// βββ βββ βββ βββββββββββββββββββ ββββββββββββββββββββββββ | ||
// βββ βββββββββ βββ ββββββ ββββββββββββββββββ ββββββββββββββ | ||
// βββ βββββββ βββ ββββββ βββ βββββββ ββββββ βββββββββββββ | ||
// | ||
// Apply `defaultsTo` for input defs that use it. | ||
// TODO: consider whether `defaultsTo` values should be automatically validated/coerced too (that would need to go in Machine.build) | ||
_.each(self.inputs, function (inputDef, inputCodeName){ | ||
// If there is no `defaultsTo` value, we obviously can't use it. | ||
if (inputDef.defaultsTo === undefined) { | ||
return; | ||
} | ||
// --β’ | ||
// Don't use the `defaultsTo` value if an argin was provided for this input. | ||
if (self._configuredInputs[inputCodeName] !== undefined) { | ||
return; | ||
} | ||
// --β’ Use the `defaultsTo` value as the runtime value (argin) for this input. | ||
self._configuredInputs[inputCodeName] = inputDef.defaultsTo; | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Note that this ^^ is currently using a direct reference. | ||
// TODO: Consider deep cloning the default value first to help prevent userland bugs due to | ||
// entanglement. Cloning would only occur the input's example does not contain any `===`s | ||
// or `->`s. (Easiest way to do that is with rttc.dehyrate().) Anyway, regardless of _how_ | ||
// this is implemented, it would need to be configurable. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// If this is a contract input, then attempt to build a submachine out of the provided `defaultsTo` function. | ||
if (!_.isUndefined(inputDef.contract) && (rttc.infer(inputDef.example) === 'lamda')) { | ||
try { | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// Note: | ||
// Currently (see TODO above for "why currently") we build machines out of default lamda input values | ||
// that specify a contract here. | ||
// | ||
// Otherwise the default functions would never get built into machine instances, because we're not actually | ||
// calling rttc.validate() on `defaultsTo` values in general. | ||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||
// If lamda input def specifies a `defaultsTo`, and no input value was provided, go ahead | ||
// and instantiate the default function into a machine using the contract. | ||
self._configuredInputs[inputCodeName] = buildLamdaMachine(inputDef.defaultsTo, inputCodeName, self, self._rootMachine||self); | ||
} catch (e) { | ||
var err_couldNotBuildSubmachineForDefaultVal = new Error( | ||
'Could not execute machine (`'+self.identity+'`). '+ | ||
'This machine definition specifies a `defaultsTo` for a contract input (`'+inputCodeName+'`), '+ | ||
'but that `defaultsTo` function could not be built into a submachine using the provided `contract`. '+ | ||
'Please check that the `contract` dictionary and `defaultsTo` function are valid.\n'+ | ||
'Error details:\n'+e.stack | ||
); | ||
err_couldNotBuildSubmachineForDefaultVal.input = inputCodeName; | ||
throw err_couldNotBuildSubmachineForDefaultVal; // << TODO: pull this `throw` into Machine.build() so it happens as early as possible | ||
} | ||
}//</if this input has a contract and `example: '->'`> | ||
});//</_.each() :: input definition> | ||
// ββββββ βββββββ ββββββββ βββββββ βββββββ βββββββ βββββββ ββββββ ββββββββββ βββββββββββ | ||
// βββββββββββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββ βββββββββββ | ||
// βββββββββββββββββββββββββββ βββββββββββββββββββ βββ βββββββββββ ββββββββββββββ | ||
// βββββββββββββββββββββββββββ βββββββββββββββββββ βββ βββββββββββ ββββββββββββββ | ||
// βββ βββββββββββββββββββββββββββββββ βββββββββββ βββββββββββ ββββββββββββββ βββββββββββ | ||
// βββ ββββββββββ ββββββββ βββββββ βββ ββββββββββ ββββββββββ βββ ββββββββββ βββββββββββ | ||
// | ||
// βββββββββββββββββββββββββββββββββββββββββ βββ βββββββ ββββββββ | ||
// ββββββββββββββββββββββββββββββββββββββββββ βββββββββββ ββββββββ | ||
// ββββββββββββββ βββ βββ βββββββββ ββββββ ββββββββββββ | ||
// ββββββββββββββ βββ βββ ββββββββββββββββ βββββββββββ | ||
// ββββββββββββββββ βββ βββ ββββββ βββββββββββββββββββββββ | ||
// ββββββββββββββββ βββ βββ ββββββ βββββ βββββββ ββββββββ | ||
// | ||
// For convenience, set up a couple of local variables for use below: | ||
// | ||
// β’ `cacheSettings` - the configured cache settings for this machine | ||
var _cache = this._cacheSettings; | ||
// | ||
// β’ `Cache` - the configured cache model to use (a Waterline model) | ||
// (if not relevant, will be left as `undefined`) | ||
var Cache; | ||
// Validate cache settings. | ||
var areCacheSettingsValid = | ||
_.isObject(_cache) && | ||
_.isObject(_cache.model) && | ||
_.isFunction(_cache.model.find) && | ||
_.isFunction(_cache.model.create) && | ||
_.isFunction(_cache.model.destroy) && | ||
_.isFunction(_cache.model.count); | ||
// If a cache model was supplied, but it is not valid, then emit a warning. | ||
if (!_.isUndefined(_cache.model) && !areCacheSettingsValid) { | ||
self.warn(new Error('Invalid cache settings: If `.cache()` is in use, then `model` must be provided as a Waterline model. For example: `.cache({ model: TweetSearchResultsCache })`. Proceeding to execute this machine, but skipping all cache reads and writes...')); | ||
} | ||
// If cache settings are NOT valid, then set `_cache` | ||
// to `false` & leave `Cache` undefined. | ||
if (!areCacheSettingsValid) { | ||
_cache = false; | ||
} | ||
// β‘ Otherwise cache settings ARE valid. So we'll use them. | ||
else { | ||
// Fold in default cache settings. | ||
_.defaults(_cache, { | ||
// Default TTL (i.e. "max age") is 3 hours | ||
ttl: 3 * 60 * 60 * 1000, | ||
// The maximum # of old cache entries to keep for each | ||
// unique combination of input values for a particular | ||
// machine type. | ||
// When this # is exceeded, a query will be performed to | ||
// wipe them out. Increasing this value increases memory | ||
// usage but reduces the # of extra gc queries. Reducing | ||
// this value minimizes memory usage but increases the # of | ||
// gc queries. | ||
// | ||
// When set to 0, performs an extra destroy() query every time | ||
// a cache entry expires (and this is actually fine in most cases, | ||
// since that might happen only a few times per day) | ||
maxOldEntriesBuffer: 0, | ||
// By default, the default (or "success") exit is cached | ||
exit: 'success' | ||
}); | ||
// Set local variable as a reference to the cache model for convenience. | ||
Cache = _cache.model; | ||
// Pre-calculate the expiration date so we only do it once | ||
// (and also so it uses a consistent timestamp since the code | ||
// below is asynchronous) | ||
_cache.expirationDate = new Date( (new Date()) - _cache.ttl); | ||
}//</else :: cache settings are valid> | ||
// βββ βββββββ βββββββ βββ βββ βββ ββββββββββ βββββββ βββββββββββββββββββ ββββββ βββββββββ | ||
// βββ βββββββββββββββββββββ ββββ βββ βββββββββββ βββββββββββββββββββββββββββ ββββββ βββββββββ | ||
// βββ βββ ββββββ ββββββββββ βββ βββββββββββ ββββββββββββββ βββββββββββ ββββββ βββ | ||
// βββ βββ ββββββ ββββββββββ βββ ββββββββββ ββββββββββββββ βββββββββββ ββββββ βββ | ||
// βββββββββββββββββββββββββββββ βββ ββββββββββββ βββ βββββββββββββββββββββββββββββββββββββββ | ||
// ββββββββ βββββββ βββββββ βββ βββ βββββββ βββ βββ βββββββββββββββββββ βββββββ βββββββββββ | ||
// | ||
// βββββββ βββ βββββββ ββββββ ββββββββββ βββββββββββ | ||
// ββββββββ βββ βββββββββββββββββββββββββββ βββββββββββ | ||
// βββββββββ βββ βββ βββββββββββ ββββββββββββββ | ||
// βββββββββββββ βββ βββββββββββ ββββββββββββββ | ||
// ββββββ ββββββ βββββββββββ ββββββββββββββ βββββββββββ | ||
// ββββββ βββββ ββββββββββ βββ ββββββββββ βββββββββββ | ||
// | ||
// ββββββββββββββ βββββββ βββββββββββ βββββββββββ βββ ββββββ ββββ βββββββββββββββ | ||
// βββββββββββββββ βββββββββββββββββββ βββββββββββ ββββββββββββββββ ββββββββββββββββ | ||
// βββ βββββββββ ββββββββββββββ βββ ββββββ βββ βββββββββββββββββ βββ βββ βββ | ||
// βββ βββββββββ ββββββββββββββ βββ ββββββ ββββ ββββββββββββββββββββββ βββ βββ | ||
// ββββββββββ βββ βββββββββββββββββββββββββββ βββββββ βββ ββββββ ββββββ βββ ββββ | ||
// βββββββββ βββ βββββββββββββββββββββββββββ βββββ βββ ββββββ βββββ βββ βββ | ||
// | ||
// Below, we'll use a hash function to create a unique hash (aka checksum) for every distinct set | ||
// of argins. We'll store this in this local variable (`hash`). | ||
// | ||
// > Note that these hashes do not include the machine identity; meaning they are l-unique | ||
// > _per machine._ So the hash representing `{a:1,b:1}` is the same, whether you're passing | ||
// > those argins in to `.multiply()` or `.subtract()`. | ||
var hash; | ||
// Now attempt a cache lookup, if configured to do so, then run the machine. | ||
(function _doCacheLookupMaybe (cb) { | ||
if (!_cache) { return cb(); } | ||
// Run hash function to calculate appropriate `hash` for these argins. | ||
try { | ||
hash = hashArgins(self); | ||
} catch (e) { | ||
// Cache lookup encountered fatal error | ||
// (could not calculate unique hash for configured input values) | ||
return cb(e); | ||
} | ||
try { | ||
// Now call `.find()` on the provided Cache model in order to look up the cached return value | ||
// for the hash representing this particular set of argins. | ||
Cache.find({ | ||
where: { | ||
createdAt: { '>': _cache.expirationDate }, | ||
hash: hash | ||
}, | ||
sort: 'createdAt DESC', | ||
limit: 1 | ||
}) | ||
.exec(function (err, cached) { | ||
if (err) { return cb(err); } | ||
// --β’ If this was a cache hit... | ||
if (cached.length && typeof cached[0].data !== 'undefined') { | ||
// console.log('cache hit', cached); | ||
var newestCacheEntry = cached[0]; | ||
// Fourth argument (`true`) tells switchback to run synchronously | ||
return switchback(self._configuredExits, undefined, undefined, true)(null, newestCacheEntry.data); | ||
} | ||
// --β’ If this was a cache miss... | ||
return cb(); | ||
});//</Cache.find() :: finding records in cache model> | ||
} catch (e) { | ||
return cb(new Error('Consistency violation: Unexpected error when attempting to communicate with the cache via the provided model. Error details: '+e.stack+'\n- - - - - - - - - - - - - - - - - - - - - - - ')); | ||
} | ||
})(function afterwards(err){ | ||
if (err) { | ||
// If cache lookup encounters a fatal error, emit a warning | ||
// but continue (i.e. we fall back to running the machine normally, without trying to mess w/ the cache.) | ||
self.warn(err); | ||
} | ||
// >- | ||
// | ||
// βββ βββββββββββββ ββββββββ βββββββββββ ββββββββββ ββββββββββ βββββββββββββββ | ||
// βββ ββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
// βββ ββ ββββββββββββββββββββ ββββββ ββββββ βββββββββββββββββββββββββ βββ βββ | ||
// ββββββββββββββββββββ ββββββ ββββββ ββββββ βββββββ βββββββββββββββββ βββ βββ | ||
// ββββββββββββββββ ββββββββ ββββββββββββ ββββββ ββββββ βββββββββββββββββββ | ||
// ββββββββ ββββββ ββββββββ βββββββββββ ββββββ ββββββ ββββββββββββββββββ | ||
// | ||
// βββββββ ββββββββ βββββββ βββββββ βββββββ βββββββ ββββββββ βββββββββββββββ βββββββ ββββ ββββ | ||
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ βββββ | ||
// ββββββββββββββ βββ βββ ββββββββββββββ βββββββββββ ββββββ βββββββββββ ββββββββββββββ | ||
// ββββββββββββββ βββ βββ ββββββββββββββ βββββββββββ ββββββ βββββββββββ ββββββββββββββ | ||
// βββ βββββββββββββββββββββββββββββββ βββββββββββββββββββ βββ βββ βββββββββββββββ βββ βββ | ||
// βββ βββββββββββ βββββββ βββββββ βββ ββββββββββ ββββββββ βββ βββ βββ βββββββ βββ βββ | ||
// | ||
// βββββββ ββββββ ββββββββββ βββββββββββ | ||
// βββββββββββββββββββββββββββ βββββββββββ | ||
// βββ βββββββββββ ββββββββββββββ | ||
// βββ βββββββββββ ββββββββββββββ | ||
// βββββββββββ ββββββββββββββ βββββββββββ | ||
// ββββββββββ βββ ββββββββββ βββββββββββ | ||
// | ||
// ββββββββββββββ βββββββ βββββββββββ βββββββββββ βββ ββββββ ββββ βββββββββββββββ | ||
// βββββββββββββββ βββββββββββββββββββ βββββββββββ ββββββββββββββββ ββββββββββββββββ | ||
// βββ βββββββββ ββββββββββββββ βββ ββββββ βββ βββββββββββββββββ βββ βββ βββ | ||
// βββ βββββββββ ββββββββββββββ βββ ββββββ ββββ ββββββββββββββββββββββ βββ βββ | ||
// ββββββββββ βββ βββββββββββββββββββββββββββ βββββββ βββ ββββββ ββββββ βββ ββββ | ||
// βββββββββ βββ βββββββββββββββββββββββββββ βββββ βββ ββββββ βββββ βββ βββ | ||
// | ||
// Perform garbage collection on cache, if necessary. | ||
// | ||
// > Old cache entries are garbage collected every time a cache miss occurs. | ||
// > | ||
// > If `> maxOldEntriesBuffer` matching cache records exist, then | ||
// > it's time to clean up. Go ahead and delete all the old unused | ||
// > cache entries except the newest one. | ||
// > | ||
// > Note that we don't need to wait for garbage collection to run the | ||
// > machine. That happens below. | ||
if (_cache) { | ||
try { | ||
Cache.count({ | ||
where: { | ||
createdAt: { | ||
'<=': _cache.expirationDate | ||
}, | ||
hash: hash | ||
} | ||
}).exec(function (err, numOldCacheEntries){ | ||
if (err) { | ||
// If this garbage collection diagnostic query encounters a fatal error, | ||
// emit a warning and don't try to proceed with garbage collection. | ||
// (That's because the rest of the code isn't waiting on this!!) | ||
self.warn(err); | ||
return; | ||
} | ||
try { | ||
// --β’ | ||
// If there aren't enough expired cache entries for this hash to warrant a wipe, just bail. | ||
if (numOldCacheEntries <= _cache.maxOldEntriesBuffer) { | ||
return; | ||
} | ||
// --β’ | ||
// Otherwise, there are enough expired cache records for this exact set of argins | ||
// to warrant a wipe. So destroy all expired cache records with this hash. | ||
Cache.destroy({ | ||
where: { | ||
createdAt: { | ||
'<=': _cache.expirationDate | ||
}, | ||
hash: hash | ||
}, | ||
sort: 'createdAt DESC', | ||
skip: _cache.maxOldEntriesBuffer | ||
}).exec(function (err, oldCacheEntries) { | ||
if (err) { | ||
// If garbage collection encounters a fatal error, emit a warning | ||
// and then don't do anything else as far as garbage collection of | ||
// expired cache entries (remember, the rest of the code isn't waiting | ||
// on this!) | ||
self.warn(err); | ||
return; | ||
} | ||
// --β’ | ||
// Sucessfully wiped all expired cache records for this exact set of argins! | ||
});//</.destroy() :: destroying expired cache records for this exact set of argins> | ||
} catch (e) { self.warn(new Error('Consistency violation: Unexpected error when attempting to communicate with the cache via the provided model. Error details: '+e.stack+'\n- - - - - - - - - - - - - - - - - - - - - - - ')); } | ||
});//</.count() :: counting expired cache records for this exact set of argins (to see if it's worth it to wipe them)> | ||
} catch (e) { self.warn(new Error('Consistency violation: Unexpected error when attempting to communicate with the cache via the provided model. Error details: '+e.stack+'\n- - - - - - - - - - - - - - - - - - - - - - - ')); } | ||
}//</if `_cache` is truthy, then we just started destroying expired cache entries> | ||
// _β_ | ||
// βββββββ βββ βββββββββ βββββββ βββββββββββ ββββββββββ βββββββ βββ βββββββ | ||
// βββββββββββ βββββββββ ββββββββ βββββββββββ βββββββββββββββββββ βββββββββββ | ||
// βββββββββββ βββββββββ βββ βββ ββββββ βββ ββ ββββββ ββββββββββββ ββββββ ββββ | ||
// βββββββββββ βββββββββ βββ βββ ββββββ βββββββββββββ βββββββββββββββββββ βββ | ||
// ββββββββββββββββββββββββββββββββββββ βββ ββββββββββββββββββββββββ βββββββββββββββ | ||
// βββββββ βββββββ ββββββββββββββββββ βββ ββββββββ βββββββ ββββββ βββββ βββββββ | ||
// | ||
// βββββββ ββββββ βββ βββ βββββββ ββββββ ββββββββββ βββββββββββ ββββββββ βββββββ βββββββ | ||
// βββββββββββββββββββ βββ βββββββββββββββββββββββββββ ββββββββββββ βββββββββββββββββββββββββ | ||
// βββ βββββββββββ βββ βββββββββββββββββββ βββββββ ββββββββ ββββββ βββ βββββββββββ | ||
// βββ βββββββββββ βββ βββββββββββββββββββ βββββββ ββββββββ ββββββ βββ βββββββββββ | ||
// βββββββββββ ββββββββββββββββββββββββββββββ ββββββββββββββ βββββββββββ βββ ββββββββββββ βββ | ||
// ββββββββββ ββββββββββββββββββββββββββ βββ βββ ββββββββββ βββββββββββ βββ βββββββ βββ βββ | ||
// | ||
// ββββ βββββββββββββββ βββββββ βββββββββββ βββββββββββββββββββββββ | ||
// βββββ ββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ | ||
// βββββββββββββββββββββββββ ββββββ ββββββ βββ βββ ββββββββ | ||
// βββββββββββββββββββββββββ ββββββ ββββββ βββ βββ ββββββββ | ||
// βββ βββ ββββββββββββββββββββββ ββββββββββββ ββββββ βββ βββββββββββ | ||
// βββ ββββββββββββββ βββββββ βββββββββββ ββββββ βββ βββββββββββ | ||
// | ||
// βββ βββββββ βββββββ βββ βββββββ βββββββββββ | ||
// βββ βββββββββββββββββββ ββββββββ βββββββββββ | ||
// βββββββββ βββββββββββββββββββ βββββββββ βββββββββ | ||
// βββββββββ βββββββ βββββββββββ βββββββββββββββββββ | ||
// βββββββ βββ βββ βββββββββββββββ βββββββββββββββββ | ||
// βββββββ βββ βββ βββ βββββββ βββ ββββββββββββββββ | ||
// | ||
// βββ βββββββ βββββββββββββββββββββββββββ ββββββββββββββββββββββ βββββββββ | ||
// βββ ββββββββ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
// βββββββββ βββββββββ βββ βββ ββββββ βββββββββββ ββββββ ββββββββ βββ | ||
// βββββββββ βββββββββββββ βββ ββββββ βββββββββββ ββββββ βββββββ βββ | ||
// βββββββ ββββββ ββββββ βββ βββββββββββ ββββββββββββββββββββββ βββ | ||
// βββββββ ββββββ βββββ βββ βββββββββββ βββ ββββββββββββββββββ βββ | ||
// | ||
// Before proceeding, ensure error exit is still configured w/ a callback. | ||
// If it is not, then get crazy and **throw** BEFORE calling the machine's `fn`. | ||
// | ||
// This is just yet another failsafe-- better to potentially terminate the process than | ||
// open up the possibility of silently swallowing errors later. | ||
if (!self._configuredExits.error){ | ||
throw new Error('Consistency violation: Cannot execute machine (`'+self.identity+'`) without providing any catchall error handling (e.g. an `error` callback).'); | ||
} | ||
// Then prune any configured exit callbacks that have `undefined` on the RHS. | ||
_.each(_.keys(self._configuredExits), function (exitCodeName) { | ||
if (self._configuredExits[exitCodeName] === undefined) { | ||
delete self._configuredExits[exitCodeName]; | ||
} | ||
});//</_.each() :: each key in dictionary of configured exit callbacks> | ||
// Now, fill in anonymous forwarding callbacks for any unhandled exits (ignoring the default `success` and `error` exits) | ||
// and have them redirect to the `error` (i.e. catchall) exit | ||
_.each(_.keys(self.exits), function (exitCodeName) { | ||
// Skip default exit and error exit (they're already accounted for.) | ||
if (exitCodeName === 'success' || exitCodeName === 'error') { | ||
return; | ||
} | ||
// If this exit is handled then we're good. | ||
if (self._configuredExits[exitCodeName]) { | ||
return; | ||
} | ||
// --β’ | ||
// Otherwise, the exit is unhandled. | ||
Debug('built fwding callback for exit "%s", where there is no implemented callback', exitCodeName); | ||
// Build a callback function for this exit. | ||
// When/if it is run, this dynamically-generated callback will: | ||
// β’Β generate an Error instance with a useful message | ||
// β’ trigger the callback configured for the `error` exit (and pass in its new Error as the first argument) | ||
self._configuredExits[exitCodeName] = function __triggeredMiscExit(_resultPassedInByMachineFn){ | ||
// Start building the error message. | ||
var errMsg = '`'+_.camelCase(self.identity)+'` triggered its `'+exitCodeName+'` exit'; | ||
// If no result was passed in to the exit, append the exit description if available. | ||
if (_.isUndefined(_resultPassedInByMachineFn)) { | ||
// Use the description, if one was provided. | ||
var exitDef = self.exits[exitCodeName]; | ||
if (!_.isObject(exitDef)) { throw new Error('Consistency violation: Live machine instance ('+self.identity+') has become corrupted! One of its exits (`'+exitCodeName+'`) has gone missing _while the machine was being run_!'); } | ||
if (exitDef.description) { | ||
errMsg += ': '+self.exits[exitCodeName].description; | ||
} | ||
} | ||
// If an Error was passed in to the exit, append its message | ||
else if (_.isError(_resultPassedInByMachineFn)) { | ||
errMsg += ': '+_resultPassedInByMachineFn.message; | ||
} | ||
// If a non-Error was passed in to the exit, inspect and append it | ||
else if (!_.isError(_resultPassedInByMachineFn)) { | ||
errMsg += ' with: \n\n' + util.inspect(_resultPassedInByMachineFn, {depth: null}); | ||
} | ||
// Get a local reference to the clean error we created at the top of this file. | ||
// We know cleanError will only be used once since we implemented a spinlock via | ||
// the "_exited" property of the machine (see intercept-exit-callbacks). | ||
var err_forwarding = cleanError; | ||
// Copy our error message | ||
err_forwarding.message = errMsg; | ||
err_forwarding.stack = 'Error: ' + errMsg + '\n' + err_forwarding.stack; | ||
err_forwarding.exit = exitCodeName; | ||
err_forwarding.code = exitCodeName; | ||
// If a result was passed in, stuff it in the generated Error instance | ||
// as the `output` property. | ||
if (!_.isUndefined(_resultPassedInByMachineFn)) { | ||
err_forwarding.output = _resultPassedInByMachineFn; | ||
} | ||
// Trigger configured error callback on `_configuredExits` - (which is already a switchback... | ||
// ...so this should work even if no error callback was explicitly configured... | ||
// ...but in case it doesn't, we already threw above if no error exit exists) | ||
// - using our new Error instance as the argument. | ||
self._configuredExits.error(err_forwarding); | ||
};//</built dynamic callback that forwards to `error`> | ||
});//</_.each() :: exit definition> | ||
// Intercept our configured exit callbacks in order to implement type coercion of runtime output. | ||
// This also takes care of some logging functionality, and if relevant, ensures at least one tick | ||
// has elapsed. Etcetera. | ||
var interceptedExitCbs = interceptExitCallbacks(self._configuredExits, _cache, hash, self); | ||
// Now it's almost time to run the machine fn. | ||
// > Use a try/catch to protect against any unexpected errors. | ||
try { | ||
// βββββββ βββ βββββββββ βββββββ | ||
// βββββββββββ βββββββββ ββββββββ | ||
// βββββββββββ βββββββββ βββ βββ | ||
// βββββββββββ βββββββββ βββ βββ | ||
// ββββββββββββββββββββββββββββββββββββ | ||
// βββββββ βββββββ ββββββββββββββββββ | ||
// | ||
// βββββββ βββββββββββ βββ ββββββββββββ ββββββββββββββββ ββββββββββββ βββββββ βββββββ | ||
// ββββββββ ββββββββββββββββ βββββββββββββ ββββββββββββββββββ βββββββββββββββββββββββββββββ | ||
// βββββββββββββββββββββββββ ββββββ βββββββββββββββββ ββββββ βββ βββ βββ βββββββββββ | ||
// βββββββββββββββββββββ βββ ββββββ βββββββββββββββββ ββββββββββ βββ βββ βββββββββββ | ||
// ββββββ βββ ββββββ βββββββββββββββββββ βββ ββββββββββββββ ββββββ βββ ββββββββββββ βββ | ||
// ββββββ ββββββ βββββββββββββββββββ ββββββββββββββ βββββ βββ βββββββ βββ βββ | ||
// | ||
// βββββββββββ βββββββββββββββ ββββββββββ ββββββββββ ββββββ ββββββββββ βββ | ||
// βββββββββββ ββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ ββββ | ||
// βββββββββββ ββ ββββββ βββ βββ βββββββββββββββββββββββββββ βββββββ | ||
// βββββββββββββββββββββ βββ βββ βββββββββββββββββββββββββββ βββββββ | ||
// βββββββββββββββββββββ βββ βββββββββββ ββββββββββββββ ββββββββββββββ βββ | ||
// ββββββββ ββββββββ βββ βββ ββββββββββ ββββββββββ βββ βββ ββββββββββ βββ | ||
// | ||
// We'll create the ***implementor*** switchback. | ||
// (fourth argument (`true`) tells the switchback to run synchronously) | ||
var implementorSwitchback = switchback(interceptedExitCbs, undefined, undefined, true); | ||
// βββββββββββββββββββββββββ ββββββββββββββββ ββββββββββββ βββββββ βββ ββββββββββββ | ||
// βββββββββββββββββββββββββ βββββββββββββββββ βββββββββββββββββββββββββ ββββββββββββ | ||
// ββββββββββββββ βββ βββ ββββββββββββββββββββ βββ ββββββ βββ βββ | ||
// ββββββββββββββ βββ βββ ββββββββββββββββββββ βββ ββββββ βββ βββ | ||
// ββββββββββββββββ βββ βββ ββββββ βββ βββββββββββββββββββββββββββββ βββ | ||
// ββββββββββββββββ βββ βββ ββββββ βββββββββββ βββββββ βββββββ βββ | ||
// | ||
// ββββββ βββ ββββββ βββββββ ββββ ββββ | ||
// βββββββββββ βββββββββββββββββββββ βββββ | ||
// βββββββββββ βββββββββββββββββββββββββββ | ||
// βββββββββββ βββββββββββββββββββββββββββ | ||
// βββ ββββββββββββββ ββββββ ββββββ βββ βββ | ||
// βββ ββββββββββββββ ββββββ ββββββ βββ | ||
// | ||
// Before calling function, set up a `setTimeout` function that will fire | ||
// when the runtime duration exceeds the configured `timeout` property. | ||
// If `timeout` is falsey or <0, then we ignore it. | ||
if (self._doTrackDuration && self.timeout && self.timeout > 0){ | ||
if (self._timeoutAlarm) { throw new Error('Consistency violation: `_timeoutAlarm` should never already exist on a machine instance before it is run. Perhaps you called `.exec()` more than once? If so, please fix and try again.'); } | ||
self._timeoutAlarm = setTimeout(function __machineTimedOut(){ | ||
// Assert that our `_exited` spinlock has not already been set. | ||
// (better to terminate the process than trigger a callback twice) | ||
if (self._exited) { throw new Error('Consistency violation: the timeout alarm was triggered when `_exited` was already set.'); } | ||
var err = new Error( | ||
'This machine took too long to execute (timeout of '+self.timeout+'ms exceeded.) '+ | ||
'There is probably an issue in the machine\'s implementation (might have forgotten to call `exits.success()`, etc.) '+ | ||
'If you are the implementor of this machine, and you\'re sure there are no problems, you can configure '+ | ||
'the maximum expected number of miliseconds for this machine using `timeout` (a top-level property in '+ | ||
'your machine definition). To disable this protection, set `timeout` to 0.'); | ||
err.code = 'E_MACHINE_TIMEOUT'; | ||
// Trigger callback | ||
implementorSwitchback.error(err); | ||
// Then immediately set the `_timedOut` flag so when/if `fn` calls its exits, | ||
// we won't trigger the relevant callback (since we've already triggered `error`). | ||
self._timedOut = true; | ||
}, self.timeout);//</set timeout alarm> | ||
// _β_ | ||
}//</if not tracking duration, or `timeout` is not set, or is less than zero for some reason> | ||
// >- | ||
// For sanity, do one last assertion to make sure `fn` is valid. | ||
if (!_.isFunction(self.fn)) { | ||
throw new Error(''+ | ||
'Consistency violation: Live machine instance ('+self.identity+') has become corrupted!\n'+ | ||
'Its `fn` property is no longer a function-- instead it\'s a '+rttc.getDisplayType(self.fn)+':\n'+ | ||
util.inspect(self.fn, {depth: null})+ | ||
''); | ||
} | ||
// --β’ | ||
// | ||
// ββββββ βββββββββββββββββββ βββ ββββββ βββ βββ βββ βββ βββββββ βββ βββββββ βββ | ||
// ββββββββββββββββββββββββββββ ββββββββββββββ βββ ββββ ββββ βββββββββββ ββββββββ βββ | ||
// βββββββββββ βββ βββ ββββββββββββββ βββ βββββββ βββββββββββ βββββββββ βββ | ||
// βββββββββββ βββ βββ ββββββββββββββ βββ βββββ βββββββββββ βββββββββββββ | ||
// βββ βββββββββββ βββ ββββββββββββ ββββββββββββββββββββββ βββ βββββββββββββββ ββββββ | ||
// βββ βββ βββββββ βββ βββββββ βββ ββββββββββββββββββββββ βββ βββ βββββββ βββ βββββ | ||
// | ||
// ββββ ββββ ββββββ ββββββββββ ββββββββββ βββββββββββ ββββββββββββ βββ | ||
// βββββ ββββββββββββββββββββββββ βββββββββββ βββββββββββ βββββββββββββ βββ | ||
// ββββββββββββββββββββββ βββββββββββββββββ βββββββββ ββββββ ββββββ βββ | ||
// ββββββββββββββββββββββ βββββββββββββββββββββββββββ ββββββ ββββββββββ | ||
// βββ βββ ββββββ ββββββββββββββ βββββββββ ββββββββββββββ βββ βββ ββββββ | ||
// βββ ββββββ βββ ββββββββββ βββββββββ βββββββββββββ βββ βββ βββββ | ||
// | ||
// Then call the machine's `fn`. | ||
self.fn.apply(self._configuredEnvironment, [self._configuredInputs, implementorSwitchback, self._configuredEnvironment]); | ||
} catch(e) { | ||
// Here we re-create the ***userland*** switchback and call it with the error that occurred. | ||
// (fourth argument (`true`) tells switchback to run synchronously) | ||
// | ||
// Note that this could probably be removed eventually, since at this point `interceptedExitCbs` | ||
// should actually already be a switchback. | ||
return switchback(interceptedExitCbs, undefined, undefined, true)(e); | ||
}//</catch> | ||
});//</doing cache lookup, if relevant, then continuing on to do more stuff ^^> | ||
// _β_ | ||
return this; | ||
}; | ||
@@ -7,8 +7,16 @@ /** | ||
var _ = require('lodash'); | ||
var flaverr = require('./private/flaverr'); | ||
/** | ||
* @returns {*} return value through machine's default exit | ||
* `Machine.prototype.execSync()` | ||
* | ||
* @returns {Ref} output from machine's success exit | ||
* @throws {Error} If machine triggers any other exit | ||
* @property {String} exit | ||
* The code name of the exit. | ||
* @property {Ref?} output | ||
* The output from the exit, if relevant. | ||
*/ | ||
module.exports = function (){ | ||
module.exports = function execSync(){ | ||
@@ -18,28 +26,12 @@ var self = this; | ||
// Check that the machine definition explicitly flagged itself | ||
// as synchronous. | ||
// Check that the machine definition explicitly flagged itself as synchronous. | ||
// > If `sync` was NOT set, then this is a usage error. | ||
// > You can't run a machine synchronously unless it proudly declares itself as such. | ||
if (!this.sync) { | ||
// If `sync` was not set, then this is a usage error. | ||
// You can't run a machine synchronously unless it proudly declares itself as such. | ||
throw (function (){ | ||
var _err = new Error('Cannot use `.execSync()` with `'+identity+'` machine because it does not enable synchronous usage (i.e. `sync:true`)'); | ||
_err.code = 'E_USAGE'; | ||
return _err; | ||
})(); | ||
throw flaverr('E_USAGE', new Error('Cannot use `.execSync()` with `'+identity+'` machine because it does not enable synchronous usage (i.e. `sync:true`)')); | ||
} | ||
// Don't allow `.execSync()` to be used if `.cache()` was called on this machine instance. | ||
if (this._willCache) { | ||
throw (function (){ | ||
var _err = new Error( | ||
'Failed to use `.execSync()` with machine ('+self.identity+').\n'+ | ||
'Cannot use `.cache()` and `.execSync()` at the same time since writing and reading\n'+ | ||
'from a cache is an asynchronous operation.\n'+ | ||
'Caching of synchronous machine results can be implemented in userland with _.memoize().\n'+ | ||
'(This is because the built-in machine cache is configurable, database-agnostic, and asynchronous.)' | ||
); | ||
_err.code = 'E_USAGE'; | ||
return _err; | ||
})(); | ||
throw flaverr('E_USAGE', new Error('Failed to use `.execSync()` with machine ('+self.identity+').\nCannot use `.cache()` and `.execSync()` at the same time since writing and reading\nfrom a cache is an asynchronous operation.\nCaching of synchronous machine results can be implemented in userland with _.memoize().\n(This is because the built-in machine cache is configurable, database-agnostic, and asynchronous.)')); | ||
} | ||
@@ -90,3 +82,3 @@ | ||
'`%s` triggered its `%s` exit.\n%s%s', | ||
self.identity, | ||
_.camelCase(self.identity), | ||
exitName, | ||
@@ -97,3 +89,3 @@ (exitDescription ? ('\nDescription:\n'+exitDescription+'\n') : ''), | ||
err = new Error(errMsg); | ||
// TODO: use a nice module to trim some of the crap from `machine` itself off of the stack. | ||
// TODO: trim some of the crap from `machine` itself off of the stack. | ||
// TODO: if this error came from a machine internal to the implementation of THIS machine, | ||
@@ -113,8 +105,4 @@ // then add some kind of message to the error message+stack about that. | ||
if (!isMachineActuallySynchronousCv) { | ||
throw (function (){ | ||
var _err = new Error('Cannot use `.execSync()` with `'+identity+'` machine because, although it declares synchronous usage (i.e. `sync:true`), it is not actually synchronous.'); | ||
// TODO: use a nice module to trim some of the crap from `machine` itself off of the stack. | ||
_err.code = 'E_MACHINE_INCONSISTENT'; | ||
return _err; | ||
})(); | ||
throw flaverr('E_MACHINE_INCONSISTENT', new Error('Cannot use `.execSync()` with `'+identity+'` machine because, although it declares synchronous usage (i.e. `sync:true`), it is not actually synchronous.')); | ||
// TODO: use a nice module to trim some of the crap from `machine` itself off of the stack. | ||
} | ||
@@ -141,3 +129,2 @@ | ||
// TODO: use a nice module to trim some of the crap from `machine` itself off of the stack. | ||
throw err; | ||
@@ -144,0 +131,0 @@ } |
{ | ||
"name": "machine", | ||
"version": "13.0.0-11", | ||
"version": "13.0.0-12", | ||
"description": "Configure and execute machines", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "node ./node_modules/mocha/bin/mocha --recursive -R dot", | ||
"test": "node ./node_modules/mocha/bin/mocha -R dot test/*.test.js", | ||
"bench": "node ./node_modules/mocha/bin/mocha -R dot --recursive -b test/benchmarks/", | ||
"licensing": "> DEPENDENCIES.md && echo '# Dependency Licenses\\n\\n> _this recursive dependency licensing report was automatically generated by [`licensing`](https://www.npmjs.org/package/licensing)_\\n\\n```' >> DEPENDENCIES.md && node node_modules/licensing . >> DEPENDENCIES.md && echo '```' >> DEPENDENCIES.md" | ||
@@ -34,3 +35,3 @@ }, | ||
"debug": "2.2.0", | ||
"include-all": "1.0.2", | ||
"include-all": "^1.0.5", | ||
"lodash": "3.10.1", | ||
@@ -41,8 +42,8 @@ "rttc": "^9.8.1", | ||
"devDependencies": { | ||
"async": "0.9.2", | ||
"async": "2.0.1", | ||
"benchmark": "2.1.0", | ||
"licensing": "0.1.0", | ||
"mocha": "1.21.5", | ||
"mocha": "3.0.2", | ||
"test-machinepack-mocha": "^2.1.3" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
215677
3445
26
1
+ Addedinclude-all@1.0.8(transitive)
- Removedinclude-all@1.0.2(transitive)
Updatedinclude-all@^1.0.5