UNPKG

realm-js

Version:
368 lines (340 loc) 12 kB
(function(isNode, $storage, Exports) { var domain = {}; var global; // Lodash is required var _ = isNode ? require('lodash') : Exports._; // Promise is required var Promise = isNode ? require('promise') : Exports.Promise; // Global storage for modules var global = isNode ? $storage : {}; var log = isNode ? require('log4js').getLogger("realm") : undefined; var logger = { fatal: function(msg) { if (log) { return log.fatal(msg); } return console.error(msg); } } // to keep it in sync with old version (wires-domain) global.__wires_services_cached__ = global.__wires_services_cached__ || {}; function getParamNames(func) { var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; var ARGUMENT_NAMES = /([^\s,]+)/g; var fnStr = func.toString().replace(STRIP_COMMENTS, ''); var result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); if (result === null) result = []; return result; }; var isPromise = function(v) { return v && _.isFunction(v.then) && _.isFunction(v.catch); }; // Extracts arguments and defined target for the require function function getInputArguments(args) { var out = {}; out.localServices = {}; if (args.length > 0) { out.source = args[0]; out.target = args[0]; if (_.isPlainObject(args[0])) { var opts = args[0]; out.target = opts.target; out.source = opts.source; out.instance = opts.instance; } // call(func, callback) if (args.length > 1) { var argsDefined = _.isString(args[0]) || _.isArray(args[0]); if (argsDefined) { if (_.isArray(args[0])) { out.source = args[0]; } else { out.source = _.isString(args[0]) ? [args[0]] : args[0]; } if (_.isFunction(args[1])) { out.target = args[1]; } if (_.isFunction(args[2])) { out.target = args[2]; } } else { if (_.isFunction(args[1])) { out.callReady = args[1]; } if (_.isPlainObject(args[1])) { out.localServices = args[1]; } } } if (args.length === 3) { if (_.isPlainObject(args[1])) { out.localServices = args[1]; } if (_.isFunction(args[2])) { out.callReady = args[2]; } } } out.target = out.target || function() {}; out.source = out.source ? out.source : out.target; out.callReady = out.callReady || function() {}; return out; }; var domainEach = function(argv, cb) { return new Promise(function(resolve, reject) { var callbacks = []; var results = []; var isObject = _.isPlainObject(argv); var index = -1; var iterate = function() { index++; if (index < _.size(argv)) { var key; var value; if (isObject) { key = _.keys(argv)[index]; value = argv[key]; } else { key = index; value = argv[index]; } if (isPromise(value)) { value.then(function(data) { results.push(data); iterate(); }).catch(reject); } else { var res = cb.call(cb, value, key); if (isPromise(res)) { res.then(function(a) { results.push(a); iterate(); }).catch(reject); } else { results.push(res); iterate(); } } } else { return resolve(results); } }; iterate(); }); } // Eval flow var evaluateFlow = function(fn, scope) { var result = fn(scope); if (result && _.isFunction(result.then)) { return result.then(function(result) { _.merge(scope, result); return scope; }); } if (_.isPlainObject(result)) { _.merge(scope, result); } return scope; } var MergePromises = function() { var scope = {}; var data = _.flatten(arguments); return domainEach(data, function(step) { if (_.isFunction(step)) { return evaluateFlow(step, scope); } }).then(function() { return scope; }); } var Chain = function(cls) { return new Promise(function(resolve, reject) { if (!_.isObject(cls)) { return reject({ status: 500, message: "Argument must be a class!" }); } var instance = _.isFunction(cls) ? new cls() : cls; instance.$break = function(manual) { instance.$finalized = true; instance.$manual = manual; } instance.$kill = function(manual) { instance.$finalized = true; instance.$killed = true; } instance.$collection = {}; var props = Object.getOwnPropertyNames(instance.constructor.prototype); var tasks = []; // collecting props and checking for setters for (var i = 1; i < props.length; i++) { var propertyName = props[i]; var isSetter = propertyName.match(/^set(.*)$/); var setterName = null; if (isSetter) { setterName = isSetter[1] setterName = setterName.charAt(0).toLowerCase() + setterName.slice(1); } if (propertyName !== "format") { tasks.push({ prop: propertyName, setter: setterName, }); } } var store = function(prop, val) { instance.$collection[prop] = val; instance[prop] = val; } var evaluate = function(task) { var result = instance[task.prop].apply(instance); if (task.setter) { if (isPromise(result)) { return result.then(function(res) { store(task.setter, res); }); } else { store(task.setter, result); } } return result; } // Evaluating each task return domainEach(tasks, function(task) { return !instance.$finalized ? evaluate(task) : false; }).then(function() { if (_.isFunction(instance["format"])) { return evaluate({ prop: "format" }); } }).then(function(specialFormat) { if (instance.$killed) { return resolve(); } if (!instance.$manual) { if (specialFormat) { return resolve(specialFormat) } return resolve(instance.$collection) } else { resolve(instance.$manual) } }).catch(reject); }) } var Realm = { each: domainEach, chain: Chain, merge: MergePromises, service: function() { this.register.apply(this, arguments); }, start: function(moduleName) { return Realm.require(moduleName, function(target) { if (target && _.isFunction(target.main)) { target.main(); } }); }, module: function(name, func, opts) { opts = opts || {}; opts.cache = true; this.register.apply(this, [name, func, opts, true]); }, register: function(name, arg1, arg2, cache) { var localArgs = null; var target = arg1; if (_.isArray(arg1)) { localArgs = arg1; target = arg2; } global.__wires_services__ = global.__wires_services__ || {}; global.__wires_services__[name] = { name: name, target: target, args: localArgs, cache: cache }; }, isRegistered: function(name) { return global.__wires_services__ && global.__wires_services__[name] !== undefined; }, requirePackage: function(name) { var _packageServices = {} var self = this; return domainEach(global.__wires_services__, function(service, serviceName) { var _package = serviceName.indexOf(name) === 0 ? name : false; if (_package[1]) { return self.require([serviceName], function(serviceInstance) { _packageServices[serviceName] = serviceInstance }) } }).then(function() { return _packageServices; }); }, storeModule: function(name, inst) { global.__wires_services_cached__[name] = inst; }, require: function() { var data = getInputArguments(arguments); var self = this; var localServices = data.localServices; var variables = _.isArray(data.source) ? data.source : getParamNames(data.source); var target = data.target; var callReady = data.callReady; var instance = data.instance; var globalServices = global.__wires_services__; var resultPromise = new Promise(function(resolve, reject) { var args = []; var avialableServices = _.merge(localServices, globalServices); for (var i in variables) { var v = variables[i]; var variableName = variables[i]; if (!avialableServices[variableName]) { logger.fatal("Error while injecting variable '" + variableName + "' into function \n" + data.source.toString()); return reject({ status: 500, message: "Service with name '" + variableName + "' was not found " }); } args.push(avialableServices[variableName]); } var results = []; return domainEach(args, function(item) { if (item.cache && global.__wires_services_cached__[item.name] !== undefined) { return global.__wires_services_cached__[item.name]; } var argService = item.target; var requiredArgs = item.args; if (_.isFunction(argService)) { var promised; var currentArgs = []; if (requiredArgs) { currentArgs = [requiredArgs, localServices, argService]; } else { currentArgs = [argService, localServices]; } return self.require.apply(self, currentArgs).then(function(dest) { if (item.cache) { self.storeModule(item.name, dest); } return dest; }); } else { return argService || item; } }).then(function(results) { return target.apply(instance || results, results); }).then(resolve).catch(reject); }); return resultPromise; } } Exports.realm = Realm; })(typeof module !== 'undefined' && module.exports, typeof module !== 'undefined' && module.exports ? global : this, this);