Created
December 4, 2015 20:16
-
-
Save wkf/2b204e967f0b7e469d30 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
var _ = require('lodash'); | |
var Promise = require('bluebird'); | |
var Immutable = require('immutable'); | |
function Component() { | |
return class extends Immutable.Record.apply(this, arguments) { | |
start() { | |
console.log(this.constructor.name + "::start"); | |
return Promise.resolve(this); | |
} | |
stop() { | |
console.log(this.constructor.name + "::stop"); | |
return Promise.resolve(this); | |
} | |
}; | |
} | |
function validate(components, dependencies) { | |
let visit = _.memoize((node, visited) => { | |
if (!components[node]) { | |
throw new Error("Unknown component: " + node); | |
} else { | |
return _.every(dependencies[node] || {}, (v, k) => { | |
if (visited.has(k)) { | |
throw new Error("Cycle detected: " + visited.add(node).join(" => ") + " => " + k); | |
} else { | |
return visit(k, visited.add(node)); | |
} | |
}); | |
}; | |
}); | |
return _.every(components, (v, k) => visit(k, Immutable.OrderedSet())); | |
} | |
function normalizeDependencies(ds) { | |
return _.mapValues(ds, v => _.isArray(v) ? _.zipObject(v, v) : v); | |
} | |
function invertDependencies(ds) { | |
return _.merge.apply( | |
_, | |
_.map(ds, (v, k) => _.zipObject( _.map(v, (vv, kk) => [vv, {[k]: k}])))); | |
} | |
function postwalk(components, dependencies, f) { | |
let visit = _.memoize((node) => { | |
return Promise.props(_.mapValues(dependencies[node] || {}, visit)).then(ds => f(components[node], ds, node)); | |
}); | |
return Promise.try(() => _.mapValues(components, (v, k) => visit(k))).props(); | |
} | |
function prewalk(components, dependencies, f) { | |
return postwalk(components, invertDependencies(dependencies), (c, ds, node) => f(c, dependencies[node], node)); | |
} | |
class System extends Component({ | |
components: Immutable.Map({}), | |
dependencies: Immutable.Map({}) | |
}) { | |
start() { | |
let self = this; | |
return postwalk(this.components, this.dependencies, (c, ds) => c.merge(ds).start()) | |
.then(cs => { | |
console.log("System::start"); | |
return self.set("components", cs); | |
}); | |
} | |
stop() { | |
let self = this; | |
console.log("System::stop"); | |
return prewalk(this.components, this.dependencies, (c, ds) => { | |
return c | |
.stop() | |
.then(c => _.reduce(ds, (c, d) => c.remove(d), c)); | |
}).then(cs => self.set("components", cs)); | |
} | |
} | |
function system(components, dependencies) { | |
let ds = normalizeDependencies(dependencies); | |
if(validate(components, ds)) { | |
return new System({ | |
components: components, | |
dependencies: ds | |
}); | |
} else { | |
throw new Error("Invalid system: " + components + "\n" + dependencies); | |
} | |
}; | |
module.exports = { | |
system: system, | |
System: System, | |
Component: Component | |
}; | |
// Testing | |
class ComponentA extends Component({c:null}) {} | |
class ComponentB extends Component({a: null, c: null}) {} | |
class ComponentC extends Component({}) {} | |
let components = { | |
"a": new ComponentA(), | |
"b": new ComponentB(), | |
"c": new ComponentC() | |
}; | |
let dependencies = { | |
"a": ["c"], | |
"b": ["a", "c"] | |
}; | |
system(components, dependencies) | |
.start() | |
.then(s => s.stop()) | |
.then(console.log); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment