"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.logFnTrace = exports.assertFunctionContractDecoratorSpecs = exports.makeFunctionDecorator = exports.decorateWith = exports.decorateWithOne = exports.getFunctionName = exports.trace = exports.makeErrorMessage = exports.deepFreeze = exports.assertSettingsContracts = exports.assertSinksContracts = exports.assertSourcesContracts = exports.isArrayOptSinks = exports.isMergeSinkFn = exports.isOptSinks = exports.isSource = exports.isObservable = exports.isArrayOf = exports.isEmptyArray = exports.isArray = exports.isString = exports.isBoolean = exports.isObject = exports.isVNode = exports.isFunction = exports.isUndefined = exports.isComponent = exports.isStrictRecord = exports.isHashMap = exports.isNullableComponentDef = exports.isNullableObject = exports.emitNullIfEmpty = exports.removeEmptyVNodes = exports.removeNullsFromArray = exports.getSinkNamesFromSinksArray = exports.projectSinksOn = exports.unfoldObjOverload = exports.checkSignature = exports.assertContract = exports.assertSignature = exports.handleError = exports.makeDivVNode = exports.ERROR_MESSAGE_PREFIX = undefined;

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
// TODO https://github.com/moll/js-standard-error
// TODO : define custom error types


var _ramda = require("ramda");

var _rx = require("rx");

var Rx = _interopRequireWildcard(_rx);

var _standardError = require("standard-error");

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

var $ = Rx.Observable;
var mapIndexed = (0, _ramda.addIndex)(_ramda.map);
var ERROR_MESSAGE_PREFIX = exports.ERROR_MESSAGE_PREFIX = 'ERROR : ';

// Type checking typings
/**
 * @typedef {String} ErrorMessage
 */
/**
 * @typedef {Boolean|Array<ErrorMessage>} SignatureCheck
 * Note : The booleam can only be true
 */

// Component typings
/**
 * @typedef {String} SourceName
 */
/**
 * @typedef {String} SinkName
 */
/**
 * @typedef {Observable} Source
 */
/**
 * @typedef {Observable|Null} Sink
 */
/**
 * @typedef {Object.<string, Source>} Sources
 */
/**
 * @typedef {Object.<string, Sink>} Sinks
 */
/**
 * @typedef {?Object.<string, ?Object>} Settings
 */
/**
 * @typedef {function(Sink, Array<Sink>, Settings):Sink} mergeSink
 */
/**
 * @typedef {Object} DetailedComponentDef
 * @property {?function(Sources, Settings)} makeLocalSources
 * @property {?function(Settings)} makeLocalSettings
 * @property {?function(Sources, Settings):Sinks} makeOwnSinks
 * @property {Object.<SinkName, mergeSink> | function} mergeSinks
 * @property {function(Sinks):Boolean} sinksContract
 * @property {function(Sources):Boolean} sourcesContract
 */
/**
 * @typedef {Object} ShortComponentDef
 * @property {?function(Sources, Settings)} makeLocalSources
 * @property {?function(Settings)} makeLocalSettings
 * @property {?function(Sources, Settings):Sinks} makeOwnSinks
 * @property {function(Component, Array<Component>, Sources, Settings)}
 * computeSinks
 * @property {function(Sinks):Boolean} sinksContract
 * @property {function(Sources):Boolean} sourcesContract
 */
/**
 * @typedef {function(Sources, Settings):Sinks} Component
 */

///////
// Helpers

/**
 * Throws an exception if the arguments parameter fails at least one
 * validation rule
 * Note that all arguments are mandatory, i.e. the function does not deal with
 * optional arguments
 * @param {String} fnName
 * @param {Array<*>} _arguments
 * @param {[Array<Object.<string, Predicate|PredicateWithError>>]} vRules
 * Validation rules.
 *
 * Given f(x, y) =  x + y, with x both int, in the body of `f`, include
 * function f(x, y) {
   *   assertSignature ('f', arguments, [{x:isInteger},{y:isInteger}],
   *                  'one of the parameters is not an integer!')
   *   ...
   * }
 */
function assertSignature(fnName, _arguments, vRules) {
  var argNames = (0, _ramda.flatten)((0, _ramda.map)(_ramda.keys, vRules));
  var ruleFns = (0, _ramda.flatten)((0, _ramda.map)(function (vRule) {
    return (0, _ramda.values)(vRule)[0];
  }, vRules));

  var args = mapIndexed(function (vRule, index) {
    return _arguments[index];
  }, vRules);

  var validatedArgs = mapIndexed(function (value, index) {
    var ruleFn = ruleFns[index];
    return ruleFn(value);
  }, args);

  var hasFailed = (0, _ramda.reduce)(function (acc, value) {
    return isFalse(value) || acc;
  }, false, validatedArgs);

  if (hasFailed) {
    var validationMessages = mapIndexed(function (errorMessageOrBool, index) {
      return isTrue(errorMessageOrBool) ? '' : [fnName + ": argument " + argNames[index] + " fails rule " + vRules[index].name, isBoolean(errorMessageOrBool) ? '' : errorMessageOrBool].join(': ');
    }, validatedArgs).join('\n');

    var errorMessage = ['assertSignature:', validationMessages].join(' ');
    throw errorMessage;
  }

  return !hasFailed;
}

/**
 * Test against a predicate, and throws an exception if the predicate
 * is not satisfied
 * @param {function(*): (Boolean|String)} contractFn Predicate that must be
 * satisfy. Returns true if predicate is satisfied, otherwise return a
 * string to report about the predicate failure
 * @param {Array<*>} contractArgs
 * @param {String} errorMessage
 * @returns {Boolean}
 * @throws
 */
function assertContract(contractFn, contractArgs, errorMessage) {
  var boolOrError = contractFn.apply(null, contractArgs);
  var isPredicateSatisfied = isBoolean(boolOrError) && boolOrError;

  if (!isPredicateSatisfied) {
    throw "assertContract: fails contract " + contractFn.name + "\n" + errorMessage + "\n " + boolOrError;
  }
  return true;
}

/**
 * Returns:
 * - `true` if the object passed as parameter passes all the predicates on
 * its properties
 * - an array with the concatenated error messages otherwise
 * @param obj
 * @param {Object.<string, Predicate>} signature
 * @param {Object.<string, string>} signatureErrorMessages
 * @param {Boolean=false} isStrict When `true` signals that the object
 * should not have properties other than the ones checked for
 * @returns {Boolean | Array<String>}
 */
function checkSignature(obj, signature, signatureErrorMessages, isStrict) {
  var arrMessages = [];
  var strict = !!isStrict;

  // Check that object properties in the signature match it
  (0, _ramda.mapObjIndexed)(function (predicate, property) {
    if (!predicate(obj[property])) {
      arrMessages.push(signatureErrorMessages[property]);
    }
  }, signature);

  // Check that object properties are all in the signature if strict is set
  if (strict) {
    (0, _ramda.mapObjIndexed)(function (value, property) {
      if (!(property in signature)) {
        arrMessages.push("Object cannot contain a property called " + property);
      }
    }, obj);
  }

  return (0, _ramda.isEmpty)(arrMessages) ? true : arrMessages;
}

/**
 * Returns an object whose keys :
 * - the first key found in `obj` for which the matching predicate was
 * fulfilled. Predicates are tested in order of indexing of the array.
 * - `_index` the index in the array where a predicate was fulfilled if
 * any, undefined otherwise
 * Ex : unfoldObjOverload('DOM', {sourceName: isString, predicate:
    * isPredicate})
 * Result : {sourceName : 'DOM'}
 * @param obj
 * @param {Array<Object.<string, Predicate>>} overloads
 * @returns {{}}
 */
function unfoldObjOverload(obj, overloads) {
  var result = {};
  var index = 0;

  overloads.some(function (overload) {
    // can only be one property
    var property = (0, _ramda.keys)(overload)[0];
    var predicate = (0, _ramda.values)(overload)[0];
    var predicateEval = predicate(obj);

    if (predicateEval) {
      result[property] = obj;
      result._index = index;
    }
    index++;

    return predicateEval;
  });
  return result;
}

/**
 * Returns true iff the parameter is a boolean whose value is false.
 * This hence does both type checking and value checking
 * @param obj
 * @returns {boolean}
 */
function isFalse(obj) {
  return isBoolean(obj) ? !obj : false;
}

/**
 * Returns true iff the parameter is a boolean whose value is false.
 * This hence does both type checking and value checking
 * @param obj
 * @returns {boolean}
 */
function isTrue(obj) {
  return isBoolean(obj) ? obj : false;
}

function isMergeSinkFn(obj) {
  return isFunction(obj);
}

/**
 * Returns true iff the passed parameter is null or undefined OR a POJO
 * @param {Object} obj
 * @returns {boolean}
 */
function isNullableObject(obj) {
  // Note that `==` is used instead of `===`
  // This allows to test for `undefined` and `null` at the same time
  return obj == null || (typeof obj === "undefined" ? "undefined" : _typeof(obj)) === 'object';
}

/**
 *
 * @param obj
 * @returns {SignatureCheck}
 */
function isNullableComponentDef(obj) {
  // Note that `==` is used instead of `===`
  // This allows to test for `undefined` and `null` at the same time

  return (0, _ramda.isNil)(obj) || checkSignature(obj, {
    makeLocalSources: (0, _ramda.either)(_ramda.isNil, isFunction),
    makeLocalSettings: (0, _ramda.either)(_ramda.isNil, isFunction),
    makeOwnSinks: (0, _ramda.either)(_ramda.isNil, isFunction),
    mergeSinks: function mergeSinks(_mergeSinks) {
      if (obj.computeSinks) {
        return !_mergeSinks;
      } else {
        return (0, _ramda.either)(_ramda.isNil, (0, _ramda.either)(isObject, isFunction))(_mergeSinks);
      }
    },
    computeSinks: (0, _ramda.either)(_ramda.isNil, isFunction),
    sinksContract: (0, _ramda.either)(_ramda.isNil, isFunction)
  }, {
    makeLocalSources: 'makeLocalSources must be undefined or a function',
    makeLocalSettings: 'makeLocalSettings must be undefined or a' + ' function',
    makeOwnSinks: 'makeOwnSinks must be undefined or a function',
    mergeSinks: 'mergeSinks can only be defined when `computeSinks` is' + ' not, and when so, it must be undefined, an object or a function',
    computeSinks: 'computeSinks must be undefined or a function',
    sinksContract: 'sinksContract must be undefined or a function'
  }, true);
}

function isUndefined(obj) {
  return typeof obj === 'undefined';
}

function isFunction(obj) {
  return typeof obj === 'function';
}

function isObject(obj) {
  return (typeof obj === "undefined" ? "undefined" : _typeof(obj)) == 'object';
}

function isBoolean(obj) {
  return typeof obj == 'boolean';
}

function isString(obj) {
  return typeof obj == 'string';
}

function isArray(obj) {
  return Array.isArray(obj);
}

function isEmptyArray(obj) {
  return (0, _ramda.allPass)([_ramda.isEmpty, isArray])(obj);
}

/**
 * Returns a function which returns true if its parameter is an array,
 * and each element of the array satisfies a given predicate
 * @param {function(*):Boolean} predicateFn
 * @returns {function():Boolean}
 */
function isArrayOf(predicateFn) {
  // TODO : should I throw instead of returning false?? I think I should
  if (typeof predicateFn !== 'function') {
    console.error('isArrayOf: predicateFn is not a function!!');
    return (0, _ramda.always)(false);
  }

  return function _isArrayOf(obj) {
    if (!Array.isArray(obj)) {
      return false;
    }

    return (0, _ramda.all)(predicateFn, obj);
  };
}

function isVNode(obj) {
  return ["children", "data", "elm", "key", "sel", "text"].every(function (prop) {
    return prop in obj;
  });
}

/**
 *
 * @param {Predicate} predicateKey
 * @param {Predicate} predicateValue
 * @returns {Predicate}
 * @throws when either predicate is not a function
 */
function isHashMap(predicateKey, predicateValue) {
  assertContract(isFunction, [predicateKey], 'isHashMap : first argument must be a' + ' predicate function!');
  assertContract(isFunction, [predicateValue], 'isHashMap : second argument must be a' + ' predicate function!');

  return (0, _ramda.both)((0, _ramda.pipe)(_ramda.keys, (0, _ramda.all)(predicateKey)), (0, _ramda.pipe)(_ramda.values, (0, _ramda.all)(predicateValue)));
}

/**
 * check that an object :
 * - does not have any extra properties than the expected ones (strictness)
 * - that its properties follow the defined specs
 * Note that if a property is optional, the spec must include that case
 * @param {Object.<String, Predicate>} recordSpec
 * @returns {Predicate}
 * @throws when recordSpec is not an object
 *
 * Example :
 * - isStrictRecordOf({a : isNumber, b : isString})({a:1, b:'2'}) -> true
 * - isStrictRecordOf({a : isNumber, b : isString})({a:1, b:'2', c:3}) -> false
 * - isStrictRecordOf({a : isNumber, b : isString})({a:1, b:2}) -> false
 */
function isStrictRecord(recordSpec) {
  assertContract(isObject, [recordSpec], 'isStrictRecord : record specification argument must be' + ' a valid object!');

  return (0, _ramda.allPass)([
  // 1. no extra properties, i.e. all properties in obj are in recordSpec
  // return true if recordSpec.keys - obj.keys is empty
  (0, _ramda.pipe)(_ramda.keys, (0, _ramda.flip)(_ramda.difference)((0, _ramda.keys)(recordSpec)), _ramda.isEmpty),
  // 2. the properties in recordSpec all pass their corresponding predicate
  // pipe(obj => mapR(key => recordSpec[key](obj[key]), keys(recordSpec)), all(identity)),
  (0, _ramda.where)(recordSpec)]);
}

/**
 * Returns true iff the parameter `obj` represents a component.
 * @param obj
 * @returns {boolean}
 */
function isComponent(obj) {
  // Without a type system, we just test that it is a function
  return isFunction(obj);
}

function isObservable(obj) {
  // duck typing in the absence of a type system
  return isFunction(obj.subscribe);
}

function isSource(obj) {
  return isObservable(obj);
}

function isSources(obj) {
  // We check the minimal contract which is not to be nil
  // In `cycle`, sources can have both regular
  // objects and observables (sign that the design could be improved).
  // Regular objects are injected dependencies (DOM, router?) which
  // are initialized in the drivers, and should be separated from
  // `sources`. `sources` could then have an homogeneous type which
  // could be checked properly
  return !(0, _ramda.isNil)(obj);
}

function isOptSinks(obj) {
  // obj can be null
  return !obj || (0, _ramda.all)((0, _ramda.either)(_ramda.isNil, isObservable), (0, _ramda.values)(obj));
}

function isArrayOptSinks(arrSinks) {
  return (0, _ramda.all)(isOptSinks, arrSinks);
}

function assertSourcesContracts(sources, sourcesContract) {
  // Check sources contracts
  assertContract(isSources, [sources], 'm : `sources` parameter is invalid');
  assertContract(sourcesContract, [sources], 'm: `sources`' + ' parameter fails contract ' + sourcesContract.name);
}

function assertSinksContracts(sinks, sinksContract) {
  assertContract(isOptSinks, [sinks], 'mergeSinks must return a hash of observable sink');
  assertContract(sinksContract, [sinks], 'fails custom contract ' + sinksContract.name);
}

function assertSettingsContracts(mergedSettings, settingsContract) {
  // Check settings contracts
  assertContract(settingsContract, [mergedSettings], 'm: `settings`' + ' parameter fails contract ' + settingsContract.name);
}

// from https://github.com/substack/deep-freeze/blob/master/index.js
function deepFreeze(o) {
  Object.freeze(o);

  Object.getOwnPropertyNames(o).forEach(function (prop) {
    if (o.hasOwnProperty(prop) && o[prop] !== null && (_typeof(o[prop]) === "object" || typeof o[prop] === "function") && !Object.isFrozen(o[prop])) {
      deepFreeze(o[prop]);
    }
  });

  return o;
}

function makeErrorMessage(errorMessage) {
  return ERROR_MESSAGE_PREFIX + errorMessage;
}

/**
 * Adds `tap` logging/tracing information to all sinks
 * @param {Sinks} sinks
 * @param {Settings} settings Settings with which the parent component is
 * called
 * @returns {*}
 */
function trace(sinks, settings) {
  // TODO BRC
  return sinks;
}

function removeNullsFromArray(arr) {
  return (0, _ramda.reject)(_ramda.isNil, arr);
}

function removeEmptyVNodes(arrVNode) {
  return (0, _ramda.reduce)(function (accNonEmptyVNodes, vNode) {
    return isNullVNode(vNode) ? accNonEmptyVNodes : (accNonEmptyVNodes.push(vNode), accNonEmptyVNodes);
  }, [], arrVNode);
}

function isNullVNode(vNode) {
  return (0, _ramda.equals)(vNode.children, []) && (0, _ramda.equals)(vNode.data, {}) && isUndefined(vNode.elm) && isUndefined(vNode.key) && isUndefined(vNode.sel) && isUndefined(vNode.text);
}

/**
 * For each element object of the array, returns the indicated property of
 * that object, if it exists, null otherwise.
 * For instance, `projectSinksOn('a', obj)` with obj :
 * - [{a: ..., b: ...}, {b:...}]
 * - result : [..., null]
 * @param {String} prop
 * @param {Array<*>} obj
 * @returns {Array<*>}
 */
function projectSinksOn(prop, obj) {
  return (0, _ramda.map)(function (x) {
    return x ? x[prop] : null;
  }, obj);
}

/**
 * Returns an array with the set of sink names extracted from an array of
 * sinks. The ordering of those names should not be relied on.
 * For instance:
 * - [{DOM, auth},{DOM, route}]
 * results in ['DOM','auth','route']
 * @param {Array<Sinks>} aSinks
 * @returns {Array<String>}
 */
function getSinkNamesFromSinksArray(aSinks) {
  return (0, _ramda.uniq)((0, _ramda.flatten)((0, _ramda.map)(getValidKeys, aSinks)));
}

function getValidKeys(obj) {
  var validKeys = [];
  (0, _ramda.mapObjIndexed)(function (value, key) {
    if (value != null) {
      validKeys.push(key);
    }
  }, obj);

  return validKeys;
}

/**
 * Turns a sink which is empty into a sink which emits `Null`
 * This is necessary for use in combination with `combineLatest`
 * As a matter of fact, `combineLatest(obs1, obs2)` will block till both
 * observables emit at least one value. So if `obs2` is empty, it will
 * never emit anything
 * @param sink
 * @returns {Observable|*}
 */
function emitNullIfEmpty(sink) {
  return (0, _ramda.isNil)(sink) ? null : $.merge(sink, sink.isEmpty().filter(function (x) {
    return x;
  }).map(function (x) {
    return null;
  }));
}

function makeDivVNode(x) {
  return {
    "children": undefined,
    "data": {},
    "elm": undefined,
    "key": undefined,
    "sel": "div",
    "text": x
  };
}

function _handleError(msg, e) {
  console.error("" + msg, e);
  throw e;
}
var handleError = (0, _ramda.curry)(_handleError);

//IE workaround for lack of function name property on Functions
//getFunctionName :: (* -> *) -> String
var getFunctionName = function (r) {
  return function (fn) {
    return fn.name || (('' + fn).match(r) || [])[1] || 'Anonymous';
  };
}(/^\s*function\s*([^\(]*)/i);

// cf.
// http://stackoverflow.com/questions/9479046/is-there-any-non-eval-way-to-create-a-function-with-a-runtime-determined-name
function NamedFunction(name, args, body, scope, values) {
  if (typeof args == "string") values = scope, scope = body, body = args, args = [];
  if (!Array.isArray(scope) || !Array.isArray(values)) {
    if ((typeof scope === "undefined" ? "undefined" : _typeof(scope)) == "object") {
      var keys = Object.keys(scope);
      values = keys.map(function (p) {
        return scope[p];
      });
      scope = keys;
    } else {
      values = [];
      scope = [];
    }
  }
  return Function(scope, "function " + name + "(" + args.join(", ") + ") {\n" + body + "\n}\nreturn " + name + ";").apply(null, values);
}

// decorateWith(decoratingFn, fnToDecorate), where log :: fn -> fn such as both have same name
// and possibly throw exception if that make sense to decoratingFn
function decorateWithOne(decoratorSpec, fnToDecorate) {
  var fnToDecorateName = getFunctionName(fnToDecorate);

  return NamedFunction(fnToDecorateName, [], "\n      const args = [].slice.call(arguments);\n      const decoratingFn = makeFunctionDecorator(decoratorSpec);\n      return decoratingFn(args, fnToDecorateName, fnToDecorate);\n", { makeFunctionDecorator: makeFunctionDecorator, decoratorSpec: decoratorSpec, fnToDecorate: fnToDecorate, fnToDecorateName: fnToDecorateName });
}

var decorateWith = (0, _ramda.curry)(function decorateWith(decoratingFnsSpecs, fnToDecorate) {
  return decoratingFnsSpecs.reduce(function (acc, decoratingFn) {
    return decorateWithOne(decoratingFn, acc);
  }, fnToDecorate);
});

/**
 * NOTE : incorrect declaration... TODO : correct one day
 * before(fnToDecorate, fnToDecorateName, args) or nil
 * after(fnToDecorate, fnToDecorateName, result) or nil
 * but not both nil
 * @returns {function(fnToDecorate: Function, fnToDecorateName:String, args:Array<*>)}
 */
function makeFunctionDecorator(_ref) {
  var before = _ref.before,
      after = _ref.after,
      name = _ref.name;

  // we can have one of the two not specified, but if we have none, there is no decorator to make
  if (typeof before !== 'function' && typeof after !== 'function') {
    throw "makeFunctionDecorator: you need to specify 'before' OR 'after' as decorating functions. You passed falsy values for both!";
  }

  var decoratorFnName = (0, _ramda.defaultTo)('anonymousDecorator', name);

  // trick to get the same name for the returned function
  // cf.
  // http://stackoverflow.com/questions/9479046/is-there-any-non-eval-way-to-create-a-function-with-a-runtime-determined-name
  var obj = _defineProperty({}, decoratorFnName, function (args, fnToDecorateName, fnToDecorate) {
    before && before(args, fnToDecorateName, fnToDecorate);

    var result = fnToDecorate.apply(undefined, _toConsumableArray(args));

    return after ? after(result, fnToDecorateName, fnToDecorate) : result;
  });

  return obj[decoratorFnName];
}

var assertFunctionContractDecoratorSpecs = function assertFunctionContractDecoratorSpecs(fnContract) {
  return {
    before: function before(args, fnToDecorateName) {
      var checkDomain = fnContract.checkDomain;
      var contractFnName = getFunctionName(checkDomain);
      var passed = checkDomain.apply(undefined, _toConsumableArray(args));

      if (!isBoolean(passed) || isBoolean(passed) && !passed) {
        // contract is failed
        console.error("assertFunctionContractDecorator: " + fnToDecorateName + " fails contract " + contractFnName + " \n\n" + (isString(passed) ? passed : ''));
        throw "assertFunctionContractDecorator: " + fnToDecorateName + " fails contract " + contractFnName;
      }
    },
    after: function after(result, fnToDecorateName) {
      var checkCodomain = fnContract.checkCodomain;
      var contractFnName = getFunctionName(checkCodomain);
      var passed = checkCodomain(result);

      if (!isBoolean(passed) || isBoolean(passed) && !passed) {
        // contract is failed
        console.error("assertFunctionContractDecorator: " + fnToDecorateName + " fails contract " + contractFnName + " \n\n" + (isString(passed) ? passed : ''));
        throw "assertFunctionContractDecorator: " + fnToDecorateName + " fails contract " + contractFnName;
      }

      return result;
    }
  };
};

var logFnTrace = function logFnTrace(title, paramSpecs) {
  return {
    before: function before(args, fnToDecorateName) {
      return console.info("==> " + title.toUpperCase() + " | " + fnToDecorateName + "(" + paramSpecs.join(', ') + "): ", args);
    },
    after: function after(result, fnToDecorateName) {
      console.info("<== " + title.toUpperCase() + " | " + fnToDecorateName + " <- ", result);
      return result;
    }
  };
};

exports.makeDivVNode = makeDivVNode;
exports.handleError = handleError;
exports.assertSignature = assertSignature;
exports.assertContract = assertContract;
exports.checkSignature = checkSignature;
exports.unfoldObjOverload = unfoldObjOverload;
exports.projectSinksOn = projectSinksOn;
exports.getSinkNamesFromSinksArray = getSinkNamesFromSinksArray;
exports.removeNullsFromArray = removeNullsFromArray;
exports.removeEmptyVNodes = removeEmptyVNodes;
exports.emitNullIfEmpty = emitNullIfEmpty;
exports.isNullableObject = isNullableObject;
exports.isNullableComponentDef = isNullableComponentDef;
exports.isHashMap = isHashMap;
exports.isStrictRecord = isStrictRecord;
exports.isComponent = isComponent;
exports.isUndefined = isUndefined;
exports.isFunction = isFunction;
exports.isVNode = isVNode;
exports.isObject = isObject;
exports.isBoolean = isBoolean;
exports.isString = isString;
exports.isArray = isArray;
exports.isEmptyArray = isEmptyArray;
exports.isArrayOf = isArrayOf;
exports.isObservable = isObservable;
exports.isSource = isSource;
exports.isOptSinks = isOptSinks;
exports.isMergeSinkFn = isMergeSinkFn;
exports.isArrayOptSinks = isArrayOptSinks;
exports.assertSourcesContracts = assertSourcesContracts;
exports.assertSinksContracts = assertSinksContracts;
exports.assertSettingsContracts = assertSettingsContracts;
exports.deepFreeze = deepFreeze;
exports.makeErrorMessage = makeErrorMessage;
exports.trace = trace;
exports.getFunctionName = getFunctionName;
exports.decorateWithOne = decorateWithOne;
exports.decorateWith = decorateWith;
exports.makeFunctionDecorator = makeFunctionDecorator;
exports.assertFunctionContractDecoratorSpecs = assertFunctionContractDecoratorSpecs;
exports.logFnTrace = logFnTrace;