#

API Docs for: v2.11.1
Show: Inherited Protected Private Deprecated

# File: packages/container/lib/registry.js

    import { dictionary, EmptyObject, assign, intern } from 'ember-utils';
    import { assert, deprecate } from 'ember-metal';
    import Container from './container';

    const VALID_FULL_NAME_REGEXP = /^[^:]+:[^:]+$/;

    /**
     A registry used to store factory and option information keyed
     by type.

     A `Registry` stores the factory and option information needed by a
     `Container` to instantiate and cache objects.

     The API for `Registry` is still in flux and should not be considered stable.

     @private
     @class Registry
     @since 1.11.0
    */
    export default function Registry(options) {
      this.fallback = options && options.fallback ? options.fallback : null;

      if (options && options.resolver) {
        this.resolver = options.resolver;

        if (typeof this.resolver === 'function') {
          deprecateResolverFunction(this);
        }
      }

      this.registrations  = dictionary(options && options.registrations ? options.registrations : null);

      this._typeInjections        = dictionary(null);
      this._injections            = dictionary(null);
      this._factoryTypeInjections = dictionary(null);
      this._factoryInjections     = dictionary(null);

      this._localLookupCache      = new EmptyObject();
      this._normalizeCache        = dictionary(null);
      this._resolveCache          = dictionary(null);
      this._failCache             = dictionary(null);

      this._options               = dictionary(null);
      this._typeOptions           = dictionary(null);
    }

    Registry.prototype = {
      /**
       A backup registry for resolving registrations when no matches can be found.

       @private
       @property fallback
       @type Registry
       */
      fallback: null,

      /**
       An object that has a `resolve` method that resolves a name.

       @private
       @property resolver
       @type Resolver
       */
      resolver: null,

      /**
       @private
       @property registrations
       @type InheritingDict
       */
      registrations: null,

      /**
       @private

       @property _typeInjections
       @type InheritingDict
       */
      _typeInjections: null,

      /**
       @private

       @property _injections
       @type InheritingDict
       */
      _injections: null,

      /**
       @private

       @property _factoryTypeInjections
       @type InheritingDict
       */
      _factoryTypeInjections: null,

      /**
       @private

       @property _factoryInjections
       @type InheritingDict
       */
      _factoryInjections: null,

      /**
       @private

       @property _normalizeCache
       @type InheritingDict
       */
      _normalizeCache: null,

      /**
       @private

       @property _resolveCache
       @type InheritingDict
       */
      _resolveCache: null,

      /**
       @private

       @property _options
       @type InheritingDict
       */
      _options: null,

      /**
       @private

       @property _typeOptions
       @type InheritingDict
       */
      _typeOptions: null,

      /**
       Creates a container based on this registry.

       @private
       @method container
       @param {Object} options
       @return {Container} created container
       */
      container(options) {
        return new Container(this, options);
      },

      /**
       Registers a factory for later injection.

       Example:

       ```javascript
       let registry = new Registry();

       registry.register('model:user', Person, {singleton: false });
       registry.register('fruit:favorite', Orange);
       registry.register('communication:main', Email, {singleton: false});
       ```

       @private
       @method register
       @param {String} fullName
       @param {Function} factory
       @param {Object} options
       */
      register(fullName, factory, options = {}) {
        assert('fullName must be a proper full name', this.validateFullName(fullName));

        if (factory === undefined) {
          throw new TypeError(`Attempting to register an unknown factory: '${fullName}'`);
        }

        let normalizedName = this.normalize(fullName);

        if (this._resolveCache[normalizedName]) {
          throw new Error(`Cannot re-register: '${fullName}', as it has already been resolved.`);
        }

        delete this._failCache[normalizedName];
        this.registrations[normalizedName] = factory;
        this._options[normalizedName] = options;
      },

      /**
       Unregister a fullName

       ```javascript
       let registry = new Registry();
       registry.register('model:user', User);

       registry.resolve('model:user').create() instanceof User //=> true

       registry.unregister('model:user')
       registry.resolve('model:user') === undefined //=> true
       ```

       @private
       @method unregister
       @param {String} fullName
       */
      unregister(fullName) {
        assert('fullName must be a proper full name', this.validateFullName(fullName));

        let normalizedName = this.normalize(fullName);

        this._localLookupCache = new EmptyObject();

        delete this.registrations[normalizedName];
        delete this._resolveCache[normalizedName];
        delete this._failCache[normalizedName];
        delete this._options[normalizedName];
      },

      /**
       Given a fullName return the corresponding factory.

       By default `resolve` will retrieve the factory from
       the registry.

       ```javascript
       let registry = new Registry();
       registry.register('api:twitter', Twitter);

       registry.resolve('api:twitter') // => Twitter
       ```

       Optionally the registry can be provided with a custom resolver.
       If provided, `resolve` will first provide the custom resolver
       the opportunity to resolve the fullName, otherwise it will fallback
       to the registry.

       ```javascript
       let registry = new Registry();
       registry.resolver = function(fullName) {
          // lookup via the module system of choice
        };

       // the twitter factory is added to the module system
       registry.resolve('api:twitter') // => Twitter
       ```

       @private
       @method resolve
       @param {String} fullName
       @param {Object} [options]
       @param {String} [options.source] the fullname of the request source (used for local lookups)
       @return {Function} fullName's factory
       */
      resolve(fullName, options) {
        assert('fullName must be a proper full name', this.validateFullName(fullName));
        let factory = resolve(this, this.normalize(fullName), options);
        if (factory === undefined && this.fallback) {
          factory = this.fallback.resolve(...arguments);
        }
        return factory;
      },

      /**
       A hook that can be used to describe how the resolver will
       attempt to find the factory.

       For example, the default Ember `.describe` returns the full
       class name (including namespace) where Ember's resolver expects
       to find the `fullName`.

       @private
       @method describe
       @param {String} fullName
       @return {string} described fullName
       */
      describe(fullName) {
        if (this.resolver && this.resolver.lookupDescription) {
          return this.resolver.lookupDescription(fullName);
        } else if (this.fallback) {
          return this.fallback.describe(fullName);
        } else {
          return fullName;
        }
      },

      /**
       A hook to enable custom fullName normalization behaviour

       @private
       @method normalizeFullName
       @param {String} fullName
       @return {string} normalized fullName
       */
      normalizeFullName(fullName) {
        if (this.resolver && this.resolver.normalize) {
          return this.resolver.normalize(fullName);
        } else if (this.fallback) {
          return this.fallback.normalizeFullName(fullName);
        } else {
          return fullName;
        }
      },

      /**
       Normalize a fullName based on the application's conventions

       @private
       @method normalize
       @param {String} fullName
       @return {string} normalized fullName
       */
      normalize(fullName) {
        return this._normalizeCache[fullName] || (
            (this._normalizeCache[fullName] = this.normalizeFullName(fullName))
          );
      },

      /**
       @method makeToString

       @private
       @param {any} factory
       @param {string} fullName
       @return {function} toString function
       */
      makeToString(factory, fullName) {
        if (this.resolver && this.resolver.makeToString) {
          return this.resolver.makeToString(factory, fullName);
        } else if (this.fallback) {
          return this.fallback.makeToString(factory, fullName);
        } else {
          return factory.toString();
        }
      },

      /**
       Given a fullName check if the container is aware of its factory
       or singleton instance.

       @private
       @method has
       @param {String} fullName
       @param {Object} [options]
       @param {String} [options.source] the fullname of the request source (used for local lookups)
       @return {Boolean}
       */
      has(fullName, options) {
        if (!this.isValidFullName(fullName)) {
          return false;
        }

        let source = options && options.source && this.normalize(options.source);

        return has(this, this.normalize(fullName), source);
      },

      /**
       Allow registering options for all factories of a type.

       ```javascript
       let registry = new Registry();
       let container = registry.container();

       // if all of type `connection` must not be singletons
       registry.optionsForType('connection', { singleton: false });

       registry.register('connection:twitter', TwitterConnection);
       registry.register('connection:facebook', FacebookConnection);

       let twitter = container.lookup('connection:twitter');
       let twitter2 = container.lookup('connection:twitter');

       twitter === twitter2; // => false

       let facebook = container.lookup('connection:facebook');
       let facebook2 = container.lookup('connection:facebook');

       facebook === facebook2; // => false
       ```

       @private
       @method optionsForType
       @param {String} type
       @param {Object} options
       */
      optionsForType(type, options) {
        this._typeOptions[type] = options;
      },

      getOptionsForType(type) {
        let optionsForType = this._typeOptions[type];
        if (optionsForType === undefined && this.fallback) {
          optionsForType = this.fallback.getOptionsForType(type);
        }
        return optionsForType;
      },

      /**
       @private
       @method options
       @param {String} fullName
       @param {Object} options
       */
      options(fullName, options = {}) {
        let normalizedName = this.normalize(fullName);
        this._options[normalizedName] = options;
      },

      getOptions(fullName) {
        let normalizedName = this.normalize(fullName);
        let options = this._options[normalizedName];

        if (options === undefined && this.fallback) {
          options = this.fallback.getOptions(fullName);
        }
        return options;
      },

      getOption(fullName, optionName) {
        let options = this._options[fullName];

        if (options && options[optionName] !== undefined) {
          return options[optionName];
        }

        let type = fullName.split(':')[0];
        options = this._typeOptions[type];

        if (options && options[optionName] !== undefined) {
          return options[optionName];
        } else if (this.fallback) {
          return this.fallback.getOption(fullName, optionName);
        }
      },

      /**
       Used only via `injection`.

       Provides a specialized form of injection, specifically enabling
       all objects of one type to be injected with a reference to another
       object.

       For example, provided each object of type `controller` needed a `router`.
       one would do the following:

       ```javascript
       let registry = new Registry();
       let container = registry.container();

       registry.register('router:main', Router);
       registry.register('controller:user', UserController);
       registry.register('controller:post', PostController);

       registry.typeInjection('controller', 'router', 'router:main');

       let user = container.lookup('controller:user');
       let post = container.lookup('controller:post');

       user.router instanceof Router; //=> true
       post.router instanceof Router; //=> true

       // both controllers share the same router
       user.router === post.router; //=> true
       ```

       @private
       @method typeInjection
       @param {String} type
       @param {String} property
       @param {String} fullName
       */
      typeInjection(type, property, fullName) {
        assert('fullName must be a proper full name', this.validateFullName(fullName));

        let fullNameType = fullName.split(':')[0];
        if (fullNameType === type) {
          throw new Error(`Cannot inject a '${fullName}' on other ${type}(s).`);
        }

        let injections = this._typeInjections[type] ||
                         (this._typeInjections[type] = []);

        injections.push({
          property: property,
          fullName: fullName
        });
      },

      /**
       Defines injection rules.

       These rules are used to inject dependencies onto objects when they
       are instantiated.

       Two forms of injections are possible:

       * Injecting one fullName on another fullName
       * Injecting one fullName on a type

       Example:

       ```javascript
       let registry = new Registry();
       let container = registry.container();

       registry.register('source:main', Source);
       registry.register('model:user', User);
       registry.register('model:post', Post);

       // injecting one fullName on another fullName
       // eg. each user model gets a post model
       registry.injection('model:user', 'post', 'model:post');

       // injecting one fullName on another type
       registry.injection('model', 'source', 'source:main');

       let user = container.lookup('model:user');
       let post = container.lookup('model:post');

       user.source instanceof Source; //=> true
       post.source instanceof Source; //=> true

       user.post instanceof Post; //=> true

       // and both models share the same source
       user.source === post.source; //=> true
       ```

       @private
       @method injection
       @param {String} factoryName
       @param {String} property
       @param {String} injectionName
       */
      injection(fullName, property, injectionName) {
        this.validateFullName(injectionName);
        let normalizedInjectionName = this.normalize(injectionName);

        if (fullName.indexOf(':') === -1) {
          return this.typeInjection(fullName, property, normalizedInjectionName);
        }

        assert('fullName must be a proper full name', this.validateFullName(fullName));
        let normalizedName = this.normalize(fullName);

        let injections = this._injections[normalizedName] ||
                         (this._injections[normalizedName] = []);

        injections.push({
          property: property,
          fullName: normalizedInjectionName
        });
      },

      /**
       Used only via `factoryInjection`.

       Provides a specialized form of injection, specifically enabling
       all factory of one type to be injected with a reference to another
       object.

       For example, provided each factory of type `model` needed a `store`.
       one would do the following:

       ```javascript
       let registry = new Registry();

       registry.register('store:main', SomeStore);

       registry.factoryTypeInjection('model', 'store', 'store:main');

       let store = registry.lookup('store:main');
       let UserFactory = registry.lookupFactory('model:user');

       UserFactory.store instanceof SomeStore; //=> true
       ```

       @private
       @method factoryTypeInjection
       @param {String} type
       @param {String} property
       @param {String} fullName
       */
      factoryTypeInjection(type, property, fullName) {
        let injections = this._factoryTypeInjections[type] ||
                         (this._factoryTypeInjections[type] = []);

        injections.push({
          property: property,
          fullName: this.normalize(fullName)
        });
      },

      /**
       Defines factory injection rules.

       Similar to regular injection rules, but are run against factories, via
       `Registry#lookupFactory`.

       These rules are used to inject objects onto factories when they
       are looked up.

       Two forms of injections are possible:

       * Injecting one fullName on another fullName
       * Injecting one fullName on a type

       Example:

       ```javascript
       let registry = new Registry();
       let container = registry.container();

       registry.register('store:main', Store);
       registry.register('store:secondary', OtherStore);
       registry.register('model:user', User);
       registry.register('model:post', Post);

       // injecting one fullName on another type
       registry.factoryInjection('model', 'store', 'store:main');

       // injecting one fullName on another fullName
       registry.factoryInjection('model:post', 'secondaryStore', 'store:secondary');

       let UserFactory = container.lookupFactory('model:user');
       let PostFactory = container.lookupFactory('model:post');
       let store = container.lookup('store:main');

       UserFactory.store instanceof Store; //=> true
       UserFactory.secondaryStore instanceof OtherStore; //=> false

       PostFactory.store instanceof Store; //=> true
       PostFactory.secondaryStore instanceof OtherStore; //=> true

       // and both models share the same source instance
       UserFactory.store === PostFactory.store; //=> true
       ```

       @private
       @method factoryInjection
       @param {String} factoryName
       @param {String} property
       @param {String} injectionName
       */
      factoryInjection(fullName, property, injectionName) {
        let normalizedName = this.normalize(fullName);
        let normalizedInjectionName = this.normalize(injectionName);

        this.validateFullName(injectionName);

        if (fullName.indexOf(':') === -1) {
          return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName);
        }

        let injections = this._factoryInjections[normalizedName] || (this._factoryInjections[normalizedName] = []);

        injections.push({
          property: property,
          fullName: normalizedInjectionName
        });
      },

      /**
       @private
       @method knownForType
       @param {String} type the type to iterate over
      */
      knownForType(type) {
        let fallbackKnown, resolverKnown;

        let localKnown = dictionary(null);
        let registeredNames = Object.keys(this.registrations);
        for (let index = 0; index < registeredNames.length; index++) {
          let fullName = registeredNames[index];
          let itemType = fullName.split(':')[0];

          if (itemType === type) {
            localKnown[fullName] = true;
          }
        }

        if (this.fallback) {
          fallbackKnown = this.fallback.knownForType(type);
        }

        if (this.resolver && this.resolver.knownForType) {
          resolverKnown = this.resolver.knownForType(type);
        }

        return assign({}, fallbackKnown, localKnown, resolverKnown);
      },

      validateFullName(fullName) {
        if (!this.isValidFullName(fullName)) {
          throw new TypeError(`Invalid Fullname, expected: 'type:name' got: ${fullName}`);
        }

        return true;
      },

      isValidFullName(fullName) {
        return !!VALID_FULL_NAME_REGEXP.test(fullName);
      },

      validateInjections(injections) {
        if (!injections) { return; }

        let fullName;

        for (let i = 0; i < injections.length; i++) {
          fullName = injections[i].fullName;

          if (!this.has(fullName)) {
            throw new Error(`Attempting to inject an unknown injection: '${fullName}'`);
          }
        }
      },

      normalizeInjectionsHash(hash) {
        let injections = [];

        for (let key in hash) {
          if (hash.hasOwnProperty(key)) {
            assert(`Expected a proper full name, given '${hash[key]}'`, this.validateFullName(hash[key]));

            injections.push({
              property: key,
              fullName: hash[key]
            });
          }
        }

        return injections;
      },

      getInjections(fullName) {
        let injections = this._injections[fullName] || [];
        if (this.fallback) {
          injections = injections.concat(this.fallback.getInjections(fullName));
        }
        return injections;
      },

      getTypeInjections(type) {
        let injections = this._typeInjections[type] || [];
        if (this.fallback) {
          injections = injections.concat(this.fallback.getTypeInjections(type));
        }
        return injections;
      },

      getFactoryInjections(fullName) {
        let injections = this._factoryInjections[fullName] || [];
        if (this.fallback) {
          injections = injections.concat(this.fallback.getFactoryInjections(fullName));
        }
        return injections;
      },

      getFactoryTypeInjections(type) {
        let injections = this._factoryTypeInjections[type] || [];
        if (this.fallback) {
          injections = injections.concat(this.fallback.getFactoryTypeInjections(type));
        }
        return injections;
      }
    };

    function deprecateResolverFunction(registry) {
      deprecate('Passing a `resolver` function into a Registry is deprecated. Please pass in a Resolver object with a `resolve` method.',
                false,
                { id: 'ember-application.registry-resolver-as-function', until: '3.0.0', url: 'http://emberjs.com/deprecations/v2.x#toc_registry-resolver-as-function' });
      registry.resolver = {
        resolve: registry.resolver
      };
    }

    /**
     Given a fullName and a source fullName returns the fully resolved
     fullName. Used to allow for local lookup.

     ```javascript
     let registry = new Registry();

     // the twitter factory is added to the module system
     registry.expandLocalLookup('component:post-title', { source: 'template:post' }) // => component:post/post-title
     ```

     @private
     @method expandLocalLookup
     @param {String} fullName
     @param {Object} [options]
     @param {String} [options.source] the fullname of the request source (used for local lookups)
     @return {String} fullName
     */
    Registry.prototype.expandLocalLookup = function Registry_expandLocalLookup(fullName, options) {
      if (this.resolver && this.resolver.expandLocalLookup) {
        assert('fullName must be a proper full name', this.validateFullName(fullName));
        assert('options.source must be provided to expandLocalLookup', options && options.source);
        assert('options.source must be a proper full name', this.validateFullName(options.source));

        let normalizedFullName = this.normalize(fullName);
        let normalizedSource = this.normalize(options.source);

        return expandLocalLookup(this, normalizedFullName, normalizedSource);
      } else if (this.fallback) {
        return this.fallback.expandLocalLookup(fullName, options);
      } else {
        return null;
      }
    };

    function expandLocalLookup(registry, normalizedName, normalizedSource) {
      let cache = registry._localLookupCache;
      let normalizedNameCache = cache[normalizedName];

      if (!normalizedNameCache) {
        normalizedNameCache = cache[normalizedName] = new EmptyObject();
      }

      let cached = normalizedNameCache[normalizedSource];

      if (cached !== undefined) { return cached; }

      let expanded = registry.resolver.expandLocalLookup(normalizedName, normalizedSource);

      return normalizedNameCache[normalizedSource] = expanded;
    }

    function resolve(registry, normalizedName, options) {
      if (options && options.source) {
        // when `source` is provided expand normalizedName
        // and source into the full normalizedName
        normalizedName = registry.expandLocalLookup(normalizedName, options);

        // if expandLocalLookup returns falsey, we do not support local lookup
        if (!normalizedName) { return; }
      }

      let cached = registry._resolveCache[normalizedName];
      if (cached !== undefined) { return cached; }
      if (registry._failCache[normalizedName]) { return; }

      let resolved;

      if (registry.resolver) {
        resolved = registry.resolver.resolve(normalizedName);
      }

      if (resolved === undefined) {
        resolved = registry.registrations[normalizedName];
      }

      if (resolved === undefined) {
        registry._failCache[normalizedName] = true;
      } else {
        registry._resolveCache[normalizedName] = resolved;
      }

      return resolved;
    }

    function has(registry, fullName, source) {
      return registry.resolve(fullName, { source }) !== undefined;
    }

    const privateNames = dictionary(null);
    const privateSuffix = `${Math.random()}${Date.now()}`;

    export function privatize([fullName]) {
      let name = privateNames[fullName];
      if (name) { return name; }

      let [type, rawName] = fullName.split(':');
      return privateNames[fullName] = intern(`${type}:${rawName}-${privateSuffix}`);
    }