fluid-chains
Version:
A simple way to organize asynchronous Javascript functions.
248 lines (214 loc) • 9.01 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Action = exports.Chain = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _ChainSettings = require('./ChainSettings');
var _ChainStorage = require('./ChainStorage');
var _ContextFactory = require('./ContextFactory');
var _ChainStatus = require('./ChainStatus');
var _ChainContext = require('./ChainContext');
var _ChainContext2 = _interopRequireDefault(_ChainContext);
var _ChainSpec = require('./ChainSpec');
var _ChainSpec2 = _interopRequireDefault(_ChainSpec);
var _middleware = require('../middleware/');
var _Validation = require('./Validation');
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _objectSizeof = require('object-sizeof');
var _objectSizeof2 = _interopRequireDefault(_objectSizeof);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Chain = exports.Chain = function () {
function Chain(name, action, next, error) {
var _this = this;
_classCallCheck(this, Chain);
(0, _Validation.ValidateConstructor)(name, action);
var context = new _ChainContext2.default();
context.set('$owner', name);
context.set('$$chain.status', _ChainStatus.STATUS_UNTOUCHED);
context.set('$$chain.next', next);
context.set('$$chain.name', name);
context.set('$$chain.error', error);
this.spec = [];
this.terminate = function () {
context.set('$isTerminated', true);
};
this.execute = function (done, pr, nxt, belt) {
var cacheEnabled = !!(0, _ChainStorage.getConfig)()[_ChainSettings.$CACHE];
context = (0, _ContextFactory.CreateContext)(context, name, belt ? nxt : next, error);
var param = !!(0, _ChainStorage.getConfig)()[_ChainSettings.$STRICT] ? (0, _ContextFactory.ConvertToContext)(pr).cloneFor(context) : (0, _ContextFactory.ConvertToContext)(pr).clone();
context.set('$$chain.status', _ChainStatus.STATUS_IN_PROGRESS);
invokeChain(done, name, next, action, _this.spec, context, param, nxt, belt, cacheEnabled);
};
this.status = function () {
return context.$$chain.status();
};
this.info = function () {
return {
name: name,
status: context.$$chain.status(),
next: next,
errorHandler: error,
responseTime: context.$responseTime ? context.$responseTime() : 0
};
};
this.addSpec = function (field, required, customValidator) {
var spec = new _ChainSpec2.default(field, required, customValidator);
_this.spec.push(spec);
context.addValidator(spec);
return new SpecWrapper(spec);
};
(0, _ChainStorage.putChain)(name, this);
}
_createClass(Chain, [{
key: 'size',
value: function size() {
return (0, _objectSizeof2.default)(this);
}
}]);
return Chain;
}();
var SpecWrapper = function SpecWrapper(spec) {
var _this2 = this;
_classCallCheck(this, SpecWrapper);
this.require = function (message) {
spec.require(message);
return _this2;
};
this.validator = function (validator) {
spec.validator(validator);
return _this2;
};
this.transform = function (transformer) {
spec.transform(transformer);
return _this2;
};
this.default = function (defaultValue) {
spec.default(defaultValue);
return _this2;
};
this.translate = function (translator) {
spec.translate(translator);
return _this2;
};
};
var ChainResponse = function ChainResponse(done, context, startTime) {
context.set('$responseTime', new Date().getTime() - startTime);
var clonedContext = context.clone();
if ((0, _ChainStorage.getConfig)()[_ChainSettings.$CACHE] && context.$$chain && context.$$chain.id) {
(0, _ChainStorage.removeState)(context.$$chain.id);
}
done(clonedContext);
};
var failed = function failed(done, context, name, err) {
context.set('$$chain.status', _ChainStatus.STATUS_FAILED);
if (context.$error) {
_lodash2.default.clone(_ChainStorage.ChainStorage[context.$error()]()).execute(done, (0, _ContextFactory.CreateErrorContext)(context.$error(), name, err, context.$next ? context.$next() : undefined));
} else {
done((0, _ContextFactory.CreateErrorContext)('unhandled', name, err, context.$next ? context.$next() : undefined));
}
};
function errorResponse() {}
function invokeChain(done, name, next, action, spec, context, param, nxt, belt, cacheEnabled) {
(0, _middleware.RunMiddleware)(name, param.clone(), context, function (errMiddleware) {
if (errMiddleware) {
failed(done, context, name, errMiddleware);
} else {
context.initSpecs(param, function (specError) {
if (param && param.$error && !context.$error) {
context.set('$error', param.$error());
}
if (specError) {
failed(done, context, name, specError);
} else if (context.$isTerminated && context.$isTerminated()) {
context.set('$$chain.status', _ChainStatus.STATUS_TERMINATED);
var clonedContext = context.clone();
done(clonedContext);
} else {
setTimeout(function () {
var startTime = new Date().getTime();
try {
if (cacheEnabled && param.$$chain && param.$$chain.id) {
var key = param.$$chain.id;
if ((0, _ChainStorage.getState)(key, name, param)) {
var cachedContext = (0, _ChainStorage.getState)(key, name, param).clone();
concludeNextAction(cachedContext, param, belt, startTime, done);
} else {
invokeAction(action, name, spec, context, param, belt, cacheEnabled, startTime, done);
}
} else {
invokeAction(action, name, spec, context, param, belt, cacheEnabled, startTime, done);
}
} catch (err) {
context.set('$responseTime', new Date().getTime() - startTime);
failed(done, context, name, err);
}
});
}
});
}
});
}
function invokeAction(action, name, spec, context, param, belt, cacheEnabled, startTime, done) {
var asyncAction = action.length === 3;
action(context, param, function (err) {
if (err && err instanceof Error) {
failed(done, context, name, err);
} else {
if (cacheEnabled) {
var key = void 0;
if (!param.$$chain || !param.$$chain.id) {
key = (0, _ChainStorage.createChainState)(name, spec, param, context);
} else {
key = param.$$chain.id;
(0, _ChainStorage.addChainState)(key, name, spec, param, context);
}
context.set('$$chain.id', key);
}
concludeNextAction(context, param, belt, startTime, done);
}
});
if (!asyncAction) {
if (cacheEnabled) {
var key = void 0;
if (!param.$$chain || !param.$$chain.id) {
key = (0, _ChainStorage.createChainState)(name, spec, param, context);
} else {
key = param.$$chain.id;
(0, _ChainStorage.addChainState)(key, name, spec, param, context);
}
context.set('$$chain.id', key);
}
concludeNextAction(context, param, belt, startTime, done);
}
}
function concludeNextAction(context, param, belt, startTime, done) {
context.set('$$chain.status', _ChainStatus.STATUS_DONE);
if (!belt && context.$next) {
if (context.$isTerminated && context.$isTerminated()) {
context.set('$$chain.status', _ChainStatus.STATUS_TERMINATED);
ChainResponse(done, context, startTime);
} else if (context.$next) {
context.set('$responseTime', new Date().getTime() - startTime);
_lodash2.default.clone(_ChainStorage.ChainStorage[context.$next()]()).execute(done, context);
} else {
ChainResponse(done, context, startTime);
}
} else {
ChainResponse(done, context, startTime);
}
}
var Action = exports.Action = function Action(target, key, descriptor) {
var chain = void 0;
if (target) {
_lodash2.default.set(target, 'CHAIN_' + key.toUpperCase(), key);
}
if (descriptor && descriptor.value instanceof Function) {
chain = new Chain(key, descriptor.value);
} else {
throw new Error('Must be declared in a function with (context, paran, next).');
}
return chain;
};