Comparing version 0.0.2 to 0.1.0
302
index.js
@@ -1,301 +0,7 @@ | ||
var parseFnArgs = require('parse-fn-args') | ||
var util = require('./lib/util') | ||
module.exports = Container | ||
exports = module.exports = require('./lib/container') | ||
function Container () { | ||
if (!(this instanceof Container)) { | ||
return new Container | ||
} | ||
} | ||
exports.nsconcat = util.nsconcat | ||
function ThisObj (name) { | ||
return function thisObj () { | ||
if (this[name].__owner === this) return this[name] | ||
this[name] = Object.create(thisObj.call(this.__proto__)) | ||
setOwner(this, name) | ||
return this[name] | ||
} | ||
} | ||
function setOwner (obj, prop) { | ||
Object.defineProperty(obj[prop], '__owner', { | ||
value: obj | ||
}) | ||
} | ||
Container.prototype.use = function (plugin) { | ||
var fn = plugin | ||
arguments[0] = this | ||
fn.apply(this, arguments) | ||
return this | ||
} | ||
Container.prototype.values = {} | ||
setOwner(Container.prototype, 'values') | ||
Container.prototype.thisValues = ThisObj('values') | ||
Container.prototype.tasks = {} | ||
setOwner(Container.prototype, 'tasks') | ||
Container.prototype.thisTasks = ThisObj('tasks') | ||
Container.prototype.aliases = {} | ||
setOwner(Container.prototype, 'aliases') | ||
Container.prototype.thisAliases = ThisObj('aliases') | ||
Container.prototype.set = function (name, val) { | ||
this.thisValues()[name] = val | ||
return this | ||
} | ||
Container.prototype.get = function (name) { | ||
var val = this.values[name] | ||
if (val !== undefined) return val | ||
if (this.aliases[name]) return this.get(this.aliases[name]) | ||
} | ||
Container.prototype.importing = function () { | ||
return this | ||
} | ||
Container.prototype.alias = function (from, to) { | ||
this.thisAliases()[from] = to | ||
return this | ||
} | ||
Container.prototype.def = function (layer, task, deps, fn) { | ||
if (typeof task != 'string') { // allow layer omission | ||
fn = deps | ||
deps = task | ||
task = layer | ||
layer = null | ||
} | ||
if (typeof deps == 'function') { // allow implicit deps | ||
fn = deps | ||
deps = fn.deps || parseFnArgs(fn) | ||
} | ||
this.thisValues()[task] = undefined | ||
this.thisTasks()[task] = { | ||
fn: fn, | ||
deps: deps, | ||
layer: layer || this._layer, | ||
name: task | ||
} | ||
return this | ||
} | ||
Container.prototype.at = function (layer, fn) { | ||
this._layer = layer | ||
try { | ||
fn.call(this, this) | ||
} finally { | ||
this._layer = null | ||
} | ||
return this | ||
} | ||
Container.prototype.install = function (namespace, app, aliases) { | ||
if (typeof namespace != 'string') { | ||
aliases = app | ||
app = namespace | ||
namespace = '' | ||
} else { | ||
namespace += '_' | ||
} | ||
function mix (target, src, cb) { | ||
cb = cb || function (val) { return val } | ||
for (var key in src) { | ||
target[namespace + key] = cb(src[key], key) | ||
} | ||
} | ||
mix(this.thisValues(), app.values) | ||
mix(this.thisTasks(), app.tasks, function (t) { | ||
return { | ||
fn: t.fn, | ||
layer: t.layer, | ||
deps: t.deps.map(function (dep) { | ||
return dep == 'done' || dep == 'eval' | ||
? dep | ||
: namespace + dep | ||
}), | ||
namespace: namespace + (t.namespace || '') | ||
} | ||
}) | ||
mix(this.thisAliases(), app.aliases, function (alias) { | ||
return namespace + alias | ||
}) | ||
for (var key in aliases) { | ||
this.alias(namespace + key, aliases[key] == '*' ? key : aliases[key]) | ||
} | ||
return this | ||
} | ||
Container.prototype.layer = function (name) { | ||
this.name = name | ||
return this | ||
} | ||
Container.prototype.run = function () { | ||
return Object.create(this) | ||
} | ||
Container.prototype.eval = function (task, cb) { | ||
var val = this.values[task] | ||
if (val !== undefined) { | ||
if (cb) val instanceof Error | ||
? cb.call(this, val) | ||
: cb.call(this, null, val) | ||
return | ||
} | ||
if (this.aliases[task]) { | ||
this.eval(this.aliases[task], function (err, val) { | ||
this.set(task, err || val) | ||
cb.call(this, err, val) | ||
}) | ||
return | ||
} | ||
var ev = '_eval_' + task | ||
if (!this[ev]) { | ||
var t = this.tasks[task] | ||
if (!t) return cb && cb(new Error('Task ' + task + ' is not defined')) | ||
new Evaluation(this, cb) | ||
.task(task, t) | ||
.start() | ||
} else { | ||
cb && this[ev].ondone(cb) | ||
} | ||
} | ||
function Evaluation (container, cb) { | ||
this.c = container | ||
this.callbacks = [] | ||
this.deps = [] | ||
cb && this.ondone(cb) | ||
} | ||
Evaluation.prototype.ondone = function (cb) { | ||
this.callbacks.push(cb) | ||
} | ||
Evaluation.prototype.task = function (name, def) { | ||
this.t = def | ||
this.name = name | ||
this.setApp() | ||
this.app['_eval_' + this.name] = this | ||
return this | ||
} | ||
Evaluation.prototype.setApp = function () { | ||
if (!this.t.layer) return this.app = this.c | ||
var app = this.c | ||
while (app.name && (app.name != this.t.layer || !app.hasOwnProperty('name'))) { | ||
app = app.__proto__ | ||
} | ||
this.app = app.name == this.t.layer ? app : this.c | ||
} | ||
Evaluation.prototype.start = function () { | ||
this.evalDeps(0) | ||
} | ||
Evaluation.prototype.evalDeps = function (index) { | ||
var sync = true | ||
, deps = this.t.deps | ||
, val | ||
while (sync) { | ||
var dep = deps[index] | ||
if (!dep) return this.exec() | ||
if (dep == 'done') { | ||
this.async = true | ||
this.deps[index++] = this.done.bind(this) | ||
continue | ||
} | ||
if (dep == 'eval') { | ||
this.deps[index++] = function (task, cb) { | ||
var name = this.t.namespace ? this.t.namespace + task : task | ||
this.app.eval(name, cb) | ||
}.bind(this) | ||
continue | ||
} | ||
val = this.app.values[dep] | ||
if (val !== undefined) { | ||
if (val instanceof Error) return this.done(val) | ||
this.deps[index++] = val | ||
continue | ||
} | ||
var done = false | ||
this.app.eval(dep, function (err, val) { | ||
if (err) return this.done(err) | ||
done = true | ||
this.deps[index++] = val | ||
if (sync) return | ||
this.evalDeps(index) | ||
}.bind(this)) | ||
sync = done | ||
} | ||
} | ||
Evaluation.prototype.exec = function () { | ||
try { | ||
if (this.async) { | ||
this.t.fn.apply(this.app, this.deps) | ||
} else { | ||
this.done(null, this.t.fn.apply(this.app, this.deps)) | ||
} | ||
} catch (e) { | ||
this.done(e) | ||
} | ||
} | ||
Evaluation.prototype.done = function (err, val) { | ||
if (this.ended) { | ||
console.error( | ||
this.name | ||
? 'Task <' + this.name + '> called its callback twice' | ||
: 'Some evaluation called its callback twice' | ||
) | ||
if (err) { | ||
console.error('Perhaps it happened because of exception in a task callback:') | ||
err.stack ? console.error(err.stack) : console.error(String(err)) | ||
} | ||
return | ||
} | ||
this.ended = true | ||
if (err != null) { | ||
if (!(err instanceof Error)) { | ||
err = new Error(String(err)) | ||
} | ||
err.task = err.task || this.name | ||
val = err | ||
} else { | ||
val = val === undefined ? null : val | ||
} | ||
this.app.set(this.name, val) | ||
this.app['_eval_' + this.name] = null // cleanup | ||
for (var i = 0; i < this.callbacks.length; i++) { | ||
this.callbacks[i].call(this.c, err, val) | ||
} | ||
} | ||
exports.nssuffix = util.nssuffix |
{ | ||
"name": "easy-app", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"description": "Simple and powerful container for applications", | ||
@@ -22,4 +22,5 @@ "author": "Eldar Gabdullin <eldargab@gmail.com>", | ||
"should": "*", | ||
"test-log": "*" | ||
"test-log": "*", | ||
"benchmark": "*" | ||
} | ||
} |
135
README.md
@@ -16,7 +16,7 @@ # easy-app | ||
app.def('baz', function (bar) { | ||
app.def('baz', function(bar) { | ||
return bar * 2 | ||
}) | ||
app.def('foo', function (bar, baz) { | ||
app.def('foo', function(bar, baz) { | ||
return bar + baz | ||
@@ -30,3 +30,3 @@ }) | ||
```javascript | ||
app.eval('foo', function (err, foo) { | ||
app.eval('foo', function(err, foo) { | ||
foo.should.equal(30) | ||
@@ -39,3 +39,3 @@ }) | ||
```javascript | ||
app.def('config', function (done) { | ||
app.def('config', function(done) { | ||
fs.readFile('config', done) | ||
@@ -49,3 +49,3 @@ }) | ||
```javascript | ||
app.def('foo', ['bar', 'baz'], function (bar, baz) { | ||
app.def('foo', ['bar', 'baz'], function(bar, baz) { | ||
return bar + baz | ||
@@ -67,6 +67,6 @@ }) | ||
// Not http_request, approval_request, etc | ||
subapp.def('req', function (bar, baz) {}) | ||
subapp.def('req', function(bar, baz) {}) | ||
// Specify missing tasks. | ||
subapp.importing( // currently noop | ||
subapp.importing( | ||
'bar', | ||
@@ -86,3 +86,3 @@ 'baz' | ||
```javascript | ||
app.def('super_req', ['super_bar', 'super_baz'], function (bar, baz) {}) | ||
app.def('super_req', ['super_bar', 'super_baz'], function(bar, baz) {}) | ||
``` | ||
@@ -106,6 +106,9 @@ | ||
'bar': 'bar', | ||
'baz': '*' // shortcut for 'baz':'baz' | ||
'baz': 'baz' | ||
}) | ||
``` | ||
But because `.install` setups aliases for undefined imports automatically | ||
we could stick with just `app.install('super', subapp)` | ||
## Layers | ||
@@ -118,15 +121,15 @@ | ||
app.layer('app') // mark current instance to be app level | ||
app.at('app', function () { | ||
app.def('config', function (done) { | ||
app.at('app', function() { | ||
app.def('config', function(done) { | ||
readJson('config.json', done) | ||
}) | ||
app.def('db', function (config) { | ||
app.def('db', function(config) { | ||
return require('monk')(config.connection_string) | ||
}) | ||
}) | ||
app.at('request', function () { | ||
app.def('session', function (db, req, done) { | ||
app.at('request', function() { | ||
app.def('session', function(db, req, done) { | ||
db.loadSession(req.cookie.session, done) | ||
}) | ||
app.def('user', function (db, session, done) { | ||
app.def('user', function(db, session, done) { | ||
db.loadUser(session.username, done) | ||
@@ -136,3 +139,3 @@ }) | ||
// ... | ||
http.createServer(function (req, res) { | ||
http.createServer(function(req, res) { | ||
app | ||
@@ -153,18 +156,28 @@ .run() // create next level instance | ||
```javascript | ||
app.def('level', 'task', function (a, b) {}) | ||
app.def('level', 'task', function(a, b) {}) | ||
``` | ||
## Misc | ||
## Notes | ||
### Error handling | ||
All task errors both sync and async are catched. In addition `err.task` | ||
property is set to the name of task which throwed an error. | ||
All task errors both sync and async are catched. In addition `err._task` | ||
property is set to the name of the task that throwed an error and `err._layer` is set | ||
to the name of the nearest named layer. | ||
### Control flow | ||
All tasks are executed sequentally one after another | ||
All tasks are executed sequentally one after another. Dependencies are evaluated | ||
from left to right. You can rely on that. | ||
### eval | ||
It is convenient to specify pre-task things as an additional dependency. For example: | ||
```javascript | ||
app.def('secretDocument', function(authorized, db) { | ||
return db.getSecret() | ||
}) | ||
``` | ||
### Evaluation of arbitrary task from within task | ||
There is another special case dependency called `eval`. No surprise that it is | ||
@@ -178,3 +191,3 @@ similar to `app.eval()` but can be used within task and works for subapp case | ||
.def('baz', baz) | ||
.def('exec', function (task, eval, done) { | ||
.def('exec', function(task, eval, done) { | ||
eval(task, done) | ||
@@ -190,2 +203,51 @@ }) | ||
## Doing things by convention | ||
Sometimes you want to do things that while accomplished with | ||
regular API require a lot of repetition. For example add security check for | ||
all tasks with a certain name pattern or define subapp dependency depending on | ||
it's namespace, etc. Eventually `.ontask()`, `.onsubapp()` hooks will be | ||
provided for doing such sort of things, but now only `.onsubapp()` is ready. | ||
```javascript | ||
// Hooks are just methods, not an events | ||
// .onsubapp() is called after subapp installation but before auto-aliasing | ||
app.onsubapp = function(ns, app) { | ||
// do your stuff here | ||
// e.g | ||
this.def(nsconcat(ns, 'foo'), createFoo(ns)) | ||
} | ||
``` | ||
In addition there is some reflection API you might find useful: | ||
```javascript | ||
app.importing('foo', 'bar') | ||
app.imports('foo') //=> true | ||
app.imports() //=> ['foo', 'bar'] | ||
``` | ||
```javascript | ||
app.set('foo', 'foo') | ||
app.alias('bar', 'foo') | ||
app.def('baz', baz) | ||
app.defined('foo') //=> true | ||
app.defined('bar') //=> true | ||
app.defined('baz') //=> true | ||
app.defined('qux') //=> false | ||
``` | ||
```javascript | ||
var nsconcat = require('easy-app').nsconcat | ||
var nssuffix = require('easy-app').nssuffix | ||
nsconcat('hello', 'world') //=> 'hello_world' | ||
nsconcat('', 'world') //=> 'world' | ||
nssuffix('hello', 'hello_world') //=> 'world' | ||
nssuffix('foo', 'hello_world') //=> null | ||
``` | ||
### .use() | ||
@@ -196,3 +258,3 @@ | ||
```javascript | ||
app.use(function plugin (container, param) { | ||
app.use(function plugin(container, param) { | ||
container.should.equal(app) | ||
@@ -208,19 +270,28 @@ this.should.equal(app) | ||
```shell | ||
``` | ||
npm install easy-app | ||
``` | ||
via component | ||
``` | ||
component install eldargab/easy-app | ||
``` | ||
## Related | ||
### Special thanks | ||
[make-flow](https://github.com/eldargab/make-flow) is an util with | ||
similar ideas but intended to be just a simple control flow util rather than a | ||
full blown container. | ||
[easy-web](https://github.com/eldargab/easy-web) is a new web framework under | ||
development on top of easy-app. Nearly ready but completely undocumented. | ||
Ping me if you seriously plan to use easy-app in that context and interested in | ||
some inspiration. | ||
## Special thanks | ||
This work intially inspired by | ||
[The-Kiln](https://github.com/straszheimjeffrey/The-Kiln) | ||
### make-flow | ||
[make-flow](https://github.com/eldargab/make-flow) is a util with | ||
similar ideas but intended to be just a simple control flow util rather than a | ||
full blown container. | ||
## License | ||
@@ -227,0 +298,0 @@ |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
22141
8
579
308
4
1