Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
@skava/modules / ___dist / chain-able / src / MergeChain.js
Size: Mime:
"use strict";

/* eslint complexity: "OFF" */
const MethodChain = require("./MethodChain");

const ChainedMapBase = require("./ChainedMapBase");

const dopemerge = require("./deps/dopemerge");

const isFunction = require("./deps/is/function");

const isUndefined = require("./deps/is/undefined");

const isTrue = require("./deps/is/true");

const isMapish = require("./deps/is/mapish");

const ObjectKeys = require("./deps/util/keys");

const constructInit = require("./deps/fp/constructInit");

const EMPTY_OBJ = require("./deps/native/EMPTY_OBJ");

const SHORTHANDS_KEY = require("./deps/meta/SHORTHANDS_KEY");

const ENV_DEVELOPMENT = require("./deps/env/dev");

const ENV_DEBUG = require("./deps/env/debug");

const ON_EXISTING_KEY = 'onExisting';
const ON_VALUE_KEY = 'onValue';
const MERGER_KEY = 'merger';
const MERGER_OPTIONS_KEY = 'opts';
const OBJ_KEY = 'obj';
/**
 * @since 1.0.0
 * @type {Map}
 * @extends {ChainedMapBase}
 * @member MergeChain
 * @memberOf Chainable
 *
 * @types MergeChain
 * @tests MergeChain
 * @see deps/dopemerge
 *
 * {@link https://sourcemaking.com/design_patterns/visitor visitor-pattern}
 *
 * @TODO consider just making this a function,
 *       because 80/20 onValue merger & onExisting
 *       are rarely used & are easily overridable with .merge
 */

let MergeChain = class MergeChain extends ChainedMapBase {
  /**
   * @static
   * @param  {Chainable | ParentType} parent ParentType required, for merging
   * @return {MergeChain} @chainable
   *
   * @example
   *
   *    let map = new Map()
   *    map.set('eh', 1)
   *    map.set('coo', 'oo')
   *
   *    MergeChain.init(map).merge({eh: 2})
   *    console.dir(map)
   *    //=> Map { 'eh' => 2, 'coo' => 'oo' }
   *
   */
  // static init(parent) {
  //   return new MergeChain(parent)
  // }

  /**
   * @inheritdoc
   */
  constructor(parent) {
    super(parent);
    /* prettier-ignore */

    this.extend([ON_EXISTING_KEY, ON_VALUE_KEY, OBJ_KEY]).set(ON_VALUE_KEY, () => true).set(MERGER_KEY, dopemerge);
  }
  /**
   * @desc options for merging with dopemerge
   *       @modifies this.merger | this.opts
   *
   * @memberOf MergeChain
   * @since 1.0.2
   * @param  {Object | Function} opts when object: options for the merger. when function: is the merger
   * @return {MergeChain} @chainable
   * @see dopemerge
   *
   * @example
   *   {
   *     stringToArray: true,
   *     boolToArray: false,
   *     boolAsRight: true,
   *     ignoreTypes: ['null', 'undefined', 'NaN'],
   *     debug: false,
   *   }
   *
   * @example
   *    .merger(require('lodash.mergewith')())
   */


  merger(opts) {
    if (isFunction(opts)) return this.set(MERGER_KEY, opts);else return this.set(MERGER_OPTIONS_KEY, opts);
  } // [v] messes comments on conditional brace style

  /* prettier-ignore */

  /**
   * @desc merges object in, goes through all keys, checks cbs, dopemerges
   *
   * @since 1.0.0
   *
   * @param  {Object} [obj2=undefined] object to merge in, defaults to this.get('obj')
   * @return {MergeChain} @chainable
   *
   * @see ChainedMap
   * @TODO issue here if we extend without shorthands &
   *       we want to merge existing values... :s
   *
   *
   * @example
   *
   *  const chain = new Chain()
   *  chain.merge({canada: {eh: true}})
   *  chain.merge({canada: {arr: [0, {'1': 2}], eh: {again: true}}})
   *  chain.entries()
   *  //=> {canada:{ eh: {again: true}, arr: [0, {'1': 2}] }}
   *
   */


  merge(obj2) {
    // better uglifying
    const parent = this.parent;

    const get = key => this.get(key);

    const onExisting = get(ON_EXISTING_KEY);
    const onValue = get(ON_VALUE_KEY);
    const opts = get(MERGER_OPTIONS_KEY);
    const obj = obj2 || get(OBJ_KEY);
    const merger = get(MERGER_KEY);
    const shorthands = parent.meta ? parent.meta(SHORTHANDS_KEY) : EMPTY_OBJ;
    const keys = ObjectKeys(obj); // @@debugger

    /* istanbul ignore next: devs */

    if (ENV_DEVELOPMENT) {
      if (!obj) {
        console.log({
          onExisting,
          opts,
          obj,
          merger,
          shorthands,
          keys,
          parent
        });
        throw new Error('must provide an object to merge');
      }
    }
    /**
     * @private
     *
     * since this would be slower
     * if I want to not have a speedy default when using .onExisting
     * should @note to use .extend
     * when using chains without a class & doing .merge (edge-case)
     *
     * @param  {Primitive} key key (shorthands[key] or just key)
     * @param  {*} value obj[key]
     * @return {void}
     *
     * @TODO could use .eq here
     * @TODO if (isMapish(obj)) obj = obj.entries()
     *
     * @example
     *  var obj = {key: 1}
     *
     *  MergeChain.init(obj).merge({key: ['value']})
     *
     *  // goes to this internal scoped function
     *  handleExisting('key', ['value'])
     *  // if there is .onValue or .onExisting, use them, default deepmerge
     *
     *  obj
     *  //=> {key: [1, 'value']}
     *
     */


    const handleExisting = (key, value) => {
      /**
       * @desc when fn is a full method, not an extended shorthand
       * @since 0.5.0
       *
       * @param {Primitive} keyToSet key we chose to set
       * @param {*} valueToSet value we chose to set (merged, existing, new)
       * @return {Parent | Chain | *} .set or [keyToSet] return
       *
       * @example
       *
       *    MergeChain.init(new Chain().extend(['eh']))
       *
       *    //isFunction: true => call parent[keyToSet](valueToSet)
       *    setChosen('eh', 1)
       *    //=> parent
       *    parent.get('eh')
       *    //=> 1
       *
       *    //=>isFunction: false => parent.set(keyToSet, valueToSet)
       *    setChosen('oh', 1)
       *    //=> parent //<- unless .set is overriden
       *    parent.get('oh')
       *    //=> 1
       *
       */
      const setChosen = (keyToSet, valueToSet) => isFunction(parent[key]) ? parent[keyToSet](valueToSet) : parent.set(keyToSet, valueToSet);
      /**
       * check if it's shorthanded
       * -> check if it has a value already
       */


      if (isTrue(parent.has(key))) {
        // get that value
        const existing = parent.get(key);
        /**
         * if we have onExisting, call it
         * else default to dopemerge
         */

        if (isUndefined(onExisting)) {
          /* istanbul ignore next: devs */
          if (ENV_DEBUG) {
            console.log('parent has: no onExisting', {
              existing,
              [key]: value
            });
          }

          setChosen(key, merger(existing, value, opts));
        } else {
          /* istanbul ignore next: devs */
          if (ENV_DEBUG) {
            console.log('parent has: has onExisting', {
              existing,
              onExisting,
              [key]: value
            });
          }
          /**
           * maybe we should not even have `.onExisting`
           * since we can just override merge method...
           * and then client can just use a custom merger...
           *
           * could add and remove subscriber but that's overhead and
           * tricky here, because if we set a value that was just set...
           */


          setChosen(key, onExisting(existing, value, opts));
        }
      } else {
        /* istanbul ignore next: devs */
        if (ENV_DEBUG) {
          console.log('parent does not have', {
            [key]: value
          });
        }

        setChosen(key, value);
      }
    };

    for (let k = 0, len = keys.length; k < len; k++) {
      // key to the current property in the data being merged
      let key = keys[k]; // we have our value, no we can change the key if needed for shorthands

      const value = obj[key]; // @NOTE: when shorthands is an object, key is the method it should call

      if (!isUndefined(shorthands[key]) && shorthands[key] !== key) {
        /* istanbul ignore next: devs */
        if (ENV_DEBUG) {
          console.log('had a shorthand with a diff key than the object (likely @alias)', {
            shorthandMethod: shorthands[key],
            key,
            value
          });
        }

        key = shorthands[key];
      } // method for the key


      const method = parent[key];
      /* istanbul ignore next: sourcemaps trigger istanbul here incorrectly */
      // use onValue when set

      if (!onValue(value, key, this)) {
        /* istanbul ignore next: devs */
        if (ENV_DEBUG) {
          console.log('had onValue, was false, ignored', {
            onValue,
            key,
            value
          });
        }

        continue;
      } // when property itself is a Chainable
      else if (isMapish(method)) {
          /* istanbul ignore next: devs */
          if (ENV_DEBUG) {
            console.log('has method or shorthand');
          }

          parent[key].merge(value);
        } // we have a method or shorthand
        else if (method) {
            /* istanbul ignore next: devs */
            if (ENV_DEBUG) {
              console.log('has method or shorthand', {
                method,
                key,
                value
              });
            }

            handleExisting(key, value);
          } // default to .set on the store
          else {
              /* istanbul ignore next: devs */
              if (ENV_DEBUG) {
                console.log('went to default', {
                  method,
                  key,
                  value
                });
              }

              parent.set(key, value);
            }
    }

    return parent;
  }

};
constructInit(MergeChain);
/**
 * @memberOf MergeChain
 * @method onExisting
 * @since 0.9.0
 * @example
 *
 *    const {Chain, MergeChain} = require('chain-able')
 *
 *    const chain = new Chain().set('str', 'stringy')
 *
 *    MergeChain.init(chain)
 *      .onExisting((a, b) => a + b)
 *      .merge({str: '+'})
 *
 *    chain.get('str')
 *    //=> 'stringy+'
 *
 */

module.exports = MergeChain; // @TODO re-enable this later
// module.exports = new MethodChain(MergeChain.prototype)
//   .methods(['onExisting', 'onValue', 'obj'])
//   .build(MergeChain)