@ix-xs/node-comfort 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +136 -549
  3. package/index.js +80 -35
  4. package/package.json +160 -33
  5. package/src/Arr.js +887 -419
  6. package/src/Async.js +585 -0
  7. package/src/Cache.js +329 -0
  8. package/src/Checker.js +971 -539
  9. package/src/Cli.js +1034 -0
  10. package/src/Color.js +415 -0
  11. package/src/Crypto.js +707 -0
  12. package/src/Emitter.js +289 -0
  13. package/src/Env.js +555 -0
  14. package/src/FS.js +1483 -894
  15. package/src/Func.js +670 -353
  16. package/src/Http.js +538 -0
  17. package/src/Id.js +384 -201
  18. package/src/Logger.js +739 -378
  19. package/src/Num.js +844 -361
  20. package/src/Obj.js +855 -372
  21. package/src/SQLite.js +1308 -1005
  22. package/src/Schema.js +1586 -0
  23. package/src/Str.js +983 -436
  24. package/src/Sys.js +511 -0
  25. package/src/Time.js +1260 -422
  26. package/src/Utils.js +321 -325
  27. package/src/errors.js +292 -0
  28. package/types/index.d.ts +693 -526
  29. package/types/src/Arr.d.ts +455 -145
  30. package/types/src/Arr.d.ts.map +1 -1
  31. package/types/src/Async.d.ts +321 -0
  32. package/types/src/Async.d.ts.map +1 -0
  33. package/types/src/Cache.d.ts +195 -0
  34. package/types/src/Cache.d.ts.map +1 -0
  35. package/types/src/Checker.d.ts +501 -235
  36. package/types/src/Checker.d.ts.map +1 -1
  37. package/types/src/Cli.d.ts +582 -0
  38. package/types/src/Cli.d.ts.map +1 -0
  39. package/types/src/Color.d.ts +294 -0
  40. package/types/src/Color.d.ts.map +1 -0
  41. package/types/src/Crypto.d.ts +534 -0
  42. package/types/src/Crypto.d.ts.map +1 -0
  43. package/types/src/Emitter.d.ts +170 -0
  44. package/types/src/Emitter.d.ts.map +1 -0
  45. package/types/src/Env.d.ts +382 -0
  46. package/types/src/Env.d.ts.map +1 -0
  47. package/types/src/FS.d.ts +686 -238
  48. package/types/src/FS.d.ts.map +1 -1
  49. package/types/src/Func.d.ts +380 -135
  50. package/types/src/Func.d.ts.map +1 -1
  51. package/types/src/Http.d.ts +374 -0
  52. package/types/src/Http.d.ts.map +1 -0
  53. package/types/src/Id.d.ts +149 -68
  54. package/types/src/Id.d.ts.map +1 -1
  55. package/types/src/Logger.d.ts +363 -46
  56. package/types/src/Logger.d.ts.map +1 -1
  57. package/types/src/Num.d.ts +473 -137
  58. package/types/src/Num.d.ts.map +1 -1
  59. package/types/src/Obj.d.ts +390 -104
  60. package/types/src/Obj.d.ts.map +1 -1
  61. package/types/src/SQLite.d.ts +646 -309
  62. package/types/src/SQLite.d.ts.map +1 -1
  63. package/types/src/Schema.d.ts +940 -0
  64. package/types/src/Schema.d.ts.map +1 -0
  65. package/types/src/Str.d.ts +569 -166
  66. package/types/src/Str.d.ts.map +1 -1
  67. package/types/src/Sys.d.ts +269 -0
  68. package/types/src/Sys.d.ts.map +1 -0
  69. package/types/src/Time.d.ts +663 -204
  70. package/types/src/Time.d.ts.map +1 -1
  71. package/types/src/Utils.d.ts +154 -102
  72. package/types/src/Utils.d.ts.map +1 -1
  73. package/types/src/errors.d.ts +292 -0
  74. package/types/src/errors.d.ts.map +1 -0
  75. package/types/index.d.ts.map +0 -1
package/src/Arr.js CHANGED
@@ -1,433 +1,901 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Array helpers. None of them mutate what you pass in, and item types carry
5
+ * through (`chunk(numbers, 2)` is a `number[][]`). Many take an iteratee:
6
+ * a property name, which your editor completes, or a function.
7
+ *
8
+ * @example
9
+ * arr.chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
10
+ * arr.groupBy(users, "role"); // { admin: [...], user: [...] }
11
+ * arr.sortBy(users, ["lastName", "age"]);
12
+ */
13
+
1
14
  /**
2
- * Array utilities.
15
+ * How to get a value out of an item: a property name, or a function
16
+ * `(item, index) => value`.
3
17
  *
4
- * Chunking, grouping, uniqueness, set operations, shuffling, flattening and
5
- * more • expressed as small pure functions that never mutate their input.
18
+ * @template T
19
+ * @typedef {(T extends object ? keyof T & (string | number) : never) | ((item: T, index: number) => unknown)} Iteratee
20
+ */
21
+
22
+ /**
23
+ * A sort key for `sortBy()`: an iteratee, or `[iteratee, "asc" | "desc"]`
24
+ * to give that key its own direction.
6
25
  *
7
- * Many methods accept an `iteratee`: either a property key (string) or a
8
- * function mapping an item to a value.
26
+ * @template T
27
+ * @typedef {Iteratee<T> | [Iteratee<T>, "asc" | "desc"]} SortKey
9
28
  */
10
29
 
11
30
  /**
12
- * Turns a key or function into a value-extracting function.
13
- * @private
14
- * @param {string|((item: any) => any)} [iteratee]
15
- * @returns {(item: any) => any}
31
+ * Options for `sortBy()`.
32
+ * @typedef {object} SortByOptions
33
+ * @property {"asc"|"desc"} [order] Direction for keys that don't set their own. Defaults to `"asc"`.
34
+ * @property {boolean} [natural] Sort text like a person would: ignore case, respect accents, `"item2"` before `"item10"`.
35
+ * @property {string} [locale] Language used by `natural`. Defaults to the system's.
16
36
  */
17
- const _iteratee = (iteratee) => {
18
- if (typeof iteratee === "function") return iteratee;
19
- if (typeof iteratee === "string") return (item) => (item == null ? undefined : item[iteratee]);
37
+
38
+ /**
39
+ * A page returned by `paginate()`.
40
+ * @template T
41
+ * @typedef {object} Page
42
+ * @property {T[]} items The items on this page.
43
+ * @property {number} page The page number, starting at 1.
44
+ * @property {number} perPage
45
+ * @property {number} total Total number of items.
46
+ * @property {number} pages Number of pages, at least 1.
47
+ * @property {boolean} hasPrev
48
+ * @property {boolean} hasNext
49
+ */
50
+
51
+ /**
52
+ * @param {unknown} iteratee
53
+ * @returns {(item: any, index: number) => any}
54
+ */
55
+ const _fn = (iteratee) => {
56
+ if (typeof iteratee === "function") return /** @type {(item: any, index: number) => any} */ (iteratee);
57
+ if (typeof iteratee === "string" || typeof iteratee === "number" || typeof iteratee === "symbol") {
58
+ return (item) => (item == null ? undefined : item[iteratee]);
59
+ }
20
60
  return (item) => item;
21
61
  };
22
62
 
23
- module.exports = {
24
- /**
25
- * Splits an array into chunks of a given size.
26
- *
27
- * @example
28
- * nodeComfort.arr.chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
29
- *
30
- * @param {any[]} array - Source array.
31
- * @param {number} size - Chunk size (must be >= 1).
32
- * @returns {any[][]} The chunked array.
33
- */
34
- chunk(array, size) {
35
- if (size < 1) return [];
36
- const result = [];
37
- for (let i = 0; i < array.length; i += size) {
38
- result.push(array.slice(i, i + size));
39
- }
40
- return result;
41
- },
42
-
43
- /**
44
- * Returns a new array with duplicate values removed.
45
- *
46
- * Pass an `iteratee` to de-duplicate by a derived key.
47
- *
48
- * @example
49
- * nodeComfort.arr.unique([1, 1, 2, 3]); // [1, 2, 3]
50
- * nodeComfort.arr.unique([{ id: 1 }, { id: 1 }], "id"); // [{ id: 1 }]
51
- *
52
- * @param {any[]} array - Source array.
53
- * @param {string|((item: any) => any)} [iteratee] - Key or function to compare by.
54
- * @returns {any[]} The de-duplicated array.
55
- */
56
- unique(array, iteratee) {
57
- if (!iteratee) return [...new Set(array)];
58
- const fn = _iteratee(iteratee);
59
- const seen = new Set();
60
- const result = [];
61
- for (const item of array) {
62
- const key = fn(item);
63
- if (!seen.has(key)) {
64
- seen.add(key);
65
- result.push(item);
66
- }
67
- }
68
- return result;
69
- },
70
-
71
- /**
72
- * Groups the items of an array by a key or function.
73
- *
74
- * @example
75
- * nodeComfort.arr.groupBy([1, 2, 3, 4], (n) => (n % 2 ? "odd" : "even"));
76
- * // { odd: [1, 3], even: [2, 4] }
77
- *
78
- * @param {any[]} array - Source array.
79
- * @param {string|((item: any) => any)} iteratee - Key or function to group by.
80
- * @returns {Record<string, any[]>} The grouped object.
81
- */
82
- groupBy(array, iteratee) {
83
- const fn = _iteratee(iteratee);
84
- const result = {};
85
- for (const item of array) {
86
- const key = fn(item);
87
- (result[key] ??= []).push(item);
88
- }
89
- return result;
90
- },
91
-
92
- /**
93
- * Builds an object keyed by a derived value, mapping each key to the
94
- * (last) matching item.
95
- *
96
- * @example
97
- * nodeComfort.arr.keyBy([{ id: "a" }, { id: "b" }], "id");
98
- * // { a: { id: "a" }, b: { id: "b" } }
99
- *
100
- * @param {any[]} array - Source array.
101
- * @param {string|((item: any) => any)} iteratee - Key or function.
102
- * @returns {Record<string, any>} The keyed object.
103
- */
104
- keyBy(array, iteratee) {
105
- const fn = _iteratee(iteratee);
106
- const result = {};
107
- for (const item of array) result[fn(item)] = item;
108
- return result;
109
- },
110
-
111
- /**
112
- * Partitions an array into two groups based on a predicate:
113
- * `[matching, notMatching]`.
114
- *
115
- * @example
116
- * nodeComfort.arr.partition([1, 2, 3, 4], (n) => n % 2 === 0);
117
- * // [[2, 4], [1, 3]]
118
- *
119
- * @param {any[]} array - Source array.
120
- * @param {(item: any, index: number) => boolean} predicate - Test function.
121
- * @returns {[any[], any[]]} The two partitions.
122
- */
123
- partition(array, predicate) {
124
- const pass = [];
125
- const fail = [];
126
- array.forEach((item, i) => (predicate(item, i) ? pass : fail).push(item));
127
- return [pass, fail];
128
- },
129
-
130
- /**
131
- * Recursively flattens a nested array up to a given depth.
132
- *
133
- * @example
134
- * nodeComfort.arr.flatten([1, [2, [3, [4]]]]); // [1, 2, 3, [4]]
135
- * nodeComfort.arr.flatten([1, [2, [3]]], Infinity); // [1, 2, 3]
136
- *
137
- * @param {any[]} array - Source array.
138
- * @param {number} [depth=1] - Maximum depth to flatten.
139
- * @returns {any[]} The flattened array.
140
- */
141
- flatten(array, depth = 1) {
142
- return array.flat(depth);
143
- },
144
-
145
- /**
146
- * Returns a shuffled copy of an array (Fisher–Yates).
147
- *
148
- * Uses `Math.random` • not cryptographically secure.
149
- *
150
- * @param {any[]} array - Source array.
151
- * @returns {any[]} A new shuffled array.
152
- */
153
- shuffle(array) {
154
- const result = [...array];
155
- for (let i = result.length - 1; i > 0; i--) {
156
- const j = Math.floor(Math.random() * (i + 1));
157
- [result[i], result[j]] = [result[j], result[i]];
158
- }
159
- return result;
160
- },
161
-
162
- /**
163
- * Returns a random element from an array.
164
- *
165
- * @param {any[]} array - Source array.
166
- * @returns {any} A random element, or undefined if empty.
167
- */
168
- sample(array) {
169
- return array.length ? array[Math.floor(Math.random() * array.length)] : undefined;
170
- },
171
-
172
- /**
173
- * Returns `n` random elements from an array (without repetition).
174
- *
175
- * @param {any[]} array - Source array.
176
- * @param {number} n - Number of elements to pick.
177
- * @returns {any[]} The sampled elements.
178
- */
179
- sampleSize(array, n) {
180
- return this.shuffle(array).slice(0, Math.max(0, n));
181
- },
182
-
183
- /**
184
- * Sorts an array by one or more iteratees, ascending by default.
185
- *
186
- * Does not mutate the input.
187
- *
188
- * @example
189
- * nodeComfort.arr.sortBy(users, "age");
190
- * nodeComfort.arr.sortBy(users, [(u) => u.age, "name"]);
191
- *
192
- * @param {any[]} array - Source array.
193
- * @param {string|((item: any) => any)|Array<string|((item: any) => any)>} iteratees - Sort key(s).
194
- * @param {"asc"|"desc"} [direction="asc"] - Sort direction.
195
- * @returns {any[]} A new sorted array.
196
- */
197
- sortBy(array, iteratees, direction = "asc") {
198
- const fns = (Array.isArray(iteratees) ? iteratees : [iteratees]).map(_iteratee);
199
- const dir = direction === "desc" ? -1 : 1;
200
- return [...array].sort((a, b) => {
201
- for (const fn of fns) {
202
- const va = fn(a);
203
- const vb = fn(b);
204
- if (va < vb) return -1 * dir;
205
- if (va > vb) return 1 * dir;
206
- }
207
- return 0;
208
- });
209
- },
210
-
211
- /**
212
- * Returns the elements present in the first array but not in the others.
213
- *
214
- * @example
215
- * nodeComfort.arr.difference([1, 2, 3, 4], [2, 4]); // [1, 3]
216
- *
217
- * @param {any[]} array - Source array.
218
- * @param {...any[]} others - Arrays to subtract.
219
- * @returns {any[]} The difference.
220
- */
221
- difference(array, ...others) {
222
- const exclude = new Set(others.flat());
223
- return array.filter((item) => !exclude.has(item));
224
- },
225
-
226
- /**
227
- * Returns the elements common to all provided arrays.
228
- *
229
- * @example
230
- * nodeComfort.arr.intersection([1, 2, 3], [2, 3, 4]); // [2, 3]
231
- *
232
- * @param {...any[]} arrays - Arrays to intersect.
233
- * @returns {any[]} The intersection.
234
- */
235
- intersection(...arrays) {
236
- if (!arrays.length) return [];
237
- const [first, ...rest] = arrays;
238
- const sets = rest.map((a) => new Set(a));
239
- return [...new Set(first)].filter((item) => sets.every((set) => set.has(item)));
240
- },
241
-
242
- /**
243
- * Returns the union (unique values) of all provided arrays.
244
- *
245
- * @example
246
- * nodeComfort.arr.union([1, 2], [2, 3], [3, 4]); // [1, 2, 3, 4]
247
- *
248
- * @param {...any[]} arrays - Arrays to unite.
249
- * @returns {any[]} The union.
250
- */
251
- union(...arrays) {
252
- return [...new Set(arrays.flat())];
253
- },
254
-
255
- /**
256
- * Zips multiple arrays together into an array of tuples.
257
- *
258
- * @example
259
- * nodeComfort.arr.zip(["a", "b"], [1, 2]); // [["a", 1], ["b", 2]]
260
- *
261
- * @param {...any[]} arrays - Arrays to zip.
262
- * @returns {any[][]} The zipped tuples.
263
- */
264
- zip(...arrays) {
265
- const length = Math.max(0, ...arrays.map((a) => a.length));
266
- const result = [];
267
- for (let i = 0; i < length; i++) {
268
- result.push(arrays.map((a) => a[i]));
269
- }
270
- return result;
271
- },
272
-
273
- /**
274
- * Splits an array into `matching` and removes falsy values.
275
- *
276
- * Removes `false`, `null`, `0`, `""`, `undefined` and `NaN`.
277
- *
278
- * @example
279
- * nodeComfort.arr.compact([0, 1, false, 2, "", 3, null]); // [1, 2, 3]
280
- *
281
- * @param {any[]} array - Source array.
282
- * @returns {any[]} The compacted array.
283
- */
284
- compact(array) {
285
- return array.filter(Boolean);
286
- },
287
-
288
- /**
289
- * Returns the first element (or the first `n` elements) of an array.
290
- *
291
- * @param {any[]} array - Source array.
292
- * @param {number} [n] - How many elements to take.
293
- * @returns {any} The first element, or the first `n` elements when `n` is given.
294
- */
295
- first(array, n) {
296
- if (n === undefined) return array[0];
297
- return array.slice(0, Math.max(0, n));
298
- },
299
-
300
- /**
301
- * Returns the last element (or the last `n` elements) of an array.
302
- *
303
- * @param {any[]} array - Source array.
304
- * @param {number} [n] - How many elements to take.
305
- * @returns {any} The last element, or the last `n` elements when `n` is given.
306
- */
307
- last(array, n) {
308
- if (n === undefined) return array[array.length - 1];
309
- return array.slice(Math.max(0, array.length - n));
310
- },
311
-
312
- /**
313
- * Removes items from an array where the predicate is true, returning a new array.
314
- *
315
- * @param {any[]} array - Source array.
316
- * @param {(item: any, index: number) => boolean} predicate - Removal test.
317
- * @returns {any[]} A new array without the removed items.
318
- */
319
- remove(array, predicate) {
320
- return array.filter((item, i) => !predicate(item, i));
321
- },
322
-
323
- /**
324
- * Moves an element from one index to another (returns a new array).
325
- *
326
- * @param {any[]} array - Source array.
327
- * @param {number} from - Source index.
328
- * @param {number} to - Destination index.
329
- * @returns {any[]} The reordered array.
330
- */
331
- move(array, from, to) {
332
- const result = [...array];
333
- const [item] = result.splice(from, 1);
334
- result.splice(to, 0, item);
335
- return result;
336
- },
337
-
338
- /**
339
- * Counts occurrences of each derived key in an array.
340
- *
341
- * @example
342
- * nodeComfort.arr.countBy(["a", "b", "a"]); // { a: 2, b: 1 }
343
- * nodeComfort.arr.countBy([1.1, 2.3, 1.7], Math.floor); // { 1: 2, 2: 1 }
344
- *
345
- * @param {any[]} array - Source array.
346
- * @param {string|((item: any) => any)} [iteratee] - Key or function.
347
- * @returns {Record<string, number>} The counts.
348
- */
349
- countBy(array, iteratee) {
350
- const fn = _iteratee(iteratee);
351
- const result = {};
352
- for (const item of array) {
353
- const key = fn(item);
354
- result[key] = (result[key] ?? 0) + 1;
355
- }
356
- return result;
357
- },
358
-
359
- /**
360
- * Sums an array by a numeric iteratee (or the values themselves).
361
- *
362
- * @example
363
- * nodeComfort.arr.sumBy([{ n: 1 }, { n: 2 }], "n"); // 3
364
- *
365
- * @param {any[]} array - Source array.
366
- * @param {string|((item: any) => number)} [iteratee] - Key or function returning a number.
367
- * @returns {number} The sum.
368
- */
369
- sumBy(array, iteratee) {
370
- const fn = _iteratee(iteratee);
371
- return array.reduce((acc, item) => acc + (Number(fn(item)) || 0), 0);
372
- },
373
-
374
- /**
375
- * Returns the item with the maximum derived value.
376
- *
377
- * @param {any[]} array - Source array.
378
- * @param {string|((item: any) => number)} [iteratee] - Key or function.
379
- * @returns {any} The item, or undefined if the array is empty.
380
- */
381
- maxBy(array, iteratee) {
382
- const fn = _iteratee(iteratee);
383
- let best;
384
- let bestVal = -Infinity;
385
- for (const item of array) {
386
- const val = fn(item);
387
- if (val > bestVal) {
388
- bestVal = val;
389
- best = item;
63
+ /**
64
+ * @param {unknown} key
65
+ * @returns {PropertyKey}
66
+ */
67
+ const _key = (key) => (typeof key === "symbol" || typeof key === "number" ? key : String(key));
68
+
69
+ /**
70
+ * Splits an array into groups of `size` items. The last group can be shorter.
71
+ *
72
+ * @example
73
+ * arr.chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
74
+ * arr.chunk(emails, 100).forEach(sendBatch);
75
+ *
76
+ * @template T
77
+ * @param {readonly T[]} array
78
+ * @param {number} size
79
+ * @returns {T[][]}
80
+ */
81
+ function chunk(array, size) {
82
+ const n = Math.floor(size);
83
+ if (!(n >= 1)) return [];
84
+ /** @type {T[][]} */
85
+ const out = [];
86
+ for (let i = 0; i < array.length; i += n) out.push(array.slice(i, i + n));
87
+ return out;
88
+ }
89
+
90
+ /**
91
+ * Sliding windows of `size` items, moving by `step`. Good for moving
92
+ * averages and comparing neighbours.
93
+ *
94
+ * @example
95
+ * arr.windows([1, 2, 3, 4], 2); // [[1, 2], [2, 3], [3, 4]]
96
+ * arr.windows([1, 2, 3, 4, 5], 3, 2); // [[1, 2, 3], [3, 4, 5]]
97
+ *
98
+ * @template T
99
+ * @param {readonly T[]} array
100
+ * @param {number} size
101
+ * @param {number} [step=1]
102
+ * @returns {T[][]}
103
+ */
104
+ function windows(array, size, step = 1) {
105
+ const n = Math.floor(size);
106
+ const s = Math.max(1, Math.floor(step));
107
+ /** @type {T[][]} */
108
+ const out = [];
109
+ if (!(n >= 1)) return out;
110
+ for (let i = 0; i + n <= array.length; i += s) out.push(array.slice(i, i + n));
111
+ return out;
112
+ }
113
+
114
+ /**
115
+ * The first item, or the first `n` items.
116
+ *
117
+ * @example
118
+ * arr.first([1, 2, 3]); // 1
119
+ * arr.first([1, 2, 3], 2); // [1, 2]
120
+ *
121
+ * @template T
122
+ * @overload
123
+ * @param {readonly T[]} array
124
+ * @returns {T | undefined}
125
+ */
126
+ /**
127
+ * The first `n` items.
128
+ *
129
+ * @template T
130
+ * @overload
131
+ * @param {readonly T[]} array
132
+ * @param {number} n
133
+ * @returns {T[]}
134
+ */
135
+ /**
136
+ * @template T
137
+ * @param {readonly T[]} array
138
+ * @param {number} [n]
139
+ * @returns {T | T[] | undefined}
140
+ */
141
+ function first(array, n) {
142
+ if (n === undefined) return array[0];
143
+ return array.slice(0, Math.max(0, n));
144
+ }
145
+
146
+ /**
147
+ * The last item, or the last `n` items.
148
+ *
149
+ * @example
150
+ * arr.last([1, 2, 3]); // 3
151
+ * arr.last([1, 2, 3], 2); // [2, 3]
152
+ *
153
+ * @template T
154
+ * @overload
155
+ * @param {readonly T[]} array
156
+ * @returns {T | undefined}
157
+ */
158
+ /**
159
+ * The last `n` items.
160
+ *
161
+ * @template T
162
+ * @overload
163
+ * @param {readonly T[]} array
164
+ * @param {number} n
165
+ * @returns {T[]}
166
+ */
167
+ /**
168
+ * @template T
169
+ * @param {readonly T[]} array
170
+ * @param {number} [n]
171
+ * @returns {T | T[] | undefined}
172
+ */
173
+ function last(array, n) {
174
+ if (n === undefined) return array[array.length - 1];
175
+ return n <= 0 ? [] : array.slice(-n);
176
+ }
177
+
178
+ /**
179
+ * One page of an array, with what you need to draw pagination controls.
180
+ * Pages start at 1; an out-of-range page is brought back into range.
181
+ *
182
+ * @example
183
+ * arr.paginate(products, 2, 20);
184
+ * // { items: [...], page: 2, perPage: 20, total: 95, pages: 5, hasPrev: true, hasNext: true }
185
+ *
186
+ * @template T
187
+ * @param {readonly T[]} array
188
+ * @param {number} [page=1]
189
+ * @param {number} [perPage=20]
190
+ * @returns {Page<T>}
191
+ */
192
+ function paginate(array, page = 1, perPage = 20) {
193
+ const size = Math.max(1, Math.floor(perPage));
194
+ const pages = Math.max(1, Math.ceil(array.length / size));
195
+ const current = Math.min(Math.max(1, Math.floor(page) || 1), pages);
196
+ const start = (current - 1) * size;
197
+ return {
198
+ items: array.slice(start, start + size),
199
+ page: current,
200
+ perPage: size,
201
+ total: array.length,
202
+ pages,
203
+ hasPrev: current > 1,
204
+ hasNext: current < pages,
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Removes duplicates. With an iteratee, items that give the same value are
210
+ * duplicates; the first one is kept.
211
+ *
212
+ * @example
213
+ * arr.unique([1, 1, 2, 3]); // [1, 2, 3]
214
+ * arr.unique(users, "email"); // one user per email
215
+ * arr.unique(tags, (t) => t.toLowerCase());
216
+ *
217
+ * @template T
218
+ * @param {readonly T[]} array
219
+ * @param {Iteratee<T>} [iteratee]
220
+ * @returns {T[]}
221
+ */
222
+ function unique(array, iteratee) {
223
+ if (iteratee === undefined) return [...new Set(array)];
224
+ const fn = _fn(iteratee);
225
+ const seen = new Set();
226
+ return array.filter((item, i) => {
227
+ const key = fn(item, i);
228
+ if (seen.has(key)) return false;
229
+ seen.add(key);
230
+ return true;
231
+ });
232
+ }
233
+
234
+ /**
235
+ * Groups items by key.
236
+ *
237
+ * @example
238
+ * arr.groupBy(users, "role"); // { admin: [...], user: [...] }
239
+ * arr.groupBy([1, 2, 3, 4], (n) => (n % 2 ? "odd" : "even")); // { odd: [1, 3], even: [2, 4] }
240
+ *
241
+ * @template T
242
+ * @param {readonly T[]} array
243
+ * @param {Iteratee<T>} iteratee
244
+ * @returns {Record<string, T[]>}
245
+ */
246
+ function groupBy(array, iteratee) {
247
+ const fn = _fn(iteratee);
248
+ /** @type {Record<PropertyKey, T[]>} */
249
+ const out = {};
250
+ array.forEach((item, i) => {
251
+ (out[_key(fn(item, i))] ??= []).push(item);
252
+ });
253
+ return out;
254
+ }
255
+
256
+ /**
257
+ * Indexes items by key. If two items share a key, the last one wins.
258
+ *
259
+ * @example
260
+ * const byId = arr.keyBy(users, "id");
261
+ * byId[42]; // user 42
262
+ *
263
+ * @template T
264
+ * @param {readonly T[]} array
265
+ * @param {Iteratee<T>} iteratee
266
+ * @returns {Record<string, T>}
267
+ */
268
+ function keyBy(array, iteratee) {
269
+ const fn = _fn(iteratee);
270
+ /** @type {Record<PropertyKey, T>} */
271
+ const out = {};
272
+ array.forEach((item, i) => {
273
+ out[_key(fn(item, i))] = item;
274
+ });
275
+ return out;
276
+ }
277
+
278
+ /**
279
+ * Builds a `Map` from an array. Unlike `keyBy()`, keys keep their type.
280
+ *
281
+ * @example
282
+ * arr.toMap(users, "id"); // Map<number, User>
283
+ * arr.toMap(users, "id", (u) => u.name); // Map<number, string>
284
+ *
285
+ * @template T
286
+ * @template [V=T]
287
+ * @param {readonly T[]} array
288
+ * @param {Iteratee<T>} key
289
+ * @param {(item: T, index: number) => V} [value] The value to store. Defaults to the item.
290
+ * @returns {Map<any, V>}
291
+ */
292
+ function toMap(array, key, value) {
293
+ const keyFn = _fn(key);
294
+ /** @type {Map<any, V>} */
295
+ const map = new Map();
296
+ array.forEach((item, i) => map.set(keyFn(item, i), value ? value(item, i) : /** @type {V} */ (/** @type {unknown} */ (item))));
297
+ return map;
298
+ }
299
+
300
+ /**
301
+ * Counts items per key.
302
+ *
303
+ * @example
304
+ * arr.countBy(["a", "b", "a"]); // { a: 2, b: 1 }
305
+ * arr.countBy(users, "country"); // { FR: 12, US: 7 }
306
+ *
307
+ * @template T
308
+ * @param {readonly T[]} array
309
+ * @param {Iteratee<T>} [iteratee] Defaults to the item itself.
310
+ * @returns {Record<string, number>}
311
+ */
312
+ function countBy(array, iteratee) {
313
+ const fn = _fn(iteratee);
314
+ /** @type {Record<PropertyKey, number>} */
315
+ const out = {};
316
+ array.forEach((item, i) => {
317
+ const key = _key(fn(item, i));
318
+ out[key] = (out[key] ?? 0) + 1;
319
+ });
320
+ return out;
321
+ }
322
+
323
+ /**
324
+ * Counts the items that pass a test.
325
+ *
326
+ * @example
327
+ * arr.count(users, (u) => u.active);
328
+ *
329
+ * @template T
330
+ * @param {readonly T[]} array
331
+ * @param {(item: T, index: number) => unknown} predicate
332
+ * @returns {number}
333
+ */
334
+ function count(array, predicate) {
335
+ let total = 0;
336
+ array.forEach((item, i) => {
337
+ if (predicate(item, i)) total++;
338
+ });
339
+ return total;
340
+ }
341
+
342
+ /**
343
+ * Splits items into those that pass a test and those that don't.
344
+ *
345
+ * @example
346
+ * const [adults, minors] = arr.partition(people, (p) => p.age >= 18);
347
+ *
348
+ * @template T
349
+ * @param {readonly T[]} array
350
+ * @param {(item: T, index: number) => unknown} predicate
351
+ * @returns {[T[], T[]]}
352
+ */
353
+ function partition(array, predicate) {
354
+ /** @type {T[]} */
355
+ const pass = [];
356
+ /** @type {T[]} */
357
+ const fail = [];
358
+ array.forEach((item, i) => (predicate(item, i) ? pass : fail).push(item));
359
+ return [pass, fail];
360
+ }
361
+
362
+ /**
363
+ * Takes one property from every item.
364
+ *
365
+ * @example
366
+ * arr.pluck(users, "email"); // ["ada@x.io", "bob@x.io"]
367
+ *
368
+ * @template T
369
+ * @template {keyof T} K
370
+ * @param {readonly T[]} array
371
+ * @param {K} key
372
+ * @returns {Array<T[K]>}
373
+ */
374
+ function pluck(array, key) {
375
+ return array.map((item) => (item == null ? /** @type {T[K]} */ (/** @type {unknown} */ (undefined)) : item[key]));
376
+ }
377
+
378
+ /**
379
+ * Sorts by one or more keys, each with its own direction if you like.
380
+ * The sort is stable and `null`/`undefined` always go last.
381
+ *
382
+ * @example
383
+ * arr.sortBy(users, "age"); // youngest first
384
+ * arr.sortBy(users, "age", "desc"); // oldest first
385
+ * arr.sortBy(users, [["age", "desc"], "lastName"]);
386
+ * arr.sortBy(files, "name", { natural: true }); // "file2" before "file10"
387
+ *
388
+ * @template T
389
+ * @param {readonly T[]} array
390
+ * @param {SortKey<T> | Array<SortKey<T>>} keys
391
+ * @param {"asc" | "desc" | SortByOptions} [order="asc"]
392
+ * @returns {T[]}
393
+ */
394
+ function sortBy(array, keys, order = "asc") {
395
+ const options = typeof order === "string" ? { order } : order;
396
+ const defaultDir = options.order === "desc" ? -1 : 1;
397
+ const collator = options.natural ? new Intl.Collator(options.locale, { numeric: true, sensitivity: "base" }) : null;
398
+ const isTuple = (/** @type {unknown} */ k) => Array.isArray(k) && k.length === 2 && (k[1] === "asc" || k[1] === "desc");
399
+ const list = Array.isArray(keys) && !isTuple(keys) ? keys : [keys];
400
+ const comparators = list.map((k) => {
401
+ const [iteratee, dir] = isTuple(k) ? /** @type {[unknown, string]} */ (k) : [k, undefined];
402
+ return { fn: _fn(iteratee), dir: dir === undefined ? defaultDir : dir === "desc" ? -1 : 1 };
403
+ });
404
+ return array
405
+ .map((item, index) => ({ item, index }))
406
+ .sort((a, b) => {
407
+ for (const { fn, dir } of comparators) {
408
+ const va = fn(a.item, a.index);
409
+ const vb = fn(b.item, b.index);
410
+ const aNil = va === null || va === undefined;
411
+ const bNil = vb === null || vb === undefined;
412
+ if (aNil || bNil) {
413
+ if (aNil && bNil) continue;
414
+ return aNil ? 1 : -1;
415
+ }
416
+ let result = 0;
417
+ if (collator && typeof va === "string" && typeof vb === "string") result = collator.compare(va, vb);
418
+ else if (va < vb) result = -1;
419
+ else if (va > vb) result = 1;
420
+ if (result) return result * dir;
390
421
  }
422
+ return a.index - b.index;
423
+ })
424
+ .map((entry) => entry.item);
425
+ }
426
+
427
+ /**
428
+ * A shuffled copy (Fisher-Yates).
429
+ *
430
+ * @template T
431
+ * @param {readonly T[]} array
432
+ * @returns {T[]}
433
+ */
434
+ function shuffle(array) {
435
+ const out = [...array];
436
+ for (let i = out.length - 1; i > 0; i--) {
437
+ const j = Math.floor(Math.random() * (i + 1));
438
+ [out[i], out[j]] = [out[j], out[i]];
439
+ }
440
+ return out;
441
+ }
442
+
443
+ /**
444
+ * One random item, or `undefined` if the array is empty.
445
+ *
446
+ * @template T
447
+ * @param {readonly T[]} array
448
+ * @returns {T | undefined}
449
+ */
450
+ function sample(array) {
451
+ return array.length ? array[Math.floor(Math.random() * array.length)] : undefined;
452
+ }
453
+
454
+ /**
455
+ * `n` random items, never the same one twice.
456
+ *
457
+ * @example
458
+ * arr.sampleSize(players, 3);
459
+ *
460
+ * @template T
461
+ * @param {readonly T[]} array
462
+ * @param {number} n
463
+ * @returns {T[]}
464
+ */
465
+ function sampleSize(array, n) {
466
+ return shuffle(array).slice(0, Math.max(0, n));
467
+ }
468
+
469
+ /**
470
+ * Rotates items. Positive `n` moves items from the start to the end,
471
+ * negative `n` the other way.
472
+ *
473
+ * @example
474
+ * arr.rotate([1, 2, 3, 4], 1); // [2, 3, 4, 1]
475
+ * arr.rotate([1, 2, 3, 4], -1); // [4, 1, 2, 3]
476
+ *
477
+ * @template T
478
+ * @param {readonly T[]} array
479
+ * @param {number} [n=1]
480
+ * @returns {T[]}
481
+ */
482
+ function rotate(array, n = 1) {
483
+ if (!array.length) return [];
484
+ const k = ((Math.trunc(n) % array.length) + array.length) % array.length;
485
+ return [...array.slice(k), ...array.slice(0, k)];
486
+ }
487
+
488
+ /**
489
+ * Moves one item to another position. Negative indexes count from the end.
490
+ *
491
+ * @example
492
+ * arr.move(["a", "b", "c"], 0, 2); // ["b", "c", "a"]
493
+ *
494
+ * @template T
495
+ * @param {readonly T[]} array
496
+ * @param {number} from
497
+ * @param {number} to
498
+ * @returns {T[]}
499
+ */
500
+ function move(array, from, to) {
501
+ const out = [...array];
502
+ const len = out.length;
503
+ const src = from < 0 ? len + from : from;
504
+ if (src < 0 || src >= len) return out;
505
+ const [item] = out.splice(src, 1);
506
+ out.splice(to < 0 ? len + to : to, 0, item);
507
+ return out;
508
+ }
509
+
510
+ /**
511
+ * Swaps two items.
512
+ *
513
+ * @example
514
+ * arr.swap(["a", "b", "c"], 0, 2); // ["c", "b", "a"]
515
+ *
516
+ * @template T
517
+ * @param {readonly T[]} array
518
+ * @param {number} i
519
+ * @param {number} j
520
+ * @returns {T[]}
521
+ */
522
+ function swap(array, i, j) {
523
+ const out = [...array];
524
+ if (i in out && j in out) [out[i], out[j]] = [out[j], out[i]];
525
+ return out;
526
+ }
527
+
528
+ /**
529
+ * Items of `array` that appear in none of the others.
530
+ *
531
+ * @example
532
+ * arr.difference([1, 2, 3, 4], [2, 4]); // [1, 3]
533
+ *
534
+ * @template T
535
+ * @param {readonly T[]} array
536
+ * @param {...ReadonlyArray<T>} others
537
+ * @returns {T[]}
538
+ */
539
+ function difference(array, ...others) {
540
+ const exclude = new Set(others.flat());
541
+ return array.filter((item) => !exclude.has(item));
542
+ }
543
+
544
+ /**
545
+ * Items present in every array, without duplicates.
546
+ *
547
+ * @example
548
+ * arr.intersection([1, 2, 3], [2, 3, 4], [3, 2]); // [2, 3]
549
+ *
550
+ * @template T
551
+ * @param {...ReadonlyArray<T>} arrays
552
+ * @returns {T[]}
553
+ */
554
+ function intersection(...arrays) {
555
+ if (!arrays.length) return [];
556
+ const [head, ...rest] = arrays;
557
+ const sets = rest.map((a) => new Set(a));
558
+ return [...new Set(head)].filter((item) => sets.every((set) => set.has(item)));
559
+ }
560
+
561
+ /**
562
+ * Items present in any array, without duplicates.
563
+ *
564
+ * @example
565
+ * arr.union([1, 2], [2, 3], [3, 4]); // [1, 2, 3, 4]
566
+ *
567
+ * @template T
568
+ * @param {...ReadonlyArray<T>} arrays
569
+ * @returns {T[]}
570
+ */
571
+ function union(...arrays) {
572
+ return [...new Set(arrays.flat())];
573
+ }
574
+
575
+ /**
576
+ * A copy without the given values. `NaN` works too.
577
+ *
578
+ * @example
579
+ * arr.without([1, 2, 3, 2], 2); // [1, 3]
580
+ *
581
+ * @template T
582
+ * @param {readonly T[]} array
583
+ * @param {...T} values
584
+ * @returns {T[]}
585
+ */
586
+ function without(array, ...values) {
587
+ const exclude = new Set(values);
588
+ return array.filter((item) => !exclude.has(item));
589
+ }
590
+
591
+ /**
592
+ * A copy without the items that pass the test.
593
+ *
594
+ * @example
595
+ * arr.remove([1, 2, 3, 4], (n) => n % 2 === 0); // [1, 3]
596
+ *
597
+ * @template T
598
+ * @param {readonly T[]} array
599
+ * @param {(item: T, index: number) => unknown} predicate
600
+ * @returns {T[]}
601
+ */
602
+ function remove(array, predicate) {
603
+ return array.filter((item, i) => !predicate(item, i));
604
+ }
605
+
606
+ /**
607
+ * Removes falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`).
608
+ *
609
+ * @example
610
+ * arr.compact([0, 1, false, 2, "", 3, null]); // [1, 2, 3]
611
+ *
612
+ * @template T
613
+ * @param {readonly T[]} array
614
+ * @returns {Array<Exclude<T, false | 0 | 0n | "" | null | undefined>>}
615
+ */
616
+ function compact(array) {
617
+ return /** @type {any} */ (array.filter(Boolean));
618
+ }
619
+
620
+ /**
621
+ * Adds the item if it's missing, removes it if it's there. Handy for
622
+ * multi-select state.
623
+ *
624
+ * @example
625
+ * arr.toggle(["a", "b"], "b"); // ["a"]
626
+ * arr.toggle(["a"], "b"); // ["a", "b"]
627
+ *
628
+ * @template T
629
+ * @param {readonly T[]} array
630
+ * @param {T} item
631
+ * @returns {T[]}
632
+ */
633
+ function toggle(array, item) {
634
+ return array.includes(item) ? array.filter((x) => x !== item) : [...array, item];
635
+ }
636
+
637
+ /**
638
+ * Replaces the item with the same identity, or appends it if there's none.
639
+ *
640
+ * @example
641
+ * arr.upsert(users, { id: 2, name: "Bob" }, "id");
642
+ *
643
+ * @template T
644
+ * @param {readonly T[]} array
645
+ * @param {T} item
646
+ * @param {Iteratee<T>} identity What makes two items "the same", like `"id"`.
647
+ * @returns {T[]}
648
+ */
649
+ function upsert(array, item, identity) {
650
+ const fn = _fn(identity);
651
+ const id = fn(item, -1);
652
+ const index = array.findIndex((x, i) => fn(x, i) === id);
653
+ if (index === -1) return [...array, item];
654
+ const out = [...array];
655
+ out[index] = item;
656
+ return out;
657
+ }
658
+
659
+ /**
660
+ * Inserts items at an index. Negative indexes count from the end.
661
+ *
662
+ * @example
663
+ * arr.insert(["a", "d"], 1, "b", "c"); // ["a", "b", "c", "d"]
664
+ *
665
+ * @template T
666
+ * @param {readonly T[]} array
667
+ * @param {number} index
668
+ * @param {...T} items
669
+ * @returns {T[]}
670
+ */
671
+ function insert(array, index, ...items) {
672
+ const out = [...array];
673
+ out.splice(index, 0, ...items);
674
+ return out;
675
+ }
676
+
677
+ /**
678
+ * Flattens nested arrays, one level by default.
679
+ *
680
+ * @example
681
+ * arr.flatten([1, [2, [3, [4]]]]); // [1, 2, [3, [4]]]
682
+ * arr.flatten([1, [2, [3, [4]]]], Infinity); // [1, 2, 3, 4]
683
+ *
684
+ * @template T
685
+ * @template {number} [D=1]
686
+ * @param {readonly T[]} array
687
+ * @param {D} [depth]
688
+ * @returns {FlatArray<T[], D>[]}
689
+ */
690
+ function flatten(array, depth) {
691
+ return /** @type {any} */ (array.flat(depth ?? 1));
692
+ }
693
+
694
+ /**
695
+ * Pairs items by position. Shorter arrays leave `undefined` holes.
696
+ *
697
+ * @example
698
+ * arr.zip(["a", "b"], [1, 2]); // [["a", 1], ["b", 2]]
699
+ *
700
+ * @template T
701
+ * @param {...ReadonlyArray<T>} arrays
702
+ * @returns {Array<Array<T | undefined>>}
703
+ */
704
+ function zip(...arrays) {
705
+ const size = Math.max(0, ...arrays.map((a) => a.length));
706
+ return Array.from({ length: size }, (_, i) => arrays.map((a) => a[i]));
707
+ }
708
+
709
+ /**
710
+ * The opposite of `zip()`: rows become columns.
711
+ *
712
+ * @example
713
+ * arr.unzip([["a", 1], ["b", 2]]); // [["a", "b"], [1, 2]]
714
+ *
715
+ * @template T
716
+ * @param {ReadonlyArray<ReadonlyArray<T>>} tuples
717
+ * @returns {Array<Array<T | undefined>>}
718
+ */
719
+ function unzip(tuples) {
720
+ return zip(...tuples);
721
+ }
722
+
723
+ /**
724
+ * Takes items from each array in turn, then appends the rest.
725
+ *
726
+ * @example
727
+ * arr.interleave([1, 3, 5], [2, 4]); // [1, 2, 3, 4, 5]
728
+ *
729
+ * @template T
730
+ * @param {...ReadonlyArray<T>} arrays
731
+ * @returns {T[]}
732
+ */
733
+ function interleave(...arrays) {
734
+ /** @type {T[]} */
735
+ const out = [];
736
+ const size = Math.max(0, ...arrays.map((a) => a.length));
737
+ for (let i = 0; i < size; i++) for (const a of arrays) if (i < a.length) out.push(a[i]);
738
+ return out;
739
+ }
740
+
741
+ /**
742
+ * Every combination of one item from each array.
743
+ *
744
+ * @example
745
+ * arr.cartesian(["S", "M"], ["red", "blue"]);
746
+ * // [["S", "red"], ["S", "blue"], ["M", "red"], ["M", "blue"]]
747
+ *
748
+ * @template T
749
+ * @param {...ReadonlyArray<T>} arrays
750
+ * @returns {T[][]}
751
+ */
752
+ function cartesian(...arrays) {
753
+ if (!arrays.length) return [];
754
+ return arrays.reduce((acc, list) => acc.flatMap((combo) => list.map((item) => [...combo, item])), /** @type {T[][]} */ ([[]]));
755
+ }
756
+
757
+ /**
758
+ * An array of `n` items, from a value or a function of the index.
759
+ *
760
+ * @example
761
+ * arr.times(3, "x"); // ["x", "x", "x"]
762
+ * arr.times(3, (i) => i * 2); // [0, 2, 4]
763
+ *
764
+ * @template T
765
+ * @param {number} n
766
+ * @param {T | ((index: number) => T)} [value]
767
+ * @returns {T[]}
768
+ */
769
+ function times(n, value) {
770
+ const size = Math.max(0, Math.floor(n));
771
+ const fn = typeof value === "function" ? /** @type {(index: number) => T} */ (value) : () => /** @type {T} */ (value);
772
+ return Array.from({ length: size }, (_, i) => fn(i));
773
+ }
774
+
775
+ /**
776
+ * Adds up a number from each item. Non-numbers count as 0.
777
+ *
778
+ * @example
779
+ * arr.sumBy(cart, "price");
780
+ * arr.sumBy(cart, (item) => item.price * item.quantity);
781
+ *
782
+ * @template T
783
+ * @param {readonly T[]} array
784
+ * @param {Iteratee<T>} [iteratee]
785
+ * @returns {number}
786
+ */
787
+ function sumBy(array, iteratee) {
788
+ const fn = _fn(iteratee);
789
+ return array.reduce((acc, item, i) => acc + (Number(fn(item, i)) || 0), 0);
790
+ }
791
+
792
+ /**
793
+ * The average of a number from each item, or `0` for an empty array.
794
+ *
795
+ * @example
796
+ * arr.averageBy(reviews, "rating"); // 4.3
797
+ *
798
+ * @template T
799
+ * @param {readonly T[]} array
800
+ * @param {Iteratee<T>} [iteratee]
801
+ * @returns {number}
802
+ */
803
+ function averageBy(array, iteratee) {
804
+ return array.length ? sumBy(array, iteratee) / array.length : 0;
805
+ }
806
+
807
+ /**
808
+ * The item with the highest value.
809
+ *
810
+ * @example
811
+ * arr.maxBy(players, "score");
812
+ *
813
+ * @template T
814
+ * @param {readonly T[]} array
815
+ * @param {Iteratee<T>} [iteratee]
816
+ * @returns {T | undefined}
817
+ */
818
+ function maxBy(array, iteratee) {
819
+ const fn = _fn(iteratee);
820
+ /** @type {T | undefined} */
821
+ let best;
822
+ /** @type {any} */
823
+ let bestValue;
824
+ array.forEach((item, i) => {
825
+ const v = fn(item, i);
826
+ if (v !== null && v !== undefined && (bestValue === undefined || v > bestValue)) {
827
+ bestValue = v;
828
+ best = item;
391
829
  }
392
- return best;
393
- },
394
-
395
- /**
396
- * Returns the item with the minimum derived value.
397
- *
398
- * @param {any[]} array - Source array.
399
- * @param {string|((item: any) => number)} [iteratee] - Key or function.
400
- * @returns {any} The item, or undefined if the array is empty.
401
- */
402
- minBy(array, iteratee) {
403
- const fn = _iteratee(iteratee);
404
- let best;
405
- let bestVal = Infinity;
406
- for (const item of array) {
407
- const val = fn(item);
408
- if (val < bestVal) {
409
- bestVal = val;
410
- best = item;
411
- }
830
+ });
831
+ return best;
832
+ }
833
+
834
+ /**
835
+ * The item with the lowest value.
836
+ *
837
+ * @example
838
+ * arr.minBy(products, "price");
839
+ *
840
+ * @template T
841
+ * @param {readonly T[]} array
842
+ * @param {Iteratee<T>} [iteratee]
843
+ * @returns {T | undefined}
844
+ */
845
+ function minBy(array, iteratee) {
846
+ const fn = _fn(iteratee);
847
+ /** @type {T | undefined} */
848
+ let best;
849
+ /** @type {any} */
850
+ let bestValue;
851
+ array.forEach((item, i) => {
852
+ const v = fn(item, i);
853
+ if (v !== null && v !== undefined && (bestValue === undefined || v < bestValue)) {
854
+ bestValue = v;
855
+ best = item;
412
856
  }
413
- return best;
414
- },
415
-
416
- /**
417
- * Creates an array of a given length, filled by a value or generator function.
418
- *
419
- * @example
420
- * nodeComfort.arr.times(3, "x"); // ["x", "x", "x"]
421
- * nodeComfort.arr.times(3, (i) => i * 2); // [0, 2, 4]
422
- *
423
- * @param {number} n - Number of items.
424
- * @param {any|((index: number) => any)} [value] - Fill value or generator.
425
- * @returns {any[]} The generated array.
426
- */
427
- times(n, value) {
428
- const fn = typeof value === "function" ? value : () => value;
429
- const result = [];
430
- for (let i = 0; i < n; i++) result.push(fn(i));
431
- return result;
432
- },
857
+ });
858
+ return best;
859
+ }
860
+
861
+ module.exports = {
862
+ chunk,
863
+ windows,
864
+ first,
865
+ last,
866
+ paginate,
867
+ unique,
868
+ groupBy,
869
+ keyBy,
870
+ toMap,
871
+ countBy,
872
+ count,
873
+ partition,
874
+ pluck,
875
+ sortBy,
876
+ shuffle,
877
+ sample,
878
+ sampleSize,
879
+ rotate,
880
+ move,
881
+ swap,
882
+ difference,
883
+ intersection,
884
+ union,
885
+ without,
886
+ remove,
887
+ compact,
888
+ toggle,
889
+ upsert,
890
+ insert,
891
+ flatten,
892
+ zip,
893
+ unzip,
894
+ interleave,
895
+ cartesian,
896
+ times,
897
+ sumBy,
898
+ averageBy,
899
+ maxBy,
900
+ minBy,
433
901
  };