@cooljs123/easyjs 0.1.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.
package/src/easy.js ADDED
@@ -0,0 +1,1457 @@
1
+ /**
2
+ * Return the input unchanged.
3
+ * @template T
4
+ * @param {T} x
5
+ * @returns {T}
6
+ */
7
+ function identity(x) {
8
+ return x;
9
+ }
10
+
11
+ /**
12
+ * Do nothing.
13
+ * @returns {void}
14
+ */
15
+ function noop() {}
16
+
17
+ /**
18
+ * Check for null or undefined.
19
+ * @param {*} v
20
+ * @returns {boolean}
21
+ */
22
+ function isNil(v) {
23
+ return v === null || v === undefined;
24
+ }
25
+
26
+ /**
27
+ * Check if a value is "empty" (null/undefined, empty string/array/object/map/set).
28
+ * @param {*} v
29
+ * @returns {boolean}
30
+ */
31
+ function isEmpty(v) {
32
+ if (isNil(v)) return true;
33
+ if (typeof v === "string") return v.length === 0;
34
+ if (Array.isArray(v)) return v.length === 0;
35
+ if (v instanceof Map || v instanceof Set) return v.size === 0;
36
+ if (typeof v === "object") return Object.keys(v).length === 0;
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Return fallback if value is null/undefined.
42
+ * @template T
43
+ * @param {T} v
44
+ * @param {T} fallback
45
+ * @returns {T}
46
+ */
47
+ function defaultTo(v, fallback) {
48
+ return isNil(v) ? fallback : v;
49
+ }
50
+
51
+ /**
52
+ * Convert to lowercase string.
53
+ * @param {*} s
54
+ * @returns {string}
55
+ */
56
+ function toLower(s) {
57
+ return String(s).toLowerCase();
58
+ }
59
+
60
+ /**
61
+ * Convert to uppercase string.
62
+ * @param {*} s
63
+ * @returns {string}
64
+ */
65
+ function toUpper(s) {
66
+ return String(s).toUpperCase();
67
+ }
68
+
69
+ /**
70
+ * Capitalize first letter.
71
+ * @param {*} s
72
+ * @returns {string}
73
+ */
74
+ function capitalize(s) {
75
+ const str = String(s);
76
+ return str.length ? str[0].toUpperCase() + str.slice(1) : "";
77
+ }
78
+
79
+ /**
80
+ * Trim whitespace.
81
+ * @param {*} s
82
+ * @returns {string}
83
+ */
84
+ function trim(s) {
85
+ return String(s).trim();
86
+ }
87
+
88
+ /**
89
+ * Lowercase + trim.
90
+ * @param {*} s
91
+ * @returns {string}
92
+ */
93
+ function normalizeString(s) {
94
+ return trim(toLower(s));
95
+ }
96
+
97
+ /**
98
+ * Slugify a string (lowercase, hyphenated).
99
+ * @param {*} s
100
+ * @returns {string}
101
+ */
102
+ function slugify(s) {
103
+ return String(s)
104
+ .toLowerCase()
105
+ .trim()
106
+ .replace(/[^a-z0-9]+/g, "-")
107
+ .replace(/^-+|-+$/g, "");
108
+ }
109
+
110
+ /**
111
+ * Left pad a string.
112
+ * @param {*} s
113
+ * @param {number} length
114
+ * @param {string} [padChar=" "]
115
+ * @returns {string}
116
+ */
117
+ function padLeft(s, length, padChar = " ") {
118
+ return String(s).padStart(length, padChar);
119
+ }
120
+
121
+ /**
122
+ * Right pad a string.
123
+ * @param {*} s
124
+ * @param {number} length
125
+ * @param {string} [padChar=" "]
126
+ * @returns {string}
127
+ */
128
+ function padRight(s, length, padChar = " ") {
129
+ return String(s).padEnd(length, padChar);
130
+ }
131
+
132
+ /**
133
+ * Repeat a string n times.
134
+ * @param {*} s
135
+ * @param {number} times
136
+ * @returns {string}
137
+ */
138
+ function repeat(s, times) {
139
+ return String(s).repeat(times);
140
+ }
141
+
142
+ /**
143
+ * Remove non-alphanumeric characters.
144
+ * @param {*} s
145
+ * @returns {string}
146
+ */
147
+ function stripNonAlpha(s) {
148
+ return String(s).replace(/[^a-z0-9]+/gi, "");
149
+ }
150
+
151
+ /**
152
+ * Check if string is blank (empty or whitespace).
153
+ * @param {*} s
154
+ * @returns {boolean}
155
+ */
156
+ function isBlank(s) {
157
+ return String(s).trim().length === 0;
158
+ }
159
+
160
+ /**
161
+ * Wrap non-array value in array; null/undefined -> [].
162
+ * @param {*} v
163
+ * @returns {Array}
164
+ */
165
+ function asArray(v) {
166
+ if (isNil(v)) return [];
167
+ return Array.isArray(v) ? v : [v];
168
+ }
169
+
170
+ function arrayFromArgs(args, fallback) {
171
+ if (args.length === 0) return fallback || [];
172
+ if (Array.isArray(args[0])) return args[0];
173
+ return Array.prototype.slice.call(args);
174
+ }
175
+
176
+ /**
177
+ * Check if value is in a list of candidates.
178
+ * @param {*} value
179
+ * @param {Array|*} candidates
180
+ * @returns {boolean}
181
+ */
182
+ function anyOf(value, candidates) {
183
+ const list = Array.isArray(candidates) ? candidates : Array.prototype.slice.call(arguments, 1);
184
+ return list.includes(value);
185
+ }
186
+
187
+ /**
188
+ * Alias for anyOf.
189
+ * @param {*} value
190
+ * @param {Array|*} candidates
191
+ * @returns {boolean}
192
+ */
193
+ function eqAny(value, candidates) {
194
+ return anyOf.apply(null, arguments);
195
+ }
196
+
197
+ /**
198
+ * Check if value is included in array.
199
+ * @param {*} value
200
+ * @param {Array} arr
201
+ * @returns {boolean}
202
+ */
203
+ function inArray(value, arr) {
204
+ if (Array.isArray(arr)) return arr.includes(value);
205
+ if (arguments.length > 2) {
206
+ const list = Array.prototype.slice.call(arguments, 1);
207
+ return list.includes(value);
208
+ }
209
+ return false;
210
+ }
211
+
212
+ /**
213
+ * Clamp a number to a range.
214
+ * @param {*} n
215
+ * @param {number} min
216
+ * @param {number} max
217
+ * @returns {number}
218
+ */
219
+ function clamp(n, min, max) {
220
+ const num = Number(n);
221
+ return Math.min(max, Math.max(min, num));
222
+ }
223
+
224
+ /**
225
+ * Check if a number is between min and max.
226
+ * @param {*} n
227
+ * @param {number} min
228
+ * @param {number} max
229
+ * @param {boolean} [inclusive=true]
230
+ * @returns {boolean}
231
+ */
232
+ function between(n, min, max, inclusive = true) {
233
+ const num = Number(n);
234
+ return inclusive ? num >= min && num <= max : num > min && num < max;
235
+ }
236
+
237
+ /**
238
+ * Parse integer with fallback to 0.
239
+ * @param {*} v
240
+ * @param {number} [base=10]
241
+ * @returns {number}
242
+ */
243
+ function toInt(v, base = 10) {
244
+ const n = parseInt(v, base);
245
+ return Number.isNaN(n) ? 0 : n;
246
+ }
247
+
248
+ /**
249
+ * Parse float with fallback to 0.
250
+ * @param {*} v
251
+ * @returns {number}
252
+ */
253
+ function toFloat(v) {
254
+ const n = parseFloat(v);
255
+ return Number.isNaN(n) ? 0 : n;
256
+ }
257
+
258
+ /**
259
+ * Convert to number with fallback 0.
260
+ * @param {*} v
261
+ * @returns {number}
262
+ */
263
+ function toNumber(v) {
264
+ const n = Number(v);
265
+ return Number.isNaN(n) ? 0 : n;
266
+ }
267
+
268
+ /**
269
+ * Check if value is a finite number.
270
+ * @param {*} v
271
+ * @returns {boolean}
272
+ */
273
+ function isNumber(v) {
274
+ return typeof v === "number" && Number.isFinite(v);
275
+ }
276
+
277
+ /**
278
+ * Check if value is an integer.
279
+ * @param {*} v
280
+ * @returns {boolean}
281
+ */
282
+ function isInt(v) {
283
+ return Number.isInteger(v);
284
+ }
285
+
286
+ /**
287
+ * Filter an array by predicate or remove null/undefined.
288
+ * @param {Array} arr
289
+ * @param {Function} [predicate]
290
+ * @returns {Array}
291
+ */
292
+ function compact(arr, predicate) {
293
+ if (!Array.isArray(arr)) {
294
+ const args = Array.prototype.slice.call(arguments);
295
+ const last = args[args.length - 1];
296
+ if (typeof last === "function") {
297
+ predicate = last;
298
+ arr = args.slice(0, -1);
299
+ } else {
300
+ arr = args;
301
+ }
302
+ }
303
+ if (typeof predicate === "function") return arr.filter(predicate);
304
+ return arr.filter(v => !isNil(v));
305
+ }
306
+
307
+ /**
308
+ * Unique values from array.
309
+ * @param {Array} arr
310
+ * @returns {Array}
311
+ */
312
+ function uniq(arr) {
313
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
314
+ return Array.from(new Set(arr));
315
+ }
316
+
317
+ /**
318
+ * Flatten one level.
319
+ * @param {Array} arr
320
+ * @returns {Array}
321
+ */
322
+ function flatten(arr) {
323
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
324
+ return arr.reduce((acc, v) => acc.concat(v), []);
325
+ }
326
+
327
+ /**
328
+ * Split array into chunks.
329
+ * @param {Array} arr
330
+ * @param {number} size
331
+ * @returns {Array<Array>}
332
+ */
333
+ function chunk(arr, size) {
334
+ if (!Array.isArray(arr)) {
335
+ const args = Array.prototype.slice.call(arguments);
336
+ size = args.pop();
337
+ arr = args;
338
+ }
339
+ if (!Array.isArray(arr) || size <= 0) return [];
340
+ const out = [];
341
+ for (let i = 0; i < arr.length; i += size) {
342
+ out.push(arr.slice(i, i + size));
343
+ }
344
+ return out;
345
+ }
346
+
347
+ /**
348
+ * Create a numeric range (end exclusive).
349
+ * @param {number} start
350
+ * @param {number} [end]
351
+ * @param {number} [step=1]
352
+ * @returns {Array<number>}
353
+ */
354
+ function range(start, end, step = 1) {
355
+ let s = start;
356
+ let e = end;
357
+ if (end === undefined) {
358
+ s = 0;
359
+ e = start;
360
+ }
361
+ if (step === 0) return [];
362
+ const out = [];
363
+ const inc = step > 0;
364
+ for (let i = s; inc ? i < e : i > e; i += step) {
365
+ out.push(i);
366
+ }
367
+ return out;
368
+ }
369
+
370
+ /**
371
+ * Minimum of array or args.
372
+ * @param {Array|number} arr
373
+ * @returns {number}
374
+ */
375
+ function min(arr) {
376
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
377
+ if (arr.length === 0) return 0;
378
+ return Math.min.apply(null, arr.map(Number));
379
+ }
380
+
381
+ /**
382
+ * Maximum of array or args.
383
+ * @param {Array|number} arr
384
+ * @returns {number}
385
+ */
386
+ function max(arr) {
387
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
388
+ if (arr.length === 0) return 0;
389
+ return Math.max.apply(null, arr.map(Number));
390
+ }
391
+
392
+ /**
393
+ * Sum array values.
394
+ * @param {Array} arr
395
+ * @returns {number}
396
+ */
397
+ function sum(arr) {
398
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
399
+ return arr.reduce((a, b) => a + Number(b || 0), 0);
400
+ }
401
+
402
+ /**
403
+ * Average array values.
404
+ * @param {Array} arr
405
+ * @returns {number}
406
+ */
407
+ function avg(arr) {
408
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
409
+ if (arr.length === 0) return 0;
410
+ return sum(arr) / arr.length;
411
+ }
412
+
413
+ /**
414
+ * Median of array or args.
415
+ * @param {Array|number} arr
416
+ * @returns {number}
417
+ */
418
+ function median(arr) {
419
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
420
+ if (arr.length === 0) return 0;
421
+ const sorted = arr.map(Number).slice().sort((a, b) => a - b);
422
+ const mid = Math.floor(sorted.length / 2);
423
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
424
+ }
425
+
426
+ /**
427
+ * Mode (most frequent) of array or args.
428
+ * @param {Array|number} arr
429
+ * @returns {number}
430
+ */
431
+ function mode(arr) {
432
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
433
+ if (arr.length === 0) return 0;
434
+ const counts = new Map();
435
+ let best = arr[0];
436
+ let bestCount = 0;
437
+ for (const v of arr) {
438
+ const c = (counts.get(v) || 0) + 1;
439
+ counts.set(v, c);
440
+ if (c > bestCount) {
441
+ bestCount = c;
442
+ best = v;
443
+ }
444
+ }
445
+ return Number(best);
446
+ }
447
+
448
+ /**
449
+ * Percentile (0-100) of array or args.
450
+ * @param {Array|number} arr
451
+ * @param {number} p
452
+ * @returns {number}
453
+ */
454
+ function percentile(arr, p) {
455
+ if (!Array.isArray(arr)) {
456
+ const args = Array.prototype.slice.call(arguments);
457
+ p = args.pop();
458
+ arr = args;
459
+ }
460
+ if (!Array.isArray(arr) || arr.length === 0) return 0;
461
+ const sorted = arr.map(Number).slice().sort((a, b) => a - b);
462
+ const idx = Math.min(sorted.length - 1, Math.max(0, Math.round((p / 100) * (sorted.length - 1))));
463
+ return sorted[idx];
464
+ }
465
+
466
+ /**
467
+ * Group by key or key function.
468
+ * @param {Array} arr
469
+ * @param {Function|string} keyFn
470
+ * @returns {Object}
471
+ */
472
+ function groupBy(arr, keyFn) {
473
+ if (!Array.isArray(arr)) {
474
+ const args = Array.prototype.slice.call(arguments);
475
+ keyFn = args.pop();
476
+ arr = args;
477
+ }
478
+ if (!Array.isArray(arr)) return {};
479
+ const fn = typeof keyFn === "function" ? keyFn : (x) => x[keyFn];
480
+ return arr.reduce((acc, item) => {
481
+ const key = fn(item);
482
+ if (!acc[key]) acc[key] = [];
483
+ acc[key].push(item);
484
+ return acc;
485
+ }, {});
486
+ }
487
+
488
+ /**
489
+ * First element of array.
490
+ * @param {Array} arr
491
+ * @returns {*}
492
+ */
493
+ function first(arr) {
494
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
495
+ return arr[0];
496
+ }
497
+
498
+ /**
499
+ * Last element of array.
500
+ * @param {Array} arr
501
+ * @returns {*}
502
+ */
503
+ function last(arr) {
504
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
505
+ return arr[arr.length - 1];
506
+ }
507
+
508
+ /**
509
+ * Take first n items.
510
+ * @param {Array} arr
511
+ * @param {number} n
512
+ * @returns {Array}
513
+ */
514
+ function take(arr, n) {
515
+ if (!Array.isArray(arr)) {
516
+ const args = Array.prototype.slice.call(arguments);
517
+ n = args.pop();
518
+ arr = args;
519
+ }
520
+ return Array.isArray(arr) ? arr.slice(0, n) : [];
521
+ }
522
+
523
+ /**
524
+ * Take last n items.
525
+ * @param {Array} arr
526
+ * @param {number} n
527
+ * @returns {Array}
528
+ */
529
+ function takeRight(arr, n) {
530
+ if (!Array.isArray(arr)) {
531
+ const args = Array.prototype.slice.call(arguments);
532
+ n = args.pop();
533
+ arr = args;
534
+ }
535
+ return Array.isArray(arr) ? arr.slice(Math.max(0, arr.length - n)) : [];
536
+ }
537
+
538
+ /**
539
+ * Drop first n items.
540
+ * @param {Array} arr
541
+ * @param {number} n
542
+ * @returns {Array}
543
+ */
544
+ function drop(arr, n) {
545
+ if (!Array.isArray(arr)) {
546
+ const args = Array.prototype.slice.call(arguments);
547
+ n = args.pop();
548
+ arr = args;
549
+ }
550
+ return Array.isArray(arr) ? arr.slice(n) : [];
551
+ }
552
+
553
+ /**
554
+ * Drop last n items.
555
+ * @param {Array} arr
556
+ * @param {number} n
557
+ * @returns {Array}
558
+ */
559
+ function dropRight(arr, n) {
560
+ if (!Array.isArray(arr)) {
561
+ const args = Array.prototype.slice.call(arguments);
562
+ n = args.pop();
563
+ arr = args;
564
+ }
565
+ return Array.isArray(arr) ? arr.slice(0, Math.max(0, arr.length - n)) : [];
566
+ }
567
+
568
+ /**
569
+ * Zip arrays into array of tuples.
570
+ * @param {Array} arrs
571
+ * @returns {Array<Array>}
572
+ */
573
+ function zip(arrs) {
574
+ const lists = Array.isArray(arrs) && Array.isArray(arrs[0]) ? arrs : Array.prototype.slice.call(arguments);
575
+ if (lists.length === 0) return [];
576
+ const minLen = Math.min.apply(null, lists.map(a => a.length));
577
+ const out = [];
578
+ for (let i = 0; i < minLen; i++) {
579
+ out.push(lists.map(a => a[i]));
580
+ }
581
+ return out;
582
+ }
583
+
584
+ /**
585
+ * Unzip array of tuples.
586
+ * @param {Array<Array>} arr
587
+ * @returns {Array<Array>}
588
+ */
589
+ function unzip(arr) {
590
+ if (!Array.isArray(arr) || arr.length === 0) return [];
591
+ return zip.apply(null, arr);
592
+ }
593
+
594
+ /**
595
+ * Intersection of arrays.
596
+ * @param {Array} arr
597
+ * @returns {Array}
598
+ */
599
+ function intersection(arr) {
600
+ const lists = Array.isArray(arr) && Array.isArray(arr[0]) ? arr : Array.prototype.slice.call(arguments);
601
+ if (lists.length === 0) return [];
602
+ return lists.reduce((acc, cur) => acc.filter(x => cur.includes(x)));
603
+ }
604
+
605
+ /**
606
+ * Difference of arrays (first minus rest).
607
+ * @param {Array} arr
608
+ * @returns {Array}
609
+ */
610
+ function difference(arr) {
611
+ const lists = Array.isArray(arr) && Array.isArray(arr[0]) ? arr : Array.prototype.slice.call(arguments);
612
+ if (lists.length === 0) return [];
613
+ const [firstArr, ...rest] = lists;
614
+ const exclude = new Set(flatten(rest));
615
+ return firstArr.filter(x => !exclude.has(x));
616
+ }
617
+
618
+ /**
619
+ * Union of arrays.
620
+ * @param {Array} arr
621
+ * @returns {Array}
622
+ */
623
+ function union(arr) {
624
+ const lists = Array.isArray(arr) && Array.isArray(arr[0]) ? arr : Array.prototype.slice.call(arguments);
625
+ return uniq(flatten(lists));
626
+ }
627
+
628
+ /**
629
+ * Pick keys from an object.
630
+ * @param {Object} obj
631
+ * @param {Array|string} keys
632
+ * @returns {Object}
633
+ */
634
+ function pick(obj, keys) {
635
+ if (!obj || typeof obj !== "object") return {};
636
+ const list = Array.isArray(keys) ? keys : Array.prototype.slice.call(arguments, 1);
637
+ const out = {};
638
+ list.forEach(k => {
639
+ if (k in obj) out[k] = obj[k];
640
+ });
641
+ return out;
642
+ }
643
+
644
+ /**
645
+ * Omit keys from an object.
646
+ * @param {Object} obj
647
+ * @param {Array|string} keys
648
+ * @returns {Object}
649
+ */
650
+ function omit(obj, keys) {
651
+ if (!obj || typeof obj !== "object") return {};
652
+ const list = new Set(Array.isArray(keys) ? keys : Array.prototype.slice.call(arguments, 1));
653
+ const out = {};
654
+ Object.keys(obj).forEach(k => {
655
+ if (!list.has(k)) out[k] = obj[k];
656
+ });
657
+ return out;
658
+ }
659
+
660
+ /**
661
+ * Check if object has own property.
662
+ * @param {Object} obj
663
+ * @param {string} key
664
+ * @returns {boolean}
665
+ */
666
+ function has(obj, key) {
667
+ return !!obj && Object.prototype.hasOwnProperty.call(obj, key);
668
+ }
669
+
670
+ /**
671
+ * Object.keys shortcut.
672
+ * @param {Object} obj
673
+ * @returns {Array<string>}
674
+ */
675
+ function keys(obj) {
676
+ return obj ? Object.keys(obj) : [];
677
+ }
678
+
679
+ /**
680
+ * Object.values shortcut.
681
+ * @param {Object} obj
682
+ * @returns {Array}
683
+ */
684
+ function values(obj) {
685
+ return obj ? Object.values(obj) : [];
686
+ }
687
+
688
+ /**
689
+ * Object.entries shortcut.
690
+ * @param {Object} obj
691
+ * @returns {Array<Array>}
692
+ */
693
+ function entries(obj) {
694
+ return obj ? Object.entries(obj) : [];
695
+ }
696
+
697
+ /**
698
+ * Object.fromEntries shortcut.
699
+ * @param {Array<Array>} arr
700
+ * @returns {Object}
701
+ */
702
+ function fromEntries(arr) {
703
+ return Object.fromEntries(Array.isArray(arr) ? arr : []);
704
+ }
705
+
706
+ /**
707
+ * Shallow merge objects (right wins).
708
+ * @param {Object} obj
709
+ * @returns {Object}
710
+ */
711
+ function merge(obj) {
712
+ const list = Array.isArray(obj) && !Array.isArray(obj[0]) ? obj : Array.prototype.slice.call(arguments);
713
+ return Object.assign({}, ...list);
714
+ }
715
+
716
+ /**
717
+ * Deep merge objects (plain objects only).
718
+ * @param {Object} obj
719
+ * @returns {Object}
720
+ */
721
+ function deepMerge(obj) {
722
+ const list = Array.prototype.slice.call(arguments);
723
+ const isPlain = (v) => v && typeof v === "object" && !Array.isArray(v);
724
+ const out = {};
725
+ list.forEach(src => {
726
+ if (!isPlain(src)) return;
727
+ Object.keys(src).forEach(k => {
728
+ if (isPlain(src[k]) && isPlain(out[k])) {
729
+ out[k] = deepMerge(out[k], src[k]);
730
+ } else {
731
+ out[k] = src[k];
732
+ }
733
+ });
734
+ });
735
+ return out;
736
+ }
737
+
738
+ /**
739
+ * Check if value is a plain object.
740
+ * @param {*} v
741
+ * @returns {boolean}
742
+ */
743
+ function isObject(v) {
744
+ return v && typeof v === "object" && !Array.isArray(v);
745
+ }
746
+
747
+ /**
748
+ * Size of array/string/object/map/set.
749
+ * @param {*} v
750
+ * @returns {number}
751
+ */
752
+ function size(v) {
753
+ if (isNil(v)) return 0;
754
+ if (typeof v === "string" || Array.isArray(v)) return v.length;
755
+ if (v instanceof Map || v instanceof Set) return v.size;
756
+ if (typeof v === "object") return Object.keys(v).length;
757
+ return 0;
758
+ }
759
+
760
+ /**
761
+ * Normalize a path to array form.
762
+ * @param {string|Array} path
763
+ * @returns {Array<string>}
764
+ */
765
+ function parsePath(path) {
766
+ if (Array.isArray(path)) return path;
767
+ return String(path)
768
+ .replace(/\[(\d+)\]/g, ".$1")
769
+ .split(".")
770
+ .filter(Boolean);
771
+ }
772
+
773
+ /**
774
+ * Safe get by path with fallback.
775
+ * @param {Object} obj
776
+ * @param {string|Array} path
777
+ * @param {*} fallback
778
+ * @returns {*}
779
+ */
780
+ function safeGet(obj, path, fallback) {
781
+ const parts = parsePath(path);
782
+ let cur = obj;
783
+ for (let i = 0; i < parts.length; i++) {
784
+ if (cur == null) return fallback;
785
+ cur = cur[parts[i]];
786
+ }
787
+ return cur === undefined ? fallback : cur;
788
+ }
789
+
790
+ /**
791
+ * Safe set by path (creates objects).
792
+ * @param {Object} obj
793
+ * @param {string|Array} path
794
+ * @param {*} value
795
+ * @returns {Object}
796
+ */
797
+ function safeSet(obj, path, value) {
798
+ const parts = parsePath(path);
799
+ if (!parts.length) return obj;
800
+ let cur = obj;
801
+ for (let i = 0; i < parts.length - 1; i++) {
802
+ const key = parts[i];
803
+ if (cur[key] == null || typeof cur[key] !== "object") cur[key] = {};
804
+ cur = cur[key];
805
+ }
806
+ cur[parts[parts.length - 1]] = value;
807
+ return obj;
808
+ }
809
+
810
+ /**
811
+ * Try/catch helper with fallback value or handler.
812
+ * @param {Function} fn
813
+ * @param {*|Function} fallback
814
+ * @returns {*}
815
+ */
816
+ function tryCatch(fn, fallback) {
817
+ try {
818
+ return fn();
819
+ } catch (err) {
820
+ return typeof fallback === "function" ? fallback(err) : fallback;
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Run a side-effect function and return the original value.
826
+ * @param {*} value
827
+ * @param {Function} fn
828
+ * @returns {*}
829
+ */
830
+ function tap(value, fn) {
831
+ if (typeof fn === "function") fn(value);
832
+ return value;
833
+ }
834
+
835
+ /**
836
+ * Sleep for ms.
837
+ * @param {number} ms
838
+ * @returns {Promise<void>}
839
+ */
840
+ function sleep(ms) {
841
+ return new Promise(resolve => setTimeout(resolve, ms));
842
+ }
843
+
844
+ /**
845
+ * Check if string includes any of the candidates.
846
+ * @param {*} str
847
+ * @param {Array|string} candidates
848
+ * @returns {boolean}
849
+ */
850
+ function containsAny(str, candidates) {
851
+ const s = String(str);
852
+ const list = Array.isArray(candidates) ? candidates : Array.prototype.slice.call(arguments, 1);
853
+ return list.some(c => s.includes(String(c)));
854
+ }
855
+
856
+ /**
857
+ * Check if string starts with any of the candidates.
858
+ * @param {*} str
859
+ * @param {Array|string} candidates
860
+ * @returns {boolean}
861
+ */
862
+ function startsWithAny(str, candidates) {
863
+ const s = String(str);
864
+ const list = Array.isArray(candidates) ? candidates : Array.prototype.slice.call(arguments, 1);
865
+ return list.some(c => s.startsWith(String(c)));
866
+ }
867
+
868
+ /**
869
+ * Check if string ends with any of the candidates.
870
+ * @param {*} str
871
+ * @param {Array|string} candidates
872
+ * @returns {boolean}
873
+ */
874
+ function endsWithAny(str, candidates) {
875
+ const s = String(str);
876
+ const list = Array.isArray(candidates) ? candidates : Array.prototype.slice.call(arguments, 1);
877
+ return list.some(c => s.endsWith(String(c)));
878
+ }
879
+
880
+ /**
881
+ * Safe JSON parse with fallback.
882
+ * @param {string} s
883
+ * @param {*} fallback
884
+ * @returns {*}
885
+ */
886
+ function safeJsonParse(s, fallback) {
887
+ try {
888
+ return JSON.parse(s);
889
+ } catch (err) {
890
+ return fallback;
891
+ }
892
+ }
893
+
894
+ /**
895
+ * Run a function only once.
896
+ * @param {Function} fn
897
+ * @returns {Function}
898
+ */
899
+ function once(fn) {
900
+ let called = false;
901
+ let value;
902
+ return function wrapped() {
903
+ if (!called) {
904
+ called = true;
905
+ value = fn.apply(this, arguments);
906
+ }
907
+ return value;
908
+ };
909
+ }
910
+
911
+ /**
912
+ * Debounce a function.
913
+ * @param {Function} fn
914
+ * @param {number} wait
915
+ * @returns {Function}
916
+ */
917
+ function debounce(fn, wait) {
918
+ let timer = null;
919
+ return function debounced() {
920
+ const args = arguments;
921
+ const ctx = this;
922
+ clearTimeout(timer);
923
+ timer = setTimeout(() => fn.apply(ctx, args), wait);
924
+ };
925
+ }
926
+
927
+ /**
928
+ * Throttle a function.
929
+ * @param {Function} fn
930
+ * @param {number} wait
931
+ * @returns {Function}
932
+ */
933
+ function throttle(fn, wait) {
934
+ let last = 0;
935
+ let timer = null;
936
+ return function throttled() {
937
+ const now = Date.now();
938
+ const remaining = wait - (now - last);
939
+ const ctx = this;
940
+ const args = arguments;
941
+ if (remaining <= 0) {
942
+ if (timer) {
943
+ clearTimeout(timer);
944
+ timer = null;
945
+ }
946
+ last = now;
947
+ fn.apply(ctx, args);
948
+ } else if (!timer) {
949
+ timer = setTimeout(() => {
950
+ last = Date.now();
951
+ timer = null;
952
+ fn.apply(ctx, args);
953
+ }, remaining);
954
+ }
955
+ };
956
+ }
957
+
958
+ /**
959
+ * Unique by key or key function.
960
+ * @param {Array} arr
961
+ * @param {Function|string} keyFn
962
+ * @returns {Array}
963
+ */
964
+ function uniqueBy(arr, keyFn) {
965
+ if (!Array.isArray(arr)) {
966
+ const args = Array.prototype.slice.call(arguments);
967
+ keyFn = args.pop();
968
+ arr = args;
969
+ }
970
+ if (!Array.isArray(arr)) return [];
971
+ const fn = typeof keyFn === "function" ? keyFn : (x) => x[keyFn];
972
+ const seen = new Set();
973
+ const out = [];
974
+ for (const item of arr) {
975
+ const key = fn(item);
976
+ if (!seen.has(key)) {
977
+ seen.add(key);
978
+ out.push(item);
979
+ }
980
+ }
981
+ return out;
982
+ }
983
+
984
+ /**
985
+ * Pluck a property from array of objects.
986
+ * @param {Array} arr
987
+ * @param {string} key
988
+ * @returns {Array}
989
+ */
990
+ function pluck(arr, key) {
991
+ if (!Array.isArray(arr)) {
992
+ const args = Array.prototype.slice.call(arguments);
993
+ key = args.pop();
994
+ arr = args;
995
+ }
996
+ if (!Array.isArray(arr)) return [];
997
+ return arr.map(x => x && x[key]);
998
+ }
999
+
1000
+ /**
1001
+ * Sort by key or key function (ascending).
1002
+ * @param {Array} arr
1003
+ * @param {Function|string} keyFn
1004
+ * @returns {Array}
1005
+ */
1006
+ function sortBy(arr, keyFn) {
1007
+ if (!Array.isArray(arr)) {
1008
+ const args = Array.prototype.slice.call(arguments);
1009
+ keyFn = args.pop();
1010
+ arr = args;
1011
+ }
1012
+ if (!Array.isArray(arr)) return [];
1013
+ const fn = typeof keyFn === "function" ? keyFn : (x) => x[keyFn];
1014
+ return arr.slice().sort((a, b) => {
1015
+ const ka = fn(a);
1016
+ const kb = fn(b);
1017
+ if (ka < kb) return -1;
1018
+ if (ka > kb) return 1;
1019
+ return 0;
1020
+ });
1021
+ }
1022
+
1023
+ /**
1024
+ * Round to a number of decimals.
1025
+ * @param {number} n
1026
+ * @param {number} decimals
1027
+ * @returns {number}
1028
+ */
1029
+ function roundTo(n, decimals) {
1030
+ // If a place value like 100 or 1000 is provided, round to that place.
1031
+ if (typeof decimals === "number" && Number.isInteger(decimals) && Math.abs(decimals) >= 10) {
1032
+ const place = decimals;
1033
+ if (place === 0) return 0;
1034
+ return Math.round(Number(n) / place) * place;
1035
+ }
1036
+ const p = Math.pow(10, decimals || 0);
1037
+ return Math.round(Number(n) * p) / p;
1038
+ }
1039
+
1040
+ /**
1041
+ * Round to decimal places (e.g. hundredths = 2).
1042
+ * @param {number} n
1043
+ * @param {number|string} place
1044
+ * @returns {number}
1045
+ */
1046
+ function roundDecimal(n, place) {
1047
+ const map = {
1048
+ tenths: 1,
1049
+ hundredths: 2,
1050
+ thousandths: 3,
1051
+ };
1052
+ const dec = typeof place === "string" ? (map[place] ?? 0) : place;
1053
+ const p = Math.pow(10, dec || 0);
1054
+ return Math.round(Number(n) * p) / p;
1055
+ }
1056
+
1057
+ /**
1058
+ * Floor a number.
1059
+ * @param {number} n
1060
+ * @returns {number}
1061
+ */
1062
+ function roundDown(n) {
1063
+ return Math.floor(n);
1064
+ }
1065
+
1066
+ /**
1067
+ * Ceil a number.
1068
+ * @param {number} n
1069
+ * @returns {number}
1070
+ */
1071
+ function roundUp(n) {
1072
+ return Math.ceil(n);
1073
+ }
1074
+
1075
+ /**
1076
+ * Linear interpolation.
1077
+ * @param {number} a
1078
+ * @param {number} b
1079
+ * @param {number} t
1080
+ * @returns {number}
1081
+ */
1082
+ function lerp(a, b, t) {
1083
+ return a + (b - a) * t;
1084
+ }
1085
+
1086
+ /**
1087
+ * Convert part/total to percentage.
1088
+ * @param {number} part
1089
+ * @param {number} total
1090
+ * @returns {number}
1091
+ */
1092
+ function percent(part, total) {
1093
+ if (total === 0) return 0;
1094
+ return (Number(part) / Number(total)) * 100;
1095
+ }
1096
+
1097
+ /**
1098
+ * Clamp to [0, 1].
1099
+ * @param {number} n
1100
+ * @returns {number}
1101
+ */
1102
+ function clamp01(n) {
1103
+ return clamp(n, 0, 1);
1104
+ }
1105
+
1106
+ /**
1107
+ * Random integer between min and max inclusive.
1108
+ * @param {number} min
1109
+ * @param {number} max
1110
+ * @returns {number}
1111
+ */
1112
+ function randomInt(min, max) {
1113
+ const a = Math.ceil(min);
1114
+ const b = Math.floor(max);
1115
+ return Math.floor(Math.random() * (b - a + 1)) + a;
1116
+ }
1117
+
1118
+ /**
1119
+ * Random float between min and max.
1120
+ * @param {number} min
1121
+ * @param {number} max
1122
+ * @returns {number}
1123
+ */
1124
+ function randomFloat(min, max) {
1125
+ return Math.random() * (max - min) + min;
1126
+ }
1127
+
1128
+ /**
1129
+ * Shuffle array (Fisher-Yates).
1130
+ * @param {Array} arr
1131
+ * @returns {Array}
1132
+ */
1133
+ function shuffle(arr) {
1134
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
1135
+ if (!Array.isArray(arr)) return [];
1136
+ const out = arr.slice();
1137
+ for (let i = out.length - 1; i > 0; i--) {
1138
+ const j = Math.floor(Math.random() * (i + 1));
1139
+ [out[i], out[j]] = [out[j], out[i]];
1140
+ }
1141
+ return out;
1142
+ }
1143
+
1144
+ /**
1145
+ * Sample one element from array.
1146
+ * @param {Array} arr
1147
+ * @returns {*}
1148
+ */
1149
+ function sample(arr) {
1150
+ if (!Array.isArray(arr)) arr = arrayFromArgs(arguments, []);
1151
+ if (!Array.isArray(arr) || arr.length === 0) return undefined;
1152
+ return arr[Math.floor(Math.random() * arr.length)];
1153
+ }
1154
+
1155
+ /**
1156
+ * Deep clone (uses structuredClone when available).
1157
+ * @param {*} value
1158
+ * @returns {*}
1159
+ */
1160
+ function deepClone(value) {
1161
+ if (typeof structuredClone === "function") return structuredClone(value);
1162
+ return JSON.parse(JSON.stringify(value));
1163
+ }
1164
+
1165
+ /**
1166
+ * Safe JSON stringify with fallback.
1167
+ * @param {*} value
1168
+ * @param {*} fallback
1169
+ * @returns {string}
1170
+ */
1171
+ function safeJsonStringify(value, fallback = "") {
1172
+ try {
1173
+ return JSON.stringify(value);
1174
+ } catch (err) {
1175
+ return String(fallback);
1176
+ }
1177
+ }
1178
+
1179
+ /**
1180
+ * Move an item within an array.
1181
+ * @param {Array} arr
1182
+ * @param {number} fromIndex
1183
+ * @param {number} toIndex
1184
+ * @returns {Array}
1185
+ */
1186
+ function move(arr, fromIndex, toIndex) {
1187
+ if (!Array.isArray(arr)) {
1188
+ const args = Array.prototype.slice.call(arguments);
1189
+ toIndex = args.pop();
1190
+ fromIndex = args.pop();
1191
+ arr = args;
1192
+ }
1193
+ if (!Array.isArray(arr)) return [];
1194
+ const out = arr.slice();
1195
+ const len = out.length;
1196
+ const from = fromIndex < 0 ? len + fromIndex : fromIndex;
1197
+ const to = toIndex < 0 ? len + toIndex : toIndex;
1198
+ if (from < 0 || from >= len || to < 0 || to >= len) return out;
1199
+ const [item] = out.splice(from, 1);
1200
+ out.splice(to, 0, item);
1201
+ return out;
1202
+ }
1203
+
1204
+ /**
1205
+ * Group array items by key function.
1206
+ * @param {Array} arr
1207
+ * @param {Function} keyFn
1208
+ * @returns {Object}
1209
+ */
1210
+ function arrayGroupBy(arr, keyFn) {
1211
+ return groupBy(arr, keyFn);
1212
+ }
1213
+
1214
+ /**
1215
+ * Map object keys to a new object.
1216
+ * @param {Object} obj
1217
+ * @param {Function} keyFn
1218
+ * @returns {Object}
1219
+ */
1220
+ function mapKeys(obj, keyFn) {
1221
+ if (!obj || typeof obj !== "object") return {};
1222
+ const out = {};
1223
+ Object.keys(obj).forEach(k => {
1224
+ const nk = keyFn(k, obj[k], obj);
1225
+ out[nk] = obj[k];
1226
+ });
1227
+ return out;
1228
+ }
1229
+
1230
+ /**
1231
+ * Invert keys and values.
1232
+ * @param {Object} obj
1233
+ * @returns {Object}
1234
+ */
1235
+ function invert(obj) {
1236
+ if (!obj || typeof obj !== "object") return {};
1237
+ const out = {};
1238
+ Object.keys(obj).forEach(k => {
1239
+ out[String(obj[k])] = k;
1240
+ });
1241
+ return out;
1242
+ }
1243
+
1244
+ /**
1245
+ * Remove null/undefined/empty properties from object.
1246
+ * @param {Object} obj
1247
+ * @returns {Object}
1248
+ */
1249
+ function objectCompact(obj) {
1250
+ if (!obj || typeof obj !== "object") return {};
1251
+ const out = {};
1252
+ Object.keys(obj).forEach(k => {
1253
+ if (!isEmpty(obj[k])) out[k] = obj[k];
1254
+ });
1255
+ return out;
1256
+ }
1257
+
1258
+ /**
1259
+ * Convert a string to Title Case.
1260
+ * @param {*} s
1261
+ * @returns {string}
1262
+ */
1263
+ function toTitleCase(s) {
1264
+ return String(s)
1265
+ .toLowerCase()
1266
+ .replace(/\b\w/g, c => c.toUpperCase());
1267
+ }
1268
+
1269
+ /**
1270
+ * Truncate string to length with ellipsis.
1271
+ * @param {*} s
1272
+ * @param {number} length
1273
+ * @returns {string}
1274
+ */
1275
+ function truncate(s, length) {
1276
+ const str = String(s);
1277
+ if (length <= 3) return str.slice(0, length);
1278
+ if (str.length <= length) return str;
1279
+ return str.slice(0, length - 3) + "...";
1280
+ }
1281
+
1282
+ /**
1283
+ * Replace all occurrences of substring.
1284
+ * @param {*} s
1285
+ * @param {string} substring
1286
+ * @param {string} newSub
1287
+ * @returns {string}
1288
+ */
1289
+ function replaceAll(s, substring, newSub) {
1290
+ return String(s).split(substring).join(newSub);
1291
+ }
1292
+
1293
+ /**
1294
+ * Count words in a string.
1295
+ * @param {*} s
1296
+ * @returns {number}
1297
+ */
1298
+ function countWords(s) {
1299
+ const words = String(s).trim().match(/\b[\w']+\b/g);
1300
+ return words ? words.length : 0;
1301
+ }
1302
+
1303
+ /**
1304
+ * Install convenience methods on Array/Object/String prototypes.
1305
+ * @returns {void}
1306
+ */
1307
+ function install() {
1308
+ const define = (proto, name, fn) => {
1309
+ if (!Object.prototype.hasOwnProperty.call(proto, name)) {
1310
+ Object.defineProperty(proto, name, {
1311
+ value: fn,
1312
+ enumerable: false,
1313
+ configurable: true,
1314
+ writable: true,
1315
+ });
1316
+ }
1317
+ };
1318
+
1319
+ define(Array.prototype, "shuffle", function () { return shuffle(this); });
1320
+ define(Array.prototype, "unique", function () { return uniq(this); });
1321
+ define(Array.prototype, "chunk", function (size) { return chunk(this, size); });
1322
+ define(Array.prototype, "move", function (from, to) { return move(this, from, to); });
1323
+ define(Array.prototype, "groupBy", function (fn) { return arrayGroupBy(this, fn); });
1324
+ define(Array.prototype, "compact", function (fn) { return compact(this, fn); });
1325
+ define(Array.prototype, "flatten", function () { return flatten(this); });
1326
+ define(Array.prototype, "first", function () { return first(this); });
1327
+ define(Array.prototype, "last", function () { return last(this); });
1328
+ define(Array.prototype, "take", function (n) { return take(this, n); });
1329
+ define(Array.prototype, "takeRight", function (n) { return takeRight(this, n); });
1330
+ define(Array.prototype, "drop", function (n) { return drop(this, n); });
1331
+ define(Array.prototype, "dropRight", function (n) { return dropRight(this, n); });
1332
+
1333
+ define(Object.prototype, "mapKeys", function (fn) { return mapKeys(this, fn); });
1334
+ define(Object.prototype, "pick", function (keys) { return pick(this, keys); });
1335
+ define(Object.prototype, "invert", function () { return invert(this); });
1336
+ define(Object.prototype, "compact", function () { return objectCompact(this); });
1337
+ define(Object.prototype, "omit", function (keys) { return omit(this, keys); });
1338
+ define(Object.prototype, "has", function (key) { return has(this, key); });
1339
+ define(Object.prototype, "merge", function () { return merge.apply(null, [this].concat(Array.prototype.slice.call(arguments))); });
1340
+ define(Object.prototype, "deepMerge", function () { return deepMerge.apply(null, [this].concat(Array.prototype.slice.call(arguments))); });
1341
+ define(Object.prototype, "size", function () { return size(this); });
1342
+
1343
+ define(String.prototype, "toTitleCase", function () { return toTitleCase(this); });
1344
+ define(String.prototype, "truncate", function (len) { return truncate(this, len); });
1345
+ define(String.prototype, "replaceAllEasy", function (sub, rep) { return replaceAll(this, sub, rep); });
1346
+ define(String.prototype, "countWords", function () { return countWords(this); });
1347
+ define(String.prototype, "slugify", function () { return slugify(this); });
1348
+ define(String.prototype, "padLeft", function (len, ch) { return padLeft(this, len, ch); });
1349
+ define(String.prototype, "padRight", function (len, ch) { return padRight(this, len, ch); });
1350
+ define(String.prototype, "repeatEasy", function (times) { return repeat(this, times); });
1351
+ define(String.prototype, "stripNonAlpha", function () { return stripNonAlpha(this); });
1352
+ define(String.prototype, "isBlank", function () { return isBlank(this); });
1353
+ define(String.prototype, "normalizeString", function () { return normalizeString(this); });
1354
+ }
1355
+
1356
+ module.exports = {
1357
+ identity,
1358
+ noop,
1359
+ isNil,
1360
+ isEmpty,
1361
+ defaultTo,
1362
+ toLower,
1363
+ toUpper,
1364
+ capitalize,
1365
+ trim,
1366
+ normalizeString,
1367
+ slugify,
1368
+ padLeft,
1369
+ padRight,
1370
+ repeat,
1371
+ stripNonAlpha,
1372
+ isBlank,
1373
+ asArray,
1374
+ anyOf,
1375
+ eqAny,
1376
+ inArray,
1377
+ clamp,
1378
+ between,
1379
+ toInt,
1380
+ toFloat,
1381
+ toNumber,
1382
+ isNumber,
1383
+ isInt,
1384
+ compact,
1385
+ uniq,
1386
+ flatten,
1387
+ chunk,
1388
+ range,
1389
+ min,
1390
+ max,
1391
+ sum,
1392
+ avg,
1393
+ median,
1394
+ mode,
1395
+ percentile,
1396
+ groupBy,
1397
+ first,
1398
+ last,
1399
+ take,
1400
+ takeRight,
1401
+ drop,
1402
+ dropRight,
1403
+ zip,
1404
+ unzip,
1405
+ intersection,
1406
+ difference,
1407
+ union,
1408
+ pick,
1409
+ omit,
1410
+ has,
1411
+ keys,
1412
+ values,
1413
+ entries,
1414
+ fromEntries,
1415
+ merge,
1416
+ deepMerge,
1417
+ isObject,
1418
+ size,
1419
+ safeGet,
1420
+ safeSet,
1421
+ tryCatch,
1422
+ tap,
1423
+ sleep,
1424
+ containsAny,
1425
+ startsWithAny,
1426
+ endsWithAny,
1427
+ safeJsonParse,
1428
+ once,
1429
+ debounce,
1430
+ throttle,
1431
+ uniqueBy,
1432
+ pluck,
1433
+ sortBy,
1434
+ roundTo,
1435
+ roundDecimal,
1436
+ roundDown,
1437
+ roundUp,
1438
+ lerp,
1439
+ percent,
1440
+ clamp01,
1441
+ randomInt,
1442
+ randomFloat,
1443
+ shuffle,
1444
+ sample,
1445
+ deepClone,
1446
+ safeJsonStringify,
1447
+ move,
1448
+ arrayGroupBy,
1449
+ mapKeys,
1450
+ invert,
1451
+ objectCompact,
1452
+ toTitleCase,
1453
+ truncate,
1454
+ replaceAll,
1455
+ countWords,
1456
+ install,
1457
+ };