Skip to content

Commit 6faa536

Browse files
committed
refactor: update comments and improve code
1 parent 8cc6f3a commit 6faa536

16 files changed

Lines changed: 788 additions & 506 deletions

src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export * from "./utils/array.utils";
2-
export * from "./utils/async.utils.ts";
2+
export * from "./utils/async.utils";
33
export * from "./utils/cache.utils";
44
export * from "./utils/context-store.utils";
55
export * from "./utils/crypto.utils";
@@ -8,11 +8,12 @@ export * from "./utils/env.utils";
88
export * from "./utils/exception.utils";
99
export * from "./utils/fs.utils";
1010
export * from "./utils/http-status-codes";
11+
export * from "./utils/id.utils";
1112
export * from "./utils/logger.utils";
1213
export * from "./utils/obj.utils";
1314
export * from "./utils/response.utils";
1415
export * from "./utils/string.utils";
1516
export * from "./utils/url.utils";
16-
export * from "./utils/env.utils";
17+
export * from "./utils/validate.utils";
1718

1819
export * from "./types/api-response";

src/utils/array.utils.ts

Lines changed: 119 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,53 @@ import { getValueByPath } from "./obj.utils";
33
/**
44
* Splits an array into chunks of the specified size.
55
*
6-
* @param arr - The array to chunk.
7-
* @param size - The number of elements per chunk.
8-
* @returns A new array of chunked arrays.
6+
* @template T The type of array elements.
7+
* @param {T[]} array - The array to split into chunks.
8+
* @param {number} size - The number of elements per chunk.
9+
* @returns {T[][]} A new array containing chunked arrays.
10+
* @throws {TypeError} If array is not an array.
11+
* @throws {Error} If chunk size is not a positive integer.
912
*/
10-
export const chunk = <T>(arr: T[], size: number): T[][] => {
11-
if (!Array.isArray(arr)) return [];
12-
if (size <= 0) throw new Error("Chunk size must be greater than zero");
13-
return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
14-
arr.slice(i * size, i * size + size),
13+
export const chunk = <T>(array: T[], size: number): T[][] => {
14+
if (!Array.isArray(array)) throw new TypeError("Expected an array");
15+
if (!array.length) return [];
16+
if (!Number.isInteger(size) || size <= 0)
17+
throw new Error("Chunk size must be a positive integer");
18+
return Array.from({ length: Math.ceil(array.length / size) }, (_, i) =>
19+
array.slice(i * size, i * size + size),
1520
);
1621
};
1722

1823
/**
1924
* Removes duplicate values from an array.
25+
* Optionally enforces uniqueness by a key function.
2026
*
21-
* @param array - The input array.
22-
* @returns A new array with unique values.
27+
* @template T The type of array elements.
28+
* @param {T[]} array - The input array.
29+
* @param {(item: T) => unknown} [keyFn] - Optional function to determine uniqueness by key.
30+
* @returns {T[]} A new array with unique values.
2331
*/
24-
export function unique<T>(array: T[]): T[] {
25-
return Array.from(new Set(array));
32+
export function unique<T>(array: T[], keyFn?: (item: T) => unknown): T[] {
33+
if (!Array.isArray(array) || array.length === 0) return [];
34+
if (!keyFn) return Array.from(new Set(array));
35+
const seen = new Set<unknown>();
36+
return array.filter((item) => {
37+
const key = keyFn(item);
38+
if (seen.has(key)) return false;
39+
seen.add(key);
40+
return true;
41+
});
2642
}
2743

2844
/**
29-
* Deeply flattens a nested array of arbitrary depth.
45+
* Deeply flattens a nested array to a single-level array.
3046
*
31-
* @param array - The input nested array.
32-
* @returns A deeply flattened array.
47+
* @template T The leaf type of array elements.
48+
* @param {any[]} array - The (possibly deeply nested) input array.
49+
* @returns {T[]} A deeply flattened array.
3350
*/
3451
export function flattenDeep<T>(array: any[]): T[] {
52+
if (!Array.isArray(array)) return [];
3553
return array.reduce<T[]>((acc, val) => {
3654
if (Array.isArray(val)) {
3755
acc.push(...flattenDeep<T>(val));
@@ -43,45 +61,70 @@ export function flattenDeep<T>(array: any[]): T[] {
4361
}
4462

4563
/**
46-
* Returns a random element from an array.
64+
* Returns a random element from an array, or undefined if empty.
4765
*
48-
* @param array - The input array.
49-
* @returns A randomly selected item, or undefined if empty.
66+
* @template T The type of array elements.
67+
* @param {T[]} array - The input array.
68+
* @returns {T | undefined} A randomly selected item, or undefined if array is empty or not an array.
5069
*/
5170
export function random<T>(array: T[]): T | undefined {
52-
if (array.length === 0) return undefined;
71+
if (!Array.isArray(array) || array.length === 0) return undefined;
5372
return array[Math.floor(Math.random() * array.length)];
5473
}
5574

75+
/* eslint-disable no-redeclare */
5676
/**
57-
* Groups items in an array by a key selector.
77+
* Groups items in an array by a key or key function.
5878
*
59-
* @param array - The input array.
60-
* @param keyFn - A function that returns the key for each item.
61-
* @returns An object mapping keys to grouped arrays.
79+
* @template T The type of array elements.
80+
* @overload
81+
* @param {T[]} array - The array to group.
82+
* @param {keyof T} key - Property key to group by.
83+
* @returns {Record<string, T[]>}
84+
* @overload
85+
* @param {T[]} array - The array to group.
86+
* @param {(item: T) => string | number | symbol} keyFn - Function to generate group key from item.
87+
* @returns {Record<K, T[]>}
88+
* @param {T[]} array - The array to group.
89+
* @param {keyof T | ((item: T) => string | number | symbol)} keyOrFn - Property key or key-generating function.
90+
* @returns {Record<string | number | symbol, T[]>} An object mapping group keys to item arrays.
6291
*/
92+
export function groupBy<T>(array: T[], key: keyof T): Record<string, T[]>;
6393
export function groupBy<T, K extends string | number | symbol>(
6494
array: T[],
6595
keyFn: (item: T) => K,
66-
): Record<K, T[]> {
96+
): Record<K, T[]>;
97+
export function groupBy<T>(
98+
array: T[],
99+
keyOrFn: keyof T | ((item: T) => string | number | symbol),
100+
): Record<string | number | symbol, T[]> {
101+
if (!Array.isArray(array) || array.length === 0) return {};
102+
const keyFn =
103+
typeof keyOrFn === "function"
104+
? keyOrFn
105+
: (item: T) => item[keyOrFn as keyof T];
67106
return array.reduce(
68107
(acc, item) => {
69-
const key = keyFn(item);
108+
const key = keyFn(item) as string | number | symbol;
70109
if (!acc[key]) acc[key] = [];
71110
acc[key].push(item);
72111
return acc;
73112
},
74-
{} as Record<K, T[]>,
113+
{} as Record<string | number | symbol, T[]>,
75114
);
76115
}
116+
/* eslint-enable no-redeclare */
77117

78118
/**
79119
* Shuffles an array using the Fisher-Yates algorithm.
80120
*
81-
* @param array - The input array.
82-
* @returns A new shuffled array.
121+
* @template T The type of array elements.
122+
* @param {T[]} array - The input array.
123+
* @returns {T[]} A new shuffled array.
124+
* @throws {TypeError} If array is not an array.
83125
*/
84126
export function shuffle<T>(array: T[]): T[] {
127+
if (!Array.isArray(array)) throw new TypeError("Expected an array");
85128
const copy = array.slice();
86129
for (let i = copy.length - 1; i > 0; i--) {
87130
const j = Math.floor(Math.random() * (i + 1));
@@ -91,81 +134,98 @@ export function shuffle<T>(array: T[]): T[] {
91134
}
92135

93136
/**
94-
* Returns values in A that are not in B.
137+
* Returns an array of property values from an array of objects.
95138
*
96-
* @param a - First array.
97-
* @param b - Second array.
98-
* @returns Elements in A that are not in B.
139+
* @template T The type of array elements.
140+
* @template K The object property to pluck.
141+
* @param {T[]} array - The input array.
142+
* @param {K} key - The property name to pluck.
143+
* @returns {T[K][]} Array of property values.
144+
*/
145+
export function pluck<T, K extends keyof T>(array: T[], key: K): T[K][] {
146+
if (!Array.isArray(array)) return [];
147+
return array.map((item) => item[key]);
148+
}
149+
150+
/**
151+
* Returns values in array A that are not in array B.
152+
*
153+
* @template T The type of array elements.
154+
* @param {T[]} a - First array.
155+
* @param {T[]} b - Second array.
156+
* @returns {T[]} Elements in A that are not in B.
99157
*/
100158
export function difference<T>(a: T[], b: T[]): T[] {
159+
if (!Array.isArray(a) || !Array.isArray(b)) return [];
101160
const setB = new Set(b);
102161
return a.filter((item) => !setB.has(item));
103162
}
104163

105164
/**
106-
* Returns common values between A and B.
165+
* Returns common values between arrays A and B.
107166
*
108-
* @param a - First array.
109-
* @param b - Second array.
110-
* @returns Elements that exist in both arrays.
167+
* @template T The type of array elements.
168+
* @param {T[]} a - First array.
169+
* @param {T[]} b - Second array.
170+
* @returns {T[]} Elements that exist in both arrays.
111171
*/
112172
export function intersect<T>(a: T[], b: T[]): T[] {
173+
if (!Array.isArray(a) || !Array.isArray(b)) return [];
113174
const setB = new Set(b);
114175
return a.filter((item) => setB.has(item));
115176
}
116177

117178
/**
118179
* Sorts an array of objects by a nested key using Merge Sort (O(n log n)).
180+
* Missing/undefined keys are sorted to the "end" (asc) or "start" (desc).
119181
*
120-
* @template T - Object type of the array elements.
121-
* @param array - Array of objects to sort.
122-
* @param key - Dot-notated key to sort by (e.g. 'profile.age').
123-
* @param direction - Sort direction: 'asc' or 'desc'.
124-
* @returns A new sorted array.
182+
* @template T The type of array elements (objects).
183+
* @param {T[]} array - Array of objects to sort.
184+
* @param {string | ((item: T) => any)} key - Dot-notated key (e.g., "profile.age") or function.
185+
* @param {"asc" | "desc"} [direction="asc"] - Sort direction: 'asc' or 'desc'.
186+
* @returns {T[]} A new sorted array.
187+
* @throws {TypeError} If array is not an array.
125188
*/
126-
export function mergeSort<T extends object>(
189+
export function mergeSort<T>(
127190
array: T[],
128-
key: string,
191+
key: string | ((item: T) => any),
129192
direction: "asc" | "desc" = "asc",
130193
): T[] {
194+
if (!Array.isArray(array)) throw new TypeError("Expected array");
131195
if (array.length <= 1) return array.slice();
132196

133-
const compare = (a: T, b: T) => {
134-
const aVal = getValueByPath(a, key);
135-
const bVal = getValueByPath(b, key);
197+
const keyFn =
198+
typeof key === "function"
199+
? key
200+
: (item: T) => getValueByPath(item as object, key);
136201

202+
const compare = (a: T, b: T) => {
203+
const aVal = keyFn(a);
204+
const bVal = keyFn(b);
205+
// Sorts undefined/null last for "asc", first for "desc"
137206
if (aVal === bVal) return 0;
138-
if (aVal == null) return 1;
139-
if (bVal == null) return -1;
140-
207+
if (aVal == null) return direction === "asc" ? 1 : -1;
208+
if (bVal == null) return direction === "asc" ? -1 : 1;
141209
return direction === "asc" ? (aVal < bVal ? -1 : 1) : aVal > bVal ? -1 : 1;
142210
};
143211

144212
const merge = (left: T[], right: T[]): T[] => {
145213
const result: T[] = [];
146214
let i = 0,
147215
j = 0;
148-
149216
while (i < left.length && j < right.length) {
150-
if (compare(left[i], right[j]) <= 0) {
151-
result.push(left[i++]);
152-
} else {
153-
result.push(right[j++]);
154-
}
217+
if (compare(left[i], right[j]) <= 0) result.push(left[i++]);
218+
else result.push(right[j++]);
155219
}
156-
157220
return result.concat(left.slice(i)).concat(right.slice(j));
158221
};
159222

160223
const sort = (arr: T[]): T[] => {
161224
if (arr.length <= 1) return arr;
162-
163225
const mid = Math.floor(arr.length / 2);
164226
const left = sort(arr.slice(0, mid));
165227
const right = sort(arr.slice(mid));
166-
167228
return merge(left, right);
168229
};
169-
170230
return sort(array);
171231
}

0 commit comments

Comments
 (0)