UNPKG

freight.js

Version:

An simple CommonJS dependency injection container for both node.js and the browser (using browserify)

238 lines (194 loc) 6.3 kB
var Definition = function Definition(id, conf, container) { var self = this; /** * the id of the definition * @type {string} */ self.id = id; /** * The callable that creates the service * @type {Function} */ self.fn = false; /** * The arguments that will be send to the callable when called, order matters * @type {Array} */ self.args = []; /** * The tags that the service will receive * @type {Array} */ self.tags = []; /** * Contains the last created instance of the service, this is reused when the service is a shared service * @type {Boolean} */ self.service = false; /** * Indicate wether the service behaves like a singleton; returning the same instance on repeated requests * @type {Boolean} */ self.shared = false; /** * Create an instance from this definition * * @method retrieve() * @return {mixed} */ self.retrieve = function retrieve() { if(self.shared === true && self.service !== false) { return self.service; } var service = self.fn(); if(service === undefined) { throw new Error('"'+id+'" service returned "'+undefined+'" on instantiation.'); } self.service = service; return service; }; /** * Add an argument (at the end) to the definition * * @method addArgument * @param {mixed} arg * @chainable */ self.addArgument = function addArgument(arg) { self.args.push(arg); return self; }; /** * Tag the service * * @method addTag() * @param {string} tag * @chainable */ self.addTag = function addTag(tag) { if(self.tags.indexOf(tag) === -1) self.tags.push(tag); return self; }; /** * Resolve arguments that are container ids * @return {Array} the resolved arguments */ self.resolveArguments = function resolveArguments(arr) { arr = typeof arr !== 'undefined' ? arr : self.args; arr.forEach(function(arg, i){ if(container.isId(arg)) { arr[i] = container.get(arg.replace(container.idPrefix, '')); } else if(Array.isArray(arg)) { arr[i] = self.resolveArguments(arg); } else if(arg.constructor == Object) { Object.keys(arg).forEach(function(k){ if(container.isId(arg[k])) arg[k] = container.get(arg[k].replace(container.idPrefix, '')); }); } }); return arr; }; /** * Create an callable that can creates instances for this definitions * * @method * @private * @param {string} fnParam the container parameter that contains the constructor or factory function * @param {string} type either 'c' for constructor or 'f' for factory * @return {Function} */ self._createCallable = function(fnParam, type) { var res = false; var getCheckCallable = function(p) { var fn = false; if(typeof p === 'function') { fn = p; } else { try { fn = container.getParameter(p.replace(container.idPrefix, '')); } catch(e) { throw new Error('Whene trying to instanciate service "'+self.id+'", the parameter specifying the '+((type == 'f') ? ('factory') : ('constructor'))+': "'+fnParam+'" did not exist.'); } } if(typeof fn !== 'function') { throw new Error('Whene trying to instanciate service "'+self.id+'", the parameter specifying the '+((type == 'f') ? ('factory') : ('constructor'))+': "'+fnParam+'" did not return a function, instead received: "'+fn+'"'); } return fn; }; function neu(constructor, args) { var instance = Object.create(constructor.prototype); var result = constructor.apply(instance, args); return typeof result === 'object' ? result : instance; } //constructor type use new if(type === 'c') { res = function() { var _c = new getCheckCallable(fnParam); var _args = self.resolveArguments(); return neu(_c, _args); }; return res; } //factory type just call it return function() { return getCheckCallable(fnParam).apply(container, self.resolveArguments()); //create using factory fn }; }; /** * Apply an defintion configuration * @param {Object} conf * @return {Definition} * @chainable */ self.configure = function(conf) { //shared if(conf.shared !== undefined) { if(String(conf.shared).toLowerCase() === 'true') { self.shared = true; } else if(String(conf.shared).toLowerCase() === 'false') { self.shared = false; } else { throw new Error('Configuration of "'+self.id+'" shared should be an boolean, received: "'+conf+'"'); } } //constructing var fnParam = conf.constructorFn; var type = 'c'; if(fnParam === undefined) { fnParam = conf.factoryFn; type = 'f'; } else if(conf.factoryFn !== undefined) { throw new Error('Configuration of "'+self.id+'" should specify a constructor OR an factory function, not both - received: "'+conf+'"'); } if(fnParam === undefined) throw new Error('Configuration of "'+self.id+'" must either specify an constructor or an factory function - received: "'+conf+'"'); if(container.isId(fnParam) === false && typeof fnParam !== 'function') { throw new Error('Constructor or an factory function of "'+self.id+'" should be specified using a parameter id - received: "'+fnParam+'"'); } self.fn = self._createCallable(fnParam, type); //arguments var args = conf.arguments; if(args !== undefined) { if(!Array.isArray(args)) throw new Error('Instantiation arguments of "'+self.id+'" should be specified as an array - received: "'+args+'"'); args.forEach(function(arg){ self.addArgument(arg); }); } //tags var tags = conf.tags; if(tags !== undefined) { if(!Array.isArray(tags)) throw new Error('Service tags of "'+self.id+'" should be specified as an array - received: "'+tags+'"'); tags.forEach(function(tag){ self.addTag(tag); }); } return self; }; //configure the definition with the provided argument self.configure(conf); }; module.exports = Definition;