all files / lib/ Container.js

100% Statements 63/63
100% Branches 24/24
100% Functions 14/14
100% Lines 63/63
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192        15×                                     160× 160× 160×                                       14×   13×   13× 13×     13×                   14×   13× 13× 13×                 232×                               204×   203× 200×   198× 198×   198×     198×     297× 295×                                         297× 297×     297×     295× 19×       276×     11×                              
"use strict";
 
var assert = require('./util').assert;
var Component = require('./Component');
var RegistrationBuilder = require('./RegistrationBuilder');
var ResolutionError = require('./ResolutionError');
var Resolver = require('./Resolver');
 
var nullContainer = {
  resolve: function(key, options) {
    if (options && options.optional) {
      return Promise.resolve(null);
    }
    throw new ResolutionError("Not found: " + key);
  },
  keys: function() {
    return [];
  }
};
 
/**
 * <strong>Private constructor</strong>. Instances are normally created with these methods:
 * <ul>
 *   <li>{@link junkie.newContainer}</li>
 *   <li>{@link Container#newChild}</li>
 * </ul>
 *
 * @param {Container|undefined} parent  The optional parent container.
 * @param {Array.<Resolver>|undefined} resolvers  The optional list of resolvers.
 *
 * @constructor
 */
function Container(parent, resolvers) {
  this._parent = parent || nullContainer;
  this._registry = {};
  this._containerResolvers = resolvers ? resolvers.slice() : [];
}
 
/** @lends Container# */
var C = Container.prototype;
 
/**
 * Obtain the parent container, or <code>null</code> if this container is an orphan.
 * @returns {Container|null} The parent container or <code>null</code>
 */
C.parent = function() {
  return this._parent === nullContainer
    ? null
    : this._parent;
};
 
/**
 * Create a new child Container.
 *
 * @param {object} options Optional configuration options
 * @param {boolean} [options.inherit=true] When <code>true</code>, the new child container inherits this container's
 *        resolvers.
 */
C.newChild = function(options) {
  this._checkDisposed();
 
  options = options || {};
 
  var resolvers = this._containerResolvers;
  if (options.inherit === false) {
    resolvers = null;
  }
 
  return new Container(this, resolvers);
};
 
/**
 * Use the given resolver middleware.
 * @param {String|Function} resolver The resolver to use. Supplying a String attempts to locate a standard resolver
 *        by name. Supplying a Function uses the Function itself as the resolver implementation.
 * @returns {Container} <code>this</code>.
 * @see Resolver
 */
C.use = function(resolver) {
  this._checkDisposed();
 
  resolver = Resolver.normalize(resolver);
  this._containerResolvers.push(resolver);
  return this;
};
 
/**
 * Dispose of this container, releasing all references to components. Using any modifying method after this
 * call will throw an Error. Resolve requests will be delegated to the parent container, if available.
 */
C.dispose = function() {
  delete this._registry; // Drop references to all components
};
 
C._checkDisposed = function() {
  if (!this._registry) {
    throw new Error("Container disposed");
  }
};
 
/**
 * Register the given component with this Container, making it directly resolvable using the
 * {@link Container#resolve} method, as well as making it available for resolution and as a
 * potential dependency of another component.
 *
 * @param {String} key The key associated with the component.
 * @param {*} component The component instance that will be tracked by this container.
 * @returns {RegistrationBuilder} A registration builder to configure the registration.
 *
 * @throws Error if key is not a string
 * @throws Error if component is not defined or <code>null</code>.
 */
C.register = function(key, component) {
  this._checkDisposed();
 
  assert.type(key, 'string', "key must be a string");
  assert(component !== undefined, "component must be defined");
 
  var comp = this._createComponent(key, component);
  this._registry[key] = comp;
 
  return new RegistrationBuilder(comp);
};
 
C._createComponent = function(key, component) {
  return new Component(key, component, this, this._containerResolvers);
};
 
C._get = function(key) {
  assert.type(key, 'string', "key must be a string");
  return this._registry
    ? this._registry[key]
    : null; // If the container was disposed, behave like a 'not found' so we continue searching the parent
};
 
/**
 * Resolve an instance for the given component key. To resolve an instance, every associated resolver is passed
 * control and given the opportunity to create and configure the resulting component instance.
 * <p>
 * When resolving dependencies of the requested component, this same method is invoked internally for each
 * dependency.
 *
 * @param {string} key The component key with which to obtain an instance.
 * @param {Object} [options] Configuration options.
 * @param {boolean} [options.optional=false] When <code>true</code>, in the event that the component cannot be resolved
 *        return <code>null</code> instead of throwing a ResolutionError.
 * @returns {Promise} A promise capturing the result of the resolution. The promise will be rejected with:
 *          - Error if key is not a string.
 *          - ResolutionError if the mandatory key cannot be located.
 *          - ResolutionError if a failure occurs during the resolution process.
 */
C.resolve = function(key, options) {
  try {
    options = options || {};
 
    // Lookup the component
    var comp = this._get(key);
 
    // If the component is not found, delegate to the parent container
    if (!comp) {
      return this._parent.resolve(key, options);
    }
 
    // Resolve the component instance
    return comp.resolve(options);
 
  } catch (e) {
    return Promise.reject(e);
  }
};
 
/**
 * Obtain an array of key names known to this container and, optionally, parent containers.
 *
 * @param {boolean} [includeParent=false] Include keys registered with the parent container, if any.
 * @returns {Array} A set of key names.
 */
C.keys = function(includeParent) {
  var keys = {};
  var addKey = function(key) {
    keys[key] = true;
  };
  if (includeParent) {
    this._parent.keys().forEach(addKey);
  }
  Object.keys(this._registry).forEach(addKey);
 
  return Object.keys(keys);
};
 
module.exports = Container;