utilitish 0.0.3 → 0.0.5
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/dist/array/array-constructor.d.ts +79 -12
- package/dist/array/array-constructor.js +32 -17
- package/dist/array/array-prototype.d.ts +42 -4
- package/dist/array/array-prototype.js +88 -69
- package/dist/map/map-prototype.d.ts +25 -0
- package/dist/map/map-prototype.js +41 -0
- package/dist/set/set-prototype.d.ts +27 -0
- package/dist/set/set-prototype.js +50 -0
- package/dist/string/string-prototype.d.ts +11 -0
- package/dist/string/string-prototype.js +15 -1
- package/dist/utils.d.ts +27 -0
- package/dist/utils.js +66 -1
- package/package.json +4 -2
|
@@ -2,25 +2,92 @@ export {};
|
|
|
2
2
|
declare global {
|
|
3
3
|
interface ArrayConstructor {
|
|
4
4
|
/**
|
|
5
|
-
* Combines multiple arrays element-wise,
|
|
6
|
-
*
|
|
7
|
-
* @
|
|
5
|
+
* Combines multiple arrays element-wise, similar to Python's `itertools.zip_longest`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* Array.zip([1, 2], ['a', 'b', 'c']);
|
|
9
|
+
* // => [[1, 'a'], [2, 'b'], [undefined, 'c']]
|
|
10
|
+
*
|
|
11
|
+
* @template T Type of the array elements
|
|
12
|
+
* @param {...T[][]} arrays Arrays to zip together
|
|
13
|
+
* @returns {Array<(T | undefined)[]>} A new array where each element is an array containing the elements at the same index from each input array. Missing elements are `undefined`.
|
|
8
14
|
*/
|
|
9
15
|
zip(...arrays: any[][]): any[][];
|
|
10
16
|
/**
|
|
11
|
-
* Generates a sequence of numbers, similar to Python's range
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* Generates a sequence of numbers, similar to Python's `range`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* Array.range(5);
|
|
21
|
+
* // => [0, 1, 2, 3, 4]
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* Array.range(2, 10, 2);
|
|
25
|
+
* // => [2, 4, 6, 8]
|
|
26
|
+
*
|
|
27
|
+
* @param {number} start First number, or the length if `end` is not provided
|
|
28
|
+
* @param {number} [end] Last number (excluded)
|
|
29
|
+
* @param {number} [step=1] Step between numbers (can be negative)
|
|
30
|
+
* @returns {number[]} An array containing the generated sequence
|
|
16
31
|
*/
|
|
17
32
|
range(start: number, end?: number, step?: number): number[];
|
|
18
33
|
/**
|
|
19
|
-
* Creates an array filled with the same value or
|
|
20
|
-
*
|
|
21
|
-
* @
|
|
22
|
-
*
|
|
34
|
+
* Creates an array filled with the same value or with values generated by a factory function.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* Array.repeat(3, 'x');
|
|
38
|
+
* // => ['x', 'x', 'x']
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* Array.repeat(3, () => Math.random());
|
|
42
|
+
* // => [0.12, 0.45, 0.78] (values will differ)
|
|
43
|
+
*
|
|
44
|
+
* @template T Type of the array elements
|
|
45
|
+
* @param {number} length Length of the array (must be a non-negative integer)
|
|
46
|
+
* @param {T | (() => T)} value Value or function generating the value
|
|
47
|
+
* @returns {T[]} The filled array
|
|
23
48
|
*/
|
|
24
49
|
repeat<T>(length: number, value: T | (() => T)): T[];
|
|
50
|
+
/**
|
|
51
|
+
* Creates an array filled with a given value or generated using a factory function.
|
|
52
|
+
* Supports creating multi-dimensional arrays by passing multiple sizes.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // 1D array with primitive values
|
|
56
|
+
* Array.create('x', 5);
|
|
57
|
+
* // => ['x', 'x', 'x', 'x', 'x']
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // 1D array with random numbers
|
|
61
|
+
* Array.create(() => Math.random(), 5);
|
|
62
|
+
* // => [0.12, 0.87, 0.45, 0.76, 0.33] (values will differ)
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // 2D array (matrix)
|
|
66
|
+
* Array.create(0, 2, 3);
|
|
67
|
+
* // => [[0, 0, 0], [0, 0, 0]]
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // 2D array with distinct objects
|
|
71
|
+
* Array.create(() => ({ id: 0 }), 2, 3);
|
|
72
|
+
* // => [
|
|
73
|
+
* // [{id:0}, {id:0}, {id:0}],
|
|
74
|
+
* // [{id:0}, {id:0}, {id:0}]
|
|
75
|
+
* // ]
|
|
76
|
+
*
|
|
77
|
+
* @notes
|
|
78
|
+
* - If `sizes.length === 0`, returns `[]`.
|
|
79
|
+
* - If `valueOrFactory` is a primitive, all cells contain the same value.
|
|
80
|
+
* - If `valueOrFactory` is a function, it is called for each cell to produce a fresh value.
|
|
81
|
+
* - `sizes` should be integers >= 0; negative or non-integer values are not supported.
|
|
82
|
+
*
|
|
83
|
+
* @template T
|
|
84
|
+
* @param {T | (() => T)} valueOrFactory The value to fill the array with, or a factory function producing values.
|
|
85
|
+
* @param {...number} sizes Sizes for each dimension (1D => one number, 2D => two numbers, etc.).
|
|
86
|
+
* @returns {any[]} A multi-dimensional array of depth `sizes.length` filled with values from `valueOrFactory`.
|
|
87
|
+
*
|
|
88
|
+
* @remarks
|
|
89
|
+
* - This method is **static** and must be called on `Array`, not on an instance.
|
|
90
|
+
*/
|
|
91
|
+
create<T>(valueOrFactory: T | (() => T), ...sizes: number[]): any[];
|
|
25
92
|
}
|
|
26
93
|
}
|
|
@@ -1,36 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
/**
|
|
5
|
+
* @see Array.zip
|
|
6
|
+
*/
|
|
7
|
+
(0, utils_1.defineStaticIfNotExists)(Array, 'zip', function (...arrays) {
|
|
4
8
|
if (arrays.length === 0)
|
|
5
9
|
return [];
|
|
6
10
|
const maxLen = Math.max(...arrays.map((arr) => arr.length));
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Array.range = function (start, end, step = 1) {
|
|
11
|
+
return Array.from({ length: maxLen }, (_, i) => arrays.map((arr) => arr[i]));
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* @see Array.range
|
|
15
|
+
*/
|
|
16
|
+
(0, utils_1.defineStaticIfNotExists)(Array, 'range', function (start, end, step = 1) {
|
|
14
17
|
if (end === undefined) {
|
|
15
18
|
end = start;
|
|
16
19
|
start = 0;
|
|
17
20
|
}
|
|
18
|
-
const result = [];
|
|
19
21
|
if (step === 0)
|
|
20
22
|
throw new Error('step must not be 0');
|
|
23
|
+
const result = [];
|
|
21
24
|
const condition = step > 0 ? (i) => i < end : (i) => i > end;
|
|
22
25
|
for (let i = start; condition(i); i += step) {
|
|
23
26
|
result.push(i);
|
|
24
27
|
}
|
|
25
28
|
return result;
|
|
26
|
-
};
|
|
27
|
-
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* @see Array.repeat
|
|
32
|
+
*/
|
|
33
|
+
(0, utils_1.defineStaticIfNotExists)(Array, 'repeat', function (length, value) {
|
|
28
34
|
if (typeof length !== 'number' || !Number.isInteger(length) || length < 0) {
|
|
29
|
-
throw new TypeError('
|
|
35
|
+
throw new TypeError('Length must be a non-negative integer');
|
|
30
36
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
return Array.from({ length }, () => (typeof value === 'function' ? value() : value));
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* @see Array.create
|
|
41
|
+
*/
|
|
42
|
+
(0, utils_1.defineStaticIfNotExists)(Array, 'create', function (valueOrFactory, ...sizes) {
|
|
43
|
+
if (sizes.length === 0)
|
|
44
|
+
return [];
|
|
45
|
+
if (!sizes.every((s) => Number.isInteger(s) && s >= 0)) {
|
|
46
|
+
throw new TypeError('All sizes must be non-negative integers');
|
|
34
47
|
}
|
|
35
|
-
|
|
36
|
-
};
|
|
48
|
+
const getValue = typeof valueOrFactory === 'function' ? valueOrFactory : () => valueOrFactory;
|
|
49
|
+
const createDimension = (dimIndex) => Array.from({ length: sizes[dimIndex] }, () => dimIndex === sizes.length - 1 ? getValue() : createDimension(dimIndex + 1));
|
|
50
|
+
return createDimension(0);
|
|
51
|
+
});
|
|
@@ -19,6 +19,7 @@ declare global {
|
|
|
19
19
|
* @returns The sum of the values.
|
|
20
20
|
*/
|
|
21
21
|
sum(this: number[]): number;
|
|
22
|
+
sum<K extends keyof T>(this: T[], key: K): number;
|
|
22
23
|
sum(this: T[], callback: (item: T) => number): number;
|
|
23
24
|
/**
|
|
24
25
|
* Returns a new array with only unique elements (based on strict equality).
|
|
@@ -39,13 +40,16 @@ declare global {
|
|
|
39
40
|
* @returns The average of the values.
|
|
40
41
|
*/
|
|
41
42
|
average(this: number[]): number;
|
|
43
|
+
average<K extends keyof T>(this: T[], key: K): number;
|
|
42
44
|
average(this: T[], callback: (item: T) => number): number;
|
|
43
45
|
/**
|
|
44
|
-
* Groups the array elements based on a key returned by the
|
|
45
|
-
*
|
|
46
|
-
* @
|
|
46
|
+
* Groups the array elements based on a key returned by the selector.
|
|
47
|
+
* The selector can be a function or a string key.
|
|
48
|
+
* @param selector Function or string key to group by.
|
|
49
|
+
* @returns A Map where keys are group values and values are arrays of grouped items.
|
|
47
50
|
*/
|
|
48
|
-
groupBy
|
|
51
|
+
groupBy<K extends keyof T>(this: T[], key: K): Map<T[K], T[]>;
|
|
52
|
+
groupBy<K>(this: T[], selector: (item: T) => K): Map<K, T[]>;
|
|
49
53
|
/**
|
|
50
54
|
* Removes all falsy values (`false`, `null`, `0`, `""`, `undefined`, and `NaN`) from the array.
|
|
51
55
|
* @returns A new array with all falsy values removed.
|
|
@@ -66,6 +70,7 @@ declare global {
|
|
|
66
70
|
* @returns A new array sorted in ascending order.
|
|
67
71
|
*/
|
|
68
72
|
sortAsc(this: number[] | string[]): T[];
|
|
73
|
+
sortAsc<K extends keyof T>(this: T[], key: K): T[];
|
|
69
74
|
sortAsc(this: T[], callback: (item: T) => number | string): T[];
|
|
70
75
|
/**
|
|
71
76
|
* Returns a sorted copy of the array in descending order.
|
|
@@ -76,6 +81,7 @@ declare global {
|
|
|
76
81
|
* @returns A new array sorted in descending order.
|
|
77
82
|
*/
|
|
78
83
|
sortDesc(this: number[] | string[]): T[];
|
|
84
|
+
sortDesc<K extends keyof T>(this: T[], key: K): T[];
|
|
79
85
|
sortDesc(this: T[], callback: (item: T) => number | string): T[];
|
|
80
86
|
/**
|
|
81
87
|
* Swaps the values at two indices in the array.
|
|
@@ -90,5 +96,37 @@ declare global {
|
|
|
90
96
|
* @returns A new shuffled array.
|
|
91
97
|
*/
|
|
92
98
|
shuffle(): T[];
|
|
99
|
+
/**
|
|
100
|
+
* Converts an array to a Map.
|
|
101
|
+
* - If the array is of pairs [K, V], returns Map<K, V>.
|
|
102
|
+
* - If a key is provided, returns Map<T[K], T>.
|
|
103
|
+
* - If keySelector and valueSelector are provided, returns Map<K, V>.
|
|
104
|
+
* @param keyOrKeySelector Key name or key selector function.
|
|
105
|
+
* @param valueSelector Value selector function.
|
|
106
|
+
*/
|
|
107
|
+
toMap<K, V>(this: [K, V][]): Map<K, V>;
|
|
108
|
+
toMap<K extends keyof T>(this: T[], key: K): Map<T[K], T>;
|
|
109
|
+
toMap<K, V>(): Map<number, T>;
|
|
110
|
+
toMap<K, V>(this: T[], keyCallback: (item: T) => K): Map<K, T>;
|
|
111
|
+
toMap<K, V>(this: T[], keyCallback: (item: T) => K, valueCallback: (item: T) => V): Map<K, V>;
|
|
112
|
+
/**
|
|
113
|
+
* Returns a Set containing the unique elements of the array.
|
|
114
|
+
* If a selector is provided, it can be a function or a string key.
|
|
115
|
+
* - If a function, it is called for each element.
|
|
116
|
+
* - If a string, it is used as a property key of each element.
|
|
117
|
+
* @param selector Optional function or string key to select the value to store in the Set.
|
|
118
|
+
* @returns A Set of unique elements or selected values.
|
|
119
|
+
*/
|
|
120
|
+
toSet(): Set<T>;
|
|
121
|
+
toSet<K extends keyof T>(this: T[], key: K): Set<T[K]>;
|
|
122
|
+
toSet<K>(this: T[], selector: (item: T) => K): Set<K>;
|
|
123
|
+
/**
|
|
124
|
+
* Returns a Map where the keys are the result of the selector (function or string key) and the values are the counts of each key.
|
|
125
|
+
* @param selector Function or string key to select the key for counting.
|
|
126
|
+
* @returns A Map with the count of each key.
|
|
127
|
+
*/
|
|
128
|
+
countBy(): Map<T, number>;
|
|
129
|
+
countBy<K extends keyof T>(this: T[], key: K): Map<T[K], number>;
|
|
130
|
+
countBy<K>(this: T[], selector: (item: T) => K): Map<K, number>;
|
|
93
131
|
}
|
|
94
132
|
}
|
|
@@ -7,14 +7,14 @@ const utils_1 = require("../utils");
|
|
|
7
7
|
(0, utils_1.defineIfNotExists)(Array.prototype, 'last', function () {
|
|
8
8
|
return this[this.length - 1];
|
|
9
9
|
});
|
|
10
|
-
(0, utils_1.defineIfNotExists)(Array.prototype, 'sum', function (
|
|
11
|
-
if (
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
if (this.every((item) => typeof item === 'number')) {
|
|
15
|
-
return this.reduce((acc, item) =>
|
|
10
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'sum', function (selector) {
|
|
11
|
+
if (this.length === 0)
|
|
12
|
+
return 0;
|
|
13
|
+
const getValue = (0, utils_1.resolveSelector)(selector, (item) => item);
|
|
14
|
+
if (this.every((item) => typeof getValue(item) === 'number')) {
|
|
15
|
+
return this.reduce((acc, item) => getValue(item) + acc, 0);
|
|
16
16
|
}
|
|
17
|
-
throw new
|
|
17
|
+
throw new TypeError('Array.prototype.sum() requires a callback who return a number unless array is number[]');
|
|
18
18
|
});
|
|
19
19
|
(0, utils_1.defineIfNotExists)(Array.prototype, 'unique', function () {
|
|
20
20
|
return [...new Set(this)];
|
|
@@ -29,32 +29,26 @@ const utils_1 = require("../utils");
|
|
|
29
29
|
}
|
|
30
30
|
return result;
|
|
31
31
|
});
|
|
32
|
-
(0, utils_1.defineIfNotExists)(Array.prototype, 'average', function (
|
|
32
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'average', function (selector) {
|
|
33
33
|
if (this.length === 0)
|
|
34
34
|
return 0;
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const getValue = (0, utils_1.resolveSelector)(selector, (item) => item);
|
|
36
|
+
if (this.every((item) => typeof getValue(item) === 'number')) {
|
|
37
|
+
return this.reduce((acc, item) => getValue(item) + acc, 0) / this.length;
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
-
return this.reduce((acc, item) => acc + item, 0) / this.length;
|
|
40
|
-
}
|
|
41
|
-
throw new Error('Array.prototype.average() requires a callback unless array is number[]');
|
|
39
|
+
throw new TypeError('Array.prototype.average() requires a callback who return a number unless array is number[]');
|
|
42
40
|
});
|
|
43
|
-
(0, utils_1.defineIfNotExists)(Array.prototype, 'groupBy', function (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (typeof key !== 'string' && typeof key !== 'number') {
|
|
51
|
-
throw new TypeError('groupBy callback must return a string or number');
|
|
41
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'groupBy', function (selector) {
|
|
42
|
+
const getKey = (0, utils_1.resolveSelector)(selector, (item) => item);
|
|
43
|
+
const map = new Map();
|
|
44
|
+
for (const item of this) {
|
|
45
|
+
const key = getKey(item);
|
|
46
|
+
if (!map.has(key)) {
|
|
47
|
+
map.set(key, []);
|
|
52
48
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
return result;
|
|
49
|
+
map.get(key).push(item);
|
|
50
|
+
}
|
|
51
|
+
return map;
|
|
58
52
|
});
|
|
59
53
|
(0, utils_1.defineIfNotExists)(Array.prototype, 'compact', function () {
|
|
60
54
|
return this.filter(Boolean);
|
|
@@ -62,55 +56,32 @@ const utils_1 = require("../utils");
|
|
|
62
56
|
(0, utils_1.defineIfNotExists)(Array.prototype, 'enumerate', function () {
|
|
63
57
|
return this.map((value, index) => [value, index]);
|
|
64
58
|
});
|
|
65
|
-
(0, utils_1.defineIfNotExists)(Array.prototype, '
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
throw new TypeError('
|
|
71
|
-
}
|
|
72
|
-
// Si pas de callback, vérifier que tous les éléments sont number ou string
|
|
73
|
-
if (!callback && !arr.every((item) => typeof item === 'number' || typeof item === 'string')) {
|
|
74
|
-
throw new TypeError('Array elements must be number or string if no callback is provided');
|
|
75
|
-
}
|
|
76
|
-
return arr.sort((a, b) => {
|
|
77
|
-
const valA = callback ? callback(a) : a;
|
|
78
|
-
const valB = callback ? callback(b) : b;
|
|
79
|
-
if ((typeof valA !== 'number' && typeof valA !== 'string') ||
|
|
80
|
-
(typeof valB !== 'number' && typeof valB !== 'string')) {
|
|
81
|
-
throw new TypeError('Callback must return number or string');
|
|
82
|
-
}
|
|
83
|
-
if (valA < valB)
|
|
84
|
-
return -1;
|
|
85
|
-
if (valA > valB)
|
|
86
|
-
return 1;
|
|
87
|
-
return 0;
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
(0, utils_1.defineIfNotExists)(Array.prototype, 'sortDesc', function (callback) {
|
|
91
|
-
const arr = this.slice();
|
|
92
|
-
if (arr.length === 0)
|
|
93
|
-
return arr;
|
|
94
|
-
if (callback && typeof callback !== 'function') {
|
|
95
|
-
throw new TypeError('Callback must be a function');
|
|
96
|
-
}
|
|
97
|
-
if (!callback && !arr.every((item) => typeof item === 'number' || typeof item === 'string')) {
|
|
98
|
-
throw new TypeError('Array elements must be number or string if no callback is provided');
|
|
59
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'sortBy', function (direction, selector) {
|
|
60
|
+
if (this.length === 0)
|
|
61
|
+
return this.slice();
|
|
62
|
+
const getValue = (0, utils_1.resolveSelector)(selector, (item) => item);
|
|
63
|
+
if (!selector && !this.every((item) => (0, utils_1.isNumberOrString)(item))) {
|
|
64
|
+
throw new TypeError('Array elements must be number or string if no selector is provided');
|
|
99
65
|
}
|
|
100
|
-
return
|
|
101
|
-
const valA =
|
|
102
|
-
const valB =
|
|
103
|
-
if ((
|
|
104
|
-
(typeof valB !== 'number' && typeof valB !== 'string')) {
|
|
66
|
+
return this.slice().sort((a, b) => {
|
|
67
|
+
const valA = getValue(a);
|
|
68
|
+
const valB = getValue(b);
|
|
69
|
+
if (!(0, utils_1.isNumberOrString)(valA) || !(0, utils_1.isNumberOrString)(valB)) {
|
|
105
70
|
throw new TypeError('Callback must return number or string');
|
|
106
71
|
}
|
|
107
72
|
if (valA > valB)
|
|
108
|
-
return -1;
|
|
73
|
+
return direction === 'asc' ? 1 : -1;
|
|
109
74
|
if (valA < valB)
|
|
110
|
-
return 1;
|
|
75
|
+
return direction === 'asc' ? -1 : 1;
|
|
111
76
|
return 0;
|
|
112
77
|
});
|
|
113
78
|
});
|
|
79
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'sortAsc', function (selector) {
|
|
80
|
+
return sortBy(this, 'asc', selector);
|
|
81
|
+
});
|
|
82
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'sortDesc', function (selector) {
|
|
83
|
+
return sortBy(this, 'desc', selector);
|
|
84
|
+
});
|
|
114
85
|
(0, utils_1.defineIfNotExists)(Array.prototype, 'swap', function (i, j) {
|
|
115
86
|
if (typeof i !== 'number' || typeof j !== 'number' || !Number.isInteger(i) || !Number.isInteger(j)) {
|
|
116
87
|
throw new TypeError('Indices must be integers');
|
|
@@ -133,3 +104,51 @@ const utils_1 = require("../utils");
|
|
|
133
104
|
}
|
|
134
105
|
return arr;
|
|
135
106
|
});
|
|
107
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'toMap', function (keySelector, valueSelector) {
|
|
108
|
+
if (!keySelector && this.length && this.every((item) => Array.isArray(item) && item.length === 2)) {
|
|
109
|
+
return new Map(this);
|
|
110
|
+
}
|
|
111
|
+
const map = new Map();
|
|
112
|
+
const getKey = (0, utils_1.resolveSelector)(keySelector, (_, index) => index);
|
|
113
|
+
const getValue = (0, utils_1.resolveSelector)(valueSelector, (item) => item);
|
|
114
|
+
for (let i = 0; i < this.length; i++) {
|
|
115
|
+
const item = this[i];
|
|
116
|
+
const key = getKey(item, i);
|
|
117
|
+
const value = getValue(item);
|
|
118
|
+
map.set(key, value);
|
|
119
|
+
}
|
|
120
|
+
return map;
|
|
121
|
+
});
|
|
122
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'toSet', function (selector) {
|
|
123
|
+
const getValue = (0, utils_1.resolveSelector)(selector, (item) => item);
|
|
124
|
+
return new Set(this.map(getValue));
|
|
125
|
+
});
|
|
126
|
+
(0, utils_1.defineIfNotExists)(Array.prototype, 'countBy', function (selector) {
|
|
127
|
+
const getKey = (0, utils_1.resolveSelector)(selector, (item) => item);
|
|
128
|
+
const map = new Map();
|
|
129
|
+
for (const item of this) {
|
|
130
|
+
const key = getKey(item);
|
|
131
|
+
map.set(key, (map.get(key) ?? 0) + 1);
|
|
132
|
+
}
|
|
133
|
+
return map;
|
|
134
|
+
});
|
|
135
|
+
function sortBy(arr, direction, selector) {
|
|
136
|
+
if (arr.length === 0)
|
|
137
|
+
return arr.slice();
|
|
138
|
+
const getValue = (0, utils_1.resolveSelector)(selector, (item) => item);
|
|
139
|
+
if (!selector && !arr.every((item) => (0, utils_1.isNumberOrString)(item))) {
|
|
140
|
+
throw new TypeError('Array elements must be number or string if no selector is provided');
|
|
141
|
+
}
|
|
142
|
+
return arr.slice().sort((a, b) => {
|
|
143
|
+
const valA = getValue(a);
|
|
144
|
+
const valB = getValue(b);
|
|
145
|
+
if (!(0, utils_1.isNumberOrString)(valA) || !(0, utils_1.isNumberOrString)(valB)) {
|
|
146
|
+
throw new TypeError('Callback must return number or string');
|
|
147
|
+
}
|
|
148
|
+
if (valA > valB)
|
|
149
|
+
return direction === 'asc' ? 1 : -1;
|
|
150
|
+
if (valA < valB)
|
|
151
|
+
return direction === 'asc' ? -1 : 1;
|
|
152
|
+
return 0;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
declare global {
|
|
3
|
+
interface Map<K, V> {
|
|
4
|
+
/**
|
|
5
|
+
* Converts the Map into a list or object, depending on the selected type.
|
|
6
|
+
* - `entries` (default): returns `[K, V][]`
|
|
7
|
+
* - `keys`: returns `K[]`
|
|
8
|
+
* - `values`: returns `V[]`
|
|
9
|
+
* - `object`: returns `{ [key: string]: V }`
|
|
10
|
+
*/
|
|
11
|
+
toList(type: 'keys'): K[];
|
|
12
|
+
toList(type: 'values'): V[];
|
|
13
|
+
toList(type: 'object'): Record<string, V>;
|
|
14
|
+
toList(type: 'entries'): [K, V][];
|
|
15
|
+
toList(): [K, V][];
|
|
16
|
+
/**
|
|
17
|
+
* Ensures that the value for the given key is an array.
|
|
18
|
+
* If the key does not exist, it sets it to an empty array.
|
|
19
|
+
*
|
|
20
|
+
* @param key - The key to look up in the map.
|
|
21
|
+
* @returns The array associated with the key.
|
|
22
|
+
*/
|
|
23
|
+
ensureArray<L extends Array<any>>(this: Map<K, L>, key: K): L;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
(0, utils_1.defineIfNotExists)(Map.prototype, 'toList', function (type = 'entries') {
|
|
5
|
+
switch (type) {
|
|
6
|
+
case 'keys':
|
|
7
|
+
return Array.from(this.keys());
|
|
8
|
+
case 'values':
|
|
9
|
+
return Array.from(this.values());
|
|
10
|
+
case 'object': {
|
|
11
|
+
const obj = {};
|
|
12
|
+
for (const [key, value] of this) {
|
|
13
|
+
if (typeof key === 'string' || typeof key === 'number' || typeof key === 'symbol') {
|
|
14
|
+
// For number keys, convert to string (object keys are always strings or symbols)
|
|
15
|
+
obj[String(key)] = value;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
throw new TypeError(`Map.prototype.toList('object') only supports string, number, or symbol keys. Got: ${typeof key}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return obj;
|
|
22
|
+
}
|
|
23
|
+
case 'entries':
|
|
24
|
+
return Array.from(this.entries());
|
|
25
|
+
default:
|
|
26
|
+
throw new TypeError(`Unknown type "${type}" for Map.prototype.toList`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
(0, utils_1.defineIfNotExists)(Map.prototype, 'ensureArray', function (key) {
|
|
30
|
+
if (key === null || key === undefined) {
|
|
31
|
+
throw new TypeError('Key cannot be null or undefined');
|
|
32
|
+
}
|
|
33
|
+
if (!this.has(key)) {
|
|
34
|
+
this.set(key, []);
|
|
35
|
+
}
|
|
36
|
+
const arr = this.get(key);
|
|
37
|
+
if (!Array.isArray(arr)) {
|
|
38
|
+
throw new TypeError('Value for the key is not an array');
|
|
39
|
+
}
|
|
40
|
+
return arr;
|
|
41
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
declare global {
|
|
3
|
+
interface Set<T> {
|
|
4
|
+
/**
|
|
5
|
+
* Converts the Set into an array, preserving insertion order.
|
|
6
|
+
* @returns An array of all values in the Set.
|
|
7
|
+
*/
|
|
8
|
+
toList<T>(): T[];
|
|
9
|
+
/**
|
|
10
|
+
* Returns true if at least one of the given items is present in the set.
|
|
11
|
+
*/
|
|
12
|
+
hasAny(...items: T[]): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if all of the given items are present in the set.
|
|
15
|
+
*/
|
|
16
|
+
includes(...items: T[]): boolean;
|
|
17
|
+
includes(items: Set<T>): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Returns a new Set that is the union of this set and all given sets.
|
|
20
|
+
*/
|
|
21
|
+
union(...others: Set<T>[]): Set<T>;
|
|
22
|
+
/**
|
|
23
|
+
* Returns a new Set that is the intersection of this set and all given sets.
|
|
24
|
+
*/
|
|
25
|
+
intersection(...others: Set<T>[]): Set<T>;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
(0, utils_1.defineIfNotExists)(Set.prototype, 'toList', function () {
|
|
5
|
+
// Always returns a new array, even for empty sets
|
|
6
|
+
return Array.from(this);
|
|
7
|
+
});
|
|
8
|
+
(0, utils_1.defineIfNotExists)(Set.prototype, 'hasAny', function (...items) {
|
|
9
|
+
if (!Array.isArray(items))
|
|
10
|
+
throw new TypeError('Arguments must be an array');
|
|
11
|
+
if (items.length === 0)
|
|
12
|
+
return false;
|
|
13
|
+
return items.some((item) => this.has(item));
|
|
14
|
+
});
|
|
15
|
+
(0, utils_1.defineIfNotExists)(Set.prototype, 'includes', function (...args) {
|
|
16
|
+
let values;
|
|
17
|
+
if (args.length === 0)
|
|
18
|
+
return true; // empty means "all included" (like [].every)
|
|
19
|
+
if (args.length === 1 && args[0] instanceof Set) {
|
|
20
|
+
values = Array.from(args[0]);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
values = args;
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(values))
|
|
26
|
+
throw new TypeError('Arguments must be an array or a Set');
|
|
27
|
+
return values.every((item) => this.has(item));
|
|
28
|
+
});
|
|
29
|
+
(0, utils_1.defineIfNotExists)(Set.prototype, 'union', function (...others) {
|
|
30
|
+
const result = new Set(this);
|
|
31
|
+
for (const other of others) {
|
|
32
|
+
if (!(other instanceof Set))
|
|
33
|
+
throw new TypeError('Arguments must be Sets');
|
|
34
|
+
for (const item of other) {
|
|
35
|
+
result.add(item);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
});
|
|
40
|
+
(0, utils_1.defineIfNotExists)(Set.prototype, 'intersection', function (...others) {
|
|
41
|
+
if (others.some((s) => !(s instanceof Set)))
|
|
42
|
+
throw new TypeError('Arguments must be Sets');
|
|
43
|
+
const result = new Set();
|
|
44
|
+
for (const item of this) {
|
|
45
|
+
if (others.every((set) => set.has(item))) {
|
|
46
|
+
result.add(item);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
});
|
|
@@ -47,5 +47,16 @@ declare global {
|
|
|
47
47
|
* @returns A slugified version of the string.
|
|
48
48
|
*/
|
|
49
49
|
slugify(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Replaces a substring between `start` and `end` indices with a given string.
|
|
52
|
+
* If `end` is not provided, only the character at `start` is replaced.
|
|
53
|
+
* If `replaceString` is not provided, the range is simply removed.
|
|
54
|
+
*
|
|
55
|
+
* @param start - Start index of the replacement (inclusive).
|
|
56
|
+
* @param end - End index of the replacement (exclusive). Defaults to `start`.
|
|
57
|
+
* @param replaceString - The string to insert in place. Defaults to `''`.
|
|
58
|
+
* @returns A new string with the specified range replaced.
|
|
59
|
+
*/
|
|
60
|
+
replaceRange(start: number, end: number, replaceString?: string): string;
|
|
50
61
|
}
|
|
51
62
|
}
|
|
@@ -12,7 +12,7 @@ const utils_1 = require("../utils");
|
|
|
12
12
|
(0, utils_1.defineIfNotExists)(String.prototype, 'camelCase', function () {
|
|
13
13
|
const words = this.splitWords().map((w) => w.toLowerCase());
|
|
14
14
|
return words
|
|
15
|
-
.map((word, i) => i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
|
|
15
|
+
.map((word, i) => (i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)))
|
|
16
16
|
.join('');
|
|
17
17
|
});
|
|
18
18
|
(0, utils_1.defineIfNotExists)(String.prototype, 'kebabCase', function () {
|
|
@@ -49,3 +49,17 @@ const utils_1 = require("../utils");
|
|
|
49
49
|
return '';
|
|
50
50
|
return this.charAt(0).toUpperCase() + this.slice(1);
|
|
51
51
|
});
|
|
52
|
+
(0, utils_1.defineIfNotExists)(String.prototype, 'replaceRange', function (start, end, replaceString = '') {
|
|
53
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) {
|
|
54
|
+
throw new TypeError('start and end must be integers');
|
|
55
|
+
}
|
|
56
|
+
if (start < 0 || end < 0) {
|
|
57
|
+
throw new RangeError('start or end cannot be negative');
|
|
58
|
+
}
|
|
59
|
+
if (start > this.length || end > this.length) {
|
|
60
|
+
throw new RangeError('start or end is out of bounds');
|
|
61
|
+
}
|
|
62
|
+
if (start > end)
|
|
63
|
+
[start, end] = [end, start];
|
|
64
|
+
return this.slice(0, start) + (replaceString ?? '') + this.slice(end);
|
|
65
|
+
});
|
package/dist/utils.d.ts
CHANGED
|
@@ -1 +1,28 @@
|
|
|
1
1
|
export declare const defineIfNotExists: <T extends object>(prototype: T, name: string, fn: Function) => void;
|
|
2
|
+
/**
|
|
3
|
+
* Defines a **static** property on a constructor if it does not already exist
|
|
4
|
+
* (or if the existing property is writable **or** configurable).
|
|
5
|
+
*
|
|
6
|
+
* - Does **not** affect instance prototypes (use `defineIfNotExists` for `Foo.prototype`).
|
|
7
|
+
* - The property is created with: { enumerable: false, configurable: false, writable: false }
|
|
8
|
+
* making it non-enumerable and immutable after definition.
|
|
9
|
+
* - If an existing property is non-configurable AND non-writable, it will **not** be replaced.
|
|
10
|
+
*
|
|
11
|
+
* @template T
|
|
12
|
+
* @param {T} constructor The constructor object (e.g., `Array`, `String`, `Map`, ...)
|
|
13
|
+
* @param {string} name The name of the static method to define (e.g., `'create'`, `'range'`)
|
|
14
|
+
* @param {Function} fn The function to attach as a static property
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Define Array.range if possible
|
|
18
|
+
* defineStaticIfNotExists(Array, 'range', function(start: number, end?: number) { ... });
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* - To force replacement, delete the property first or use Object.defineProperty directly.
|
|
22
|
+
* - This function is meant to standardize the addition of static methods in utilitish.
|
|
23
|
+
*/
|
|
24
|
+
export declare const defineStaticIfNotExists: <T extends object>(constructor: T, name: string, fn: Function) => void;
|
|
25
|
+
export declare function resolveSelector<T, R>(selector?: keyof T | ((item: T) => R), fallback?: (item: T, index?: number) => R, name?: string): (item: T, index?: number) => R;
|
|
26
|
+
export declare function assertValidSelector<T, R>(selector: any, name?: string): asserts selector is Selector<T, R>;
|
|
27
|
+
export declare function isNumberOrString(value: unknown): value is string | number;
|
|
28
|
+
export type Selector<T, K> = keyof T | ((item: T) => K);
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defineIfNotExists = void 0;
|
|
3
|
+
exports.defineStaticIfNotExists = exports.defineIfNotExists = void 0;
|
|
4
|
+
exports.resolveSelector = resolveSelector;
|
|
5
|
+
exports.assertValidSelector = assertValidSelector;
|
|
6
|
+
exports.isNumberOrString = isNumberOrString;
|
|
4
7
|
const defineIfNotExists = (prototype, name, fn) => {
|
|
5
8
|
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
6
9
|
if (!descriptor || descriptor.writable || descriptor.configurable) {
|
|
@@ -13,3 +16,65 @@ const defineIfNotExists = (prototype, name, fn) => {
|
|
|
13
16
|
}
|
|
14
17
|
};
|
|
15
18
|
exports.defineIfNotExists = defineIfNotExists;
|
|
19
|
+
/**
|
|
20
|
+
* Defines a **static** property on a constructor if it does not already exist
|
|
21
|
+
* (or if the existing property is writable **or** configurable).
|
|
22
|
+
*
|
|
23
|
+
* - Does **not** affect instance prototypes (use `defineIfNotExists` for `Foo.prototype`).
|
|
24
|
+
* - The property is created with: { enumerable: false, configurable: false, writable: false }
|
|
25
|
+
* making it non-enumerable and immutable after definition.
|
|
26
|
+
* - If an existing property is non-configurable AND non-writable, it will **not** be replaced.
|
|
27
|
+
*
|
|
28
|
+
* @template T
|
|
29
|
+
* @param {T} constructor The constructor object (e.g., `Array`, `String`, `Map`, ...)
|
|
30
|
+
* @param {string} name The name of the static method to define (e.g., `'create'`, `'range'`)
|
|
31
|
+
* @param {Function} fn The function to attach as a static property
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Define Array.range if possible
|
|
35
|
+
* defineStaticIfNotExists(Array, 'range', function(start: number, end?: number) { ... });
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* - To force replacement, delete the property first or use Object.defineProperty directly.
|
|
39
|
+
* - This function is meant to standardize the addition of static methods in utilitish.
|
|
40
|
+
*/
|
|
41
|
+
const defineStaticIfNotExists = (constructor, name, fn) => {
|
|
42
|
+
const descriptor = Object.getOwnPropertyDescriptor(constructor, name);
|
|
43
|
+
if (!descriptor || descriptor.writable || descriptor.configurable) {
|
|
44
|
+
Object.defineProperty(constructor, name, {
|
|
45
|
+
value: fn,
|
|
46
|
+
enumerable: false,
|
|
47
|
+
configurable: false,
|
|
48
|
+
writable: false,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.defineStaticIfNotExists = defineStaticIfNotExists;
|
|
53
|
+
function resolveSelector(selector, fallback, name = 'selector') {
|
|
54
|
+
assertValidSelector(selector);
|
|
55
|
+
if (typeof selector === 'function') {
|
|
56
|
+
return selector;
|
|
57
|
+
}
|
|
58
|
+
if (typeof selector === 'string') {
|
|
59
|
+
return (item) => item[selector];
|
|
60
|
+
}
|
|
61
|
+
if (selector) {
|
|
62
|
+
throw new TypeError(`${name} must be a function or a string key`);
|
|
63
|
+
}
|
|
64
|
+
if (fallback) {
|
|
65
|
+
if (typeof fallback === 'function') {
|
|
66
|
+
return fallback;
|
|
67
|
+
}
|
|
68
|
+
throw new TypeError(`fallback must be a function`);
|
|
69
|
+
}
|
|
70
|
+
throw new TypeError(`fallback must be given if no selector`);
|
|
71
|
+
}
|
|
72
|
+
function assertValidSelector(selector, name = 'selector') {
|
|
73
|
+
const isValid = typeof selector === 'function' || typeof selector === 'string';
|
|
74
|
+
if (selector !== undefined && !isValid) {
|
|
75
|
+
throw new TypeError(`${name} must be a function or a string key`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function isNumberOrString(value) {
|
|
79
|
+
return typeof value === 'string' || typeof value === 'number';
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "utilitish",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
|
-
"test": "jest"
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"docs": "typedoc"
|
|
13
14
|
},
|
|
14
15
|
"keywords": [
|
|
15
16
|
"typescript",
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"@types/jest": "^29.5.14",
|
|
23
24
|
"jest": "^29.7.0",
|
|
24
25
|
"ts-jest": "^29.3.4",
|
|
26
|
+
"typedoc": "^0.28.10",
|
|
25
27
|
"typescript": "^5.8.3"
|
|
26
28
|
}
|
|
27
29
|
}
|