UNPKG

fluid

Version:

Create fluent interfaces, inline, around any object, allowing simple chained async method calls.

234 lines (204 loc) 7.07 kB
var async = require("async"); var contextManager = require("./contextManager.js"); module.exports = function(initialContext) { if (typeof(initialContext) == "string") { return new FluidArgHandler(initialContext); } /* Builder State */ var taskGroups = []; var executionContext = contextManager(); var infoForNextTask = null; var isDebug = false; /* Builder State */ return withContext(initialContext); function withContext(userContext) { var fluidContext = createFluidContext(userContext); return fluidContext; function createFluidContext(context) { newFluidContext = function(options) { return info(options); } newFluidContext.with = withContext; newFluidContext.go = go; newFluidContext.series = createFlowMode("series", async.series); newFluidContext.parallel = createFlowMode("parallel", async.parallel); newFluidContext.info = info; newFluidContext.custom = custom; if (context) { newFluidContext.self = createWrapperMethod("self", context) Object.keys(context).forEach(function(propertyName) { var contextProperty = context[propertyName]; if (contextProperty instanceof Function) { var fluidContextPropertyName = newFluidContext[propertyName] ? "_"+ propertyName : propertyName; newFluidContext[fluidContextPropertyName] = createWrapperMethod(propertyName, contextProperty); } }); } return newFluidContext; } function go(options, callback) { var uid = parseInt(Math.random() * 10000000); logDebug("Starting tasks: "+ uid); if (arguments.length === 1) { callback = options; options = {}; } isDebug = options && options.debug ? options.debug : false; logDebug("Executing "+ taskGroups.length + " task group(s)") async.forEachSeries(taskGroups, function(taskGroup, next) { logDebug("Executing "+ taskGroup.actualTaskCount +" async task(s) in : "+ taskGroup.modeType); taskGroup.mode(taskGroup.tasks, function(err) { if (err) { next(err); } else { next(); } }) },function(err) { logDebug("Tasks finished: "+ uid); var returnValue = executionContext.getContext(); taskGroups = []; executionContext = null; callback(err ? err : null, returnValue); }); } function createFlowMode(name, modeFunction) { return function() { if (taskGroups.length > 0 && taskGroups[taskGroups.length-1].actualTaskCount === 0) { taskGroups.pop(); } taskGroups.push({ modeType : name, mode : modeFunction, tasks : [], actualTaskCount : 0 }); return fluidContext; }; } function createWrapperMethod(propertyName, contextProperty) { return function() { var args = []; for (var i=0; i<arguments.length; i++) { args.push(arguments[i]); } var info = getInfoForNextTask(); var placeHolderName = info.name ? info.name : propertyName; var customError = info.error ? info.error : null; executionContext.addPlaceHolder(placeHolderName); addTaskToCurrentGroup(createMethodTask(args, contextProperty, placeHolderName, customError), true); return fluidContext; }; } function createMethodTask(args, contextProperty, placeHolderName, customError) { return function(callback) { resolveArguments(placeHolderName, args, function(err, resolvedArgs) { if (err) { callback(err); } else { resolvedArgs.push(function(err, res) { logDebug("Finished Executing '"+ placeHolderName + "'") if (err) { callback(getErrorMessage(err, customError)); } else { executionContext.setResult(placeHolderName, res); callback(); } }); contextProperty.apply(executionContext.getContext(), resolvedArgs); } }); } } function resolveArguments(placeHolderName, args, callback) { async.map(args, function(arg, next) { resolveArgument(placeHolderName, arg, function(err, resolvedArg) { if (err) { callback(err); } else { next(null, resolvedArg); } }); }, function(err, resolvedArgs) { if (err) {callback(err) } else { callback(null, resolvedArgs); } }); } function resolveArgument(placeHolderName, arg, callback) { if (arg instanceof FluidArgHandler) { var errored = false; var resolvedArg = null; try { resolvedArg = arg.getValue(placeHolderName, executionContext.getContext()); } catch(e) { errored = true; callback({ message : "Error resolving argument", error : e.toString() }); } if (!errored) { callback(null, resolvedArg); } } else if (arg instanceof Object) { var resolvedArgObject = {}; async.forEach(Object.keys(arg), function(propName, next) { if (arg[propName] instanceof FluidArgHandler) { var errored = false; try { resolvedArgObject[propName] = arg[propName].getValue(placeHolderName, executionContext.getContext()); } catch(e) { errored = true; callback({ message : "Error resolving argument", error : e.toString() }); } if (!errored) { next(); } } else { resolvedArgObject[propName] = arg[propName]; next(); } }, function(err) { if (err) { callback(err); } else { callback(null, resolvedArgObject); } }); } else { callback(null, arg); } } function info(options) { var infoDetail = { name : options.name, error : options.error }; infoForNextTask = infoDetail; addTaskToCurrentGroup(function(callback) { infoForNextTask = infoDetail; callback(); }); return fluidContext; } function custom(customCallback) { return createWrapperMethod("custom", customCallback)(); } function getInfoForNextTask() { var info = infoForNextTask ? infoForNextTask : {}; infoForNextTask = null; return info; } function addTaskToCurrentGroup(task, isActualTask) { if (taskGroups.length === 0) { fluidContext.series(); } var taskGroup = taskGroups[taskGroups.length-1]; if (isActualTask) { taskGroup.actualTaskCount++; } taskGroup.tasks.push(task); } function getErrorMessage(err, customError) { return customError ? customError instanceof Function ? customError(err) : { message : customError, error : err } : err; } function logDebug(message) { if (isDebug) { console.log(message); } } } }; function FluidArgHandler(expression) { this.getValue = function(placeHolderName, context) { var value = null; try { value = executor.apply(context, []); return value; } catch(e) { throw new Error("Error evaluating expression for property '"+ placeHolderName +"' : "+ expression + "\n"+ e); } }; function executor() { var ret = null; eval("ret = "+ expression); return ret; } }