import { identity, not } from 'wherehows-web/utils/helpers/functions'; /** * Composable function that will in turn consume an item from a list an emit a result of equal or same type * @type Iteratee */ export type Iteratee = (a: A) => R; /** * Aliases a type T or an array of type T * @template T * @alias */ export type Many = T | Array; /** * Takes a number of elements in the list from the start up to the length of the list * @template T type of elements in array * @param {number} [n=0] number of elements to take from the start of the array */ export const take = (n: number = 0): ((list: Array) => Array) => (list: Array): Array => Array.prototype.slice.call(list, 0, n < 0 ? 0 : n); /** * Convenience utility takes a type-safe mapping function, and returns a list mapping function * @template T * @template U * @param {(param: T, index: number, collection: Array) => U} predicate maps a type T to type U * @returns {((array: Array) => Array)} */ export const arrayMap = ( predicate: (param: T, index: number, collection: Array) => U ): ((array: Array) => Array) => (array = []) => array.map(predicate); /** * Iteratee-first data-last each function * @template T type of elements in array * @param {(param: T) => void} predicate iteratee * @returns {((array: Array) => void)} */ export const arrayEach = (predicate: (param: T) => void): ((array: Array) => void) => (array = []) => array.forEach(predicate); /** * Partitions an array into a tuple containing elements that meet the predicate in the zeroth index, * and excluded elements in the next * `iterate-first data-last` function * @template T type of source element list * @template U subtype of T in first partition * @param {(param: T) => param is U} predicate is a type guard function * @returns {((array: Array) => [Array, Array>])} */ export const arrayPartition = ( predicate: (param: T) => param is U ): ((array: Array) => [Array, Array>]) => (array = []) => [ array.filter(predicate), array.filter>((v: T): v is Exclude => not(predicate)(v)) ]; /** * Convenience utility takes a type-safe filter function, and returns a list filtering function * @param {(param: T) => boolean} predicate * @return {(array: Array) => Array} */ export const arrayFilter = (predicate: (param: T) => boolean): ((array: Array) => Array) => (array = []) => array.filter(predicate); /** * Type safe utility `iterate-first data-last` function for array every * @template T * @param {(param: T) => boolean} predicate * @returns {((array: Array) => boolean)} */ export const arrayEvery = (predicate: (param: T) => boolean): ((array: Array) => boolean) => (array = []) => array.every(predicate); /** * Type safe utility `iterate-first data-last` function for array some * @template T * @param {(param: T) => boolean} predicate * @return {(array: Array) => boolean} */ export const arraySome = (predicate: (param: T) => boolean): ((array: Array) => boolean) => (array = []) => array.some(predicate); /** * Composable reducer abstraction, curries a reducing iteratee and returns a reducing function that takes a list * @template U * @param {(acc: U) => U} iteratee * @param {U} init the initial value in the reduction sequence * @return {(arr: Array) => U} */ export const arrayReduce = ( iteratee: (accumulator: U, element: T, index: number, collection: Array) => U, init: U ): ((arr: Array) => U) => (array = []) => array.reduce(iteratee, init); // arrayPipe overloads export function arrayPipe(f1: (a1: T) => R1): (x: T) => R1; export function arrayPipe(f1: (a1: T) => R1, f2: (a2: R1) => R2): (x: T) => R2; export function arrayPipe( f1: (a1: T) => R1, f2: (a2: R1) => R2, f3: (a3: R2) => R3 ): (x: T) => R3; export function arrayPipe( f1: (a1: T) => R1, f2: (a2: R1) => R2, f3: (a3: R2) => R3, f4: (a4: R3) => R4 ): (x: T) => R4; export function arrayPipe( f1: (a1: T) => R1, f2: (a2: R1) => R2, f3: (a3: R2) => R3, f4: (a4: R3) => R4, f5: (a5: R4) => R5 ): (x: T) => R5; export function arrayPipe( f1: (a1: T) => R1, f2: (a2: R1) => R2, f3: (a3: R2) => R3, f4: (a4: R3) => R4, f5: (a5: R4) => R5, f6: (a6: R5) => R6 ): (x: T) => R6; export function arrayPipe( f1: (a1: T) => R1, f2: (a2: R1) => R2, f3: (a3: R2) => R3, f4: (a4: R3) => R4, f5: (a5: R4) => R5, f6: (a6: R5) => R6, f7: (a7: R6) => R7 ): (x: T) => R7; /** * overload to handle case of too many functions being piped, provides less type safety once args exceeds 7 iteratees * @param {(a1: T) => R1} f1 * @param {(a2: R1) => R2} f2 * @param {(a3: R2) => R3} f3 * @param {(a4: R3) => R4} f4 * @param {(a5: R4) => R5} f5 * @param {(a6: R5) => R6} f6 * @param {(a7: R6) => R7} f7 * @param {Many>} fns * @return {(arg: T) => any} */ export function arrayPipe( f1: (a1: T) => R1, f2: (a2: R1) => R2, f3: (a3: R2) => R3, f4: (a4: R3) => R4, f5: (a5: R4) => R5, f6: (a6: R5) => R6, f7: (a7: R6) => R7, // eslint-disable-next-line @typescript-eslint/no-explicit-any ...fns: Array>> ): // eslint-disable-next-line @typescript-eslint/no-explicit-any (arg: T) => any; /** * Takes a list (array / separate args) of iteratee functions, with each successive iteratee is * invoked with the result of the previous iteratee invocation * @template T the type of elements in the array * @template R the result of executing the last iteratee * @param {Many>} fns * @return {(x: T) => R} */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function arrayPipe(...fns: Array>>): (x: T) => R { // eslint-disable-next-line @typescript-eslint/no-explicit-any return arrayReduce<(a: T) => any, (x: any) => R>((acc, f) => (x): R => acc(f(x)), identity)( // eslint-disable-next-line @typescript-eslint/no-explicit-any ([] as Array>).concat(...fns.reverse()) // flatten if arg is of type Array<> ); } /** * Duplicate check using every to short-circuit iteration * @template T * @param {Array} [list = []] list to check for dupes * @return {boolean} true is unique */ export const isListUnique = (list: Array = []): boolean => new Set(list).size === list.length; /** * Extracts all non falsey values from a list. * @template T * @param {Array} list the list of items to compact * @return {Array} */ export const compact = (list: Array = []): Array => list.filter(item => item); /** * Defines the interface for options that may be passed into the chunk function * @interface {IChunkArrayOptions} */ interface IChunkArrayOptions { chunkSize?: 50 | 100; context?: null | object; } /** * Asynchronously traverses a list in small chunks ensuring that a list can be iterated over without * blocking the browser main thread. * @template T type of values in list to be iterated over * @template U the type of the value that is produced by an iteration of the list * @param {(arr?: Array) => U} iterateeSync an iteratee that consumes an list and returns a value of type U * @param {(res: U) => U} accumulator a function that combines the result of successive iterations of the original list * @param {IChunkArrayOptions} [{chunkSize = 50, context = null}={chunkSize: 50, context: null}] * @param {50 | 100} chunkSize the maximum size to chunk at a time * @param {object | null} [context] the optional execution context for the iteratee invocation * @return {(list: Array) => Promise} */ export const chunkArrayAsync = ( iterateeSync: (arr?: Array) => U, accumulator: (res: U) => U, { chunkSize = 50, context = null }: IChunkArrayOptions = { chunkSize: 50, context: null } ): ((list: Array) => Promise) => (list: Array) => new Promise(function(resolve) { const queue = list.slice(0); // creates a shallow copy of the original list let result: U; requestAnimationFrame(function chunk() { const startTime = +new Date(); do { result = accumulator(iterateeSync.call(context, queue.splice(0, chunkSize))); } while (queue.length && +new Date() + startTime < 18); // recurse through list if there are more items left return queue.length ? requestAnimationFrame(chunk) : resolve(result); }); }); /** * Asynchronously traverse a list and accumulate another list based on the iteratee * @template T the type of values in the original list * @template U the type of values in the transformed list * @param {(arr?: Array) => Array} iteratee consumes a list and returns a new list of values * @return {(list: Array, context?: any) => Promise>} */ export const iterateArrayAsync = ( iteratee: (arr?: Array) => Array ): ((list: Array, context?: null) => Promise>) => (list: Array, context = null): Promise> => { const accumulator = (base: Array): ((arr: Array) => Array) => (arr: Array) => (base = [...base, ...arr]); return chunkArrayAsync(iteratee, accumulator([]), { chunkSize: 50, context })(list); }; /** * Asynchronously traverse a list and accumulate a value of type U, applies to cases of reduction or accumulation * @template T the type of values in the original list * @template U the type of value to be produced by reducing the list * @param {(arr?: Array) => U} reducer consumes a list and produces a single value * @return {(list: Array, context?: any) => Promise} */ export const reduceArrayAsync = ( reducer: (arr?: Array) => U ): ((list: Array, context?: null) => Promise) => (list: Array, context = null): Promise => { const accumulator = (base: U): ((int: U) => U) => (int: U) => Object.assign(base, int); return chunkArrayAsync(reducer, accumulator(reducer.call(context)))(list); };