New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

machine

Package Overview
Dependencies
Maintainers
5
Versions
132
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

machine - npm Package Compare versions

Comparing version 13.0.0-2 to 13.0.0-3

114

lib/hash-machine.js

@@ -5,40 +5,102 @@ /**

var hashObject = require('object-hash');
var util = require('util');
var crypto = require('crypto');
var _ = require('lodash');
var rttc = require('rttc');
/**
* [exports description]
* @param {[type]} machine [description]
* @param {Function} done [description]
* @return {[type]} [description]
* hashArgins()
*
* Compute a hash string from the configured argins in the provided live machine instance.
*
* > Note:
* > + Argins which _might not be JSON serializable_ are not included when computing the hash.
* > + Key order does not matter (no matter how deep).
* > + This logic assumes argins have _already been validated and potentially coerced._
* > + It also assumes that default values have already been folded in, where appropriate.
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* @param {LiveMachine} liveMachine
* @returns {String}
* The hash computed from the configured argins.
*/
module.exports = function hash_machine (machine, done) {
var hash;
module.exports = function hashArgins (liveMachine) {
if (_.isUndefined(liveMachine)) {
throw new Error('Consistency violation: Expecting `liveMachine` to be provided as argument to hashArgins(). (But it was not.)');
}
if (!_.isObject(liveMachine) || !_.isObject(liveMachine._configuredInputs)) {
throw new Error('Consistency violation: Expecting `liveMachine` to be provided as argument to hashArgins(). Should have a property called `_configuredInputs`, which is a dictionary of all configured argins. But instead, I got this lousy thing:\n'+util.inspect(liveMachine, {depth:null}));
}
try {
// console.log('CALCULATED HASH ON:',getUniquelyIdentifyingObj(machine));
// Build a modified copy of all argins that will be hashed, and sort their keys (recursively).
var keySortifiedArginsToHash = _.reduce(liveMachine._configuredInputs, function (memo, argin, inputCodeName){
// see https://github.com/puleos/object-hash
// TODO: optimize this, or at least allow for setImmediate (i.e. nextTick)
// ( BUT ONLY WHEN `machine.runnincSynchronously` IS FALSE!! )
hash = hashObject(getUniquelyIdentifyingObj(machine));
// Figure out if the input's example indicates that this argin MIGHT NOT be JSON-serializable.
//
// > Note: We're just using `rebuild()` here (^^) because it's a good, safe iterator.
var mightNotBeJSONSerializable;
var inputDef = liveMachine.inputs[inputCodeName];
rttc.rebuild(inputDef.example, function (exemplarPiece){
if (exemplarPiece === '->' || exemplarPiece === '===') {
mightNotBeJSONSerializable = true;
}
});//</rttc.rebuild()>
// console.log('AND I GOT: ',hash);
}
catch (e) {
// console.log('HASH CALCULATION ERR:',e);
return done(e);
}
// If we don't know for sure this argin is JSON-serializable, then just skip it
// and move on to the next. (It won't be included in the hash.)
if (mightNotBeJSONSerializable) {
return memo;
}
return done(null, hash);
};
// --•
// At this point, since we're assuming the argin has already been validated, we can safely trust
// that it is JSON-serializable.
// Build a modified ("deep-ish") clone of this argin with all of its keys sorted-- recursively deep.
var sortifiedArgin = (function _sortKeysRecursive(val){
// --• misc
if (!_.isObject(val)) { return val; }
function getUniquelyIdentifyingObj(machine) {
return {
id: machine.identity || machine.fn.toString(),
data: machine._configuredInputs
};
}
// --• array
if (_.isArray(val)) {
return _.map(val, function (item){
return _sortKeysRecursive(item);
});//</_.map()>
}
// --• dictionary
var sortedSubKeys = _.keys(val).sort();
return _.reduce(sortedSubKeys, function (memo, subKey) {
memo[subKey] = _sortKeysRecursive(val[subKey]);
return memo;
}, {});//</_.reduce()>
})(argin);
// Track this sortified argin on our dictionary of stuff that will get hashed.
memo[inputCodeName] = sortifiedArgin;
// And continue.
return memo;
}, {});//</_.reduce() :: argins to hash>
// Now encode that as a JSON string.
var stringifiedStuffToHash = JSON.stringify(keySortifiedArginsToHash);
// Finally, compute & return an MD5 hash.
var computedHash = crypto.createHash('md5').update(stringifiedStuffToHash).digest('hex');
return computedHash;
} catch (e) { throw new Error('Consistency violation: Attempted to hash provided argins (runtime input values) for caching purposes, but could not calculate hash. Details:\n'+e.stack); }
};

@@ -352,10 +352,10 @@ /**

else if (!_.isObject(callbacks)) {
throw new Error('Machine must be configured with a single callback or an object of exit callbacks- not:'+configuredExits);
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 obj
// Handle exits dictionary
else {
// Make sure only declared exits are configured.
var undeclaredExits = _.difference(_.keys(callbacks), _.keys(this.exits));
if (undeclaredExits.length) {
throw new Error('The following exits were configured, but they aren\'t valid for this machine: `' + undeclaredExits.join(', ') + '`.');
if (undeclaredExits.length > 0) {
throw new Error('One or more callbacks were configured for exits that are not recognized by this machine: `' + undeclaredExits.join(', ') + '`.');
}

@@ -362,0 +362,0 @@ _.extend(this._configuredExits, callbacks);

@@ -6,4 +6,5 @@ /**

var util = require('util');
var Debug = require('debug');
var _ = require('lodash');
var Debug = require('debug');
var rttc = require('rttc');
var switchback = require('switchback');

@@ -13,3 +14,2 @@ var calculateHash = require('./hash-machine');

var validateConfiguredInputValues = require('./validate-configured-input-values');
var rttc = require('rttc');
var buildLamdaMachine = require('./build-lamda-machine');

@@ -19,11 +19,28 @@

/**
* [exec description]
* @param {[type]} configuredExits [description]
* @chainable
* .exec()
*
* Run this machine's `fn` and trigger the appropriate exit callback.
*
*
* NOTE:
* If the machine is synchronous, then an artificial `setTimeout(0)` will be introduced
* to ensure that the relevant exit callback is called in a subsequent tick of the event
* loop.
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* @param? {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()`.
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
module.exports = function Machine_prototype_exec (configuredExits) {
module.exports = function Machine_prototype_exec (done) {
var self = this;
// Track timestamp
// If duration-tracking is enabled, track current timestamp
// as a JavaScript Date instance in `_execBeginTimestamp`.
if (self._doTrackDuration){

@@ -33,109 +50,192 @@ self._execBeginTimestamp = new Date();

if (configuredExits) {
this.setExits(configuredExits);
// If `done` was provided...
if (done !== undefined) {
// 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})+
'');
}
this.setExits(done);
}//</if :: `done` was provided>
// >-
// At this point, if a(ny) callback(s) were provided, we've folded them in.
//
// So before continuing, validate that we have an `error` callback of some sort.
// If it is not, then get crazy and **throw** BEFORE calling the machine's `fn`.
//
// (Better to potentially terminate the process than open up the possibility of silently swallowing errors later.)
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;
}
// Also, as a sanity check, make sure it's a valid callback function and not something else crazy.
if (!_.isFunction(self._configuredExits.error)) {
throw new Error('Consistency violation: Cannot execute machine (`'+self.identity+'`) because its configured `error` callback is invalid. It should be a function, but instead, it\'s:\n'+util.inspect(self._configuredExits.error, {depth: null}));
}
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);
Debug('machine:'+self.identity+':exec')('');
// Debug(paddedIdentity)(' -< '+(self.friendlyName||''));
Debug(paddedIdentity)(' •- '+(self.friendlyName||''));
// 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);
// Only validate & coerce configured input values if `unsafeMode` is disabled.
var coercedInputValues;
Debug('machine:'+self.identity+':exec')('');
// Debug(paddedIdentity)(' -< '+(self.friendlyName||''));
Debug(paddedIdentity)(' •- '+(self.friendlyName||''));
})();//</just logged debug message>
// --
// 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) {
var validationResults = validateConfiguredInputValues(self);
coercedInputValues = validationResults.values;
var errors = validationResults.errors;
// If there are (still) `e.errors`, then we've got to call the error callback.
// 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, trigger the error exit
// if any inputs are invalid.
// If runtime type checking is enabled, then...
if(self._runTimeTypeCheck) {
// Fourth argument (`true`) tells switchback to run synchronously
return switchback(self._configuredExits, undefined, undefined, true)((function (){
// var err = new Error(util.format('`%s` machine: %d error(s) encountered validating inputs:\n', self.identity, errors.length, util.inspect(errors)));
var errMsg = (function (){
var prettyPrintedValidationErrorsStr = _.map(errors, function (rttcValidationErr){
return ' • '+rttcValidationErr.message;
}).join('\n');
return 'Could not run `'+self.identity+'` due to '+errors.length+' '+
'validation error'+(errors.length>1?'s':'')+':\n'+prettyPrintedValidationErrorsStr;
})();
var err = new Error(errMsg);
err.code = 'E_MACHINE_RUNTIME_VALIDATION';
err.machineInstance = self;
err.errors = errors;
return err;
})());
}
}
}
// Build an appropriate runtime validation error.
var err_machineRuntimeValidation = (function _buildMachineRuntimeValidationErr() {
// If the `_inputCoercion` flag is enabled, configure the machine with the
// newly coerced input values.
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(coercedInputValues);
self.setInputs(potentiallyCoercedArgins);
}
// Apply `defaultsTo` for input defs that use it.
// TODO: consider whether `defaultsTo` values should be automatically
// validated/coerced too.
_.each(self.inputs, function (inputDef, inputName){
if (_.isUndefined(inputDef.defaultsTo)) { return; }
if (_.isUndefined(self._configuredInputs[inputName])){
self._configuredInputs[inputName] = inputDef.defaultsTo;
// 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){
// 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 (!_.isUndefined(inputDef.contract) && (rttc.infer(inputDef.example) === 'lamda')) {
try {
// If there is no `defaultsTo` value, we obviously can't use it.
if (inputDef.defaultsTo === undefined) {
return;
}
// 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[inputName] = buildLamdaMachine(inputDef.defaultsTo, inputName, self, self._rootMachine||self);
}
catch (e) {
e.input = inputName;
e.message = 'machine:'+self.identity+' => Invalid usage- the `defaultsTo` for lamda input "'+inputName+'" could not be built into a machine using the provided `contract`. Please check that the `contract` def and `defaultsTo` function are valid. The machine was not executed.\nError details:\n'+e.stack;
throw e;
// TODO: pull this `throw` into Machine.build() so it happens as early as possible
}
}
// --•
// Don't use the `defaultsTo` value if an argin was provided for this input.
if (self._configuredInputs[inputCodeName] !== undefined) {
return;
}
});
// Prune undefined configured exits
self._configuredExits = (function pruneKeysWithUndefinedValues(obj) {
// Prune undefined values from the specified object.
_.each(obj, function (val, key) {
if (val === undefined) {
delete obj[key];
// --• 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
}
});
return obj;
})(self._configuredExits);
}//</if this input has a contract and `example: '->'`>
});//</_.each() :: input definition>
// ********************************************************************************
// Should we implement Deferred/promise usage..?
// No- not here. Better to keep things simple in this module and use
// something like Bluebird to present this abstraction in a wrapper module.
// Leaving this TODO here for other folks. Hit @mikermcneil up on
// Twitter if you're interested in using machines via promises
// and we'll figure something out.
// ********************************************************************************
// 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>
// 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;
// If `_cache` is not valid, null it out.
if (
! (
// Validate cache settings.
var areCacheSettingsValid =
_.isObject(_cache) &&

@@ -146,9 +246,14 @@ _.isObject(_cache.model) &&

_.isFunction(_cache.model.destroy) &&
_.isFunction(_cache.model.count)
)
) {
_.isFunction(_cache.model.count);
// If cache settings are NOT valid, then set `_cache`
// to `false` & leave `Cache` undefined.
if (!areCacheSettingsValid) {
_cache = false;
}
// Otherwise if _cache IS valid, normalize it and apply defaults
// ‡ Otherwise cache settings ARE valid. So we'll use them.
else {
// Fold in default cache settings.
_.defaults(_cache, {

@@ -178,2 +283,5 @@

// 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

@@ -183,27 +291,20 @@ // (and also so it uses a consistent timestamp since the code

_cache.expirationDate = new Date( (new Date()) - _cache.ttl);
}
}//</else :: cache settings are valid>
// Cache lookup
// 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`).
//
// The cache uses a hash function to create a unique id for every distinct
// input configuration (these hash sums are only unique per-machine-type.)
// > 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;
//
// Note that this means the machine cache is global not to any particular
// machine instance, but to the machine type itself-- that is, within the
// scope of the cache model.
//
// e.g.
// The cached result of a given set of inputs for a particular type of machine
// will be the same for all instances of that machine using the same cache model
// (which could be shared across devices/routes/processes/apps/servers/clouds/continents)
//
// Old cache entries are garbage collected every time a cache miss occurs
// (also see `maxOldEntriesBuffer` option above for details)
// Now attempt a cache lookup, if configured to do so, then run the machine.
(function _cacheLookup (cb) {
if (!_cache) return cb();
(function _doCacheLookupMaybe (cb) {
if (!_cache) { return cb(); }

@@ -215,3 +316,3 @@ // Run hash function to calculate appropriate `hash` criterion

// (could not calculate unique hash for configured input values)
if (err) return cb(err);
if (err) { return cb(err); }

@@ -221,30 +322,17 @@ // Hashsum was calculated successfully

// Now hit the provided cache model
// (remember- we know it's valid because we validated/normalized
// our `_cache` variable ahead of time)
_cache.model.find((function _buildFindCriteria(options){
// Get the criteria to pass to `.find()` when looking up
// existing values in this cache for a particular hash.
return {
where: {
createdAt: {
'>': options.expirationDate
},
hash: options.hash
},
sort: 'createdAt DESC',
limit: 1
};
})({
hash: hash,
expirationDate: _cache.expirationDate
}))
// 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) {
// Cache lookup encountered fatal error
if (err) {
return cb(err);
}
if (err) { return cb(err); }
// Cache hit
else if (cached.length && typeof cached[0].data !== 'undefined') {
// --• If this was a cache hit...
if (cached.length && typeof cached[0].data !== 'undefined') {
// console.log('cache hit', cached);

@@ -256,8 +344,8 @@ var newestCacheEntry = cached[0];

// Cache miss
// --• If this was a cache miss...
return cb();
});
});
})(function afterCacheLookup(err){
});//</Cache.find() :: finding records in cache model>
});//</calculateHash() :: calculating hash>
})(function afterwards(err){
if (err) {

@@ -269,18 +357,40 @@ // If cache lookup encounters a fatal error, emit a warning

// Run the machine
(function _runMachine () {
// >-
// 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) {
// Perform garbage collection on cache, if necessary.
//
// 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.
//
// (TODO: pull all this craziness out into a separate module/file)
if (_cache) {
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 then don't do anything else for now.
self.warn(err);
return;
}
_cache.model.count({
// --•
// 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: {

@@ -291,157 +401,166 @@ createdAt: {

hash: hash
}
}).exec(function (err, numOldCacheEntries){
},
sort: 'createdAt DESC',
skip: _cache.maxOldEntriesBuffer
}).exec(function (err, oldCacheEntries) {
if (err) {
// If this garbage collection diagnostic query encounters a fatal error,
// emit a warning and then don't do anything else for now.
// If garbage collection encounters a fatal error, emit a warning
// and then don't do anything else for now.
self.warn(err);
return;
}
if (numOldCacheEntries > _cache.maxOldEntriesBuffer) {
// console.log('gc();');
// --•
// Sucessfully wiped all expired cache records for this exact set of argins!
_cache.model.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 for now.
self.warn(err);
}
});//</.destroy() :: destroying expired cache records for this exact set of argins>
});//</.count() :: counting expired cache records for this exact set of argins (to see if it's worth it to wipe them)>
}//</if `_cache` is truthy, then we just started destroying expired cache entries>
// _∏_
// Garbage collection was successful.
// console.log('-gc success-');
});
}
});
}
// 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 a 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).');
}
// Before proceeding, ensure error exit is configured w/
// a callback. If it is not, then get crazy and **throw** BEFORE
// calling the machine's `fn`.
//
// TODO: can probably remove this-- it's just here as a failsafe
if (!self._configuredExits.error){
// console.log('machine:'+self.identity+' => NO ERROR EXIT CONFIGURED!');
// Fill in anonymous forwarding callbacks for any unhandled exits (ignoring the default exit)
// and have them redirect to the `error` (i.e. catchall) exit
_.each(_.keys(self.exits), function (exitCodeName) {
// If it does not, throw.
throw new Error('machine:'+self.identity+' => Invalid usage- an `error` callback must be provided. The machine was not executed.');
// Skip default exit and error exit (they're already accounted for.)
if (exitCodeName === 'success' || exitCodeName === 'error') {
return;
}
// console.log('machine:'+self.identity+' has error exit.');
// If this exit is handled then we're good.
if (self._configuredExits[exitCodeName]) {
return;
}
// Fill in anonymous forwarding callbacks for any unhandled exits (ignoring the default exit)
// and have them redirect to the `error` (i.e. catchall) exit
_.each(_.keys(self.exits), function (exitName) {
// --•
// Otherwise, the exit is unhandled.
Debug('built fwding callback for exit "%s", where there is no implemented callback', exitCodeName);
// Skip default exit and error exit
if ((exitName === 'success') || (exitName === 'error')) {
return;
}
// 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){
// If exit is unhandled, handle it with a function which triggers the `error` exit.
// Generates an Error instance with a useful message and passes it as the first argument.
if (!self._configuredExits[exitName]) {
Debug('built fwding callback for exit "%s", where there is no implemented callback', exitName);
self._configuredExits[exitName] = function forwardToErrorExit (_resultPassedInByMachineFn){
// Build an error instance.
var errMsg = '`'+self.identity+'` triggered its `'+exitName+'` exit';
// Build an error instance
var _err = new Error(util.format('`%s` triggered its `%s` exit', self.identity, exitName) + ((self.exits[exitName] && self.exits[exitName].description)?': '+self.exits[exitName].description:'') + '');
_err.code = exitName;
_err.exit = exitName;
// Use the description, if one was provided.
var exitDef = self.exits[exitName];
if (!_.isObject(exitDef)) { throw new Error('Consistency violation: Live machine instance ('+self.identity+') has become corrupted! One of its exits (`'+exitName+'`) has gone missing _while the machine was being run_!'); }
if (exitDef.description) {
errMsg += ': '+self.exits[exitName].description;
}
// If a result was passed in, it will be stuff it in the generated Error instance
// as the `output` property.
if (!_.isUndefined(_resultPassedInByMachineFn)) {
_err.output = _resultPassedInByMachineFn;
}
// Construct the error instance, then add the `exit` property.
// (also set `code` for compatibility)
var err_forwarding = new Error(errMsg);
err_forwarding.exit = exitName;
err_forwarding.code = exitName;
// 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);
};
// If a result was passed in, it will be 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);
// Intercept the exits to implement exit type coercion, some logging functionality,
// ensure at least one tick has elapsed (if relevant), etc.
var interceptedExits = interceptExitCallbacks(self._configuredExits, _cache, hash, self);
};//</built dynamic callback that forwards to `error`>
// Now it's time to run the machine fn.
try {
// We'll create the ***implementor*** switchback
// (fourth argument (`true`) tells the switchback to run synchronously)
var implementorSwitchback = switchback(interceptedExits, undefined, undefined, true);
});//</_.each() :: exit definition>
// 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('Unexpected error occurred: `_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 (){
if (self._exited) {
throw new Error('Unexpected error occurred: timeout alarm should never be triggered when `_exited` is set. Perhaps you called `.exec()` more than once? If so, please fix and try again.');
}
self._exited = 'error';
var err = new Error( util.format(
'This machine took too long to execute (timeout of %dms 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.',
(self.timeout)
) );
err.code = 'E_TIMEOUT';
// 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);
// Trigger callback
implementorSwitchback.error(err);
// Now it's time to run the machine fn.
// > Use a try/catch to protect against any unexpected errors.
try {
// 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;
// We'll create the ***implementor*** switchback.
// (fourth argument (`true`) tells the switchback to run synchronously)
var implementorSwitchback = switchback(interceptedExitCbs, undefined, undefined, true);
}, self.timeout);
}
// 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){
// For sanity, do one last assertion that `fn` is valid.
if (!_.isFunction(self.fn)) {
throw new Error(
'This machine ('+self.identity+') is corrupted! Its `fn` property is not a function '+
'(instead it\'s a '+rttc.getDisplayType(self.fn)+')'
);
}
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.'); }
// Then call the machine's `fn`.
self.fn.apply(self._configuredEnvironment, [self._configuredInputs, implementorSwitchback, self._configuredEnvironment]);
return;
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('The timeout alarm was triggered when `_exited` was already set. Perhaps you called `.exec()` more than once? If so, please fix and try again.'); }
self._exited = 'error';
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_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})+
'');
}
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 `interceptedExits`
// should actually already be a switchback.
return switchback(interceptedExits, undefined, undefined, true)(e);
}
})();
});
// --•
// 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;
};
{
"name": "machine",
"version": "13.0.0-2",
"version": "13.0.0-3",
"description": "Configure and execute machines",

@@ -36,3 +36,2 @@ "main": "index.js",

"lodash": "3.10.1",
"object-hash": "0.3.0",
"rttc": "^9.8.1",

@@ -39,0 +38,0 @@ "switchback": "2.0.0"

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc