Source: members/add.js

/**
 * @file Dynasty, asynchronous dependency injection.
 * @author Adam Mill <hismajesty@theroyalwhee.com>
 * @copyright Copyright 2019-2021 Adam Mill
 * @license Apache-2.0
 */

/**
 * Imports.
 */
const { isString, isFunction } = require('@theroyalwhee0/istype');

/**
 * Item factory.
 *
 * @param The initial values to add to the factory.
 * @returns {object} The item that was created.
 */
function itemFactory(initial) {
  const values = {
    ...initial,
  };
  const item = {
    get name() {
      return values.name;
    },
    get creator() {
      return values.creator;
    },
    set creator(creator) {
      if('creator' in values) {
        throw new Error(`"creator" already attached to node`);
      }
      values.creator = creator;
    },
  };
  return item;
}

/**
 * Core add, wrapped by addFactory.
 *
 * @param {Object} context The shared Dynasty context.
 * @param {string} name The name of the item.
 * @param {promise<array<function>>} props An array of property functions to apply to this item.
 * @returns {promise} No results.
 */
async function addCore(context, name, props) {
  const { items } = context;
  if(!isString(name)) {
    return Promise.reject(new Error('"name" must be a string'));
  } else if(name in items) {
    return Promise.reject(new Error(`item named "${name}" already added`));
  }
  items[name] = true; // Placeholder value to indicate this is being added.
  let item = itemFactory({ name });
  for(let prop of props) {
    const modifier = await prop;
    item = await modifier(item);
    if(!item) {
      throw new Error(`modifier "${modifier}" returned nothing in "${name}"`);
    }
  }
  if(!isFunction(item.creator)) {
    return Promise.reject(new Error(`item named "${name}" missing a creator`));
  }
  items[name] = item;
};

/**
 * Add Function factory.
 * @param {Object} The shared Dynasty context.
 * @returns {Function} The add function.
 */
function addFactory(context) {
  /**
   * Add a named item to the Dynasty collection.
   * @public
   * @typedef add
   * @function
   * @param {string} name The name of the item.
   * @param {...Function} prop Various property functions to apply to this item.
   * @returns {promise} No results.
   */
  return function add(name, ...props) {
    const promise = addCore(context, name, props);
    // NOTE: Because the user does not have to chain the add's promises we need to
    // keep track of them so we can catch rejections and resolve everything later.
    context.actions.add.push(promise);
    return promise;
  };
}

/**
 * Exports.
 */
module.exports = {
  addFactory,
};