/** * Convenience utility takes a type-safe mapping function, and returns a list mapping function * @param {(param: T) => U} mappingFunction maps a single type T to type U * @return {(array: Array) => Array} */ const arrayMap = (mappingFunction: (param: T) => U): ((array: Array) => Array) => (array = []) => array.map(mappingFunction); /** * Convenience utility takes a type-safe filter function, and returns a list filtering function * @param {(param: T) => boolean} filtrationFunction * @return {(array: Array) => Array} */ const arrayFilter = (filtrationFunction: (param: T) => boolean): ((array: Array) => Array) => (array = []) => array.filter(filtrationFunction); /** * Type safe utility `iterate-first data-last` function for array every * @template T * @param {(param: T) => boolean} filter * @returns {((array: Array) => boolean)} */ const arrayEvery = (filter: (param: T) => boolean): ((array: Array) => boolean) => (array = []) => array.every(filter); /** * Type safe utility `iterate-first data-last` function for array some * @template T * @param {(param: T) => boolean} filter * @return {(array: Array) => boolean} */ const arraySome = (filter: (param: T) => boolean): ((array: Array) => boolean) => (array = []) => array.some(filter); /** * 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} */ const arrayReduce = ( iteratee: (accumulator: U, element: T, index: number, collection: Array) => U, init: U ): ((arr: Array) => U) => (array = []) => array.reduce(iteratee, init); /** * Duplicate check using every to short-circuit iteration * @template T * @param {Array} [list = []] list to check for dupes * @return {boolean} true is unique */ 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} */ 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} */ const chunkArrayAsync = ( iterateeSync: (arr?: Array) => U, accumulator: (res: U) => U, { chunkSize = 50, context = null }: IChunkArrayOptions = { chunkSize: 50, context: null } ) => (list: Array) => new Promise(function(resolve) { const queue = list.slice(0); // creates a shallow copy of the original list const delay = 25; let result: U; setTimeout(function chunk() { const startTime = +new Date(); do { result = accumulator(iterateeSync.call(context, queue.splice(0, chunkSize))); } while (queue.length && +new Date() + startTime < 50); // recurse through list if there are more items left return queue.length ? setTimeout(chunk, delay) : resolve(result); }, delay); }); /** * 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>} */ const iterateArrayAsync = (iteratee: (arr?: Array) => Array) => ( list: Array, context = null ): Promise> => { const accumulator = (base: Array) => (arr: Array) => [...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} */ const reduceArrayAsync = (reducer: (arr?: Array) => U) => (list: Array, context = null): Promise => { const accumulator = (base: U) => (int: U) => Object.assign(base, int); return chunkArrayAsync(reducer, accumulator(reducer.call(context)))(list); }; export { arrayMap, arrayFilter, arrayReduce, isListUnique, compact, arrayEvery, arraySome, iterateArrayAsync, reduceArrayAsync };