restocat/restocat

View on GitHub
lib/loaders/CollectionsLoader.js

Summary

Maintainability
A
25 mins
Test Coverage
const path = require('path');

/**
 * Implements the collections Loader class.
 */
class CollectionsLoader {

  /**
   * Creates new instance
   *
   * @param {ServiceLocator} locator The Service Locator for resolving dependencies.
   */
  constructor(locator) {
    this._locator = locator;

    /**
     * Current event bus.
     *
     * @type {EventEmitter}
     * @private
     */
    this._events = locator.resolve('events');

    /**
     * Current collections finder.
     *
     * @type {CollectionsFinder}
     * @private
     */
    this._collectionsFinder = locator.resolve('collectionsFinder');

    /**
     * Current map of the loaded collections by their names.
     *
     * @type {Object}
     * @private
     */
    this._loadedCollections = null;
  }

  /**
   * Loads all collections into the memory.
   *
   * @returns {Promise<Object>} The promise for map of loaded collections.
   */
  async load() {
    if (this._loadedCollections) {
      return this._loadedCollections;
    }

    const result = Object.create(null);
    const collections = await this._collectionsFinder.find();

    Object
      .keys(collections)
      .map(collection_name => this._getCollection(collections[collection_name]))
      .filter(collection => collection && typeof (collection) === 'object')
      .forEach(collection => {
        this._events.emit('collectionLoaded', collection);
        result[collection.name] = collection;
      });

    this._loadedCollections = result;
    this._events.emit('allCollectionsLoaded', result);

    return this._loadedCollections;
  }

  /**
   * Load/Reload collection
   *
   * @param {Object} descriptor Collection's descriptor
   * @return {void}
   * @private
   */
  loadCollection(descriptor) {
    const collection = this._getCollection(descriptor);

    if (collection) {
      this._loadedCollections[descriptor.name] = collection;
      this._events.emit('collectionLoaded', collection);
    }
  }

  /**
   * Remove collection by name
   *
   * @param {String} name Collection's name
   * @return {void}
   */
  removeCollection(name) {
    if (this._loadedCollections && this._loadedCollections[name]) {
      delete this._loadedCollections[name];
    }
  }

  /**
   * Gets a collection object by the found collection details.
   *
   * collection = {
   *   constructor: Class,
   *   name: ''
   *   properties: {
   *      logic: '',
   *      endpoints: {}
   *   }
   *   path: ''
   * }
   *
   * @param {Object} descriptor The found details.
   * @returns {Object} The collection object.
   * @private
   */
  _getCollection(descriptor) {
    const logic_path = this._getLogicPath(descriptor);

    let constructor;
    try {
      constructor = require(logic_path);
    } catch (e) {
      // remove, if it was previously loaded
      this.removeCollection(descriptor.name);

      if (e.code === 'ENOENT' || e.message.indexOf(logic_path) !== -1) {
        this._events.emit('error', `In collection "${descriptor.name}" the logic file has been moved or deleted. Skipping...`);
      } else {
        this._events.emit('error', `In collection "${descriptor.name}" error. Skipping... \n ${e.stack}`);
      }

      return null;
    }

    if (typeof (constructor) !== 'function') {
      this._events.emit('warn',
        `File at ${logic_path} of collection "${descriptor.name}" not found or does not export the constructor. Skipping...`
      );
      return null;
    }

    const collection = Object.create(descriptor);
    collection.constructor = constructor;

    return collection;
  }

  /**
   * Return descriptor of collection by name
   *
   * @param {String} name Collection's name
   * @return {Object} Loaded collection
   */
  getCollectionByName(name) {
    return this._loadedCollections[name];
  }

  /**
   * Return collections by name
   * @return {Object} Loaded collections
   */
  getCollectionsByNames() {
    return this._loadedCollections;
  }

  /**
   * Gets an absolute path to the collection's logic file.
   * @param {Object} collectionDetails The collection details object.
   * @returns {string} The absolute path to the logic file.
   * @private
   */
  _getLogicPath(collectionDetails) {
    return path.resolve(path.dirname(collectionDetails.path), collectionDetails.properties.logic);
  }
}

module.exports = CollectionsLoader;