registry/RegistryEntry.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Logan Byam
 */

/**
 * API Status: **Development**
 * @module bajaux/registry/RegistryEntry
 */
define([
  'log!bajaux.registry.RegistryEntry',
  'Promise',
  'underscore',
  'nmodule/js/rc/asyncUtils/asyncUtils' ], function (
  log,
  Promise,
  _,
  asyncUtils) {

  'use strict';

  const logSevere = log.severe.bind(log);
  const { difference, flatten } = _;
  const { doRequire } = asyncUtils;

////////////////////////////////////////////////////////////////
// Support functions
////////////////////////////////////////////////////////////////

  /**
   * Check to see if any of the desired values exist in the array.
   *
   * @inner
   * @param {Array} toTest
   * @param {Array} desired
   * @returns {boolean} true if `desired` is empty ("I don't care") or if any
   * of the values in `desired` also exist in `toTest`
   */
  function hasAny(toTest, desired) {
    if (!desired.length) {
      return true; //don't care
    }

    for (let i = 0; i < desired.length; i++) {
      if (toTest.indexOf(desired[i]) >= 0) {
        return true;
      }
    }
  }

  /**
   * Check to see if all of the desired values exist in the array.
   *
   * @inner
   * @param {Array} toTest
   * @param {Array} desired
   * @returns {boolean} true if all values in `desired` also exist in `toTest`
   * (independent of order)
   */
  function hasAll(toTest, desired) {
    return !difference(desired, toTest).length;
  }

////////////////////////////////////////////////////////////////
// Exports
////////////////////////////////////////////////////////////////

  /**
   * An entry intended to be stored in a local `Registry`, representing a single
   * RequireJS module. Additional parameters can be used to store metadata
   * about this module, allowing it to be queried later in more detail.
   *
   * There is usually no reason to instantiate this class directly;
   * a `Registry` will create them as needed.
   *
   * @class
   * @alias module:bajaux/registry/RegistryEntry
   * @param {module:bajaux/registry/Registry~QueryParams} params
   * @since Niagara 4.10
   */
  class RegistryEntry {
    constructor(params) {
      if (!params || typeof params.rjs !== 'string') {
        throw new Error('rjs parameter required');
      }

      this.$rjs = params.rjs;
      this.$deps = params.deps || [];
      this.$tags = params.tags || [];
    }

    /**
     * Resolve the RequireJS module (and any dependencies) that is represented
     * by this module.
     *
     * @returns {Promise} promise to be resolved with the contents of
     * the RequireJS module, or rejected if the module ID or any of its
     * dependencies could not be loaded
     */
    resolve() {
      return doRequire(this.$rjs, this.$deps)
        .catch((e) => {
          logSevere(e);
          throw e;
        });
    }

    /**
     * Check to see if this entry's metadata matches a registry query.
     *
     * @param {module:bajaux/registry/Registry~QueryParams|module:bajaux/registry/RegistryEntry} [params]
     * @returns {boolean} true if this entry matches the query parameters and
     * so should be included in the results
     */
    matches(params) {
      if (params instanceof RegistryEntry) {
        //noinspection JSUnresolvedFunction
        params = params.toJSON();
      }

      params = params || {};

      const any = params.hasAny || params.tags,
        all = params.hasAll,
        rjs = params.rjs,
        deps = params.deps;

      if (any && !hasAny(this.$tags, any)) {
        return false;
      }

      if (all && !hasAll(this.$tags, all)) {
        return false;
      }

      if (rjs && this.$rjs !== rjs) {
        return false;
      }

      //noinspection RedundantIfStatementJS
      if (deps && !hasAll(flatten(this.$deps), flatten(deps))) {
        return false;
      }

      return true;
    }

    /**
     * Get the RequireJS ID for this entry.
     * @returns {string}
     */
    getJsId() { return this.$rjs; }

    /**
     * Get the list of RequireJS dependencies (typically builtfiles) that must
     * be loaded before requiring the main RequireJS ID.
     * @returns {Array.<Array.<String>>}
     */
    getJsDependencies() { return this.$deps; }

    /**
     * Get a list of tags that serve as metadata for this entry.
     * @returns {Array.<String>}
     */
    getTags() { return this.$tags; }

    /**
     * Return a raw object representation of this entry. As a contractual
     * requirement, the output of this function should be able to be passed right
     * back to the constructor to create a new instance. Return `undefined` to
     * indicate that this entry should never be serialized.
     * @returns {Object|undefined}
     */
    toJSON() {
      return {
        rjs: this.getJsId(),
        deps: this.getJsDependencies(),
        tags: this.getTags()
      };
    }
  }

  /**
   * Compute a hash string for a query params object. Since the registry
   * contents should never change while apps are running, we can memoize calls
   * to registry query functions.
   *
   * @private
   * @param {Object} params params object as expected by `matches()`
   * @returns {String} a hash string
   */
  RegistryEntry.$hashParams = function (params) {
    params = params || {};
    const tags = params.tags,
      all = params.hasAll,
      any = params.hasAny,
      rjs = params.rjs,
      deps = params.deps;

    return 't' + (tags ? tags.join() : '') +
      'r' + (rjs || '') +
      'd' + (deps ? deps.join() : '') +
      'l' + (all ? all.join() : '') +
      'y' + (any ? any.join() : '');
  };

  return RegistryEntry;
});