electrolyte
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -6,8 +6,10 @@ // Load modules. | ||
, Promise = require('promise') | ||
, FactorySpec = require('./patterns/factory') | ||
, ConstructorSpec = require('./patterns/constructor') | ||
, LiteralSpec = require('./patterns/literal') | ||
, Assembly = require('./assembly') | ||
, FactoryComponent = require('./patterns/factory') | ||
, ConstructorComponent = require('./patterns/constructor') | ||
, LiteralComponent = require('./patterns/literal') | ||
, InjectedContainer = require('./injectedcontainer') | ||
, SpecNotFoundError = require('./errors/specnotfound') | ||
, deprecate = require('depd')('electrolyte') | ||
, InterfaceNotFoundError = require('./errors/interfacenotfound') | ||
, ComponentNotFoundError = require('./errors/componentnotfound') | ||
, ComponentCreateError = require('./errors/componentcreate') | ||
, debug = require('debug')('electrolyte'); | ||
@@ -23,9 +25,17 @@ | ||
* | ||
* Objects are created from a specification. A specification defines the | ||
* requirements needed in order to create an object. Such requirements include | ||
* the objects required by the object being created. When an object requires | ||
* other objects, the required objects will be created prior to the requiring | ||
* object, and so on, transitively assembling the complete graph of objects as | ||
* necessary. | ||
* Objects are created according to a component specification. A component | ||
* specification defines the requirements necessary to create an object. Such | ||
* requirements include the objects required by the object being created. When | ||
* an object requires other objects, the required objects will be created prior | ||
* to the requiring object, and so on, transitively assembling the complete | ||
* graph of objects as necessary. Component specifications, and the objects | ||
* created from them, are often colloquially referred to as simply "components." | ||
* | ||
* A component specification can request objects that conform to an interface. | ||
* An interface declares the abstract behavior of an object, without concern | ||
* for implementation details. The container will dynamically create an object | ||
* that conforms to the interface according to the runtime environment and | ||
* configuration. This component-based approach to software development | ||
* increases modularity of the system. | ||
* | ||
* @constructor | ||
@@ -36,5 +46,5 @@ * @api public | ||
EventEmitter.call(this); | ||
this._specs = {}; | ||
this._sources = {}; | ||
this._assemblies = {}; | ||
this._order = []; | ||
this._components = {}; | ||
this._resolvers = []; | ||
@@ -50,49 +60,49 @@ | ||
/** | ||
* Utilize a source of objects. | ||
* Utilize an assembly. | ||
* | ||
* The container creates a objects from object sources. Sources are typically | ||
* a directory on the file system or a package of objects that are specifically | ||
* designed to function together. | ||
* The container creates objects from assemblies. An assembly is a collection | ||
* of components that are built to work together and form a logical unit of | ||
* functionality. An assembly is typically distributed as a package, | ||
* facilitating resuse across applications. For application-specific components | ||
* that are not reusable, an assembly can simply consist of a directory on the | ||
* file system. | ||
* | ||
* @param {string} ns - The namespace under which to mount the source. | ||
* @param {function} fn - Loader of object specifications from the source. | ||
* @param {string?} ns - optional namespace under which to mount the assembly. | ||
* @param {function|object} asm - assembly of components. | ||
* @public | ||
*/ | ||
Container.prototype.use = function(ns, s, options) { | ||
Container.prototype.use = function(ns, asm) { | ||
if (typeof ns !== 'string') { | ||
options = s; | ||
s = ns; | ||
// force ns to be an empty string if not specified | ||
asm = ns; | ||
ns = ''; | ||
} | ||
options = options || {}; | ||
var load = s | ||
, manifest = {} | ||
, ids, aid, spec, i, len; | ||
if (typeof s == 'object') { | ||
manifest = s; | ||
load = s.load || function noop(id) { return; }; | ||
asm = asm || {}; | ||
// accept either object or loader function | ||
if (typeof asm == 'function') { | ||
asm = { load: asm, export: true }; | ||
} | ||
if (typeof load != 'function') { | ||
throw new Error("Container#use requires a load function, was passed a '" + (typeof load) + "'"); | ||
if (typeof asm.load != 'function') { | ||
throw new TypeError("Container#use requires `asm` to be either a function or an object with a `load` function, '" + (typeof asm.load) + "' has been passed"); | ||
} | ||
var h = this._order.length; | ||
var source = { namespace: ns, load: load }; | ||
this._sources[h] = source; | ||
asm = new Assembly(h, ns, asm); | ||
this._assemblies[h] = asm; | ||
this._order.unshift(h); | ||
ids = Object.keys(manifest); | ||
var ids = asm.components | ||
, comp, i, len; | ||
for (i = 0, len = ids.length; i < len; ++i) { | ||
aid = path.join(source.namespace, ids[i]); | ||
spec = manifest[ids[i]]; | ||
if (spec === load) { continue; } | ||
this._registerSpec(aid, spec, source); | ||
comp = asm.load(ids[i]); | ||
if (!comp) { | ||
throw new ComponentNotFoundError("Cannot find component '" + ids[i] + "'"); | ||
} | ||
this._registerComponent(ids[i], comp, asm); | ||
} | ||
return this; | ||
} | ||
Container.prototype.loader = deprecate.function(Container.prototype.use, 'Container#loader: Use Container#use instead'); | ||
@@ -102,11 +112,11 @@ /** | ||
* | ||
* Creates an object from the specifications registered with the container. | ||
* When the object being created requires other objects, the required objects | ||
* will automatically be created and injected into the requiring object. In | ||
* this way, complex graphs of objects can be created in a single single line of | ||
* Creates an object from the assemblies registered with the container. When | ||
* the object being created requires other objects, the required objects will | ||
* automatically be created and injected into the requiring object. In this | ||
* way, complex graphs of objects can be created in a single single line of | ||
* code, eliminating extraneous boilerplate. | ||
* | ||
* A specification can declare an object to be a singleton. In such cases, only | ||
* one instance of the object will be created. Subsequent calls to create the | ||
* object will return the singleton instance. | ||
* A component specification can declare an object to be a singleton. In such | ||
* cases, only one instance of the object will be created. Subsequent calls to | ||
* create the object will return the singleton instance. | ||
* | ||
@@ -119,21 +129,49 @@ * Examples: | ||
* | ||
* @param {string} id - The id of the object to create. | ||
* @param {Spec} [parent] - (private) The parent specification requiring the object. | ||
* @param {string} id - The id or interface of the object to create. | ||
* @param {Component} [parent] - (private) The parent component requiring the object. | ||
* @param {Component} [ecomp] - (private) The component to create. | ||
* @returns {Promise} | ||
* @public | ||
*/ | ||
Container.prototype.create = function(id, parent) { | ||
return this._create(id, parent); | ||
}; | ||
Container.prototype._create = function(id, parent) { | ||
var psource = parent ? parent._source : undefined; | ||
Container.prototype.create = function(id, parent, ecomp) { | ||
// built-ins | ||
switch (id) { | ||
case '!container': | ||
return Promise.resolve(new InjectedContainer(this, parent, psource && psource.namespace)); | ||
return Promise.resolve(new InjectedContainer(this, parent)); | ||
} | ||
return new Promise(function(resolve, reject) { | ||
var self = this; | ||
function create(comp) { | ||
if (!parent || comp._assembly.h == parent._assembly.h) { | ||
// The object is being created by another object within the same | ||
// assembly, or by the main script. | ||
return resolve(comp.create(self)); | ||
} else { | ||
// The object is being created by an object from another assembly. | ||
// Most typically, a component interface is requested, allowing dynamic | ||
// object creation according to the runtime environment and | ||
// configuration. | ||
// | ||
// Assemblies declare the components they export for use by other | ||
// assemblies. Any non-exported components are considered private, and | ||
// usable only within the assembly itself. Restrictions are put in | ||
// place to prevent private components from being used outside of their | ||
// assembly. | ||
if (!comp._assembly.isExported(comp.id)) { | ||
return reject(new ComponentCreateError("Private component '" + comp.id + "' required by '" + parent.id + "'")); | ||
} | ||
return resolve(comp.create(self)); | ||
} | ||
} | ||
if (ecomp) { | ||
// A component has been exposed via introspection, and an object is being | ||
// created from the component specification. This is a fast path, | ||
// implemented as an optimization to avoid redundant resolution | ||
// operations. | ||
return create(ecomp); | ||
} | ||
if (parent && id[0] == '.') { | ||
@@ -144,15 +182,18 @@ // resolve relative component ID | ||
id = this.resolve(id, parent); | ||
var spec = this._specs[id]; | ||
if (spec) { | ||
return resolve(spec.create(this)); | ||
var comp = this._components[id]; | ||
if (comp) { | ||
return create(comp); | ||
} else { | ||
var self = this; | ||
this._loadSpec(id, function(err, spec) { | ||
if (err && err.code == 'SPEC_NOT_FOUND') { | ||
reject(new Error('Unable to create object "' + id + '" required by: ' + (parent && parent.id || 'unknown'))); | ||
this._loadComponent(id, function(err, comp) { | ||
if (err instanceof ComponentNotFoundError) { | ||
// Reject with a more informative error message that indicates the | ||
// requiring component. This assists the developer in finding and | ||
// fixing the cause of error. | ||
reject(new ComponentNotFoundError("Unable to create component '" + id + "' required by '" + (parent && parent.id || 'unknown') + "'")); | ||
} else if (err) { | ||
return reject(err); | ||
} | ||
resolve(spec.create(self)) | ||
return create(comp); | ||
}); | ||
@@ -163,12 +204,10 @@ } | ||
Container.prototype.specs = function() { | ||
// TODO: Wrap specs so they can be created in an optimzied way | ||
var ids = Object.keys(this._specs) | ||
, specs = [] | ||
Container.prototype.components = function() { | ||
var ids = Object.keys(this._components) | ||
, comps = [] | ||
, i, len; | ||
for (i = 0, len = ids.length; i < len; ++i) { | ||
specs.push(this._specs[ids[i]]); | ||
comps.push(this._components[ids[i]]); | ||
} | ||
return specs; | ||
return comps; | ||
} | ||
@@ -184,3 +223,3 @@ | ||
} | ||
throw new Error('Unable to resolve interface "' + id + '" required by: ' + (parent && parent.id || 'unknown')); | ||
throw new InterfaceNotFoundError("Cannot find component implementing interface '" + id + "' required by '" + (parent && parent.id || 'unknown') + "'", id); | ||
} | ||
@@ -194,5 +233,5 @@ | ||
/** | ||
* Load object specification. | ||
* Load component specification. | ||
* | ||
* As a prerequisite for creating an object, an object specification must be | ||
* As a prerequisite for creating an object, a component specification must be | ||
* available. The specification declares instructions about how to create an | ||
@@ -202,27 +241,27 @@ * object, such as whether the object should be a singleton instance and any | ||
* | ||
* Object instances will be created by invoking the specification's factory | ||
* function. A factory function is typically a function that returns the | ||
* object or a constructor that is invoked using the `new` operator. | ||
* Object instances will be created by invoking the component's factory | ||
* function. A factory function is typically a function that returns the object | ||
* or a constructor that is invoked using the `new` operator. | ||
* | ||
* @param {string} id - The id of the specification to load. | ||
* @param {string} id - The id of the component specification to load. | ||
* @private | ||
*/ | ||
Container.prototype._loadSpec = function(id, cb) { | ||
Container.prototype._loadComponent = function(id, cb) { | ||
debug('autoload %s', id); | ||
var order = this._order | ||
, source, mod, rid | ||
, asm, comp, rid | ||
, i, len; | ||
for (i = 0, len = order.length; i < len; ++i) { | ||
source = this._sources[order[i]]; | ||
rid = path.relative(source.namespace, id); | ||
asm = this._assemblies[order[i]]; | ||
rid = path.relative(asm.namespace, id); | ||
if (rid.indexOf('../') == 0) { continue; } | ||
mod = source.load(rid); | ||
comp = asm.load(id); | ||
if (mod) { | ||
spec = this._registerSpec(id, mod, source); | ||
if (comp) { | ||
spec = this._registerComponent(id, comp, asm); | ||
return cb(null, spec); | ||
} | ||
} | ||
return cb(new SpecNotFoundError('Cannot find spec "' + id + '"')); | ||
return cb(new ComponentNotFoundError("Cannot find component '" + id + "'")); | ||
} | ||
@@ -251,3 +290,3 @@ | ||
*/ | ||
Container.prototype._registerSpec = function(id, mod, source) { | ||
Container.prototype._registerComponent = function(id, mod, source) { | ||
var spec, pattern; | ||
@@ -272,7 +311,7 @@ | ||
debug('register factory %s', id); | ||
spec = new FactorySpec(id, mod, source); | ||
spec = new FactoryComponent(id, mod, source); | ||
break; | ||
case 'constructor': | ||
debug('register constructor %s', id); | ||
spec = new ConstructorSpec(id, mod, source); | ||
spec = new ConstructorComponent(id, mod, source); | ||
break; | ||
@@ -282,7 +321,7 @@ case 'literal': | ||
debug('register literal %s', id); | ||
spec = new LiteralSpec(id, mod, source); | ||
spec = new LiteralComponent(id, mod, source); | ||
break; | ||
} | ||
this._specs[spec.id] = spec; | ||
this._components[spec.id] = spec; | ||
return spec; | ||
@@ -289,0 +328,0 @@ } |
// Load modules. | ||
var path = require('canonical-path') | ||
, ExposedSpec = require('./exposedspec'); | ||
, ExposedComponent = require('./exposedcomponent'); | ||
@@ -9,3 +9,3 @@ | ||
* | ||
* When a "factory" requires that the container itself be injected, the | ||
* When a factory requires that the container itself be injected, the | ||
* container is first wrapped. This wrapper provides an interface that can be | ||
@@ -24,6 +24,8 @@ * used by the factory to introspect its environment, which is useful when | ||
*/ | ||
function InjectedContainer(c, spec, ns) { | ||
function InjectedContainer(c, parent) { | ||
var pasm = parent ? parent._assembly : undefined; | ||
this._c = c; | ||
this._spec = spec; | ||
this._ns = ns || ''; | ||
this._parent = parent; | ||
this._ns = (pasm && pasm.namespace) || ''; | ||
} | ||
@@ -34,25 +36,19 @@ | ||
* | ||
* This function is equivalent to Container#create, with the exception that the | ||
* object being created must be within the namespace object creating it. | ||
* This function is equivalent to Container#create. | ||
* | ||
* @param {string} id - The id of the object to create. | ||
* @param {Component} [comp] - (private) The component to create. | ||
* @returns {Promise} | ||
* @public | ||
*/ | ||
InjectedContainer.prototype.create = function(id) { | ||
var aid = path.join(this._ns, id); | ||
var rid = path.relative(this._ns, aid); | ||
if (rid.indexOf('../') == 0) { | ||
return Promise.reject(new Error(id + ' not within namespace')); | ||
} | ||
return this._c.create(aid, this._spec); | ||
InjectedContainer.prototype.create = function(id, comp) { | ||
return this._c.create(id, this._parent, comp); | ||
} | ||
/** | ||
* Introspect object specifications. | ||
* Introspect component specifications. | ||
* | ||
* This function is equivalent to Container#specs, with the exception that the | ||
* object object specifications returned are restricted to those within the | ||
* namespace object introspecting them. | ||
* This function is equivalent to Container#components, with the exception that | ||
* the object component specifications returned can be filtered in various ways | ||
* relative to the component performing introspecting. | ||
* | ||
@@ -62,21 +58,35 @@ * @returns {array} | ||
*/ | ||
InjectedContainer.prototype.specs = function(q) { | ||
if (q && !Array.isArray(q)) { | ||
InjectedContainer.prototype.components = function(q, filt) { | ||
if (typeof q == 'string' && !Array.isArray(q)) { | ||
q = [ q ]; | ||
} else if (typeof q == 'boolean') { | ||
filt = q; | ||
q = undefined; | ||
} | ||
// Filter the exposed specs to only those that exist within the namespace | ||
// of the object being created. | ||
var specs = this._c.specs() | ||
if (filt === true) { | ||
// When `filt` is set to true, the exposed components will be filtered to | ||
// consist of only those that exist within the namespace of the | ||
// introspecting component. | ||
filt = this._ns; | ||
} | ||
var comps = this._c.components() | ||
, exposed = [] | ||
, spec, rid, i, len; | ||
for (i = 0, len = specs.length; i < len; ++i) { | ||
spec = specs[i]; | ||
rid = path.relative(this._ns, spec.id); | ||
if (rid.indexOf('../') == 0) { continue; } | ||
, comp, rid, i, len; | ||
for (i = 0, len = comps.length; i < len; ++i) { | ||
comp = comps[i]; | ||
if (typeof filt == 'string') { | ||
// Filter the exposed specs to only those that exist within the namespace | ||
// of the object being created. | ||
rid = path.relative(filt, comp.id); | ||
if (rid.indexOf('../') == 0) { continue; } | ||
} | ||
if (!q) { | ||
exposed.push(new ExposedSpec(rid, spec)); | ||
} else if (spec.implements.some(function(i) { return i == q })) { | ||
// The spec implements one of the requested interfaces. | ||
exposed.push(new ExposedSpec(rid, spec)); | ||
exposed.push(new ExposedComponent(comp, this)); | ||
} else if (comp.implements.some(function(i) { return i == q })) { | ||
// The component implements one of the requested interfaces. | ||
exposed.push(new ExposedComponent(comp, this)); | ||
} | ||
@@ -83,0 +93,0 @@ } |
// Load modules. | ||
var Spec = require('../spec') | ||
var Component = require('../component') | ||
, util = require('util') | ||
, debug = require('debug')('electrolyte'); | ||
, ComponentCreateError = require('../errors/componentcreate'); | ||
/** | ||
* A specification using a constructor. | ||
* A component created using a constructor. | ||
* | ||
@@ -14,22 +14,21 @@ * Objects will be created by applying the `new` operator to the constructor | ||
* @constructor | ||
* @param {string} id - The id of the specification. | ||
* @param {object} mod - The module containing the object factory. | ||
* @param {number} hs - The handle of the source from which the spec was loaded. | ||
* @param {string} id - The id of the component. | ||
* @param {object} mod - The module containing the object constructor. | ||
* @param {number} asm - The assembly from which the component was loaded. | ||
* @protected | ||
*/ | ||
function ConstructorSpec(id, ctor, hs) { | ||
Spec.call(this, id, ctor, hs); | ||
function ConstructorComponent(id, ctor, hs) { | ||
Component.call(this, id, ctor, hs); | ||
this._ctor = ctor; | ||
} | ||
// Inherit from `Spec`. | ||
util.inherits(ConstructorSpec, Spec); | ||
// Inherit from `Component`. | ||
util.inherits(ConstructorComponent, Component); | ||
/** | ||
* Instantiate an object from the specification. | ||
* Instantiate an object from the component specification. | ||
* | ||
* @private | ||
*/ | ||
ConstructorSpec.prototype.instantiate = function() { | ||
debug('instantiate %s', this.id); | ||
ConstructorComponent.prototype.instantiate = function() { | ||
var args = [].slice.call(arguments) | ||
@@ -50,3 +49,3 @@ , ctor = this._ctor; | ||
} | ||
throw new Error("Constructor for object '" + this.id + "' requires too many arguments"); | ||
throw new ComponentCreateError("Constructor for object '" + this.id + "' requires too many arguments"); | ||
} | ||
@@ -56,2 +55,2 @@ | ||
// Expose constructor. | ||
module.exports = ConstructorSpec; | ||
module.exports = ConstructorComponent; |
// Load modules. | ||
var Spec = require('../spec') | ||
var Component = require('../component') | ||
, util = require('util') | ||
@@ -8,3 +8,3 @@ , debug = require('debug')('electrolyte'); | ||
/** | ||
* A specification using a factory function. | ||
* A component created using a factory function. | ||
* | ||
@@ -15,21 +15,21 @@ * Objects will be created by calling the factory function with any required | ||
* @constructor | ||
* @param {string} id - The id of the specification. | ||
* @param {object} mod - The module containing the object factory. | ||
* @param {number} hs - The handle of the source from which the spec was loaded. | ||
* @param {string} id - The id of the component. | ||
* @param {object} fn - The module containing the object factory. | ||
* @param {number} asm - The assembly from which the component was loaded. | ||
* @protected | ||
*/ | ||
function FactorySpec(id, fn, hs) { | ||
Spec.call(this, id, fn, hs); | ||
function FactoryComponent(id, fn, asm) { | ||
Component.call(this, id, fn, asm); | ||
this._fn = fn; | ||
} | ||
// Inherit from `Spec`. | ||
util.inherits(FactorySpec, Spec); | ||
// Inherit from `Component`. | ||
util.inherits(FactoryComponent, Component); | ||
/** | ||
* Instantiate an object from the specification. | ||
* Instantiate an object from the component specification. | ||
* | ||
* @private | ||
*/ | ||
FactorySpec.prototype.instantiate = function() { | ||
FactoryComponent.prototype.instantiate = function() { | ||
debug('instantiate %s', this.id); | ||
@@ -39,3 +39,3 @@ | ||
id: this.id, | ||
baseNS: this._source.namespace | ||
baseNS: this._assembly.namespace | ||
} | ||
@@ -47,2 +47,2 @@ return this._fn.apply(ctx, arguments); | ||
// Expose constructor. | ||
module.exports = FactorySpec; | ||
module.exports = FactoryComponent; |
// Load modules. | ||
var Spec = require('../spec') | ||
var Component = require('../component') | ||
, util = require('util'); | ||
@@ -7,3 +7,3 @@ | ||
/** | ||
* A specification of an object literal. | ||
* A component that is an object literal. | ||
* | ||
@@ -22,17 +22,17 @@ * A literal is returned directly when created by the IoC container. | ||
* @constructor | ||
* @param {string} id - The id of the specification. | ||
* @param {object} mod - The module containing the object factory. | ||
* @param {number} hs - The handle of the source from which the spec was loaded. | ||
* @param {string} id - The id of the component. | ||
* @param {object} obj - The module containing the literal object. | ||
* @param {number} asm - The assembly from which the component was loaded. | ||
* @protected | ||
*/ | ||
function LiteralSpec(id, obj, hs) { | ||
Spec.call(this, id, obj, hs); | ||
function LiteralComponent(id, obj, asm) { | ||
Component.call(this, id, obj, asm); | ||
this._instance = obj; | ||
} | ||
// Inherit from `Spec`. | ||
util.inherits(LiteralSpec, Spec); | ||
// Inherit from `Component`. | ||
util.inherits(LiteralComponent, Component); | ||
// Expose constructor. | ||
module.exports = LiteralSpec; | ||
module.exports = LiteralComponent; |
@@ -27,5 +27,6 @@ /** | ||
} catch (ex) { | ||
return; | ||
if (ex.code == 'MODULE_NOT_FOUND') { return; } | ||
throw ex; | ||
} | ||
}; | ||
}; |
{ | ||
"name": "electrolyte", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Elegant dependency injection for Node.js.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -98,4 +98,3 @@ # Electrolyte | ||
Async components are defined in an identical manner to traditional components | ||
except that the factory function should return a promise. An additional annotation | ||
is used to signify that the dependency should be treated as asynchronous. | ||
except that the factory function should return a promise. | ||
@@ -117,3 +116,2 @@ Let's rewrite the database component above slightly to return a promise. | ||
exports['@async'] = true; | ||
exports['@singleton'] = true; | ||
@@ -134,3 +132,2 @@ exports['@require'] = [ 'settings' ]; | ||
exports['@async'] = true; | ||
exports['@singleton'] = true; | ||
@@ -152,7 +149,2 @@ exports['@require'] = [ 'database' ]; | ||
- `@async` Indicates that the component returns a promise OR depends on a | ||
component that is computed asynchronously. An async component can only be created | ||
using the `.createAsync` method. Attempting to create an async component using | ||
the standard `.create` will throw an exception. | ||
#### Creating Components | ||
@@ -159,0 +151,0 @@ |
49100
20
786
279