Comparing version 0.2.1 to 0.3.0
159
lib/beat.js
@@ -1,85 +0,108 @@ | ||
var Beat = module.exports = function Beat(alias) { | ||
this._alias = alias || 'unnamed'; | ||
this._properties = {}; | ||
this._factories = {}; | ||
this._factoryDependencies = {}; | ||
this._path = []; | ||
var Beat = module.exports = function Beat(alias, beats) { | ||
this._alias = alias || 'unnamed'; | ||
this._properties = {}; | ||
this._factories = {}; | ||
this._factoryDependencies = {}; | ||
this._path = []; | ||
this._map = {}; | ||
var rootPath = process.cwd(); | ||
for(var i in beats) { | ||
var item = beats[i]; | ||
for(var alias in item) { | ||
if(typeof item == 'string') alias = item; | ||
else item = item[alias]; | ||
if(item[0]=='/') item = rootPath+item; | ||
this._map[alias] = item; | ||
break; | ||
} | ||
} | ||
}; | ||
Beat.prototype._error = function _error(msg) { | ||
this._path.unshift(colorize(this._alias, 'blue')); | ||
var stack = this._path.map(function(p){return colorize(p, 'blue');}).join(' -> '); | ||
this._path = []; | ||
return new Error(colorize(msg, 'red')+' at '+stack); | ||
this._path.unshift(colorize(this._alias, 'blue')); | ||
var stack = this._path.map(function(p){return colorize(p, 'blue');}).join(' -> '); | ||
this._path = []; | ||
return new Error(colorize(msg, 'red')+' at '+stack); | ||
}; | ||
Beat.prototype.load = function load(beat) { | ||
if(!beat instanceof Beat) | ||
throw this._error('Can not load Beat "'+beat+'". Expected a instance of Beat.'); | ||
for(var key in beat._properties) | ||
this._properties[key] = beat._properties[key]; | ||
for(var key in beat._factories) | ||
this._factories[key] = beat._factories[key]; | ||
for(var key in beat._factoryDependencies) | ||
this._factoryDependencies[key] = beat._factoryDependencies[key]; | ||
return this; | ||
if(!beat instanceof Beat) | ||
throw this._error('Can not load Beat "'+beat+'". Expected a instance of Beat.'); | ||
for(var key in beat._properties) | ||
this._properties[key] = beat._properties[key]; | ||
for(var key in beat._factories) | ||
this._factories[key] = beat._factories[key]; | ||
for(var key in beat._factoryDependencies) | ||
this._factoryDependencies[key] = beat._factoryDependencies[key]; | ||
for(var key in beat._map) | ||
this._map[key] = beat._map[key]; | ||
return this; | ||
}; | ||
Beat.prototype._require = function(path){ | ||
return require(path); | ||
}; | ||
Beat.prototype._getBlockInfo = function _getBlockInfo(block) { | ||
if(block instanceof Array) | ||
return {fn:block.pop(), deps:block}; | ||
if(typeof block !== 'function') | ||
throw this._error('Can not get arguments from "'+block+'". Expected a function.'); | ||
var match = block.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m); | ||
var deps = match && match[1] && match[1].split(',').map(function(arg) { | ||
var match = arg.match(/\/\*([^\*]*)\*\//m); | ||
return match ? match[1].trim() : arg.trim(); | ||
}) || []; | ||
return {fn:block, deps:deps}; | ||
if(block instanceof Array) | ||
return {fn:block.pop(), deps:block}; | ||
if(typeof block !== 'function') | ||
throw this._error('Can not get arguments from "'+block+'". Expected a function.'); | ||
var match = block.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m); | ||
var deps = match && match[1] && match[1].split(',').map(function(arg) { | ||
var match = arg.match(/\/\*([^\*]*)\*\//m); | ||
return match ? match[1].trim() : arg.trim(); | ||
}) || []; | ||
return {fn:block, deps:deps}; | ||
}; | ||
Beat.prototype.value = function value(alias, value) { | ||
if(typeof alias != 'string' && typeof alias != 'number') | ||
throw this._error('Invalid key "'+alias+'" for value "'+value+'"'); | ||
this._properties[alias] = value; | ||
return this; | ||
if(typeof alias != 'string' && typeof alias != 'number') | ||
throw this._error('Invalid key "'+alias+'" for value "'+value+'"'); | ||
this._properties[alias] = value; | ||
return this; | ||
}; | ||
Beat.prototype.factory = function factory(alias, fn) { | ||
this._factories[alias] = this._getBlockInfo(fn); | ||
return this; | ||
this._factories[alias] = this._getBlockInfo(fn); | ||
return this; | ||
}; | ||
Beat.prototype.get = function get(alias) { | ||
var alreadyResolving = this._path.indexOf(alias) !== -1; | ||
this._path.push(alias); | ||
if(alreadyResolving) | ||
throw this._error('Can not resolve circular dependency for "'+alias+'".'); | ||
if(typeof this._properties[alias] === 'undefined') { | ||
if(typeof this._factories[alias] === 'undefined') | ||
throw this._error('No provider for "'+alias+'".'); | ||
var resolvedDependencies = []; | ||
for(var i=0;i<this._factories[alias].deps.length;i++) | ||
resolvedDependencies.push(this.get(this._factories[alias].deps[i])); | ||
this._properties[alias] = this._factories[alias].fn.apply(this, resolvedDependencies); | ||
delete this._factories[alias]; | ||
} | ||
this._path.pop(); | ||
return this._properties[alias]; | ||
for(var a in this._map) { | ||
var module = typeof this._map[a] == 'string'? this._require(this._map[a]): this._map[a]; | ||
if(module instanceof Beat) this.load(module); | ||
else this._properties[a.replace(/(\-\w)/g, function(match){return match[1].toUpperCase();})] = module; | ||
delete this._map[a]; | ||
} | ||
var alreadyResolving = this._path.indexOf(alias) !== -1; | ||
this._path.push(alias); | ||
if(alreadyResolving) | ||
throw this._error('Can not resolve circular dependency for "'+alias+'".'); | ||
if(typeof this._properties[alias] === 'undefined') { | ||
if(typeof this._factories[alias] === 'undefined') | ||
throw this._error('No provider for "'+alias+'".'); | ||
var resolvedDependencies = []; | ||
for(var i=0;i<this._factories[alias].deps.length;i++) | ||
resolvedDependencies.push(this.get(this._factories[alias].deps[i])); | ||
this._properties[alias] = this._factories[alias].fn.apply(this, resolvedDependencies); | ||
delete this._factories[alias]; | ||
} | ||
this._path.pop(); | ||
return this._properties[alias]; | ||
}; | ||
Beat.prototype.run = function run(block) { | ||
this._path = []; | ||
var info = this._getBlockInfo(block); | ||
var resolvedDependencies = []; | ||
for(var i=0;i<info.deps.length;i++) | ||
resolvedDependencies.push(this.get(info.deps[i])); | ||
info.fn.apply(this, resolvedDependencies); | ||
return this; | ||
this._path = []; | ||
var info = this._getBlockInfo(block); | ||
var resolvedDependencies = []; | ||
for(var i=0;i<info.deps.length;i++) | ||
resolvedDependencies.push(this.get(info.deps[i])); | ||
info.fn.apply(this, resolvedDependencies); | ||
return this; | ||
}; | ||
function colorize(str, color) { | ||
var options = { | ||
red: '\u001b[31m', | ||
green: '\u001b[32m', | ||
yellow: '\u001b[33m', | ||
blue: '\u001b[34m', | ||
magenta: '\u001b[35m', | ||
cyan: '\u001b[36m', | ||
gray: '\u001b[90m', | ||
reset: '\u001b[0m' | ||
}; | ||
return options[color]+str+options.reset; | ||
var options = { | ||
red: '\u001b[31m', | ||
green: '\u001b[32m', | ||
yellow: '\u001b[33m', | ||
blue: '\u001b[34m', | ||
magenta: '\u001b[35m', | ||
cyan: '\u001b[36m', | ||
gray: '\u001b[90m', | ||
reset: '\u001b[0m' | ||
}; | ||
return options[color]+str+options.reset; | ||
}; |
@@ -6,3 +6,3 @@ { | ||
"repository": "git://github.com/edinella/beat.git", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"license": "MIT", | ||
@@ -24,4 +24,7 @@ "main": "lib/beat.js", | ||
"injection", | ||
"injector" | ||
"injector", | ||
"container", | ||
"dependency management", | ||
"dependency injection" | ||
] | ||
} |
@@ -142,2 +142,57 @@ # beat | ||
}]); | ||
``` | ||
### Dependencies | ||
Beat instances can import properties from another instances. | ||
Therefore, declare them as array at constructor second parameter: | ||
```js | ||
var Beat = require('beat'); | ||
var db = module.exports = new Beat('db', ['mongoose']); | ||
db.factory('db', function(mongoose, conf) { | ||
if(conf.env == 'test') | ||
mongoose.set('debug', true); | ||
return mongoose.createConnection(conf.mongo).once('open', function() { | ||
console.log('Mongoose connected'); | ||
}); | ||
}); | ||
``` | ||
You can also use objects for aliasing: | ||
```js | ||
var Beat = require('beat'); | ||
var db = module.exports = new Beat('db', ['mongoose', {conf:'../config.json'}]); | ||
db.factory('db', function(mongoose, conf) { | ||
if(conf.env == 'test') | ||
mongoose.set('debug', true); | ||
return mongoose.createConnection(conf.mongo).once('open', function() { | ||
console.log('Mongoose connected'); | ||
}); | ||
}); | ||
``` | ||
_file paths starting with `/` will be relative to process cwd._ | ||
If the required module provides a Beat object, their properties will be mixed with local ones: | ||
```js | ||
var Beat = require('beat'); | ||
var routes = module.exports = new Beat('routes', [ | ||
'/lib/middlewares', | ||
'/lib/models', | ||
'/lib/app' | ||
]); | ||
routes.factory('routes', function routes(app, authMiddleware, ProductsModel){ | ||
app.all('/api/*', authMiddleware); | ||
app.get('/api/products/:id', function show(req, res) { | ||
ProductsModel.findById(req.params.id, function(err, doc) { | ||
res.send(err?400:(doc||404)); | ||
}); | ||
}); | ||
}); | ||
``` |
var Beat = require('../../'); | ||
describe('Beat', function(){ | ||
it('should be an construtor', function(){ | ||
expect(Beat).to.be.an('function'); | ||
it('should be an construtor', function(){ | ||
expect(Beat).to.be.an('function'); | ||
}); | ||
describe('instance dependencies', function(){ | ||
it('can be a reference to a beat module', function(){ | ||
var testValue = {}; | ||
var beatA = new Beat('a'); | ||
beatA.value('x', testValue); | ||
Beat.prototype._require = function(path){ | ||
if(path == 'beatA') return beatA; | ||
}; | ||
var beatB = new Beat('b', ['beatA']); | ||
expect(beatB.get('x')).to.be.equal(testValue); | ||
}); | ||
describe('instance', function(){ | ||
var beat; | ||
beforeEach(function(){ | ||
beat = new Beat(); | ||
}); | ||
// basics | ||
it('.run method should recieve and execute a function', function(){ | ||
var spy = sinon.spy(); | ||
beat.run(spy); | ||
expect(spy.calledOnce).to.be.equal(true); | ||
expect(spy.calledOn(beat)).to.be.equal(true); | ||
expect(spy.calledWith()).to.be.equal(true); | ||
}); | ||
it('.value method should set a value to be obtained by .get', function(){ | ||
var mv = {}; | ||
beat.value('myValue', mv); | ||
expect(beat.get('myValue')).to.be.equal(mv); | ||
}); | ||
it('.factory method should set a factory function of a value', function(){ | ||
var mv = {}; | ||
var stub = sinon.stub().returns(mv); | ||
beat.factory('myValue', stub); | ||
expect(beat.get('myValue')).to.be.equal(mv); | ||
expect(stub.calledOn(beat)).to.be.equal(true); | ||
expect(stub.calledWith()).to.be.equal(true); | ||
}); | ||
it('factories should be called once', function(){ | ||
var stub = sinon.stub().returns({}); | ||
beat.factory('fValue', stub); | ||
beat.get('fValue'); | ||
beat.get('fValue'); | ||
expect(stub.calledOnce).to.be.equal(true); | ||
}); | ||
it('.run method should be able to require values, including fabricated ones', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.run(function(myFabricatedValue, myValue){ | ||
expect(myFabricatedValue).to.be.equal(y); | ||
expect(myValue).to.be.equal(x); | ||
done(); | ||
}); | ||
}); | ||
it('factories should be able to require values, including fabricated ones', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
var z = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.factory('test', function(myFabricatedValue, myValue){ | ||
expect(myFabricatedValue).to.be.equal(y); | ||
expect(myValue).to.be.equal(x); | ||
return z; | ||
}); | ||
beat.run(function(test){ | ||
expect(test).to.be.equal(z); | ||
done(); | ||
}); | ||
}); | ||
it('can be a reference to any other module', function(){ | ||
var testValue = {}; | ||
Beat.prototype._require = function(path){ | ||
if(path == 'anyModule') return testValue; | ||
}; | ||
var beat = new Beat('myBeat', ['anyModule']); | ||
expect(beat.get('anyModule')).to.be.equal(testValue); | ||
}); | ||
it('can be a reference to any other module with an alias', function(){ | ||
var testValue = {}; | ||
Beat.prototype._require = function(path){ | ||
if(path == 'anyModule') return testValue; | ||
}; | ||
var beat = new Beat('myBeat', [{yes: 'anyModule'}]); | ||
expect(beat.get('yes')).to.be.equal(testValue); | ||
}); | ||
it('can be file paths rooted as the process', function(){ | ||
var testValue = {}; | ||
var beatA = new Beat('a', [{pkg: '/package'}]); | ||
Beat.prototype._require = function(path){ | ||
expect(path).to.be.equal(process.cwd()+'/package'); | ||
return testValue; | ||
}; | ||
expect(beatA.get('pkg')).to.be.equal(testValue); | ||
}); | ||
}); | ||
describe('instance', function(){ | ||
var beat; | ||
beforeEach(function(){ | ||
beat = new Beat(); | ||
}); | ||
// basics | ||
it('.run method should recieve and execute a function', function(){ | ||
var spy = sinon.spy(); | ||
beat.run(spy); | ||
expect(spy.calledOnce).to.be.equal(true); | ||
expect(spy.calledOn(beat)).to.be.equal(true); | ||
expect(spy.calledWith()).to.be.equal(true); | ||
}); | ||
it('.value method should set a value to be obtained by .get', function(){ | ||
var mv = {}; | ||
beat.value('myValue', mv); | ||
expect(beat.get('myValue')).to.be.equal(mv); | ||
}); | ||
it('.factory method should set a factory function of a value', function(){ | ||
var mv = {}; | ||
var stub = sinon.stub().returns(mv); | ||
beat.factory('myValue', stub); | ||
expect(beat.get('myValue')).to.be.equal(mv); | ||
expect(stub.calledOn(beat)).to.be.equal(true); | ||
expect(stub.calledWith()).to.be.equal(true); | ||
}); | ||
it('factories should be called once', function(){ | ||
var stub = sinon.stub().returns({}); | ||
beat.factory('fValue', stub); | ||
beat.get('fValue'); | ||
beat.get('fValue'); | ||
expect(stub.calledOnce).to.be.equal(true); | ||
}); | ||
it('.run method should be able to require values, including fabricated ones', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.run(function(myFabricatedValue, myValue){ | ||
expect(myFabricatedValue).to.be.equal(y); | ||
expect(myValue).to.be.equal(x); | ||
done(); | ||
}); | ||
}); | ||
it('factories should be able to require values, including fabricated ones', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
var z = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.factory('test', function(myFabricatedValue, myValue){ | ||
expect(myFabricatedValue).to.be.equal(y); | ||
expect(myValue).to.be.equal(x); | ||
return z; | ||
}); | ||
beat.run(function(test){ | ||
expect(test).to.be.equal(z); | ||
done(); | ||
}); | ||
}); | ||
// chainability | ||
it('.run method should be chainable', function(){ | ||
expect(beat.run(function(){})).to.be.equal(beat); | ||
}); | ||
it('.value method should be chainable', function(){ | ||
expect(beat.value('myValue')).to.be.equal(beat); | ||
}); | ||
it('.factory method should be chainable', function(){ | ||
expect(beat.factory('myValue', function(){})).to.be.equal(beat); | ||
}); | ||
// array declaration | ||
it('.run method should be able to deal with an array for declaring dependencies', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.run(['myFabricatedValue', 'myValue', function(a, b){ | ||
expect(a).to.be.equal(y); | ||
expect(b).to.be.equal(x); | ||
done(); | ||
}]); | ||
}); | ||
it('.factory method should be able to deal with an array for declaring dependencies', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.factory('z', ['myFabricatedValue', 'myValue', function(a, b){ | ||
expect(a).to.be.equal(y); | ||
expect(b).to.be.equal(x); | ||
done(); | ||
}]); | ||
beat.get('z'); | ||
}); | ||
// comment aliases | ||
it('.run method should be able to deal with comments for declaring dependencies', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.run(function(/* myFabricatedValue */ a, /* myValue */ b){ | ||
expect(a).to.be.equal(y); | ||
expect(b).to.be.equal(x); | ||
done(); | ||
}); | ||
}); | ||
it('.factory method should be able to deal with comments for declaring dependencies', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.factory('z', function(/* myFabricatedValue */ a, /* myValue */ b){ | ||
expect(a).to.be.equal(y); | ||
expect(b).to.be.equal(x); | ||
done(); | ||
}); | ||
beat.get('z'); | ||
}); | ||
it('.load method should import properties from a beat instance', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
var anotherBeat = new Beat('another'); | ||
anotherBeat.value('myValueX', x); | ||
anotherBeat.value('myValueY', y); | ||
beat.load(anotherBeat); | ||
beat.run(function(myValueX, myValueY){ | ||
expect(myValueX).to.be.equal(x); | ||
expect(myValueY).to.be.equal(y); | ||
done(); | ||
}); | ||
}); | ||
it('.load method should import factories from a beat instance', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
var anotherBeat = new Beat('another'); | ||
anotherBeat.factory('myValueX', function(){return x;}); | ||
anotherBeat.factory('myValueY', function(){return y;}); | ||
beat.load(anotherBeat); | ||
beat.run(function(myValueX, myValueY){ | ||
expect(myValueX).to.be.equal(x); | ||
expect(myValueY).to.be.equal(y); | ||
done(); | ||
}); | ||
}); | ||
it('.load method should import factories with dependencies from a beat instance', function(done){ | ||
var x = {}; | ||
var anotherBeat = new Beat('another'); | ||
anotherBeat.factory('generateX', function(X){return X;}); | ||
anotherBeat.value('X', x); | ||
beat.load(anotherBeat); | ||
beat.run(function(generateX){ | ||
expect(generateX).to.be.equal(x); | ||
done(); | ||
}); | ||
}); | ||
// chainability | ||
it('.run method should be chainable', function(){ | ||
expect(beat.run(function(){})).to.be.equal(beat); | ||
}); | ||
it('.value method should be chainable', function(){ | ||
expect(beat.value('myValue')).to.be.equal(beat); | ||
}); | ||
it('.factory method should be chainable', function(){ | ||
expect(beat.factory('myValue', function(){})).to.be.equal(beat); | ||
}); | ||
// array declaration | ||
it('.run method should be able to deal with an array for declaring dependencies', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.run(['myFabricatedValue', 'myValue', function(a, b){ | ||
expect(a).to.be.equal(y); | ||
expect(b).to.be.equal(x); | ||
done(); | ||
}]); | ||
}); | ||
it('.factory method should be able to deal with an array for declaring dependencies', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.factory('z', ['myFabricatedValue', 'myValue', function(a, b){ | ||
expect(a).to.be.equal(y); | ||
expect(b).to.be.equal(x); | ||
done(); | ||
}]); | ||
beat.get('z'); | ||
}); | ||
// comment aliases | ||
it('.run method should be able to deal with comments for declaring dependencies', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.run(function(/* myFabricatedValue */ a, /* myValue */ b){ | ||
expect(a).to.be.equal(y); | ||
expect(b).to.be.equal(x); | ||
done(); | ||
}); | ||
}); | ||
it('.factory method should be able to deal with comments for declaring dependencies', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
beat.value('myValue', x); | ||
beat.factory('myFabricatedValue', sinon.stub().returns(y)); | ||
beat.factory('z', function(/* myFabricatedValue */ a, /* myValue */ b){ | ||
expect(a).to.be.equal(y); | ||
expect(b).to.be.equal(x); | ||
done(); | ||
}); | ||
beat.get('z'); | ||
}); | ||
it('.load method should import properties from a beat instance', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
var anotherBeat = new Beat('another'); | ||
anotherBeat.value('myValueX', x); | ||
anotherBeat.value('myValueY', y); | ||
beat.load(anotherBeat); | ||
beat.run(function(myValueX, myValueY){ | ||
expect(myValueX).to.be.equal(x); | ||
expect(myValueY).to.be.equal(y); | ||
done(); | ||
}); | ||
}); | ||
it('.load method should import factories from a beat instance', function(done){ | ||
var x = {}; | ||
var y = {}; | ||
var anotherBeat = new Beat('another'); | ||
anotherBeat.factory('myValueX', function(){return x;}); | ||
anotherBeat.factory('myValueY', function(){return y;}); | ||
beat.load(anotherBeat); | ||
beat.run(function(myValueX, myValueY){ | ||
expect(myValueX).to.be.equal(x); | ||
expect(myValueY).to.be.equal(y); | ||
done(); | ||
}); | ||
}); | ||
it('.load method should import factories with dependencies from a beat instance', function(done){ | ||
var x = {}; | ||
var anotherBeat = new Beat('another'); | ||
anotherBeat.factory('generateX', function(X){return X;}); | ||
anotherBeat.value('X', x); | ||
beat.load(anotherBeat); | ||
beat.run(function(generateX){ | ||
expect(generateX).to.be.equal(x); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
16187
310
197
2