Comparing version 1.0.0 to 1.1.0
var getArgs = require('typedef').getArguments; | ||
var getName = require('typedef').getName; | ||
var _ = require('underscore'); | ||
@@ -7,5 +8,5 @@ | ||
/** | ||
* Basic dependency resolver that allows for the registering of types and | ||
* factories via callback functions, as well as auto-guessing constructor | ||
* injecting via parameter names | ||
* Basic dependency resolver / IoC container that allows for the registering of | ||
* types and factories via callback functions, as well as auto-guessing | ||
* constructor injecting via parameter names. | ||
* @constructor | ||
@@ -16,4 +17,18 @@ */ | ||
this._types = {}; | ||
this.register('container', this); | ||
} | ||
this.register('resolver', this); | ||
/** | ||
* @param {Function=} T | ||
* @param {Function=} closure | ||
* @param {boolean=} shared | ||
* @param {Object=} instance | ||
*/ | ||
function Entry(T, closure, deps, shared, instance) | ||
{ | ||
this.T = T || null; | ||
this.closure = closure || null; | ||
this.deps = deps || []; | ||
this.shared = shared !== undefined ? shared : false; | ||
this.instance = instance || null; | ||
} | ||
@@ -26,25 +41,60 @@ | ||
* @param {boolean=} shared If true, this dep will only be instantiated once (a | ||
* la singleton) | ||
* la singleton). | ||
*/ | ||
Container.prototype.register = function(name, T, shared) | ||
{ | ||
if (this._types[name]) | ||
throw new Error( | ||
'Already registered dependency: "' + name + '"'); | ||
if (T instanceof Function) | ||
return this.registerConstructor(name, T, shared); | ||
else | ||
return this.registerInstance(name, T, shared); | ||
}; | ||
// Determine if there are any other deps in here | ||
var deps = getArgs(T); | ||
/** | ||
* @param {String} name The name we want to register this dependency under. | ||
* @param {Object} instance The object we want returned every time we resolve | ||
* this dependency. | ||
*/ | ||
Container.prototype.registerInstance = function(name, instance) | ||
{ | ||
this._types[name] = new Entry( | ||
null, null, null, true, instance); | ||
}; | ||
this._types[name] = { | ||
type: T, | ||
deps: deps, | ||
shared: !!shared, | ||
instance: null | ||
}; | ||
/** | ||
* @param {String} name The name we want to register this dependency under. | ||
* @param {Function} closure A callback function we want to call when creating | ||
* this dependency. | ||
* @param {boolean=} shared If true, this dependency will only be instantiated | ||
* once (a la singleton). | ||
*/ | ||
Container.prototype.registerClosure = function(name, closure, shared) | ||
{ | ||
this._types[name] = new Entry( | ||
null, closure, getArgs(closure), !!shared, null); | ||
}; | ||
/** | ||
* @param {Function} T | ||
* @param {Function} factory | ||
*/ | ||
Container.prototype.registerFactory = function(T, factory) | ||
{ | ||
this._types['$$factory_function$$' + getName(T)] = new Entry( | ||
T, factory, getArgs(factory), false, null); | ||
}; | ||
/** | ||
* @param {String} name The name we want to register this dependency under. | ||
* @param {boolean=} shared If true, this dependency will only be instantiated | ||
* once (a la singleton). | ||
*/ | ||
Container.prototype.registerConstructor = function(name, T, shared) | ||
{ | ||
this._types[name] = new Entry( | ||
T, null, getArgs(T), !!shared, null); | ||
}; | ||
/** | ||
* Register a singleton (instantiated-once) class constructor dependency into | ||
* this resolver. | ||
* @param {String} name The name we want register something under. | ||
* this container. | ||
* @param {Function} T The class constructor of the type we want to register. | ||
@@ -58,12 +108,28 @@ */ | ||
/** | ||
* @param {String} name Abstract name. | ||
* @return {bool} True if a type is registered already. | ||
* @param {Function} T Constructor function we want to find the factory for. | ||
* @return {Object} The dependency. | ||
*/ | ||
Container.prototype.isRegistered = function(name) | ||
Container.prototype.factory = function(T) | ||
{ | ||
return !!this._types[name]; | ||
for (var n in this._types) { | ||
var entry = this._types[n]; | ||
if (entry.T !== T || !entry.closure) continue; | ||
var deps = this._resolveDeps(entry.deps); | ||
return entry.closure.call(null, deps); | ||
} | ||
return this.build(T); | ||
}; | ||
/** | ||
* Create an object while resolving any dependencies we have have | ||
* @param {Function} T | ||
* @return {Object} The dependency. | ||
*/ | ||
Container.prototype.build = function(T) | ||
{ | ||
var deps = this._resolveDeps(getArgs(T)); | ||
return callNew(T, deps); | ||
}; | ||
/** | ||
* @param {String} name The string tag of the dependency we want to create. | ||
@@ -74,52 +140,49 @@ * @return {Object} The dependency. | ||
{ | ||
var deps; | ||
var _this = this; | ||
// Use parameter names of the function to bring in more deps | ||
if (name instanceof Function) { | ||
var T = name; | ||
deps = _(getArgs(T)).map(function(d) { return _this.make(d); }); | ||
return callNew(T, deps); | ||
return this.factory(name); | ||
} else if (!_(name).isString()) { | ||
return name; | ||
} | ||
// Nop if we are passing in something weird | ||
if (!_(name).isString()) | ||
return name; | ||
var entry = this._resolve(name); | ||
var info = this._resolve(name); | ||
// Short circuit if we've already got our | ||
if (entry.shared && entry.instance) { | ||
return entry.instance; | ||
} | ||
// If we have just a normal thing, return it | ||
if (!(info.type instanceof Function)) | ||
return info.type; | ||
// Resolve all of the expressed dependencies | ||
var deps = this._resolveDeps(entry.deps); | ||
// Resolve all string deps into make deps | ||
// Create | ||
var instance; | ||
if (!info.shared || !info.instance) { | ||
deps = _(info.deps).map(function(d) { return _this.make(d); }); | ||
instance = callNew(info.type, deps); | ||
// Stash singleton | ||
if (info.shared) | ||
info.instance = instance; | ||
if (entry.T) { | ||
instance = callNew(entry.T, deps); | ||
} else { | ||
instance = info.instance; | ||
instance = entry.closure.call(null, deps); | ||
} | ||
// Stash singleton | ||
if (entry.shared) | ||
entry.instance = instance; | ||
return instance; | ||
}; | ||
// Give us a way to instantiate a new class with an array of args | ||
function callNew(T, args) | ||
/** | ||
* Resolve a list of string dependencies into real dependencies | ||
* @param {Array.<String>} deps | ||
* @return {Array.<Object>} | ||
* @private | ||
*/ | ||
Container.prototype._resolveDeps = function(deps) | ||
{ | ||
function F() { return T.apply(this, args); } | ||
F.prototype = T.prototype; | ||
return new F(); | ||
} | ||
var _this = this; | ||
return _(deps).map(function(d) { return _this.make(d); }); | ||
}; | ||
/** | ||
* Resolve the name of a dep | ||
* @param {String} name The dep name. | ||
* @return {{type: Function, deps: Array.<String>}} Type T. | ||
* Resolve information about a dependency. | ||
* @param {String} name The dependency name. | ||
* @return {Entry} Information about this named dependency. | ||
* @private | ||
@@ -136,1 +199,9 @@ */ | ||
// Give us a way to instantiate a new class with an array of args | ||
function callNew(T, args) | ||
{ | ||
function F() { return T.apply(this, args); } | ||
F.prototype = T.prototype; | ||
return new F(); | ||
} | ||
{ | ||
"name": "sack", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Inversion-of-control container for all your dependency injection needs.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -19,3 +19,3 @@ # Sack | ||
## Rationale | ||
## Introduction | ||
@@ -33,29 +33,47 @@ This is a simple *Inversion of Control Container*. It provides the mechanism | ||
The functionality of Sack lies within its `Container` class. | ||
Dependencies are all managed via a `Container` instance: | ||
### Registering Objects | ||
Register a dependency with a container with a stringly-typed tag and either a | ||
constructor, instance, or closure. | ||
```javascript | ||
var Container = require('sack').Container; | ||
... | ||
var container = new Container(); | ||
``` | ||
// Create a new instance every request: | ||
### Registering Objects | ||
Register a class constructor that will get executed every time the dependency | ||
is resolved, creating a new instance: | ||
```javascript | ||
container.register('service', MyService); | ||
``` | ||
// Create a new instance on first request, then reuse (singleton-esque): | ||
Register a constructor function that will get executed *one time* when the | ||
first time a dependency is resolved, and then re-used after that (singleton): | ||
```javascript | ||
container.shared('service', MyService); | ||
``` | ||
// Use an existing instance: | ||
container.register('service', new Service()); | ||
Register an existing object instance as a dependency: | ||
// Have a (lazily evaluated) callback provide the dependency every request | ||
```javascript | ||
container.register('service', someService); | ||
``` | ||
Register a (lazily evaluated) callback to provide the dependency on every | ||
request: | ||
```javascript | ||
container.register('service', function() { | ||
return new MyService(); | ||
}); | ||
``` | ||
// Use a callback to provide the dependency, but only once (singleton): | ||
Registered a callback to provide the dependency the first time it is requested, | ||
and then re-use it all subsequent times (singleton via callback): | ||
```javascript | ||
container.shared('service', function() { | ||
@@ -67,2 +85,12 @@ return new MyService(); | ||
Register a factory callback for a specific class constructor. This is useful | ||
for allowing objects to create several of another object without explictly | ||
knowing how: | ||
```javascript | ||
container.registerFactory(GameEntity, function(pool) { | ||
return pool.aquire(GameEntity); | ||
}); | ||
``` | ||
### Resolving Objects | ||
@@ -69,0 +97,0 @@ |
var test = require('tape'); | ||
var Resolver = require('../lib/Container.js'); | ||
var Container = require('../lib/Container.js'); | ||
@@ -7,3 +7,3 @@ test('Basic single dep constructor registration', function(t) { | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
var x = 't'; | ||
@@ -17,20 +17,6 @@ var T = function T() {}; | ||
test('Register throws', function(t) { | ||
t.plan(1); | ||
var r = new Resolver(); | ||
var x = 't'; | ||
var T = function T() {}; | ||
r.register(x, T); | ||
t.throws(function() { | ||
r.register(x, T); | ||
}, 'Dupe on register'); | ||
}); | ||
test('Resolve method throws', function(t) { | ||
t.plan(1); | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
@@ -43,5 +29,5 @@ t.throws(function() { | ||
test('Basic dep track', function(t) { | ||
t.plan(5); | ||
t.plan(5); | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
var A = function() { t.ok(true, 'A ctor fired'); }; | ||
@@ -66,3 +52,3 @@ var B = function() { t.ok(true, 'B ctor fired'); }; | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
r.register('T', function(A, B, C) {}); | ||
@@ -78,3 +64,3 @@ t.throws(function() { | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
function T(a) { | ||
@@ -93,3 +79,3 @@ t.strictEqual(true, a instanceof A, 'a passed in'); | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
function T() { t.ok(true, 'T ctor fired'); } | ||
@@ -105,3 +91,3 @@ r.shared('t', T); | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
function T(a) { | ||
@@ -126,3 +112,3 @@ t.ok(true, 'T ctor called'); | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
var weird = {}; | ||
@@ -137,3 +123,3 @@ | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
var DEP = {}; | ||
@@ -167,3 +153,3 @@ var B_DEP = {}; | ||
var r = new Resolver(); | ||
var r = new Container(); | ||
r.register('a', thing); | ||
@@ -174,1 +160,28 @@ t.strictEqual(thing, r.make('a'), 'making returns instance'); | ||
}); | ||
test('Self register', function(t) { | ||
t.plan(2); | ||
var c = new Container(); | ||
t.strictEqual(c.make('container'), c, 'got em'); | ||
t.strictEqual(c.make('container'), c, 'got em'); | ||
}); | ||
test('Factory style', function(t) { | ||
t.plan(4); | ||
function M() { } | ||
var LOL = {}; | ||
var container = new Container(); | ||
container.registerFactory(M, function() { | ||
t.pass('factory fired'); | ||
return LOL; | ||
}); | ||
t.strictEqual(container.factory(M), LOL, 'made it'); // 2x | ||
t.strictEqual(container.factory(M), LOL, 'made it'); // 2x | ||
}); |
15213
11
304
156