function-tree
Version:
When a function is not enough
302 lines (295 loc) • 15.5 kB
JavaScript
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
import EventEmitter from 'eventemitter3';
import executeTree from './executeTree';
import createStaticTree from './staticTree';
import resolveProvider from './providers/Resolve';
import Path from './Path';
import Provider from './Provider';
import { Primitive } from './primitives';
import { FunctionTreeExecutionError } from './errors';
import { isPromise } from './utils';
/*
Need to create a unique ID for each execution to identify it
in debugger
*/
function createUniqueId() {
return Date.now() + '_' + Math.floor(Math.random() * 10000);
}
/*
Validate any returned value from a function. Has
to be nothing or an object
*/
function isValidResult(result) {
return !result || _typeof(result) === 'object' && !Array.isArray(result);
}
/*
Create an error with execution details
*/
function createErrorObject(error, execution, functionDetails, payload) {
var errorToReturn = error;
errorToReturn.execution = execution;
errorToReturn.functionDetails = functionDetails;
errorToReturn.payload = Object.assign({}, payload, {
_execution: {
id: execution.id,
functionIndex: functionDetails.functionIndex
},
error: error.toJSON ? error.toJSON() : {
name: error.name,
message: error.message,
stack: error.stack
}
});
return errorToReturn;
}
var FunctionTreeExecution = /*#__PURE__*/function () {
function FunctionTreeExecution(name, staticTree, functionTree, errorCallback) {
_classCallCheck(this, FunctionTreeExecution);
this.id = createUniqueId();
this.name = name || staticTree.name || this.id;
this.staticTree = staticTree;
this.functionTree = functionTree;
this.datetime = Date.now();
this.errorCallback = errorCallback;
this.hasThrown = false;
this.isAsync = false;
this.runFunction = this.runFunction.bind(this);
}
/*
Creates the context for the current function to be run,
emits events and handles its returned value. Also handles
the returned value being a promise
*/
return _createClass(FunctionTreeExecution, [{
key: "runFunction",
value: function runFunction(funcDetails, payload, prevPayload, next) {
if (this.hasThrown) {
return;
}
var context = this.createContext(funcDetails, payload, prevPayload);
var functionTree = this.functionTree;
var errorCallback = this.errorCallback;
var execution = this;
var result;
functionTree.emit('functionStart', execution, funcDetails, payload);
try {
result = funcDetails["function"](context);
} catch (error) {
this.hasThrown = true;
return errorCallback(createErrorObject(error, execution, funcDetails, payload), execution, funcDetails, payload);
}
/*
If result is a promise we want to emit an event and wait for it to resolve to
move on
*/
if (isPromise(result)) {
functionTree.emit('asyncFunction', execution, funcDetails, payload, result);
this.isAsync = true;
result.then(function (result) {
if (result instanceof Path) {
functionTree.emit('functionEnd', execution, funcDetails, payload, result);
next(result.toJSON());
} else if (funcDetails.outputs) {
functionTree.emit('functionEnd', execution, funcDetails, payload, result);
throw new FunctionTreeExecutionError(execution, funcDetails, payload, new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path of either ' + Object.keys(funcDetails.outputs)));
} else if (isValidResult(result)) {
functionTree.emit('functionEnd', execution, funcDetails, payload, result);
next({
payload: result
});
} else {
functionTree.emit('functionEnd', execution, funcDetails, payload, result);
throw new FunctionTreeExecutionError(execution, funcDetails, payload, new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result'));
}
})["catch"](function (result) {
if (execution.hasThrown) {
return;
}
if (result instanceof Error) {
execution.hasThrown = true;
errorCallback(createErrorObject(result, execution, funcDetails, payload), execution, funcDetails, payload);
} else if (result instanceof Path) {
functionTree.emit('functionEnd', execution, funcDetails, payload, result);
next(result.toJSON());
} else if (funcDetails.outputs) {
var error = new FunctionTreeExecutionError(execution, funcDetails, payload, new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path of either ' + Object.keys(funcDetails.outputs)));
execution.hasThrown = true;
errorCallback(createErrorObject(error, execution, funcDetails, payload), execution, funcDetails, payload);
} else if (isValidResult(result)) {
functionTree.emit('functionEnd', execution, funcDetails, payload, result);
next({
payload: result
});
} else {
var _error = new FunctionTreeExecutionError(execution, funcDetails, payload, new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result'));
execution.hasThrown = true;
errorCallback(createErrorObject(_error, execution, funcDetails, payload), execution, funcDetails, payload);
}
});
} else if (result instanceof Path) {
functionTree.emit('functionEnd', execution, funcDetails, payload, result);
next(result.toJSON());
} else if (funcDetails.outputs) {
var error = new FunctionTreeExecutionError(execution, funcDetails, payload, new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' needs to be a path of either ' + Object.keys(funcDetails.outputs)));
this.hasThrown = true;
errorCallback(createErrorObject(error, execution, funcDetails, payload), execution, funcDetails, payload);
} else if (isValidResult(result)) {
functionTree.emit('functionEnd', execution, funcDetails, payload, result);
next({
payload: result
});
} else {
var _error2 = new FunctionTreeExecutionError(execution, funcDetails, payload, new Error('The result ' + JSON.stringify(result) + ' from function ' + funcDetails.name + ' is not a valid result'));
this.hasThrown = true;
errorCallback(createErrorObject(_error2, execution, funcDetails, payload), execution, funcDetails, payload);
}
}
/*
Creates the context for the next running function
*/
}, {
key: "createContext",
value: function createContext(functionDetails, payload, prevPayload) {
var contextProviders = this.functionTree.contextProviders;
var newContext = {
execution: this,
props: payload || {},
functionDetails: functionDetails,
path: functionDetails.outputs ? Object.keys(functionDetails.outputs).reduce(function (output, outputPath) {
output[outputPath] = function (payload) {
return new Path(outputPath, payload);
};
return output;
}, {}) : null
};
var debuggerProvider = contextProviders["debugger"] && contextProviders["debugger"].get(newContext, functionDetails, payload, prevPayload);
var context = Object.keys(contextProviders).reduce(function (currentContext, name) {
var provider = contextProviders[name];
if (provider instanceof Provider) {
currentContext[name] = provider.get(currentContext, functionDetails, payload, prevPayload);
} else {
currentContext[name] = provider;
}
return currentContext;
}, newContext);
if (debuggerProvider) {
return Object.keys(context).reduce(function (currentContext, name) {
var provider = contextProviders[name];
if (provider && provider instanceof Provider && provider.wrap) {
currentContext[name] = typeof provider.wrap === 'function' ? provider.wrap(context, functionDetails) : provider.getWrapped(name, context);
} else {
currentContext[name] = context[name];
}
return currentContext;
}, {});
}
return context;
}
}]);
}();
export var FunctionTree = /*#__PURE__*/function (_EventEmitter) {
function FunctionTree() {
var _this;
var contextProviders = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, FunctionTree);
_this = _callSuper(this, FunctionTree);
_this.cachedTrees = [];
_this.cachedStaticTrees = [];
_this.executeBranchWrapper = options.executeBranchWrapper || function (cb) {
cb();
};
if (_typeof(contextProviders) !== 'object' || contextProviders === null || Array.isArray(contextProviders)) {
throw new Error('You have to pass an object of context providers to FunctionTree');
}
var providerKeys = Object.keys(contextProviders);
if (providerKeys.indexOf('props') >= 0 || providerKeys.indexOf('path') >= 0 || providerKeys.indexOf('resolve') >= 0 || providerKeys.indexOf('execution') >= 0 || providerKeys.indexOf('debugger') >= 0) {
throw new Error('You are trying to add a provider with protected key. "props", "path", "resolve", "execution" and "debugger" are protected');
}
_this.contextProviders = Object.assign({}, contextProviders, {
resolve: resolveProvider
});
_this.run = _this.run.bind(_this);
return _this;
}
/*
Analyses the tree to identify paths and its validity. This analysis
is cached. Then the method creates an execution for the tree to run.
*/
_inherits(FunctionTree, _EventEmitter);
return _createClass(FunctionTree, [{
key: "run",
value: function run() {
var _this2 = this;
var name;
var tree;
var payload;
var cb;
var staticTree;
var args = [].slice.call(arguments);
args.forEach(function (arg) {
if (typeof arg === 'string') {
name = arg;
} else if (Array.isArray(arg) || arg instanceof Primitive) {
tree = arg;
} else if (!tree && typeof arg === 'function') {
tree = arg;
} else if (typeof arg === 'function') {
cb = arg;
} else {
payload = arg;
}
});
if (!tree) {
throw new Error('function-tree - You did not pass in a function tree');
}
var withResolveAndReject = function withResolveAndReject(resolve, reject) {
var treeIdx = _this2.cachedTrees.indexOf(tree);
if (treeIdx === -1) {
staticTree = createStaticTree(name, tree);
_this2.cachedTrees.push(tree);
_this2.cachedStaticTrees.push(staticTree);
} else {
staticTree = _this2.cachedStaticTrees[treeIdx];
}
var execution = new FunctionTreeExecution(name, staticTree, _this2, function (error, execution, funcDetails, finalPayload) {
_this2.emit('error', error, execution, funcDetails, finalPayload);
reject(error);
});
_this2.emit('start', execution, payload);
executeTree(execution, payload, _this2.executeBranchWrapper, function (funcDetails, path, currentPayload) {
_this2.emit('pathStart', path, execution, funcDetails, currentPayload);
}, function (currentPayload) {
_this2.emit('pathEnd', execution, currentPayload);
}, function (currentPayload, functionsToResolve) {
_this2.emit('parallelStart', execution, currentPayload, functionsToResolve);
}, function (currentPayload, functionsResolved) {
_this2.emit('parallelProgress', execution, currentPayload, functionsResolved);
}, function (currentPayload, functionsResolved) {
_this2.emit('parallelEnd', execution, currentPayload, functionsResolved);
}, function (finalPayload) {
_this2.emit('end', execution, finalPayload);
resolve === reject ? resolve(null, finalPayload) : resolve(finalPayload);
});
};
if (cb) {
withResolveAndReject(cb, cb);
} else {
return new Promise(withResolveAndReject);
}
}
}]);
}(EventEmitter);
//# sourceMappingURL=FunctionTree.js.map