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.
@@ -2,25 +2,92 @@ export {};
2
2
  declare global {
3
3
  interface ArrayConstructor {
4
4
  /**
5
- * Combines multiple arrays element-wise, like Python's itertools.zip_longest .
6
- * @param arrays Arrays to zip together
7
- * @returns An array of arrays, each containing the i-th elements of the input arrays, undefined for the shortest arrays
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
- * @param start First number, or length if end is not defined
13
- * @param end Last number (excluded)
14
- * @param step Step of the sequence (positive or negative)
15
- * @returns An array of numbers
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 a generated value.
20
- * @param length Length of the array
21
- * @param value Value or function generating a value
22
- * @returns Filled array
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
- Array.zip = function (...arrays) {
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
- const result = [];
8
- for (let i = 0; i < maxLen; i++) {
9
- result.push(arrays.map((arr) => arr[i]));
10
- }
11
- return result;
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
- Array.repeat = function (length, value) {
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('Indices must be integers');
35
+ throw new TypeError('Length must be a non-negative integer');
30
36
  }
31
- const result = [];
32
- for (let i = 0; i < length; i++) {
33
- result.push(typeof value === 'function' ? value() : value);
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
- return result;
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 callback function.
45
- * @param callback - Function that returns a key to group by.
46
- * @returns An object where keys are the group names and values are arrays of grouped items.
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(callback: (item: T) => string | number): Record<string | number, T[]>;
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 (callback) {
11
- if (typeof callback === 'function') {
12
- return this.reduce((acc, item) => acc + callback(item), 0);
13
- }
14
- if (this.every((item) => typeof item === 'number')) {
15
- return this.reduce((acc, item) => acc + item, 0);
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 Error('Array.prototype.sum() requires a callback unless array is number[]');
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 (callback) {
32
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'average', function (selector) {
33
33
  if (this.length === 0)
34
34
  return 0;
35
- if (typeof callback === 'function') {
36
- return this.reduce((acc, item) => acc + callback(item), 0) / this.length;
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
- if (this.every((item) => typeof item === 'number')) {
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 (callback) {
44
- if (typeof callback !== 'function') {
45
- throw new TypeError('Callback must be a function');
46
- }
47
- const result = {};
48
- this.forEach((item) => {
49
- const key = callback(item);
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
- if (!result[key])
54
- result[key] = [];
55
- result[key].push(item);
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, 'sortAsc', function (callback) {
66
- const arr = this.slice();
67
- if (arr.length === 0)
68
- return arr;
69
- if (callback && typeof callback !== 'function') {
70
- throw new TypeError('Callback must be a function');
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 arr.sort((a, b) => {
101
- const valA = callback ? callback(a) : a;
102
- const valB = callback ? callback(b) : b;
103
- if ((typeof valA !== 'number' && typeof valA !== 'string') ||
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",
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
  }