Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
{
"name": "es5-full",
"path": "lib/dist/es5/mod/ts-utils.js",
"limit": "22.5 kb",
"limit": "23.5 kb",
"brotli": false,
"running": false
},
{
"name": "es6-full",
"path": "lib/dist/es6/mod/ts-utils.js",
"limit": "21.5 kb",
"limit": "22.5 kb",
"brotli": false,
"running": false
},
Expand All @@ -23,21 +23,21 @@
{
"name": "es6-full-brotli",
"path": "lib/dist/es6/mod/ts-utils.js",
"limit": "8 kb",
"limit": "8.5 kb",
"brotli": true,
"running": false
},
{
"name": "es5-zip",
"path": "lib/dist/es5/mod/ts-utils.js",
"limit": "9 Kb",
"limit": "9.5 Kb",
"gzip": true,
"running": false
},
{
"name": "es6-zip",
"path": "lib/dist/es6/mod/ts-utils.js",
"limit": "9 Kb",
"limit": "9.5 Kb",
"gzip": true,
"running": false
},
Expand Down
6 changes: 3 additions & 3 deletions README.md

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions lib/src/array/chunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* @nevware21/ts-utils
* https://github.com/nevware21/ts-utils
*
* Copyright (c) 2026 NevWare21 Solutions LLC
* Licensed under the MIT license.
*/

import { isArrayLike } from "../helpers/base";
import { arrForEach } from "./forEach";

/**
* The arrChunk() method returns a new array with elements divided into groups of a specified size.
* The last group may have fewer elements if the array length is not divisible by the chunk size.
* @function
* @since 0.13.0
* @group Array
* @group ArrayLike
* @typeParam T - Identifies the base type of array elements
* @param theArray - The array or array-like object to chunk
* @param size - The size of each chunk. Must be a positive integer
* @returns A new array of chunks, where each chunk is an array of the specified size
* @example
* ```ts
* arrChunk([1, 2, 3, 4, 5, 6, 7], 2); // [[1, 2], [3, 4], [5, 6], [7]]
* arrChunk([1, 2, 3, 4, 5], 3); // [[1, 2, 3], [4, 5]]
* arrChunk([1, 2, 3], 1); // [[1], [2], [3]]
* arrChunk([1, 2, 3], 5); // [[1, 2, 3]]
* arrChunk([], 2); // []
*
* // Array-like objects
* arrChunk({ length: 5, 0: "a", 1: "b", 2: "c", 3: "d", 4: "e" }, 2);
* // [["a", "b"], ["c", "d"], ["e"]]
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function arrChunk<T>(theArray: ArrayLike<T> | null | undefined, size: number): T[][] {
const result: T[][] = [];

if (isArrayLike(theArray) && size > 0) {
let idx = 0;
let chunkIdx = 0;

arrForEach(theArray, (item) => {
if (idx % size === 0) {
result.push([]);
chunkIdx = result.length - 1;
Comment on lines +42 to +47
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable chunkIdx is recalculated as result.length - 1 on every chunk creation, but it could be simplified. Since chunks are added sequentially, you could just increment chunkIdx or track it more efficiently. However, the current implementation is clear and the performance impact is negligible, so this is a very minor optimization opportunity rather than a bug.

Suggested change
let chunkIdx = 0;
arrForEach(theArray, (item) => {
if (idx % size === 0) {
result.push([]);
chunkIdx = result.length - 1;
let chunkIdx = -1;
arrForEach(theArray, (item) => {
if (idx % size === 0) {
result.push([]);
chunkIdx++;

Copilot uses AI. Check for mistakes.
}

result[chunkIdx].push(item);
idx++;
});
}

return result;
}
46 changes: 46 additions & 0 deletions lib/src/array/compact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* @nevware21/ts-utils
* https://github.com/nevware21/ts-utils
*
* Copyright (c) 2026 NevWare21 Solutions LLC
* Licensed under the MIT license.
*/

import { isArrayLike } from "../helpers/base";
import { arrForEach } from "./forEach";

/**
* The arrCompact() method returns a new array with all falsy values removed.
* Falsy values include: false, 0, -0, 0n, "", null, undefined, and NaN.
* @function
* @since 0.13.0
* @group Array
* @group ArrayLike
* @typeParam T - Identifies the base type of array elements
* @param theArray - The array or array-like object to compact
* @returns A new array with all falsy values filtered out
* @example
* ```ts
* arrCompact([0, 1, false, 2, "", 3, null, undefined, 4]); // [1, 2, 3, 4]
* arrCompact([false, 0, "", null, undefined]); // []
* arrCompact([1, 2, 3]); // [1, 2, 3]
* arrCompact([]); // []
*
* // Array-like objects
* arrCompact({ length: 5, 0: 0, 1: 1, 2: false, 3: 2, 4: null }); // [1, 2]
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function arrCompact<T>(theArray: ArrayLike<T | null | undefined | false | 0 | ""> | null | undefined): T[] {
const result: T[] = [];

if (isArrayLike(theArray)) {
arrForEach(theArray, (item) => {
if (item) {
result.push(item);
}
});
}

return result;
}
67 changes: 67 additions & 0 deletions lib/src/array/flatten.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* @nevware21/ts-utils
* https://github.com/nevware21/ts-utils
*
* Copyright (c) 2026 NevWare21 Solutions LLC
* Licensed under the MIT license.
*/

import { isArray, isArrayLike, isUndefined } from "../helpers/base";
import { arrForEach } from "./forEach";

function _addItems(result: any[], arr: any, d: number): void {
const arrLen = arr.length;
let arrIdx = 0;
while (arrIdx < arrLen) {
const item = arr[arrIdx];
if (d > 0 && isArray(item)) {
_addItems(result, item, d - 1);
} else {
result.push(item);
}
arrIdx++;
}
}


/**
* The arrFlatten() method returns a new array with all sub-array elements flattened
* up to the specified depth (default 1).
* @function
* @since 0.13.0
* @group Array
* @group ArrayLike
* @typeParam T - Identifies the base type of array elements
* @param theArray - The array or array-like object to flatten
* @param depth - The flattening depth, defaults to 1. Use Infinity for complete flattening
* @returns A new flattened array
* @example
* ```ts
* arrFlatten([1, [2, 3], [4, [5, 6]]]); // [1, 2, 3, 4, [5, 6]]
* arrFlatten([1, [2, 3], [4, [5, 6]]], 2); // [1, 2, 3, 4, 5, 6]
* arrFlatten([1, [2, 3], [4, [5, 6]]], Infinity); // [1, 2, 3, 4, 5, 6]
* arrFlatten([1, 2, 3]); // [1, 2, 3]
* arrFlatten([]); // []
*
* // With array-like objects
* arrFlatten({ length: 2, 0: 1, 1: [2, 3] }); // [1, 2, 3]
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function arrFlatten<T>(theArray: ArrayLike<T | any[]> | null | undefined, depth?: number): any[] {
const result: any[] = [];

if (isArrayLike(theArray)) {
const d = isUndefined(depth) ? 1 : depth;

arrForEach(theArray, (item) => {
if (d > 0 && isArray(item)) {
_addItems(result, item, d - 1);
} else {
result.push(item);
}
});
}

return result;
}
68 changes: 68 additions & 0 deletions lib/src/array/groupBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* @nevware21/ts-utils
* https://github.com/nevware21/ts-utils
*
* Copyright (c) 2026 NevWare21 Solutions LLC
* Licensed under the MIT license.
*/

import { isArrayLike, isFunction } from "../helpers/base";
import { objHasOwn } from "../object/has_own";
import { asString } from "../string/as_string";
import { arrForEach } from "./forEach";

/**
* Callback function type for arrGroupBy
* @typeParam T - Identifies the base type of array elements
*/
export type ArrGroupByCallbackFn<T> = (value: T, index: number, array: ArrayLike<T>) => string | number | symbol;

/**
* The arrGroupBy() method groups array elements by the result of a callback function,
* returning an object where keys are group identifiers and values are arrays of grouped elements.
* @function
* @since 0.13.0
* @group Array
* @group ArrayLike
* @typeParam T - Identifies the base type of array elements
* @param theArray - The array or array-like object to group
* @param callbackFn - Function that determines the group key for each element
* @param thisArg - The value to use as 'this' when executing callbackFn
* @returns An object with group keys as properties and arrays of grouped elements as values
* @example
* ```ts
* const numbers = [1, 2, 3, 4, 5, 6];
* const grouped = arrGroupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd");
* // { odd: [1, 3, 5], even: [2, 4, 6] }
*
* const people = [
* { name: "Alice", age: 30 },
* { name: "Bob", age: 25 },
* { name: "Charlie", age: 30 }
* ];
* const byAge = arrGroupBy(people, (p) => p.age);
* // { "25": [{ name: "Bob", age: 25 }], "30": [{ name: "Alice", age: 30 }, { name: "Charlie", age: 30 }] }
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function arrGroupBy<T>(
theArray: ArrayLike<T> | null | undefined,
callbackFn: ArrGroupByCallbackFn<T>,
thisArg?: any
): Record<string | number | symbol, T[]> {
const result: Record<string | number | symbol, T[]> = {};

if (isArrayLike(theArray) && isFunction(callbackFn)) {
arrForEach(theArray, (item, idx) => {
const keyStr = asString(callbackFn.call(thisArg, item, idx, theArray));

if (!objHasOwn(result, keyStr)) {
result[keyStr] = [];
}

result[keyStr].push(item);
Comment on lines +57 to +63
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callback return value is being converted to string using asString, but the function signature indicates it can return string | number | symbol. Converting symbols to strings defeats their purpose as unique identifiers and may cause unexpected behavior. Numbers are fine to convert to strings for object keys, but symbols should be handled differently.

Consider checking the type and handling symbols separately, or document that symbols will be converted to strings (which would make them lose their uniqueness property). The return type and implementation should be consistent.

Suggested change
const keyStr = asString(callbackFn.call(thisArg, item, idx, theArray));
if (!objHasOwn(result, keyStr)) {
result[keyStr] = [];
}
result[keyStr].push(item);
const rawKey = callbackFn.call(thisArg, item, idx, theArray);
const key: string | number | symbol = typeof rawKey === "symbol" ? rawKey : asString(rawKey);
if (!objHasOwn(result, key)) {
result[key] = [];
}
result[key].push(item);

Copilot uses AI. Check for mistakes.
});
}

return result;
}
55 changes: 55 additions & 0 deletions lib/src/array/unique.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* @nevware21/ts-utils
* https://github.com/nevware21/ts-utils
*
* Copyright (c) 2026 NevWare21 Solutions LLC
* Licensed under the MIT license.
*/

import { isArrayLike, isUndefined } from "../helpers/base";
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isUndefined import is unused and should be removed to keep the code clean and reduce bundle size.

Suggested change
import { isArrayLike, isUndefined } from "../helpers/base";
import { isArrayLike } from "../helpers/base";

Copilot uses AI. Check for mistakes.
import { normalizeJsName } from "../helpers/encode";
import { objCreate } from "../object/create";
import { objHasOwn } from "../object/has_own";
import { arrForEach } from "./forEach";

/**
* The arrUnique() method returns a new array with duplicate elements removed.
* Uses strict equality (===) for comparison and maintains insertion order.
* @function
* @since 0.13.0
* @group Array
* @group ArrayLike
* @typeParam T - Identifies the base type of array elements
* @param theArray - The array or array-like object to remove duplicates from
* @returns A new array with duplicate values removed, preserving order of first occurrence
* @example
* ```ts
* arrUnique([1, 2, 2, 3, 1, 4]); // [1, 2, 3, 4]
* arrUnique(["a", "b", "a", "c"]); // ["a", "b", "c"]
* arrUnique([1, "1", 1, "1"]); // [1, "1"]
* arrUnique([]); // []
* arrUnique([1]); // [1]
*
* // Array-like objects
* arrUnique({ length: 4, 0: 1, 1: 2, 2: 2, 3: 3 }); // [1, 2, 3]
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function arrUnique<T>(theArray: ArrayLike<T> | null | undefined): T[] {
const result: T[] = [];

if (isArrayLike(theArray)) {
const seen: any = objCreate(null);

arrForEach(theArray, (item) => {
const key = normalizeJsName((typeof item) + "_" + item);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using normalizeJsName for deduplication keys is problematic. The normalizeJsName function is designed to convert strings into valid JavaScript identifiers by replacing invalid characters with underscores, not for creating unique deduplication keys.

This implementation has potential issues:

  1. It's unnecessary overhead - the function performs regex replacement meant for identifier normalization
  2. It could theoretically cause key collisions if values contain characters that get normalized (e.g., spaces, dots, special chars)
  3. The approach works for primitives but would silently fail for objects (all objects would map to "object_[object Object]")

A simpler and more reliable approach would be to use the value directly as the key with type prefix, since JavaScript object keys are already strings:

const key = (typeof item) + ":" + item;

This avoids the normalization overhead and is clearer in intent. Alternatively, for ES6+ environments, consider using a Set or Map for better performance and correctness.

Copilot uses AI. Check for mistakes.

if (!objHasOwn(seen, key)) {
seen[key] = 1;
result.push(item);
}
});
}

return result;
}
28 changes: 27 additions & 1 deletion lib/src/helpers/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Licensed under the MIT license.
*/

import { ArrCls, FUNCTION, NULL_VALUE, OBJECT, ObjProto, TO_STRING, UNDEFINED, UNDEF_VALUE } from "../internal/constants";
import { ArrCls, FUNCTION, LENGTH, NULL_VALUE, OBJECT, ObjProto, TO_STRING, UNDEFINED, UNDEF_VALUE } from "../internal/constants";
import { _isPolyfillType } from "../internal/poly_helpers";
import { _pureRef } from "../internal/treeshake_helpers";
import { safeGet } from "./safe_get";
Expand Down Expand Up @@ -542,6 +542,32 @@ export function isObject<T>(value: T): value is T {
*/
export const isArray: <T = any>(arg: any) => arg is Array<T> = (/* #__PURE__*/_pureRef<typeof ArrCls.isArray>(ArrCls as any, "isArray"));

/**
* Checks if the type of value is array-like (has a numeric length property and numeric indices).
* @since 0.13.0
* @function
* @group Type Identity
* @group Array
* @param arg - Value to be checked.
* @return True if the value is array-like, false otherwise.
* @example
* ```ts
* import { isArrayLike } from "@nevware21/ts-utils";
*
* isArrayLike([1, 2, 3]); // true
* isArrayLike("hello"); // true
* isArrayLike({ length: 3, 0: "a", 1: "b", 2: "c" }); // true
* isArrayLike({ length: "3" }); // false (length is not a number)
* isArrayLike({ 0: "a", 1: "b" }); // false (no length property)
* isArrayLike(null); // false
* isArrayLike(undefined); // false
* ```
*/
/*#__NO_SIDE_EFFECTS__*/
export function isArrayLike(arg: any): arg is ArrayLike<any> {
return !isStrictNullOrUndefined(arg) && !isFunction(arg) && isNumber(arg[LENGTH]) && arg[LENGTH] >= 0;
}

/**
* Check if an object is of type Date
* @function
Expand Down
2 changes: 1 addition & 1 deletion lib/src/helpers/customError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function _setName(baseClass: any, name: string) {
* @param constructCb - [Optional] An optional callback function to call when a
* new Custom Error instance is being created.
* @param errorBase - [Optional] (since v0.9.6) The error class to extend for this class, defaults to Error.
* @param superArgsFn - [Optional] (since v0.12.7) An optional function that receives the constructor arguments and
* @param superArgsFn - [Optional] (since v0.13.0) An optional function that receives the constructor arguments and
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version number in the JSDoc comment for the superArgsFn parameter has been changed from v0.12.7 to v0.13.0. This change should be verified:

  • If the parameter was actually added in v0.12.7, this change is incorrect and breaks the documentation history
  • If the parameter is being added in this PR (v0.13.0), the change is correct
  • If the parameter existed but is being significantly modified in v0.13.0, that should be noted differently in the documentation

Please verify the git history to confirm whether this parameter existed in v0.12.7 or is being introduced in v0.13.0.

Suggested change
* @param superArgsFn - [Optional] (since v0.13.0) An optional function that receives the constructor arguments and
* @param superArgsFn - [Optional] (since v0.12.7) An optional function that receives the constructor arguments and

Copilot uses AI. Check for mistakes.
* returns the arguments to pass to the base class constructor. When not provided all constructor
* arguments are forwarded to the base class. Use this to support a different argument order or
* to pass a subset of arguments to the base class (similar to calling `super(...)` in a class).
Expand Down
Loading