@ix-xs/node-comfort 2.0.0 → 2.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/CHANGELOG.md +150 -0
- package/README.md +32 -30
- package/index.js +3 -3
- package/package.json +8 -7
- package/src/Arr.js +222 -58
- package/src/Async.js +322 -90
- package/src/Cache.js +148 -44
- package/src/Checker.js +257 -26
- package/src/Cli.js +404 -145
- package/src/Color.js +224 -83
- package/src/Crypto.js +481 -106
- package/src/Emitter.js +104 -37
- package/src/Env.js +176 -42
- package/src/FS.js +203 -60
- package/src/Func.js +226 -76
- package/src/Http.js +311 -149
- package/src/Id.js +281 -96
- package/src/Logger.js +302 -78
- package/src/Num.js +182 -60
- package/src/Obj.js +340 -125
- package/src/SQLite.js +426 -105
- package/src/Schema.js +602 -87
- package/src/Str.js +316 -86
- package/src/Sys.js +197 -54
- package/src/Time.js +572 -317
- package/src/Utils.js +62 -41
- package/src/errors.js +54 -11
- package/src/internal/shared.js +415 -0
- package/types/index.d.ts +59 -17
- package/types/src/Arr.d.ts +78 -0
- package/types/src/Arr.d.ts.map +1 -1
- package/types/src/Async.d.ts +52 -1
- package/types/src/Async.d.ts.map +1 -1
- package/types/src/Cache.d.ts +33 -7
- package/types/src/Cache.d.ts.map +1 -1
- package/types/src/Checker.d.ts +155 -2
- package/types/src/Checker.d.ts.map +1 -1
- package/types/src/Cli.d.ts +97 -15
- package/types/src/Cli.d.ts.map +1 -1
- package/types/src/Color.d.ts +7 -1
- package/types/src/Color.d.ts.map +1 -1
- package/types/src/Crypto.d.ts +99 -13
- package/types/src/Crypto.d.ts.map +1 -1
- package/types/src/Emitter.d.ts +36 -17
- package/types/src/Emitter.d.ts.map +1 -1
- package/types/src/Env.d.ts +31 -1
- package/types/src/Env.d.ts.map +1 -1
- package/types/src/FS.d.ts +36 -5
- package/types/src/FS.d.ts.map +1 -1
- package/types/src/Func.d.ts +147 -19
- package/types/src/Func.d.ts.map +1 -1
- package/types/src/Http.d.ts +113 -43
- package/types/src/Http.d.ts.map +1 -1
- package/types/src/Id.d.ts +60 -8
- package/types/src/Id.d.ts.map +1 -1
- package/types/src/Logger.d.ts +53 -2
- package/types/src/Logger.d.ts.map +1 -1
- package/types/src/Num.d.ts +48 -7
- package/types/src/Num.d.ts.map +1 -1
- package/types/src/Obj.d.ts +67 -4
- package/types/src/Obj.d.ts.map +1 -1
- package/types/src/SQLite.d.ts +123 -14
- package/types/src/SQLite.d.ts.map +1 -1
- package/types/src/Schema.d.ts +256 -7
- package/types/src/Schema.d.ts.map +1 -1
- package/types/src/Str.d.ts +49 -1
- package/types/src/Str.d.ts.map +1 -1
- package/types/src/Sys.d.ts +51 -6
- package/types/src/Sys.d.ts.map +1 -1
- package/types/src/Time.d.ts +130 -34
- package/types/src/Time.d.ts.map +1 -1
- package/types/src/Utils.d.ts +15 -11
- package/types/src/Utils.d.ts.map +1 -1
- package/types/src/errors.d.ts +27 -3
- package/types/src/errors.d.ts.map +1 -1
- package/types/src/internal/shared.d.ts +123 -0
- package/types/src/internal/shared.d.ts.map +1 -0
package/src/Arr.js
CHANGED
|
@@ -48,6 +48,16 @@
|
|
|
48
48
|
* @property {boolean} hasNext
|
|
49
49
|
*/
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Options for `toSentence()`.
|
|
53
|
+
* @typedef {object} ToSentenceOptions
|
|
54
|
+
* @property {string} [locale] Language of the joining word. Defaults to `"en"`.
|
|
55
|
+
* @property {"conjunction" | "disjunction" | "unit"} [type] `"conjunction"` joins with "and", `"disjunction"` with "or", `"unit"` with commas only. Defaults to `"conjunction"`.
|
|
56
|
+
* @property {"long" | "short" | "narrow"} [style] Defaults to `"long"`.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
const { intl, preciseSum } = require("./internal/shared.js");
|
|
60
|
+
|
|
51
61
|
/**
|
|
52
62
|
* @param {unknown} iteratee
|
|
53
63
|
* @returns {(item: any, index: number) => any}
|
|
@@ -245,12 +255,16 @@ function unique(array, iteratee) {
|
|
|
245
255
|
*/
|
|
246
256
|
function groupBy(array, iteratee) {
|
|
247
257
|
const fn = _fn(iteratee);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
258
|
+
// A Map, so keys like "constructor" or "__proto__" are plain data.
|
|
259
|
+
/** @type {Map<PropertyKey, T[]>} */
|
|
260
|
+
const groups = new Map();
|
|
261
|
+
for (let i = 0; i < array.length; i++) {
|
|
262
|
+
const key = _key(fn(array[i], i));
|
|
263
|
+
const group = groups.get(key);
|
|
264
|
+
if (group) group.push(array[i]);
|
|
265
|
+
else groups.set(key, [array[i]]);
|
|
266
|
+
}
|
|
267
|
+
return Object.fromEntries(groups);
|
|
254
268
|
}
|
|
255
269
|
|
|
256
270
|
/**
|
|
@@ -267,12 +281,10 @@ function groupBy(array, iteratee) {
|
|
|
267
281
|
*/
|
|
268
282
|
function keyBy(array, iteratee) {
|
|
269
283
|
const fn = _fn(iteratee);
|
|
270
|
-
/** @type {
|
|
271
|
-
const out =
|
|
272
|
-
array.
|
|
273
|
-
|
|
274
|
-
});
|
|
275
|
-
return out;
|
|
284
|
+
/** @type {Map<PropertyKey, T>} */
|
|
285
|
+
const out = new Map();
|
|
286
|
+
for (let i = 0; i < array.length; i++) out.set(_key(fn(array[i], i)), array[i]);
|
|
287
|
+
return Object.fromEntries(out);
|
|
276
288
|
}
|
|
277
289
|
|
|
278
290
|
/**
|
|
@@ -311,13 +323,13 @@ function toMap(array, key, value) {
|
|
|
311
323
|
*/
|
|
312
324
|
function countBy(array, iteratee) {
|
|
313
325
|
const fn = _fn(iteratee);
|
|
314
|
-
/** @type {
|
|
315
|
-
const out =
|
|
316
|
-
array.
|
|
317
|
-
const key = _key(fn(
|
|
318
|
-
out
|
|
319
|
-
}
|
|
320
|
-
return out;
|
|
326
|
+
/** @type {Map<PropertyKey, number>} */
|
|
327
|
+
const out = new Map();
|
|
328
|
+
for (let i = 0; i < array.length; i++) {
|
|
329
|
+
const key = _key(fn(array[i], i));
|
|
330
|
+
out.set(key, (out.get(key) ?? 0) + 1);
|
|
331
|
+
}
|
|
332
|
+
return Object.fromEntries(out);
|
|
321
333
|
}
|
|
322
334
|
|
|
323
335
|
/**
|
|
@@ -394,34 +406,46 @@ function pluck(array, key) {
|
|
|
394
406
|
function sortBy(array, keys, order = "asc") {
|
|
395
407
|
const options = typeof order === "string" ? { order } : order;
|
|
396
408
|
const defaultDir = options.order === "desc" ? -1 : 1;
|
|
397
|
-
const collator = options.natural ?
|
|
409
|
+
const collator = options.natural ? intl.collator(options.locale, { numeric: true, sensitivity: "base" }) : null;
|
|
398
410
|
const isTuple = (/** @type {unknown} */ k) => Array.isArray(k) && k.length === 2 && (k[1] === "asc" || k[1] === "desc");
|
|
399
411
|
const list = Array.isArray(keys) && !isTuple(keys) ? keys : [keys];
|
|
400
412
|
const comparators = list.map((k) => {
|
|
401
413
|
const [iteratee, dir] = isTuple(k) ? /** @type {[unknown, string]} */ (k) : [k, undefined];
|
|
402
414
|
return { fn: _fn(iteratee), dir: dir === undefined ? defaultDir : dir === "desc" ? -1 : 1 };
|
|
403
415
|
});
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
416
|
+
const size = array.length;
|
|
417
|
+
const width = comparators.length;
|
|
418
|
+
// Each sort value is computed once, not on every comparison.
|
|
419
|
+
/** @type {any[]} */
|
|
420
|
+
const values = new Array(size * width);
|
|
421
|
+
/** @type {number[]} */
|
|
422
|
+
const indexes = new Array(size);
|
|
423
|
+
for (let i = 0; i < size; i++) {
|
|
424
|
+
indexes[i] = i;
|
|
425
|
+
for (let k = 0; k < width; k++) values[i * width + k] = comparators[k].fn(array[i], i);
|
|
426
|
+
}
|
|
427
|
+
indexes.sort((a, b) => {
|
|
428
|
+
for (let k = 0; k < width; k++) {
|
|
429
|
+
const va = values[a * width + k];
|
|
430
|
+
const vb = values[b * width + k];
|
|
431
|
+
const aNil = va === null || va === undefined;
|
|
432
|
+
const bNil = vb === null || vb === undefined;
|
|
433
|
+
if (aNil || bNil) {
|
|
434
|
+
if (aNil && bNil) continue;
|
|
435
|
+
return aNil ? 1 : -1;
|
|
421
436
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
437
|
+
let result = 0;
|
|
438
|
+
if (collator && typeof va === "string" && typeof vb === "string") result = collator.compare(va, vb);
|
|
439
|
+
else if (va < vb) result = -1;
|
|
440
|
+
else if (va > vb) result = 1;
|
|
441
|
+
if (result) return result * comparators[k].dir;
|
|
442
|
+
}
|
|
443
|
+
return a - b;
|
|
444
|
+
});
|
|
445
|
+
/** @type {T[]} */
|
|
446
|
+
const out = new Array(size);
|
|
447
|
+
for (let i = 0; i < size; i++) out[i] = array[indexes[i]];
|
|
448
|
+
return out;
|
|
425
449
|
}
|
|
426
450
|
|
|
427
451
|
/**
|
|
@@ -435,7 +459,9 @@ function shuffle(array) {
|
|
|
435
459
|
const out = [...array];
|
|
436
460
|
for (let i = out.length - 1; i > 0; i--) {
|
|
437
461
|
const j = Math.floor(Math.random() * (i + 1));
|
|
438
|
-
|
|
462
|
+
const item = out[i];
|
|
463
|
+
out[i] = out[j];
|
|
464
|
+
out[j] = item;
|
|
439
465
|
}
|
|
440
466
|
return out;
|
|
441
467
|
}
|
|
@@ -463,7 +489,17 @@ function sample(array) {
|
|
|
463
489
|
* @returns {T[]}
|
|
464
490
|
*/
|
|
465
491
|
function sampleSize(array, n) {
|
|
466
|
-
|
|
492
|
+
const out = [...array];
|
|
493
|
+
const count = Math.min(out.length, Math.max(0, Math.floor(n)));
|
|
494
|
+
// A partial shuffle: only the items that are returned get moved.
|
|
495
|
+
for (let i = 0; i < count; i++) {
|
|
496
|
+
const j = i + Math.floor(Math.random() * (out.length - i));
|
|
497
|
+
const item = out[i];
|
|
498
|
+
out[i] = out[j];
|
|
499
|
+
out[j] = item;
|
|
500
|
+
}
|
|
501
|
+
out.length = count;
|
|
502
|
+
return out;
|
|
467
503
|
}
|
|
468
504
|
|
|
469
505
|
/**
|
|
@@ -631,7 +667,8 @@ function compact(array) {
|
|
|
631
667
|
* @returns {T[]}
|
|
632
668
|
*/
|
|
633
669
|
function toggle(array, item) {
|
|
634
|
-
|
|
670
|
+
// `includes` finds NaN, so the removal has to as well.
|
|
671
|
+
return array.includes(item) ? array.filter((x) => x !== item && (x === x || item === item)) : [...array, item];
|
|
635
672
|
}
|
|
636
673
|
|
|
637
674
|
/**
|
|
@@ -702,8 +739,7 @@ function flatten(array, depth) {
|
|
|
702
739
|
* @returns {Array<Array<T | undefined>>}
|
|
703
740
|
*/
|
|
704
741
|
function zip(...arrays) {
|
|
705
|
-
|
|
706
|
-
return Array.from({ length: size }, (_, i) => arrays.map((a) => a[i]));
|
|
742
|
+
return _zip(arrays);
|
|
707
743
|
}
|
|
708
744
|
|
|
709
745
|
/**
|
|
@@ -717,9 +753,28 @@ function zip(...arrays) {
|
|
|
717
753
|
* @returns {Array<Array<T | undefined>>}
|
|
718
754
|
*/
|
|
719
755
|
function unzip(tuples) {
|
|
720
|
-
return
|
|
756
|
+
return _zip(tuples);
|
|
721
757
|
}
|
|
722
758
|
|
|
759
|
+
/**
|
|
760
|
+
* @template T
|
|
761
|
+
* @param {ReadonlyArray<ReadonlyArray<T>>} arrays
|
|
762
|
+
* @returns {Array<Array<T | undefined>>}
|
|
763
|
+
*/
|
|
764
|
+
const _zip = (arrays) => {
|
|
765
|
+
let size = 0;
|
|
766
|
+
for (const array of arrays) if (array.length > size) size = array.length;
|
|
767
|
+
/** @type {Array<Array<T | undefined>>} */
|
|
768
|
+
const out = new Array(size);
|
|
769
|
+
for (let i = 0; i < size; i++) {
|
|
770
|
+
/** @type {Array<T | undefined>} */
|
|
771
|
+
const row = new Array(arrays.length);
|
|
772
|
+
for (let k = 0; k < arrays.length; k++) row[k] = arrays[k][i];
|
|
773
|
+
out[i] = row;
|
|
774
|
+
}
|
|
775
|
+
return out;
|
|
776
|
+
};
|
|
777
|
+
|
|
723
778
|
/**
|
|
724
779
|
* Takes items from each array in turn, then appends the rest.
|
|
725
780
|
*
|
|
@@ -733,7 +788,8 @@ function unzip(tuples) {
|
|
|
733
788
|
function interleave(...arrays) {
|
|
734
789
|
/** @type {T[]} */
|
|
735
790
|
const out = [];
|
|
736
|
-
|
|
791
|
+
let size = 0;
|
|
792
|
+
for (const array of arrays) if (array.length > size) size = array.length;
|
|
737
793
|
for (let i = 0; i < size; i++) for (const a of arrays) if (i < a.length) out.push(a[i]);
|
|
738
794
|
return out;
|
|
739
795
|
}
|
|
@@ -786,7 +842,10 @@ function times(n, value) {
|
|
|
786
842
|
*/
|
|
787
843
|
function sumBy(array, iteratee) {
|
|
788
844
|
const fn = _fn(iteratee);
|
|
789
|
-
|
|
845
|
+
/** @type {number[]} */
|
|
846
|
+
const values = new Array(array.length);
|
|
847
|
+
for (let i = 0; i < array.length; i++) values[i] = Number(fn(array[i], i)) || 0;
|
|
848
|
+
return preciseSum(values);
|
|
790
849
|
}
|
|
791
850
|
|
|
792
851
|
/**
|
|
@@ -821,13 +880,14 @@ function maxBy(array, iteratee) {
|
|
|
821
880
|
let best;
|
|
822
881
|
/** @type {any} */
|
|
823
882
|
let bestValue;
|
|
824
|
-
array.
|
|
825
|
-
const v = fn(
|
|
826
|
-
if (v
|
|
883
|
+
for (let i = 0; i < array.length; i++) {
|
|
884
|
+
const v = fn(array[i], i);
|
|
885
|
+
if (v === null || v === undefined || v !== v) continue;
|
|
886
|
+
if (bestValue === undefined || v > bestValue) {
|
|
827
887
|
bestValue = v;
|
|
828
|
-
best =
|
|
888
|
+
best = array[i];
|
|
829
889
|
}
|
|
830
|
-
}
|
|
890
|
+
}
|
|
831
891
|
return best;
|
|
832
892
|
}
|
|
833
893
|
|
|
@@ -848,16 +908,115 @@ function minBy(array, iteratee) {
|
|
|
848
908
|
let best;
|
|
849
909
|
/** @type {any} */
|
|
850
910
|
let bestValue;
|
|
851
|
-
array.
|
|
852
|
-
const v = fn(
|
|
853
|
-
if (v
|
|
911
|
+
for (let i = 0; i < array.length; i++) {
|
|
912
|
+
const v = fn(array[i], i);
|
|
913
|
+
if (v === null || v === undefined || v !== v) continue;
|
|
914
|
+
if (bestValue === undefined || v < bestValue) {
|
|
854
915
|
bestValue = v;
|
|
855
|
-
best =
|
|
916
|
+
best = array[i];
|
|
856
917
|
}
|
|
857
|
-
}
|
|
918
|
+
}
|
|
858
919
|
return best;
|
|
859
920
|
}
|
|
860
921
|
|
|
922
|
+
/**
|
|
923
|
+
* The items that appear more than once, each listed once.
|
|
924
|
+
*
|
|
925
|
+
* @example
|
|
926
|
+
* arr.duplicates([1, 2, 2, 3, 3, 3]); // [2, 3]
|
|
927
|
+
* arr.duplicates(users, "email"); // users whose email is taken by an earlier one
|
|
928
|
+
*
|
|
929
|
+
* @template T
|
|
930
|
+
* @param {readonly T[]} array
|
|
931
|
+
* @param {Iteratee<T>} [iteratee] What makes two items "the same". Defaults to the item itself.
|
|
932
|
+
* @returns {T[]} For each repeated value, its second occurrence.
|
|
933
|
+
*/
|
|
934
|
+
function duplicates(array, iteratee) {
|
|
935
|
+
const fn = _fn(iteratee);
|
|
936
|
+
/** @type {Map<unknown, number>} */
|
|
937
|
+
const seen = new Map();
|
|
938
|
+
/** @type {T[]} */
|
|
939
|
+
const out = [];
|
|
940
|
+
for (let i = 0; i < array.length; i++) {
|
|
941
|
+
const key = fn(array[i], i);
|
|
942
|
+
const count = (seen.get(key) ?? 0) + 1;
|
|
943
|
+
seen.set(key, count);
|
|
944
|
+
if (count === 2) out.push(array[i]);
|
|
945
|
+
}
|
|
946
|
+
return out;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Items found in exactly one of the arrays, without duplicates.
|
|
951
|
+
*
|
|
952
|
+
* @example
|
|
953
|
+
* arr.xor([1, 2, 3], [2, 3, 4]); // [1, 4]
|
|
954
|
+
*
|
|
955
|
+
* @template T
|
|
956
|
+
* @param {...ReadonlyArray<T>} arrays
|
|
957
|
+
* @returns {T[]}
|
|
958
|
+
*/
|
|
959
|
+
function xor(...arrays) {
|
|
960
|
+
/** @type {Map<T, number>} */
|
|
961
|
+
const counts = new Map();
|
|
962
|
+
for (const array of arrays) for (const item of new Set(array)) counts.set(item, (counts.get(item) ?? 0) + 1);
|
|
963
|
+
/** @type {T[]} */
|
|
964
|
+
const out = [];
|
|
965
|
+
for (const [item, count] of counts) if (count === 1) out.push(item);
|
|
966
|
+
return out;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* The items from the start, up to the first one that fails the test.
|
|
971
|
+
*
|
|
972
|
+
* @example
|
|
973
|
+
* arr.takeWhile([1, 2, 5, 1], (n) => n < 3); // [1, 2]
|
|
974
|
+
*
|
|
975
|
+
* @template T
|
|
976
|
+
* @param {readonly T[]} array
|
|
977
|
+
* @param {(item: T, index: number) => unknown} predicate
|
|
978
|
+
* @returns {T[]}
|
|
979
|
+
*/
|
|
980
|
+
function takeWhile(array, predicate) {
|
|
981
|
+
let end = 0;
|
|
982
|
+
while (end < array.length && predicate(array[end], end)) end++;
|
|
983
|
+
return array.slice(0, end);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Skips items from the start while they pass the test, and returns the rest.
|
|
988
|
+
*
|
|
989
|
+
* @example
|
|
990
|
+
* arr.dropWhile([1, 2, 5, 1], (n) => n < 3); // [5, 1]
|
|
991
|
+
*
|
|
992
|
+
* @template T
|
|
993
|
+
* @param {readonly T[]} array
|
|
994
|
+
* @param {(item: T, index: number) => unknown} predicate
|
|
995
|
+
* @returns {T[]}
|
|
996
|
+
*/
|
|
997
|
+
function dropWhile(array, predicate) {
|
|
998
|
+
let start = 0;
|
|
999
|
+
while (start < array.length && predicate(array[start], start)) start++;
|
|
1000
|
+
return array.slice(start);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Joins items the way a sentence would, in any language.
|
|
1005
|
+
*
|
|
1006
|
+
* @example
|
|
1007
|
+
* arr.toSentence(["Ada", "Bob", "Cy"]); // "Ada, Bob, and Cy"
|
|
1008
|
+
* arr.toSentence(["Ada", "Bob"], { locale: "fr" }); // "Ada et Bob"
|
|
1009
|
+
* arr.toSentence(["red", "blue"], { type: "disjunction" }); // "red or blue"
|
|
1010
|
+
*
|
|
1011
|
+
* @param {Iterable<unknown>} items
|
|
1012
|
+
* @param {ToSentenceOptions} [options]
|
|
1013
|
+
* @returns {string}
|
|
1014
|
+
*/
|
|
1015
|
+
function toSentence(items, options = {}) {
|
|
1016
|
+
const list = Array.from(items, (item) => String(item));
|
|
1017
|
+
return intl.list(options.locale ?? "en", { type: options.type ?? "conjunction", style: options.style ?? "long" }).format(list);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
861
1020
|
module.exports = {
|
|
862
1021
|
chunk,
|
|
863
1022
|
windows,
|
|
@@ -898,4 +1057,9 @@ module.exports = {
|
|
|
898
1057
|
averageBy,
|
|
899
1058
|
maxBy,
|
|
900
1059
|
minBy,
|
|
1060
|
+
duplicates,
|
|
1061
|
+
xor,
|
|
1062
|
+
takeWhile,
|
|
1063
|
+
dropWhile,
|
|
1064
|
+
toSentence,
|
|
901
1065
|
};
|