@rzl-zone/utils-js
Version:
A modern, lightweight set of JavaScript utility functions with TypeScript support for everyday development, crafted to enhance code readability and maintainability.
145 lines (140 loc) • 7.29 kB
TypeScript
/*!
* ====================================================
* Rzl Utils-JS.
* ----------------------------------------------------
* Version: 3.11.0.
* Author: Rizalvin Dwiky.
* Repository: https://github.com/rzl-zone/utils-js.
* ====================================================
*/
import { NumberRangeUnion } from '@rzl-zone/ts-types-plus';
/** ----------------------------------------------------------------------
* * ***Utility: `findDuplicates`.***
* ----------------------------------------------------------------------
* **Finds duplicate values in an array by deep equality comparison.**
* - **Behavior:**
* - Uses ***`isEqual` utility function*** to compare elements
* (handles objects, arrays, dates, NaN, etc.).
* - Returns a new array containing only the *first occurrences* of duplicated values.
* - Does **not mutate** the original array.
* - Throws ***{@link TypeError | `TypeError`}*** if input is not an array.
* @template T Type of elements in the input array.
* @param {T[]} values - The array to check for duplicates.
* @returns {T[]} An array of the duplicate values found in the input,
* preserving order of their first duplicate appearance.
* @throws **{@link TypeError | `TypeError`}** if the provided `values` argument is not an array.
* @example
* findDuplicates([1, 2, 2, 3, 4, 4]);
* // ➔ [2, 4]
* findDuplicates(["apple", "banana", "apple", "orange"]);
* // ➔ ["apple"]
* findDuplicates([{ a: 1 }, { a: 1 }, { a: 2 }]);
* // ➔ [{ a: 1 }]
* findDuplicates([NaN, NaN, 1]);
* // ➔ [NaN]
* findDuplicates([true, false, true]);
* // ➔ [true]
* findDuplicates([1, 2, 3]);
* // ➔ []
*/
declare const findDuplicates: <T>(values: T[]) => T[];
/** --------------------------------
* * ***Utility: `omitKeys`.***
* --------------------------------
* **This function creates a shallow copy of the given object omitting the
* specified keys.**
* - **Behavior:**
* - It will return a new object without mutating the original.
* - It also validates that ***`keysToOmit`*** does not contain duplicate keys.
* - **ℹ️ Internally:**
* - It uses ***`isEqual`*** to check for duplicates in
* the ***`keysToOmit`*** array.
* @template I The type of the input object.
* @template K The keys to omit from the object.
* @param {I} object - The source object to omit keys from.
* @param {K[]} keysToOmit - An array of keys to exclude from the returned object.
* @returns {Omit<I, K>} A new object without the specified keys.
* @throws **{@link TypeError | `TypeError`}** if `keysToOmit` is not an array.
* @throws **{@link Error | `Error`}** if duplicate keys are found in `keysToOmit`.
* @example
* omitKeys({ a: 1, b: 2, c: 3 }, ["b", "c"]);
* //➔ { a: 1 }
* omitKeys({ name: "John", age: 30 }, ["age"]);
* //➔ { name: "John" }
* omitKeys({ a: 1, b: 2 }, []);
* //➔ { a: 1, b: 2 } (no changes)
*/
declare const omitKeys: <I extends Record<string, unknown>, K extends keyof I>(object: I, keysToOmit: K[]) => Omit<I, K>;
type IndexArray = NumberRangeUnion<0, 30>;
type DotPath<T, Prev extends string = ""> = T extends Array<infer U> ? DotPath<U, `${Prev}${Prev extends "" ? "" : "."}${IndexArray}`> : T extends object ? {
[K in keyof T & string]: `${Prev}${Prev extends "" ? "" : "."}${K}` | DotPath<T[K], `${Prev}${Prev extends "" ? "" : "."}${K}`>;
}[keyof T & string] : never;
/** ------------------------------------------------------
* * ***Utility: `omitKeysDeep`.***
* ------------------------------------------------------
* **Recursively omits properties from an object using dot notation paths.**
* - **Behavior:**
* - Removes resulting empty objects (`{}`) and arrays (`[]`), cascading upwards
* to remove empty parents until root if needed.
* - **⚠️ Be careful:**
* - If after omission an object or array becomes empty, it will be removed entirely
* including all the way up to the root if necessary, resulting in `{}`.
* - **ℹ️ Note:**
* - For array indices, TypeScript autocomplete only suggests `0`–`30`
* (to prevent editor lag on large unions).
* However, higher indices are still fully supported at runtime — you can
* manually type `"arr.99.key"` and it will work the same.
* @template I - Type of the input object
* @param {I} object
* The object to process, should be a plain nested object or array structure.
* @param {DotPath<I>[]} keysToOmit
* An array of string paths in dot notation indicating the properties to remove, paths
* can include numeric indices to target array elements, e.g. `"arr.0.x"` to
* remove `x` from the first object inside the `arr` array.
* @returns {Partial<I>}
* A new deeply cloned object with the specified keys omitted, with resulting
* empty objects or arrays fully removed (even if it collapses to `{}`).
* @throws **{@link TypeError | `TypeError`}** if `keysToOmit` is not an array.
* @throws **{@link Error | `Error`}** if `keysToOmit` contains duplicate paths.
* @example
* omitKeysDeep({ arr: [{ a: 1 }] }, ["arr.0.a"]);
* // ➔ {} (array becomes empty and removed)
* omitKeysDeep({ a: { b: { c: 1 }, d: 2 }, e: 3 }, ["a.b.c"]);
* // ➔ { a: { d: 2 }, e: 3 }
* omitKeysDeep({ a: [{ b: 1 }, { c: 2 }] }, ["a.0.b"]);
* // ➔ { a: [{ c: 2 }] }
* omitKeysDeep({ a: [{ b: 1 }] }, ["a.0.b"]);
* // ➔ {} (array becomes empty and removed)
* omitKeysDeep({ complex: [{ deep: [{ x: 1, y: 2 }] }] }, ["complex.0.deep.0.x"]);
* // ➔ { complex: [{ deep: [{ y: 2 }] }] }
* omitKeysDeep({ complex: [{ deep: [{ x: 1 }] }] }, ["complex.0.deep.0.x"]);
* // ➔ {} (deep chain emptied and collapsed)
* omitKeysDeep({ data: [[{ foo: 1, bar: 2 }]] }, ["data.0.0.foo"]);
* // ➔ { data: [[{ bar: 2 }]] }
* omitKeysDeep({ data: [[{ foo: 1 }]] }, ["data.0.0.foo"]);
* // ➔ {} (nested arrays emptied completely)
* omitKeysDeep({ x: [{ y: [{ z: 1 }, { w: 2 }] }] }, ["x.0.y.0.z"]);
* // ➔ { x: [{ y: [{ w: 2 }] }] }
* omitKeysDeep({ x: [{ y: [{ z: 1 }] }] }, ["x.0.y.0.z"]);
* // ➔ {} (entire nested arrays removed)
* omitKeysDeep({ p: { q: { r: 5 } }, s: 6 }, ["p.q.r"]);
* // ➔ { s: 6 } (`p` removed because it becomes empty)
* omitKeysDeep({ arr: [{ a: 1, b: 2 }, { c: 3 }] }, ["arr.0.a"]);
* // ➔ { arr: [{ b: 2 }, { c: 3 }] }
* omitKeysDeep({ root: [{ sub: [{ leaf: 10 }] }] }, ["root.0.sub.0.leaf"]);
* // ➔ {} (deep nested arrays emptied to root)
* omitKeysDeep({ meta: { tags: ["x", "y"], count: 2 } }, ["meta.count"]);
* // ➔ { meta: { tags: ["x", "y"] } }
* omitKeysDeep({ arr: [[{ a: 1 }, { b: 2 }]] }, ["arr.0.0.a"]);
* // ➔ { arr: [[{ b: 2 }]] }
* omitKeysDeep({ arr: [[{ a: 1 }]] }, ["arr.0.0.a"]);
* // ➔ {} (double nested emptied)
* omitKeysDeep({ nested: [{ list: [{ id: 1, val: 2 }] }] }, ["nested.0.list.0.val"]);
* // ➔ { nested: [{ list: [{ id: 1 }] }] }
* omitKeysDeep({ nested: [{ list: [{ id: 1 }] }] }, ["nested.0.list.0.id"]);
* // ➔ {} (full collapse to empty)
* omitKeysDeep({ mixed: { a: [1, 2, 3], b: { c: 4 } } }, ["mixed.b.c"]);
* // ➔ { mixed: { a: [1, 2, 3] } }
*/
declare const omitKeysDeep: <I extends Record<string, unknown>>(object: I, keysToOmit: DotPath<I>[]) => Partial<I>;
export { findDuplicates, omitKeys, omitKeysDeep };