Source: utils.js

// external dependencies
import deepFreeze from 'deep-freeze-strict';
import identity from 'lodash/identity';
import isArray from 'lodash/isArray';
import isPlainObject from 'lodash/isPlainObject';
import stringifier from 'stringifier';

// constants
import {
  CRIO_SYMBOL,
  IS_PRODUCTION,
  REACT_ELEMENT_TYPE,
  STRINGIFIER_OPTIONS
} from './constants';

/**
 * @private
 *
 * @function freeze
 *
 * @description
 * freeze the object if it is production
 *
 * @param {CrioArray|CrioObject} crio object to freeze
 *
 * @returns {T} frozen object
 */
export const freeze = ((isProduction) => {
  return isProduction ? identity : deepFreeze;
})(IS_PRODUCTION);

export const hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * @private
 *
 * @function isComplexObject
 *
 * @description
 * is the object an array or plain object
 *
 * @param  {*} object object to test
 *
 * @returns {boolean} is the object a complex object
 */
export const isComplexObject = (object) => {
  return isArray(object) || isPlainObject(object);
};

/**
 * @private
 *
 * @function isCrio
 *
 * @description
 * is the object a crio object or not
 *
 * @param {*} object object to test
 *
 * @returns {boolean} is the object a crio
 */
export const isCrio = (object) => {
  return !!(object && object[CRIO_SYMBOL]);
};

/**
 * @private
 *
 * @function isCrioArray
 *
 * @description
 * is the object a crio array
 *
 * @param {*} object object to test
 * @returns {boolean} is the object a crio array
 */
export const isCrioArray = (object) => {
  return isCrio && object.isArray();
};

/**
 * @private
 *
 * @function isEqual
 *
 * @description
 * are the crio objects equal
 *
 * @param {CrioArray|CrioObject} crio crio object to test against
 * @param {*} object object to test equality with crio object for
 *
 * @returns {boolean} are the objects equal
 */
export const isEqual = (crio, object) => {
  return isCrio(object) && crio.hashCode === object.hashCode;
};

/**
 * @private
 *
 * @function isReactElement
 *
 * @description
 * is the object passed a react element
 *
 * @param {*} object object to test
 * @returns {boolean} is object a react element
 */
export const isReactElement = (object) => {
  return !!object && object.$$typeof === REACT_ELEMENT_TYPE;
};

/**
 * @private
 *
 * @function getCorrectConstructor
 *
 * @description
 * get the constructor that is valid for the object type passed
 *
 * @param {*} object object to test
 * @param {CrioArray} CrioArray constructor for CrioArray class
 * @param {CrioObject} CrioObject constructor for CrioObject class
 * @returns {CrioArray|CrioObject} constructor correct for object
 */
export const getCorrectConstructor = (object, CrioArray, CrioObject) => {
  return isArray(object) ? CrioArray : CrioObject;
};

/**
 * @private
 *
 * @function getCrioValue
 *
 * @description
 * get the value based on its type
 *
 * @param {*} object object to get value of
 * @param {Function} Constructor function to call new of if object is complex
 *
 * @returns {*} object with clean value
 */
export const getCrioValue = (object, Constructor) => {
  if (isCrio(object) || isReactElement(object)) {
    return object;
  }

  if (isComplexObject(object)) {
    return new Constructor(object);
  }

  return object;
};

/**
 * @private
 *
 * @function getKeysMetadata
 *
 * @description
 * get the value at the parent key location
 *
 * @param {Array<number|string>} keys keys to get value of
 * @param {CrioArray|CrioObject} instance crio instance
 *
 * @returns {{currentValue: *, lastIndex: number}} parent key metadata
 */
export const getKeysMetadata = (keys, instance) => {
  const lastIndex = keys.length - 1;
  const parentKeys = keys.slice(0, lastIndex);
  const currentValue = instance.getIn(parentKeys);

  return {
    currentValue,
    lastIndex,
    parentKeys
  };
};

/**
 * @private
 *
 * @function getRelativeValue
 *
 * @description
 * get the relative value used in copyWithin
 *
 * @param {number} value value used as baseline
 * @param {number} length the length of the crio
 * @returns {number} the relative number value
 */
export const getRelativeValue = (value, length) => {
  return value < 0 ? Math.max(length + value, 0) : Math.min(value, length);
};

/**
 * @private
 *
 * @function getStandardValue
 *
 * @description
 * get the standard value (thawed if crio)
 *
 * @param {*} object object to get standard version of
 *
 * @returns {*} standard version of object
 */
export const getStandardValue = (object) => {
  return isCrio(object) ? object.thaw() : object;
};

/**
 * @private
 *
 * @function createAssignToObject
 *
 * @description
 * create a function that will assign a value to an object
 *
 * @param {Function} CrioArray constructor for crio arrays
 * @param {Function} CrioObject constructor for crio objects
 *
 * @returns {function((Array<*>|Object), function): function(*, string): void} assignment function
 */
export const createAssignToObject = (CrioArray, CrioObject) => {
  return (object, getValue) => {
    return (value, key) => {
      object[key] = getValue(
        value,
        getCorrectConstructor(value, CrioArray, CrioObject)
      );
    };
  };
};

/**
 * @function keys
 *
 * @description
 * get the keys for the given object
 *
 * @param {Object} object the object to get the keys for
 * @returns {Array<string>} the array of keys
 */
export const keys = (object) => {
  let ownKeys = [];

  for (let key in object) {
    if (hasOwnProperty.call(object, key)) {
      ownKeys.push(key);
    }
  }

  return ownKeys;
};

/**
 * @private
 *
 * @function stringify
 *
 * @description
 * convert object to string
 *
 * @param {*} object object to stringify
 *
 * @returns {string} stringified version of object
 */
export const stringify = stringifier(STRINGIFIER_OPTIONS);