UNPKG

workflow-4-node

Version:

Workflow 4 Node is a .NET Workflow Foundation like framework for Node.js. The goal is to reach feature equivalence and beyond.

412 lines (370 loc) 12.5 kB
"use strict"; /* jshint -W061 */ let _ = require("lodash"); let errors = require("../common/errors"); let Activity = require("./activity"); let is = require("../common/is"); let path = require("path"); let fs = require("fs"); let Reflection = require("backpack-node").system.Reflection; let templateHelpers = require('./templateHelpers'); const activityTypeNameRex = /^\@([a-zA-Z_]+[0-9a-zA-Z_]*)$/; function getActivityTypeName(str) { if (_.isString(str)) { let result = activityTypeNameRex.exec(str); if (result && result.length === 2) { return result[1]; } } return null; } function requireFromRoot(resource) { try { return require(resource); } catch (e) { _.noop(e); } let pPos = resource.indexOf("/"); if (pPos === -1) { return require(resource); } let module = resource.substr(0, pPos); if (!module) { return require(resource); } try { module = require(module); let obj = module; for (let key of resource.substr(pPos + 1).split("/")) { obj = obj[key]; } return obj; } catch (e) { return require(resource); } } function ActivityMarkup() { this._systemTypes = new Map(); this._registerSystemTypes(); } ActivityMarkup.prototype._registerSystemTypes = function () { this._registerTypes(__dirname); }; ActivityMarkup.prototype._registerTypes = function (sourcePath) { this._registerTypesTo(this._systemTypes, sourcePath); }; ActivityMarkup.prototype._registerTypesTo = function (types, sourcePath) { let self = this; let obj = requireFromRoot(sourcePath); Reflection.visitObject(obj, function (inObj) { let alias = self.getAlias(inObj); if (alias && !types.has(alias)) { // This is an activity type types.set(alias, inObj); } return alias === null; }); }; ActivityMarkup.prototype.getAlias = function (type) { if (_.isFunction(type) && !_.isUndefined(type.super_)) { let alias = this._toCamelCase(type.name); do { if (type.super_ === Activity) { return alias; } type = type.super_; } while (type); } return null; }; ActivityMarkup.prototype._toCamelCase = function (id) { return id[0].toLowerCase() + id.substr(1); }; ActivityMarkup.prototype.parse = function (markup) { if (!markup) { throw new TypeError("Parameter 'markup' expected."); } if (_.isString(markup)) { markup = JSON.parse(markup); } if (!_.isPlainObject(markup)) { throw new TypeError("Parameter 'markup' is not a plain object."); } let types = new Map(); for (let kvp of this._systemTypes.entries()) { types.set(kvp[0], kvp[1]); } let req = markup["@require"]; if (req) { this._require(types, req); } let activity = this._createActivity(types, markup); if (req) { activity["@require"] = req; } return activity; }; ActivityMarkup.prototype._createActivity = function (types, markup) { let filedNames = _.filter(_.keys(markup), function (k) { return k !== "@require"; }); if (filedNames.length !== 1) { throw new errors.ActivityMarkupError("There should be one field." + this._errorHint(markup)); } let activityAlias = getActivityTypeName(filedNames[0]); if (activityAlias) { return this._createAndInitActivityInstance(types, activityAlias, markup); } else { throw new errors.ActivityMarkupError("Root entry is not an activity type name '" + filedNames[0] + "'." + this._errorHint(markup)); } }; ActivityMarkup.prototype._createAndInitActivityInstance = function (types, typeName, markup) { let activity = this._createActivityInstance(types, typeName); if (!activity) { throw new errors.ActivityMarkupError("Unknown activity type name '" + typeName + "'." + this._errorHint(markup)); } let activityRef = { name: typeName, value: activity }; let pars = markup["@" + typeName]; if (pars) { this._setupActivity(types, activityRef, pars); } return activityRef.value; }; ActivityMarkup.prototype._createActivityInstance = function (types, alias) { let Constructor = types.get(alias); if (_.isUndefined(Constructor)) { return null; } return new Constructor(); }; ActivityMarkup.prototype._setupActivity = function (types, activityRef, pars) { let self = this; let activity = activityRef.value; function noFunction(fieldName) { return activity.codeProperties.has(fieldName); } if (_.isArray(pars)) { // args activity.args = []; for (let obj of pars) { activity.args.push(self._createValue(types, obj, false, is.template(activity))); } } else if (_.isObject(pars)) { let to = null; // values for (let fieldName in pars) { if (pars.hasOwnProperty(fieldName)) { if (activity.isArrayProperty(fieldName)) { let v = self._createValue(types, pars[fieldName], true, is.template(activity)); if (!_.isArray(v)) { v = [v]; } activity[fieldName] = v; } else if (fieldName === "@to") { if (to) { throw new errors.ActivityMarkupError("Multiple to defined in activity '" + activityRef.name + "." + this._errorHint(pars)); } to = pars[fieldName]; } else if (fieldName[0] === "!") { // Promoted: if (!activity.promotedProperties || !_.isFunction(activity.promoted)) { throw new errors.ActivityMarkupError("Activity '" + activityRef.name + " cannot have promoted properties." + this._errorHint(pars)); } activity.promoted(fieldName.substr(1), self._createValue(types, pars[fieldName], true, is.template(activity))); } else if (fieldName[0] === "`") { // Reserved: if (!activity.reservedProperties || !_.isFunction(activity.reserved)) { throw new errors.ActivityMarkupError("Activity '" + activityRef.name + " cannot have reserved properties." + this._errorHint(pars)); } activity.reserved(fieldName.substr(1), self._createValue(types, pars[fieldName], true, is.template(activity))); } else if (fieldName === "@require") { // Require: self._require(types, pars[fieldName]); } else { activity[fieldName] = self._createValue(types, pars[fieldName], false, is.template(activity), noFunction(fieldName)); } } } if (to) { let current = activity; let assign = activityRef.value = this._createActivityInstance(types, "assign"); assign.value = current; assign.to = to; } } else { // 1 arg activity.args = [self._createValue(types, pars, false, is.template(activity))]; } }; ActivityMarkup.prototype._require = function (types, markup) { let self = this; if (_.isArray(markup)) { for (let item of markup) { self._require(types, item); } } else if (_.isString(markup)) { self._registerTypesTo(types, markup); } else { throw new errors.ActivityMarkupError("Cannot register '" + markup + "'." + self._errorHint(markup)); } }; ActivityMarkup.prototype._createValue = function (types, markup, canBeArray, noTemplate, noFunction) { let self = this; // Helpers function toTemplate(_markup) { let template = self._createActivityInstance(types, "template"); template.declare = _markup; return template; } function toFunc(f) { let func = self._createActivityInstance(types, "func"); func.code = f; return func; } function toExpression(str) { let expr = self._createActivityInstance(types, "expression"); expr.expr = str; return expr; } if (_.isArray(markup)) { if (canBeArray) { let result = []; for (let v of markup) { result.push(self._createValue(types, v)); } return result; } else if (!noTemplate && templateHelpers.isTemplate(markup)) { return toTemplate(markup); } } else if (_.isPlainObject(markup)) { let filedNames = _.keys(markup); if (filedNames.length === 1) { let fieldName = filedNames[0]; let fieldValue = markup[fieldName]; if (fieldName === "_") { // Escape: return fieldValue; } let activityTypeName = getActivityTypeName(fieldName); if (activityTypeName) { // Activity: return self._createAndInitActivityInstance(types, activityTypeName, markup); } } // Plain object: if (!noTemplate && templateHelpers.isTemplate(markup)) { return toTemplate(markup); } } else if (_.isString(markup)) { let str = markup.trim(); if (templateHelpers.isFunctionString(str)) { let f; eval("f = function(_){return (" + str + ");}"); f = f(_); if (!noFunction) { return toFunc(f); } else { return f; // aka when func.code } } else if (str.length > 1) { if (str[0] === "=") { // Expression return toExpression(str.substr(1)); } } } else if (_.isFunction(markup)) { if (!noFunction) { return toFunc(markup); } } return this._clone(markup); }; ActivityMarkup.prototype._clone = function(obj) { return templateHelpers.cloneDeep(obj); }; ActivityMarkup.prototype._errorHint = function (markup) { let len = 20; let json = JSON.stringify(markup); if (json.length > len) { json = json.substr(0, len) + " ..."; } return "\nSee error near:\n" + json; }; ActivityMarkup.prototype.stringify = function (obj) { if (_.isString(obj)) { return obj; } if (is.activity(obj)) { obj = this.toMarkup(obj); } if (!_.isPlainObject(obj)) { throw new TypeError("Parameter 'obj' is not a plain object."); } let cloned = _.cloneDeep(obj); this._functionsToString(cloned); return JSON.stringify(cloned); }; ActivityMarkup.prototype._functionsToString = function (obj) { let self = this; for (let fieldName in obj) { let fieldValue = obj[fieldName]; if (_.isFunction(fieldValue)) { obj[fieldName] = fieldValue.toString(); } else if (_.isObject(fieldValue)) { self._functionsToString(fieldValue); } else if (_.isArray(fieldValue)) { for (let v of fieldValue) { self._functionsToString(v); } } } }; // To Markup: ActivityMarkup.prototype.toMarkup = function (activity) { /*if (!is.activity(activity)) { throw new TypeError("Argument is not an activity instance."); } let markup = {}; let alias = this.getAlias(activity.constructor); let activityMarkup = this._createMarkupOfActivity(activity);*/ throw new Error("Not supported yet!"); }; // Exports: let activityMarkup = null; module.exports = { parse: function (markup) { return (activityMarkup = (activityMarkup || new ActivityMarkup())).parse(markup); }, toMarkup: function (activity) { return (activityMarkup = (activityMarkup || new ActivityMarkup())).toMarkup(activity); }, stringify: function (obj) { return (activityMarkup = (activityMarkup || new ActivityMarkup())).stringify(obj); }, getAlias: function (activity) { return (activityMarkup = (activityMarkup || new ActivityMarkup())).getAlias(activity.constructor); } };