Skip to content

Instantly share code, notes, and snippets.

@wkf
Created December 4, 2015 20:16
Show Gist options
  • Save wkf/2b204e967f0b7e469d30 to your computer and use it in GitHub Desktop.
Save wkf/2b204e967f0b7e469d30 to your computer and use it in GitHub Desktop.
"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