@catbee/utils
Version:
A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.
369 lines • 12.5 kB
JavaScript
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import { getValueByPath } from "./obj.utils";
/**
* Splits an array into chunks of the specified size.
*
* @template T The type of array elements.
* @param {T[]} array - The array to split into chunks.
* @param {number} size - The number of elements per chunk.
* @returns {T[][]} A new array containing chunked arrays.
* @throws {TypeError} If array is not an array.
* @throws {Error} If chunk size is not a positive integer.
*/
export function chunk(array, size) {
if (!Array.isArray(array))
throw new TypeError("Expected an array");
if (!array.length)
return [];
if (!Number.isInteger(size) || size <= 0)
throw new Error("Chunk size must be a positive integer");
return Array.from({ length: Math.ceil(array.length / size) }, function (_, i) {
return array.slice(i * size, i * size + size);
});
}
/**
* Removes duplicate values from an array.
* Optionally enforces uniqueness by a key function.
*
* @template T The type of array elements.
* @param {T[]} array - The input array.
* @param {(item: T) => unknown} [keyFn] - Optional function to determine uniqueness by key.
* @returns {T[]} A new array with unique values.
*/
export function unique(array, keyFn) {
if (!Array.isArray(array) || array.length === 0)
return [];
if (!keyFn)
return Array.from(new Set(array));
var seen = new Set();
return array.filter(function (item) {
var key = keyFn(item);
if (seen.has(key))
return false;
seen.add(key);
return true;
});
}
/**
* Deeply flattens a nested array to a single-level array.
*
* @template T The leaf type of array elements.
* @param {any[]} array - The (possibly deeply nested) input array.
* @returns {T[]} A deeply flattened array.
*/
export function flattenDeep(array) {
if (!Array.isArray(array))
return [];
return array.reduce(function (acc, val) {
if (Array.isArray(val)) {
acc.push.apply(acc, __spreadArray([], __read(flattenDeep(val)), false));
}
else {
acc.push(val);
}
return acc;
}, []);
}
/**
* Returns a random element from an array, or undefined if empty.
*
* @template T The type of array elements.
* @param {T[]} array - The input array.
* @returns {T | undefined} A randomly selected item, or undefined if array is empty or not an array.
*/
export function random(array) {
if (!Array.isArray(array) || array.length === 0)
return undefined;
return array[Math.floor(Math.random() * array.length)];
}
export function groupBy(array, keyOrFn) {
if (!Array.isArray(array) || array.length === 0)
return {};
var keyFn = typeof keyOrFn === "function"
? keyOrFn
: function (item) { return item[keyOrFn]; };
return array.reduce(function (acc, item) {
var key = keyFn(item);
if (!acc[key])
acc[key] = [];
acc[key].push(item);
return acc;
}, {});
}
/* eslint-enable no-redeclare */
/**
* Shuffles an array using the Fisher-Yates algorithm.
*
* @template T The type of array elements.
* @param {T[]} array - The input array.
* @returns {T[]} A new shuffled array.
* @throws {TypeError} If array is not an array.
*/
export function shuffle(array) {
var _a;
if (!Array.isArray(array))
throw new TypeError("Expected an array");
var copy = array.slice();
for (var i = copy.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
_a = __read([copy[j], copy[i]], 2), copy[i] = _a[0], copy[j] = _a[1];
}
return copy;
}
/**
* Returns an array of property values from an array of objects.
*
* @template T The type of array elements.
* @template K The object property to pluck.
* @param {T[]} array - The input array.
* @param {K} key - The property name to pluck.
* @returns {T[K][]} Array of property values.
*/
export function pluck(array, key) {
if (!Array.isArray(array))
return [];
return array.map(function (item) { return item[key]; });
}
/**
* Returns values in array A that are not in array B.
*
* @template T The type of array elements.
* @param {T[]} a - First array.
* @param {T[]} b - Second array.
* @returns {T[]} Elements in A that are not in B.
*/
export function difference(a, b) {
if (!Array.isArray(a) || !Array.isArray(b))
return [];
var setB = new Set(b);
return a.filter(function (item) { return !setB.has(item); });
}
/**
* Returns common values between arrays A and B.
*
* @template T The type of array elements.
* @param {T[]} a - First array.
* @param {T[]} b - Second array.
* @returns {T[]} Elements that exist in both arrays.
*/
export function intersect(a, b) {
if (!Array.isArray(a) || !Array.isArray(b))
return [];
var setB = new Set(b);
return a.filter(function (item) { return setB.has(item); });
}
/**
* Sorts an array of objects by a nested key using Merge Sort (O(n log n)).
* Missing/undefined keys are sorted to the "end" (asc) or "start" (desc).
*
* @template T The type of array elements (objects).
* @param {T[]} array - Array of objects to sort.
* @param {string | ((item: T) => any)} key - Dot-notated key (e.g., "profile.age") or function.
* @param {"asc" | "desc"} [direction="asc"] - Sort direction: 'asc' or 'desc'.
* @returns {T[]} A new sorted array.
* @throws {TypeError} If array is not an array.
*/
export function mergeSort(array, key, direction) {
if (direction === void 0) { direction = "asc"; }
if (!Array.isArray(array))
throw new TypeError("Expected array");
if (array.length <= 1)
return array.slice();
var keyFn = typeof key === "function"
? key
: function (item) { return getValueByPath(item, key); };
var compare = function (a, b) {
var aVal = keyFn(a);
var bVal = keyFn(b);
// Sorts undefined/null last for "asc", first for "desc"
if (aVal === bVal)
return 0;
if (aVal == null)
return direction === "asc" ? 1 : -1;
if (bVal == null)
return direction === "asc" ? -1 : 1;
return direction === "asc" ? (aVal < bVal ? -1 : 1) : aVal > bVal ? -1 : 1;
};
var merge = function (left, right) {
var result = [];
var i = 0, j = 0;
while (i < left.length && j < right.length) {
if (compare(left[i], right[j]) <= 0)
result.push(left[i++]);
else
result.push(right[j++]);
}
return result.concat(left.slice(i)).concat(right.slice(j));
};
var sort = function (arr) {
if (arr.length <= 1)
return arr;
var mid = Math.floor(arr.length / 2);
var left = sort(arr.slice(0, mid));
var right = sort(arr.slice(mid));
return merge(left, right);
};
return sort(array);
}
/**
* Combines multiple arrays into a single array of grouped elements.
* Output array length equals the length of the shortest input array.
*
* @example zip([1, 2], ['a', 'b']) => [[1, 'a'], [2, 'b']]
* @param {...Array<T>[]} arrays - Two or more arrays to zip together.
* @returns {Array<T[]>} Array of grouped elements.
*/
export function zip() {
var arrays = [];
for (var _i = 0; _i < arguments.length; _i++) {
arrays[_i] = arguments[_i];
}
if (arrays.length === 0)
return [];
if (arrays.some(function (arr) { return !Array.isArray(arr); })) {
throw new TypeError("All arguments must be arrays");
}
var minLength = Math.min.apply(Math, __spreadArray([], __read(arrays.map(function (arr) { return arr.length; })), false));
var result = [];
var _loop_1 = function (i) {
result.push(arrays.map(function (arr) { return arr[i]; }));
};
for (var i = 0; i < minLength; i++) {
_loop_1(i);
}
return result;
}
/**
* Splits an array into two arrays based on a predicate function.
*
* @template T The type of array elements.
* @param {T[]} array - The input array.
* @param {(item: T, index: number, array: T[]) => boolean} predicate - Function to test each element.
* @returns {[T[], T[]]} A tuple of two arrays: [matched, unmatched].
*/
export function partition(array, predicate) {
if (!Array.isArray(array))
return [[], []];
return array.reduce(function (_a, item, index) {
var _b = __read(_a, 2), pass = _b[0], fail = _b[1];
return predicate(item, index, array)
? [__spreadArray(__spreadArray([], __read(pass), false), [item], false), fail]
: [pass, __spreadArray(__spreadArray([], __read(fail), false), [item], false)];
}, [[], []]);
}
/**
* Generates an array of numbers within a specified range.
*
* @param {number} start - Start of range (inclusive).
* @param {number} end - End of range (exclusive).
* @param {number} [step=1] - Step between numbers.
* @returns {number[]} Array of numbers in range.
*/
export function range(start, end, step) {
if (step === void 0) { step = 1; }
if (!Number.isFinite(start) ||
!Number.isFinite(end) ||
!Number.isFinite(step)) {
throw new TypeError("Arguments must be finite numbers");
}
if (step === 0)
throw new Error("Step cannot be zero");
var isAscending = step > 0;
if ((isAscending && start >= end) || (!isAscending && start <= end)) {
return [];
}
var length = Math.max(Math.ceil((end - start) / step), 0);
var result = new Array(length);
for (var i = 0, value = start; i < length; i++, value += step) {
result[i] = value;
}
return result;
}
/**
* Returns the first n elements of an array.
*
* @template T The type of array elements.
* @param {T[]} array - The input array.
* @param {number} [n=1] - Number of elements to take.
* @returns {T[]} New array with first n elements.
*/
export function take(array, n) {
if (n === void 0) { n = 1; }
if (!Array.isArray(array) || n <= 0)
return [];
return array.slice(0, n);
}
/**
* Takes elements from the array while predicate returns true.
*
* @template T The type of array elements.
* @param {T[]} array - The input array.
* @param {(item: T, index: number) => boolean} predicate - Function to test each element.
* @returns {T[]} New array with taken elements.
*/
export function takeWhile(array, predicate) {
if (!Array.isArray(array))
return [];
var result = [];
for (var i = 0; i < array.length; i++) {
if (!predicate(array[i], i))
break;
result.push(array[i]);
}
return result;
}
/**
* Removes all falsy values from an array.
* False, null, 0, "", undefined, and NaN are falsy.
*
* @template T The type of array elements.
* @param {T[]} array - The input array.
* @returns {NonNullable<T>[]} New array with falsy values removed.
*/
export function compact(array) {
if (!Array.isArray(array))
return [];
return array.filter(Boolean);
}
/**
* Counts array elements by a key function.
*
* @template T The type of array elements.
* @param {T[]} array - The input array.
* @param {(item: T) => string | number | symbol} keyFn - Function to generate count key.
* @returns {Record<string, number>} Object with counts by key.
*/
export function countBy(array, keyFn) {
if (!Array.isArray(array))
return {};
return array.reduce(function (acc, item) {
var key = String(keyFn(item));
acc[key] = (acc[key] || 0) + 1;
return acc;
}, {});
}
//# sourceMappingURL=array.utils.js.map