Comparing version 1.3.0 to 2.0.0
# Change History | ||
## 2.0.0 (2014-04-13) | ||
* API change for registering (building pattern style). See README file. | ||
* Dependencies can now be registered as weak (can be overridden) or strong. | ||
* Container no longer registers itself as a dependency. | ||
* Removal of `call()` and `registerInjects()` (for now). | ||
## 1.3.0 (2014-02-22) | ||
@@ -4,0 +11,0 @@ |
module.exports = { | ||
Container: require('./lib/Container.js') | ||
}; |
module.exports = Container; | ||
var getArgs = require('typedef').getArguments; | ||
var getName = require('typedef').getName; | ||
var _ = require('underscore'); | ||
var getArguments = require('typedef').getArguments; | ||
var _ = require('underscore'); | ||
var IoCBinding = require('./IoCBinding.js'); | ||
/** | ||
* Basic dependency resolver / IoC container that allows for the registering of | ||
* types and factories via callback functions, as well as auto-guessing | ||
* constructor and function injecting via parameter names. | ||
* types and instances, as well as auto-guessing constructor and function | ||
* injecting via parameter names. | ||
* @constructor | ||
@@ -15,237 +15,60 @@ */ | ||
{ | ||
this._types = {}; | ||
// Injected post-instance | ||
this._injects = []; | ||
this.register('container', this); | ||
/** | ||
* @type {object.<string,IoCBinding>} | ||
*/ | ||
this._bindings = {}; | ||
} | ||
/** | ||
* @param {Function=} T | ||
* @param {Function=} closure | ||
* @param {boolean=} shared | ||
* @param {Object=} instance | ||
* Add a new dependency | ||
* @param {string} name | ||
* @param {Function|object} source | ||
* @return {IoCBinding} | ||
*/ | ||
function Entry(T, closure, deps, shared, instance) | ||
Container.prototype.register = function(name, source) | ||
{ | ||
this.T = T || null; | ||
this.closure = closure || null; | ||
this.deps = deps || []; | ||
this.shared = shared !== undefined ? shared : false; | ||
this.instance = instance || null; | ||
} | ||
var existing = this._bindings[name]; | ||
if (existing && !existing.isWeak()) | ||
throw new Error('Cannot override: ' + name); | ||
/** | ||
* Register a class constructor or object instance into the container under a | ||
* string tag. | ||
* @param {String} name The name we want register something under. | ||
* @param {Function} T The class constructor of the type we want to register. | ||
* @param {boolean=} shared If true, this dep will only be instantiated once (a | ||
* la singleton). | ||
*/ | ||
Container.prototype.register = function(name, T, shared) | ||
{ | ||
if (T instanceof Function) | ||
return this.registerConstructor(name, T, shared); | ||
else | ||
return this.registerInstance(name, T, shared); | ||
}; | ||
// Create the binding with a ref back to the building function | ||
var _this = this; | ||
var binding = new IoCBinding(name, source, function(b) { | ||
return _this._build(b._source, b.getDependencyNames()); | ||
}); | ||
/** | ||
* Register an existing object instance into the container under a string tag. | ||
* @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._bindings[name] = binding; | ||
return binding; | ||
}; | ||
/** | ||
* Register a callback function into the container under a string tag. | ||
* @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). | ||
* @param {string} thing | ||
* @return {object} | ||
*/ | ||
Container.prototype.registerClosure = function(name, closure, shared) | ||
Container.prototype.make = function(thing) | ||
{ | ||
this._types[name] = new Entry( | ||
null, closure, getArgs(closure), !!shared, null); | ||
}; | ||
/** | ||
* Register a factory callback function under an object constructor. | ||
* @param {Function} T Constructor to register. | ||
* @param {Function} factory Callback function that fires to create the class. | ||
*/ | ||
Container.prototype.registerFactory = function(T, factory) | ||
{ | ||
this._types['$$factory_function$$' + getName(T)] = new Entry( | ||
T, factory, getArgs(factory), false, null); | ||
}; | ||
/** | ||
* Register a constructor function under a string tag. Dependencies are inject | ||
* via constructor parameters. | ||
* @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 container. | ||
* @param {Function} T The class constructor of the type we want to register. | ||
*/ | ||
Container.prototype.shared = function(name, T) | ||
{ | ||
return this.register(name, T, true); | ||
}; | ||
/** | ||
* For a given type that comes out of a factory, assign instance properties to | ||
* it everytime. | ||
* @param {Function} T The class. | ||
* @param {Object.<string,*>} injectMap | ||
*/ | ||
Container.prototype.registerInjects = function(T, injectMap) | ||
{ | ||
var map = this._getInjectMap(T); | ||
_.extend(map, injectMap); | ||
}; | ||
/** | ||
* Request the object provided by the container registed under construct T. | ||
* @param {Function} T Constructor function we want to find the factory for. | ||
* @return {Object} The dependency. | ||
*/ | ||
Container.prototype.factory = function(T) | ||
{ | ||
// Attempt to find our registered class to know how to make it... | ||
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); | ||
if (typeof thing === 'string') { | ||
var binding = this._resolve(thing); | ||
return binding.build(); | ||
} | ||
// Otherwise fall back to build | ||
return this.build(T); | ||
}; | ||
/** | ||
* Create a new object T with named dependencies in its constructor resolved. | ||
* @param {Function} T | ||
* @return {Object} The dependency. | ||
*/ | ||
Container.prototype.build = function(T) | ||
{ | ||
var deps = this._resolveDeps(getArgs(T)); | ||
return this._doInjects(T, callNew(T, deps)); | ||
}; | ||
/** | ||
* Create a stringly-named dependency from the container. | ||
* @param {String} name The string tag of the dependency we want to create. | ||
* @return {Object} The dependency. | ||
*/ | ||
Container.prototype.make = function(name) | ||
{ | ||
// Fall back to call if provided 2 args | ||
if (arguments.length === 2) { | ||
return this.call.apply(this, arguments); | ||
// Fall back to factory if provided a constructor | ||
} else if (name instanceof Function) { | ||
return this.factory(name); | ||
// If not a string (like a rando object), just NOP | ||
} else if (!_(name).isString()) { | ||
return name; | ||
if (typeof thing === 'function') { | ||
var depNames = getArguments(thing); | ||
return this._build(thing, depNames); | ||
} | ||
var entry = this._resolve(name); | ||
// Short circuit if we've already got our object cached | ||
if (entry.shared && entry.instance) { | ||
return entry.instance; | ||
} | ||
// Resolve all of the expressed dependencies | ||
var deps = this._resolveDeps(entry.deps); | ||
// Create | ||
var instance; | ||
if (entry.T) { | ||
instance = this._doInjects(entry.T, callNew(entry.T, deps)); | ||
} else { | ||
instance = entry.closure.call(null, deps); | ||
} | ||
// Stash singleton | ||
if (entry.shared) | ||
entry.instance = instance; | ||
return instance; | ||
// Nop if it's not a string dep or a constructor/closure | ||
return thing; | ||
}; | ||
/** | ||
* Execute a function in some context, resolving all its parameters as | ||
* dependencies. | ||
* @param {Object} context Function execution context. | ||
* @param {Function} f Function to call. | ||
* @return {*} Whatever the function returns. | ||
* Where the magic happens | ||
* @param {IoCBinding} binding | ||
* @return {object} | ||
*/ | ||
Container.prototype.call = function(context, f) | ||
Container.prototype._build = function(T, depNames) | ||
{ | ||
var deps = this._resolveDeps(getArgs(f)); | ||
return f.apply(context, deps); | ||
}; | ||
Container.prototype._doInjects = function(T, instance) | ||
{ | ||
var map = this._getInjectMap(T); | ||
_.extend(instance, map); | ||
return instance; | ||
}; | ||
/** | ||
* Get any inject maps for instances based on class T | ||
* @param {Function} T | ||
* @return {Object.<String,*>} | ||
*/ | ||
Container.prototype._getInjectMap = function(T) | ||
{ | ||
for (var n = 0; n < this._injects.length; n++) { | ||
var entry = this._injects[n]; | ||
if (entry.T === T) | ||
return entry.map; | ||
} | ||
// New one | ||
var map = {}; | ||
this._injects.push({T: T, map: map }); | ||
return map; | ||
}; | ||
/** | ||
* Resolve a list of string dependencies into real dependencies | ||
* @param {Array.<String>} deps | ||
* @return {Array.<Object>} | ||
* @private | ||
*/ | ||
Container.prototype._resolveDeps = function(deps) | ||
{ | ||
var _this = this; | ||
return _(deps).map(function(d) { return _this.make(d); }); | ||
var deps = _.map(depNames, function(d) { return _this.make(d); }); | ||
return callNew(T, deps); | ||
}; | ||
@@ -256,3 +79,3 @@ | ||
* @param {String} name The dependency name. | ||
* @return {Entry} Information about this named dependency. | ||
* @return {IoCBinding} Information about this named dependency. | ||
* @private | ||
@@ -262,10 +85,13 @@ */ | ||
{ | ||
var info = this._types[name]; | ||
if (!info) | ||
var binding = this._bindings[name]; | ||
if (!binding) | ||
throw new Error( | ||
'Cannot resolve dependency "' + name + '"'); | ||
return info; | ||
return binding; | ||
}; | ||
// Give us a way to instantiate a new class with an array of args | ||
/** | ||
* Give us a way to instantiate a new class with an array of args | ||
* @private | ||
*/ | ||
function callNew(T, args) | ||
@@ -277,2 +103,1 @@ { | ||
} | ||
{ | ||
"name": "sack", | ||
"version": "1.3.0", | ||
"version": "2.0.0", | ||
"description": "An Inversion-of-Control container for all your dependency injection needs.", | ||
@@ -48,5 +48,5 @@ "keywords": [ | ||
"dependencies": { | ||
"underscore": "~1.5.2", | ||
"typedef": "~1.0.2" | ||
"typedef": "^1.0.2", | ||
"underscore": "^1.6.0" | ||
} | ||
} |
@@ -69,3 +69,3 @@ # Sack | ||
In large applicaitons, things like model stores, application | ||
In large applications, things like model stores, application | ||
configs, service handles, etc. are all used across several different parts of | ||
@@ -77,3 +77,10 @@ an application. Relying on every consumer object to manage its dependencies | ||
The goal is to reduce how tightly coupled your various objects are by removing | ||
the knowledge of *how to create* other dependency objects, and simply rely on | ||
expressing *what kind* of objects we need. | ||
This facilitates testing, as complex dependencies that are injected can be | ||
substituted with [mocks](http://en.wikipedia.org/wiki/Mock_object), and the | ||
consuming classes are not tied to a specific implementation. | ||
## Usage | ||
@@ -99,6 +106,7 @@ | ||
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): | ||
first time the dependency is resolved, and then re-used after that (singleton | ||
pattern): | ||
```javascript | ||
container.shared('service', MyService); | ||
container.register('service', MyService).asSingleton(); | ||
``` | ||
@@ -109,3 +117,3 @@ | ||
```javascript | ||
container.register('service', someService); | ||
container.register('service', someService).asInstance(); | ||
``` | ||
@@ -126,38 +134,8 @@ | ||
```javascript | ||
container.shared('service', function() { | ||
container.register('service', function() { | ||
return new MyService(); | ||
}); | ||
}).asSingleton(); | ||
``` | ||
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); | ||
}); | ||
``` | ||
To be more explicit, you can use the more verbose methods of registration | ||
instead of having Sack guess: | ||
```javascript | ||
container.registerConstructor('users', MySqlUserStore); | ||
container.registerInstance('config', { version: '1.0.0' }); | ||
container.registerClosure('currentUser', function(users) { | ||
return users.fetchLoggedInUser(); | ||
}); | ||
``` | ||
You can also register certain properties to be set every time a depedency is | ||
created via any of the factory methods for a specific class. | ||
```javascript | ||
container.registerInjects(MyService, { securityToken: '123-123' }); | ||
``` | ||
### Resolving Objects | ||
@@ -198,3 +176,3 @@ | ||
```javascript | ||
container.register('users', new LocalStorageUsers()); | ||
container.register('users', LocalStorageUsers).asSingleton(); | ||
``` | ||
@@ -209,2 +187,25 @@ | ||
### Strong vs Weak Dependencies | ||
By default, all registered dependencies are considered "strong", that is, they | ||
cannot be overridden. Attempting to register a dependency with the same name as | ||
another one will result in an error: | ||
```javascript | ||
container.register('server', express()); | ||
container.register('server', http.createServer()); | ||
> Error: Cannot override: config | ||
``` | ||
Registering a dependency as weak allows it to be overriden later: | ||
```javascript | ||
container.register('config', {}).asWeak(); | ||
container.register('config', new ConfigStore()); | ||
// No error | ||
``` | ||
## Architecture Patterns | ||
@@ -211,0 +212,0 @@ |
var test = require('tape'); | ||
var Container = require('../lib/Container.js'); | ||
@@ -16,12 +17,2 @@ | ||
test('Resolve method throws', function(t) { | ||
t.plan(1); | ||
var r = new Container(); | ||
t.throws(function() { | ||
r._resolve('not there'); | ||
}, 'Missing type'); | ||
}); | ||
test('Basic dep track', function(t) { | ||
@@ -47,13 +38,2 @@ t.plan(5); | ||
test('Throw on unmet dep', function(t) { | ||
t.plan(1); | ||
var r = new Container(); | ||
r.register('T', function(A, B, C) {}); | ||
t.throws(function() { | ||
r.make('T'); | ||
}, 'no deps'); | ||
}); | ||
test('Make from ctor', function(t) { | ||
@@ -78,3 +58,3 @@ t.plan(2); | ||
function T() { t.ok(true, 'T ctor fired'); } | ||
r.shared('t', T); | ||
r.register('t', T).asSingleton(); | ||
@@ -98,3 +78,3 @@ t.strictEqual(r.make('t'), r.make('t'), 'Same instance created'); | ||
r.register('a', A); | ||
r.shared('t', T); | ||
r.register('t', T).asSingleton(); | ||
@@ -106,2 +86,3 @@ r.make('t'); | ||
test('Nop', function(t) { | ||
@@ -117,2 +98,3 @@ t.plan(1); | ||
test('Using closures', function(t) { | ||
@@ -156,55 +138,1 @@ t.plan(3); | ||
}); | ||
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 | ||
}); | ||
test('call method', function(t) { | ||
t.plan(8); | ||
var context = { }; | ||
var ret = {}; | ||
context.f = function(b, a) { | ||
t.strictEqual(this, context); | ||
t.ok(a instanceof A); | ||
t.ok(a instanceof A); | ||
return ret; | ||
} | ||
function A() { } | ||
function B() { } | ||
var container = new Container(); | ||
container.register('a', A); | ||
container.register('b', B); | ||
var r = container.call(context, context.f); | ||
t.strictEqual(r, ret); | ||
// with make defaulting out | ||
t.strictEqual(ret, container.make(context, context.f)); | ||
}); |
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
14
236
23802
300
1
+ Addedtypedef@1.1.0(transitive)
+ Addedunderscore@1.13.7(transitive)
- Removedtypedef@1.0.4(transitive)
- Removedunderscore@1.5.2(transitive)
Updatedtypedef@^1.0.2
Updatedunderscore@^1.6.0