const { isEmpty, pipe, all, equals, isNil, assoc, curry, is, prop, dissoc, last, append, reduce, map, identity, allPass, filter, complement } = require("ramda");
/**
* @function notEmpty
* @description
* Complement of <code>isEmpty</code>. Returns <code>false</code> if the given value is its type's empty value; <code>true</code> otherwise.
*
* @param {*} val The value to test.
* @returns {Boolean} <code>false</code> if the object is empty, <code>true</code> otherwise.
*
* @see http://ramdajs.com/docs/#isEmpty
*
* @example
* notEmpty( [1, 2, 3] ); //=> true
* notEmpty( [] ); //=> false
* notEmpty( "" ); //=> false
* notEmpty( null ); //=> true
* notEmpty( {} ); //=> false
* notEmpty( {length: 0} ); //=> true
*/
const notEmpty = complement( isEmpty );
/**
* @function notNil
* @description
* Complement of <code>isNil</code>. Returns <code>true</code> if the given value is **not** <code>null</code> or <code>undefined</code>, <code>false</code> otherwise.
*
* @param {*} val The value to test.
* @returns {Boolean} <code>false</code> if the object is <code>null</code> or <code>undefined</code>, <code>false</code> otherwise.
*
* @see http://ramdajs.com/docs/#isNil
*
* @example
* notNil( null ); //=> false
* notNil( undefined ); //=> false
* notNil( 0 ); //=> true
* notNil( [] ); //=> true
*/
const notNil = complement( isNil );
/**
* @function allEqual
* @description
* Composition of <code>all</code> and <code>equals</code>.
* Returns <code>true</code> if all the values in the given list are equal to the value to test against.
*
* @param {*} val The value to test equality against.
* @param {List} list The list we wish to check.
* @returns {Boolean} <code>true</code> if all the values in the list equal <code>val</code>, <code>false</code> otherwise.
*
* @see http://ramdajs.com/docs/#all
* @see http://ramdajs.com/docs/#equal
*
* @example
* allEqual( 1, [1, 1, 1] ); //=> true
* allEqual( 1, [] ); //=> true
* allEqual( true, [true, false, true] ); //=> false
*/
const allEqual = curry(
( value, list ) => all( equals( value ), list )
);
/**
* @function isFunction
* @description
* Returns <code>true</code> of the given value is a Function, <code>false</code> otherwise.
*
* @param {*} val The value to test if it is a Function.
* @returns {Boolean} <code>true</code> if <code>val</code> is a Function, <code>false</code> otherwise.
*
* @see http://ramdajs.com/docs/#is
*
* @example
* isFunction( console.log ); //=> true
* isFunction( 1 ); //=> false
* isFunction( "" ); //=> false
* isFunction( [] ); //=> false
* isFunction( new Map() ); //=> false
*/
const isFunction = is( Function );
/**
* @function isNumber
* @description
* Returns <code>true</code> of the given value is a Number, <code>false</code> otherwise.
*
* @param {*} val The value to test if it is a Number.
* @returns {Boolean} <code>true</code> if <code>val</code> is a Number, <code>false</code> otherwise.
*
* @see http://ramdajs.com/docs/#is
*
* @example
* isNumber( console.log ); //=> false
* isNumber( 1 ); //=> true
* isNumber( "" ); //=> false
* isNumber( [] ); //=> false
* isNumber( new Map() ); //=> false
*/
const isNumber = is( Number );
/**
* @function isString
* @description
* Returns <code>true</code> of the given value is a String, <code>false</code> otherwise.
*
* @param {*} val The value to test if it is a String.
* @returns {Boolean} <code>true</code> if <code>val</code> is a String, <code>false</code> otherwise.
*
* @see http://ramdajs.com/docs/#is
*
* @example
* isString( console.log ); //=> false
* isString( 1 ); //=> false
* isString( "" ); //=> true
* isString( [] ); //=> false
* isString( new Map() ); //=> false
*/
const isString = is( String );
/**
* @function isArray
* @description
* Returns <code>true</code> of the given value is an Array, <code>false</code> otherwise.
*
* @param {*} val The value to test if it is an Array.
* @returns {Boolean} <code>true</code> if <code>val</code> is an Array, <code>false</code> otherwise.
*
* @see http://ramdajs.com/docs/#is
*
* @example
* isArray( console.log ); //=> false
* isArray( 1 ); //=> false
* isArray( "" ); //=> false
* isArray( [] ); //=> true
* isArray( new Map() ); //=> false
*/
const isArray = is( Array );
/**
* @function isMap
* @description
* Returns <code>true</code> of the given value is a Map, <code>false</code> otherwise.
*
* @param {*} val The value to test if it is a Map.
* @returns {Boolean} <code>true</code> if <code>val</code> is a Map, <code>false</code> otherwise.
*
* @see http://ramdajs.com/docs/#is
*
* @example
* isMap( console.log ); //=> false
* isMap( 1 ); //=> false
* isMap( "" ); //=> false
* isMap( [] ); //=> false
* isMap( new Map() ); //=> true
*/
const isMap = is( Map );
/**
* @function compact
* @description
* Returns a copy of the array with all falsy and empty values removed.
*
* @param {List} list The list to compact.
* @returns {List} The new compacted list.
*
* @example
* compact( [0, 1, false, 2, '', 3, [], {}] ); //=> [1, 2, 3]
*/
const compact = filter(
allPass([identity, notEmpty])
);
/**
* @function assocTrans
* @description
* Makes a shallow clone of the target object, setting or overriding the specified property with the result of the given function over the target object.
* Note that this copies and flattens prototype properties onto the new object as well. All non-primitive properties are copied by reference.
*
* @param {String} prop The property name to set.
* @param {Function} fn The transformation function to apply to <code>obj</code>. The result of the evaluation of this function will be the value of <code>prop</code>.
* @param {Object} obj The object to clone.
* @returns {Object} A new object equivalent to <code>obj</code> but with <code>prop</code> changed to have the result of <code>fn</code> as a value.
*
* @see http://ramdajs.com/docs/#assoc
*
* @example
* const getMean = pipe( prop( "earnings" ), mean );
* assocTrans( "mean", getMean, { earnings: [1, 2, 3], trimester: 1 } ); //=> { earnings: [1, 2, 3], trimester: 1, mean: 2 };
*/
const assocTrans = curry(
( prop, transformation, object ) => assoc( prop, transformation( object ), object )
);
/**
* @function renameProp
* @description
* Makes a shallow clone of the target object, renaming the given property.
* Note that this copies and flattens prototype properties onto the new object as well. All non-primitive properties are copied by reference.
*
* @param {String} oldPropName The property to be renamed.
* @param {String} newPropName The new name of the property.
* @param {Object} obj The object to clone.
* @returns {Object} A new object equivalent to <code>obj</code> but with the given property renamed.
*
* @example
* renameProp( "tree", "plant", { tree: "sunflower" } ); //=> { plant: "sunflower" };
*/
const renameProp = curry(
( oldPropName, newPropName, object ) => pipe(
assocTrans( newPropName, prop(oldPropName) ),
dissoc( oldPropName )
)( object )
);
/**
* @function mapToSequentialPromises
* @description
* Applies <code>fn</code> sequentially to each element of <code>list</code>.
* If <code>fn</code> is async and returns a Promise, then each Promise will be executed only when the previous Promise has resolved.
* If <code>fn</code> does not return a Promise, then its result is lifted into one.
*
* @param {Function} fn The function to apply to each element of <code>list</code>.
* @param {List} list The list to be iterated over sequentially.
* @returns {List} A list of Promises with the results of applying <code>fn</code> to <code>list</code>.
*
* @example
* const waitAndPrint = delay( 1000, console.log );
* mapToSequentialPromises( waitAndPrint, [1, 2, 3] ); //=> 1, 2, 3 ( each with 1000ms of delay between )
*/
const mapToSequentialPromises = curry(
( transformationFn, dataArray ) =>
reduce(
( array, currData ) => {
const next = isEmpty( array ) ?
Promise.resolve( transformationFn( currData ) ) :
last(array).then( () => transformationFn( currData ) );
return append( next, array );
},
[ ],
dataArray
)
);
/**
* @function mapAsyncFn
* @description
* Applies <code>fn</code> concurrently to each element of <code>list</code>.
* Unlike <code>mapToSequentialPromises</code> the execution of Promise N+1 will not wait for the resolution of Promise N.
* Finishes once all the Promises are done via use of Promise.all.
*
* @param {Function} fn The function to apply to each element of <code>list</code>.
* @param {List} list The list to be iterated over concurrently.
* @returns {Promise} A promise containing the new list.
*
* @example
* const double = x => x * 2;
* const waitAndDouble = delay( 1000, double );
* mapAsyncFn( waitAndDouble, [1, 2, 3] ).then( console.log ); //=> 2, 4, 6 ( all at the same time, 1 second after `mapAsyncFn` is executed )
*/
const mapAsyncFn = curry(
( asyncFn, list ) => Promise.all( map( asyncFn, list ) )
);
/**
* @function seedlessReduce
* @description
* Reduce transformation using the head of the given list as initial input.
*
* @param {Function} fn The reduce function.
* @param {List} list List of values to reduce.
* @returns {*} The result of the iteration function.
*
* @example
* const add = ( x, y ) => x + y;
* seedlessReduce( add, [ 1, 2, 3 ] ); //=> returns 6
*/
const seedlessReduce = curry(
( fn, list ) => list.reduce( fn )
);
/**
* @function promiseAll
* @description
* Alias for Promise.all. Receives a list of promises and waits for all of them to complete or for one to reject.
* Returns a single Promise.
*
* @param {List} pList The list of promises.
* @returns {Promise} A single promise containing the list of results if every promise in <code>pList</code> was fulfilled.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
*
* @example
* promiseAll( [ Promise.resolve(1), Promise.resolve(2) ] ).then( console.log ); //=> [1, 2]
*/
const promiseAll = pList => Promise.all( pList );
/**
* @function then
* @description
* Executes the given function after <code>promise</code> finishes resolving.
*
* @param {Function} fn The function to execute after <code>promise</code>.
* @param {Promise} promise The promise to run.
* @returns {Promise} A promise with the return value of <code>fn</code>.
*
* @example
* then( () => console.log("hello"), wait(1000) ); //=> Prints 'hello' after 1 second
*/
const then = curry(
( fn, promise ) => promise.then( fn )
);
/**
* @function wait
* @description
* Equivalent of `setTimeout` but in Promise format.
*
* @param {Number} ms The amount of milliseconds to wait.
* @returns {Promise} An empty promise that always resolves.
*
* @example
* const sayHello = ( ) => console.log( "hello" );
* wait( 1000 ).then( sayHello ); //=> print 'hello' after 1 second
*/
const wait = ms => new Promise( resolve => setTimeout( resolve, ms ) );
/**
* @function delay
* @description
* Waits `ms` milliseconds and then executes the given function with the given data.
*
* @param {Number} ms The amount of milliseconds to wait.
* @param {Function} fn The function to execute after `ms` milliseconds have passed.
* @param {*} data The data to be passed to `fn` for it to execute.
* @returns {Promise} A promise with the execution result of `fn`.
*
* @example
* const double = x => x * 2;
* const waitAndDouble = delay( 1000, double );
*
* waitAndDouble( 2 ).then( console.log ); //=> prints 4 after 1 second
*/
const delay = curry(
( ms, fn, data ) => wait( ms ).then( ( ) => fn( data ) )
);
module.exports = Object.freeze( {
notEmpty,
allEqual,
notNil,
assocTrans,
isFunction,
isNumber,
isMap,
isString,
isArray,
renameProp,
mapToSequentialPromises,
promiseAll,
mapAsyncFn,
compact,
seedlessReduce,
then,
wait,
delay
} );