Computer JS
Simple computed properties.
Computer JS combines AMD-style input declaration, dependency injection, and the ability to use wildcard patterns on both input and output properties (one wildcard input per output). This allows your functions to have both active and passive dependencies, where active dependecies trigger a recomputation and passive ones just get pulled in as needed, which is great for computing accumulated values. What's more, if your computed property is accumulated (many to one, rather than one or many to many), then Computer JS will only compute that property once per cycle, regardless of how many properties matching the wildcard have actually changed value.
Per cycle? Yes. Computer JS is inherently lazy and won't do anything unless you tell it to. It has an input method to which you pass the names of any properties that have changed value. Internally, it checks to see if those properties are inputs to any of your computed props, and if so, marks those properties as dirty. Nothing actually gets computed until you call .compute(), at which point all the dirty props since the last compute get computed.
works with
- Node
- Browser
- AMD
install
with npm do:
$ npm install defx-computerwith bower do:
$ bower install defx-computerdescribe
var proto = {
name: 'defx'
},
computer = new Computer(proto);
computer.def('greeting', ['name'], function (name) {
return "hello " + name
})
computer.input('name')
/*======
compute all properties who have had one or more inputs
passed to computer.input since the last compute
======*/
computer.compute()
expect(proto.greeting).to.equal('hello defx')
proto.todos = [{
completed: false
}, {
completed: true
}, {
completed: false
}]
/*======
any properties from proto can be injected after your inputs
======*/
var called = 0;
var remaining = function (completed, todos) {
called++;
return proto.todos.reduce(function (acc, todo) {
return todo.completed ? acc : acc + 1
}, 0)
}
/*======
if the i/o pattern is many to one, then the property will only be computed once
per cycle, regardless of how many properties actually changed
======*/
computer.def('remaining', ['todos.*.completed'], remaining)
computer.input('todos.0.completed')
computer.input('todos.1.completed')
computer.input('todos.2.completed')
computer.compute()
expect(proto.remaining).to.equal(2)
expect(called).to.equal(1)
it("doesnt allow inputs terminated with wildcards", function(){
var fn = function(){
computer.def('cities.*.population', ['cities.*'], function (city) {
return 10000
})
}
expect(fn).to.throw('input cannot be terminated with a wildcard')
})
it("doesn't allow more than one wildcard input per property", function(){
var fn = function(){
computer.def('totalPopulation', ['towns.*.population', 'cities.*.population'], function (_, _, towns, cities) {
return "WTF?!"
})
}
expect(fn).to.throw('only one wildcard input per property')
})
it("does allow you to pass existing computed properties as inputs to new properties", function(){
var proto = {
cities: [
{
name: 'berlin',
population: 3502000
},
{
name: 'london',
population: 8308000
},
{
name: 'copenhagen',
population: 569557
}
],
towns: [
{
name: 'portree',
population: 2491
},
{
name: 'reine',
population: 1059
},
{
name: 'marsaxlokk',
population: 3499
}
]
}
var c = new Computer(proto)
c
.def('cityPopulation', ['cities.*.population'], function(_, cities){
return cities.reduce(function(pop, city){
return pop + city.population
}, 0)
})
.def('townPopulation', ['towns.*.population'], function(_, towns){
return towns.reduce(function(pop, town){
return pop + town.population
}, 0)
})
.def('totalPopulation', ['cityPopulation', 'townPopulation'], function(cityPop, townPop){
return cityPop + townPop
})
c.input('cities.0.population')
c.input('towns.0.population')
c.compute()
expect(proto.cityPopulation).to.equal(12379557)
expect(proto.townPopulation).to.equal(7049)
expect(proto.totalPopulation).to.equal(12386606)
})