topkat-utils
Version:
A comprehensive collection of TypeScript/JavaScript utility functions for common programming tasks. Includes validation, object manipulation, date handling, string formatting, and more. Zero dependencies, fully typed, and optimized for performance.
164 lines (152 loc) • 7.59 kB
text/typescript
//----------------------------------------
// LOOP UTILS
//----------------------------------------
import { ObjectGeneric } from './types'
import { err500IfNotSet } from './error-utils'
import { isObject } from './is-object'
/**
* Iterates over a specified number of times, passing each iteration's result to the next callback
* @returns Array containing results from all iterations
*
* @example
* ```typescript
* // Generate Fibonacci sequence
* forI(8, (i, prev, results) => {
* if (i <= 1) return 1
* return results[i-1] + results[i-2]
* })
* ```
* // Returns: [1, 1, 2, 3, 5, 8, 13, 21]
*/
export function forI<T extends any[] | any>(
/** Number of times to iterate */
nbIterations: number,
/** Function called for each iteration with:
* - number: Current iteration index (0-based)
* - previousValue: Result from previous iteration
* - arrayOfPreviousValues: Array of all previous results
*/
callback: (
/** Current iteration index (0-based) */
number: number,
/** Result from previous iteration */
previousValue,
/** Array of all previous results */
arrayOfPreviousValues: any[]
) => T
): T[] {
const results: any[] = []
for (let i = 0; i < nbIterations; i++) {
const prevValue = results[results.length - 1]
results.push(callback(i, prevValue, results))
}
return results
}
/**
* Iterates over a specified number of times, passing each iteration's result to the next callback
* @returns Array containing results from all iterations
*
* @example
* ```typescript
* // Generate Fibonacci sequence ASYNC
* await forI(8, async (i, prev, results) => {
* if (i <= 1) return 1
* return new Promise(resolve => setTimeout(() => resolve(results[i-1] + results[i-2]), 100))
* })
* ```
* // Returns: [1, 1, 2, 3, 5, 8, 13, 21]
*/
export async function forIasync<T extends any[] | any>(nbIterations: number, callback: (number) => T): Promise<T[]> {
const results: any[] = []
for (let i = 0; i < nbIterations; i++) {
results.push(await callback(i))
}
return results
}
export type RecursiveCallback = (item: any, addr: string, lastElementKey: string | number, parent: ObjectGeneric | any[]) => false | any
export type RecursiveConfig = {
disableCircularDependencyRemoval?: boolean
isObjectTestFunction?: (item: any) => boolean
}
/**
* @param {any} item the first array or object or whatever you want to recursively browse
* @param {function} callback the callback you want to apply on items including the main one
* * this callback has 2 arguments: (item, address) =>
* * `item` => the actual item
* * `addr` => the address of the item, not including root (Eg: subItem1.sub2.[3].[2]) array indexes are juste written as numbers
* * `lastElementKey` => the key of last item. May be a number if last item is an array
* * `parent` => reference the parent object as this is the only way of reassigning a value for the item. Eg: parent[lastElementKey] = myNewItem
* * **NOTE** => if a key of an item contains dots, they will be replaced by '%' in `addr`
* * **NOTE2** => if false is returned by the callback it will stop all other iterations (but not in an array)
* @param {string} addr$ optional, the base address for the callback function
* @param lastElementKey technical field
* NOTE: will remove circular references
* /!\ check return values
*/
export async function recursiveGenericFunction(
/** The object or array you want to recursively browse */
item: ObjectGeneric | any[],
/** The callback you want to apply on items including the main one */
callback: RecursiveCallback,
/** Optional configuration object */
config: RecursiveConfig = {},
/** Optional base address for the callback function */
addr$ = '',
/** Technical field */
lastElementKey: string | number = '',
parent?,
techFieldToAvoidCircularDependency: any[] = []
) {
err500IfNotSet({ callback })
if (!config.isObjectTestFunction) config.isObjectTestFunction = isObject
if (!techFieldToAvoidCircularDependency.includes(item)) {
const result = addr$ === '' ? true : await callback(item, addr$, lastElementKey, parent)
if (result !== false) {
if (Array.isArray(item)) {
if (config?.disableCircularDependencyRemoval !== true) techFieldToAvoidCircularDependency.push(item)
await Promise.all(item.map(
(e, i) => recursiveGenericFunction(e, callback, config, addr$ + '[' + i + ']', i, item, techFieldToAvoidCircularDependency)
))
} else if (config.isObjectTestFunction(item)) {
if (config?.disableCircularDependencyRemoval !== true) techFieldToAvoidCircularDependency.push(item)
await Promise.all(Object.entries(item).map(
([key, val]) => recursiveGenericFunction(val, callback, config, (addr$ ? addr$ + '.' : '') + key.replace(/\./g, '%'), key, item, techFieldToAvoidCircularDependency)
))
}
}
}
return item
}
/**
* @param {any} item the first array or object or whatever you want to recursively browse
* @param {function} callback the callback you want to apply on items including the main one
* * this callback has 2 arguments: (item, address) =>
* * `item` => the actual item
* * `addr` => the address of the item, not including root (Eg: subItem1.sub2.[3].[2]) array indexes are juste written as numbers
* * `lastElementKey` => the key of last item. May be a number if last item is an array
* * `parent` => reference the parent object as this is the only way of reassigning a value for the item. Eg: parent[lastElementKey] = myNewItem
* * **NOTE** => if a key of an item contains dots, they will be replaced by '%' in `addr`
* * **NOTE2** => if false is returned by the callback it will stop all other iterations (but not in an array)
* * **NOTE3** => to reassign a key use => parent[lastElementKey] = myNewItem
* @param {string} addr$ optional, the base address for the callback function
* @param lastElementKey technical field
* NOTE: will remove circular references
* /!\ check return values
*/
export function recursiveGenericFunctionSync(item: ObjectGeneric | any[], callback: RecursiveCallback, config: RecursiveConfig = {}, addr$ = '', lastElementKey: string | number = '', parent?, techFieldToAvoidCircularDependency: any[] = []) {
err500IfNotSet({ callback })
if (!config.isObjectTestFunction) config.isObjectTestFunction = isObject
if (!techFieldToAvoidCircularDependency.includes(item)) {
const result = addr$ === '' ? true : callback(item, addr$, lastElementKey, parent)
if (result !== false) {
if (Array.isArray(item)) {
if (config?.disableCircularDependencyRemoval !== true) techFieldToAvoidCircularDependency.push(item) // do not up one level
item.forEach((e, i) => recursiveGenericFunctionSync(e, callback, config, addr$ + '[' + i + ']', i, item, techFieldToAvoidCircularDependency))
} else if (config.isObjectTestFunction(item)) {
if (config?.disableCircularDependencyRemoval !== true) techFieldToAvoidCircularDependency.push(item)
Object.entries(item).forEach(([key, val]) => recursiveGenericFunctionSync(val, callback, config, (addr$ ? addr$ + '.' : '') + key.replace(/\./g, '%'), key, item, techFieldToAvoidCircularDependency))
}
}
}
return item
}