@inglorious/utils 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/package.json +2 -2
- package/src/data-structures/array.js +11 -1
- package/src/data-structures/array.test.js +22 -1
- package/src/data-structures/object.js +6 -0
- package/src/data-structures/objects.js +61 -14
- package/src/data-structures/objects.test.js +102 -5
- package/src/functions/function.js +9 -0
- package/src/functions/function.test.js +15 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A set of general-purpose utility functions designed with functional programming principles in mind.",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"./*": "./src/*"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"src
|
|
27
|
+
"src"
|
|
28
28
|
],
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
|
@@ -29,7 +29,17 @@ export function contains(arr, item) {
|
|
|
29
29
|
* @returns {Array} - The original array or the value wrapped in a new array.
|
|
30
30
|
*/
|
|
31
31
|
export function ensureArray(arr) {
|
|
32
|
-
return
|
|
32
|
+
return isArray(arr) ? arr : [arr]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a value is an array.
|
|
37
|
+
* @param {*} arr - The value to check.
|
|
38
|
+
*
|
|
39
|
+
* @returns {boolean} True if the value is an array, false otherwise.
|
|
40
|
+
*/
|
|
41
|
+
export function isArray(arr) {
|
|
42
|
+
return Array.isArray(arr)
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
/**
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { expect, test } from "vitest"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
contains,
|
|
5
|
+
ensureArray,
|
|
6
|
+
isArray,
|
|
7
|
+
max,
|
|
8
|
+
min,
|
|
9
|
+
pop,
|
|
10
|
+
push,
|
|
11
|
+
remove,
|
|
12
|
+
} from "./array.js"
|
|
4
13
|
|
|
5
14
|
test("it should check if an array contains a value", () => {
|
|
6
15
|
const arr = [3, 2, 6, 1, 7, 4, 5]
|
|
@@ -38,6 +47,18 @@ test("it should wrap the value in an array if it is not an array", () => {
|
|
|
38
47
|
expect(ensureArray(str)).toStrictEqual([str])
|
|
39
48
|
})
|
|
40
49
|
|
|
50
|
+
test("it should correctly identify an array", () => {
|
|
51
|
+
const arr = [1, "two", { three: 3 }]
|
|
52
|
+
|
|
53
|
+
expect(isArray(arr)).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("it should correctly identify a non-array", () => {
|
|
57
|
+
const str = "hello"
|
|
58
|
+
|
|
59
|
+
expect(isArray(str)).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
|
|
41
62
|
test("it should find the maximum value of an array", () => {
|
|
42
63
|
const arr = [3, 2, 6, 1, 7, 4, 5]
|
|
43
64
|
const expectedResult = 7
|
|
@@ -3,6 +3,7 @@ const NEXT_LEVEL = 2
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Creates a deep clone of the given object.
|
|
6
|
+
*
|
|
6
7
|
* @param {Object} obj - The object to clone.
|
|
7
8
|
* @returns {Object} A deep clone of the input object.
|
|
8
9
|
*/
|
|
@@ -12,6 +13,7 @@ export function clone(obj) {
|
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Filters the properties of an object based on a callback function.
|
|
16
|
+
*
|
|
15
17
|
* @param {Object} obj - The object to filter.
|
|
16
18
|
* @param {Function} callback - A function that determines whether a property should be included.
|
|
17
19
|
* Receives (key, value, obj) as arguments.
|
|
@@ -27,6 +29,7 @@ export function filter(obj, callback) {
|
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Finds the first property in an object that satisfies the callback function.
|
|
32
|
+
*
|
|
30
33
|
* @param {Object} obj - The object to search.
|
|
31
34
|
* @param {Function} callback - A function that determines whether a property matches.
|
|
32
35
|
* Receives (key, value, obj) as arguments.
|
|
@@ -40,6 +43,7 @@ export function find(obj, callback) {
|
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* Checks if a value is a plain object.
|
|
46
|
+
*
|
|
43
47
|
* @param {*} obj - The value to check.
|
|
44
48
|
* @returns {boolean} True if the value is a plain object, false otherwise.
|
|
45
49
|
*/
|
|
@@ -49,6 +53,7 @@ export function isObject(obj) {
|
|
|
49
53
|
|
|
50
54
|
/**
|
|
51
55
|
* Maps the properties of an object using a callback function.
|
|
56
|
+
*
|
|
52
57
|
* @param {Object} obj - The object to map.
|
|
53
58
|
* @param {Function} callback - A function that transforms each property.
|
|
54
59
|
* Receives (key, value, obj) as arguments.
|
|
@@ -99,6 +104,7 @@ export function produce(baseState, recipe, ...args) {
|
|
|
99
104
|
|
|
100
105
|
/**
|
|
101
106
|
* Converts an object or array to a formatted string representation.
|
|
107
|
+
*
|
|
102
108
|
* @param {*} obj - The object or array to convert.
|
|
103
109
|
* @param {number} [indentationLevel=INITIAL_LEVEL] - The current indentation level (used for nested structures).
|
|
104
110
|
* @returns {string} A string representation of the input object or array.
|
|
@@ -1,45 +1,92 @@
|
|
|
1
|
+
import { isFunction } from "../functions/function.js"
|
|
2
|
+
import { isArray } from "./array.js"
|
|
1
3
|
import { isObject } from "./object.js"
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
+
* Creates a new object by deeply merging a target object with one or more source objects.
|
|
7
|
+
* This function is immutable; it does not modify the original `target` or `sources`.
|
|
8
|
+
* Nested objects are merged recursively.
|
|
9
|
+
* Arrays from a source object will overwrite arrays in the target object.
|
|
6
10
|
*
|
|
7
|
-
* @param {Object} target - The
|
|
11
|
+
* @param {Object} target - The base object.
|
|
8
12
|
* @param {...Object} sources - The source objects to merge into the target object.
|
|
9
|
-
* @returns {Object} -
|
|
13
|
+
* @returns {Object} - A new object containing the merged properties.
|
|
14
|
+
* @see {@link merge} for a mutable version.
|
|
10
15
|
*/
|
|
11
16
|
export function extend(target, ...sources) {
|
|
12
|
-
return
|
|
17
|
+
return extendWith(undefined, target, ...sources)
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Merges multiple source objects into a target object.
|
|
17
|
-
*
|
|
22
|
+
* This function is mutable; it modifies the `target` object in place.
|
|
23
|
+
* Nested objects are merged recursively.
|
|
24
|
+
* Arrays from a source object will overwrite arrays in the target object.
|
|
18
25
|
*
|
|
19
26
|
* @param {Object} target - The target object to merge into.
|
|
20
27
|
* @param {...Object} sources - The source objects to merge from.
|
|
21
28
|
* @returns {Object} - The merged target object.
|
|
29
|
+
* @see {@link extend} for an immutable version.
|
|
22
30
|
*/
|
|
23
31
|
export function merge(target, ...sources) {
|
|
32
|
+
return mergeWith(undefined, target, ...sources)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new object by deeply merging a target object with one or more source objects,
|
|
37
|
+
* using a custom merger function to handle specific properties.
|
|
38
|
+
* This function is immutable.
|
|
39
|
+
*
|
|
40
|
+
* @param {Function} [merger] - A function to customize merging behavior. It receives `(targetValue, sourceValue)`. If it returns `undefined`, the default merge logic is used.
|
|
41
|
+
* @param {Object} target - The base object.
|
|
42
|
+
* @param {...Object} sources - The source objects to merge.
|
|
43
|
+
* @returns {Object} A new object with the merged properties.
|
|
44
|
+
*/
|
|
45
|
+
export function extendWith(merger, target, ...sources) {
|
|
46
|
+
return mergeWith(merger, {}, target, ...sources)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Merges multiple source objects into a target object, using a custom merger function.
|
|
51
|
+
* This function is mutable; it modifies the `target` object in place.
|
|
52
|
+
*
|
|
53
|
+
* @param {Function} [merger] - A function to customize merging behavior. It receives `(targetValue, sourceValue)`. If it returns `undefined`, the default merge logic is used.
|
|
54
|
+
* @param {Object} target - The target object to merge into.
|
|
55
|
+
* @param {...Object} sources - The source objects to merge from.
|
|
56
|
+
* @returns {Object} The merged target object.
|
|
57
|
+
*/
|
|
58
|
+
export function mergeWith(merger, target, ...sources) {
|
|
24
59
|
return sources
|
|
25
60
|
.filter((source) => source != null)
|
|
26
|
-
.reduce((acc, source) => deepMerge(acc, source), target)
|
|
61
|
+
.reduce((acc, source) => deepMerge(acc, source, merger), target)
|
|
27
62
|
}
|
|
28
63
|
|
|
29
64
|
/**
|
|
30
|
-
* Recursively merges
|
|
65
|
+
* Recursively merges properties from a source object into a target object.
|
|
66
|
+
* This is a helper function for `merge` and `extend`.
|
|
31
67
|
*
|
|
32
68
|
* @param {Object} target - The target object to merge into.
|
|
33
69
|
* @param {Object} source - The source object to merge from.
|
|
34
|
-
* @
|
|
70
|
+
* @param {Function} [merger] - An optional function to customize merging behavior for specific keys.
|
|
71
|
+
* @returns {Object} - The modified target object.
|
|
35
72
|
*/
|
|
36
|
-
function deepMerge(target, source) {
|
|
73
|
+
function deepMerge(target, source, merger) {
|
|
37
74
|
for (const [key, value] of Object.entries(source)) {
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
75
|
+
if (isFunction(merger)) {
|
|
76
|
+
const mergedValue = merger(target[key], value)
|
|
77
|
+
if (mergedValue !== undefined) {
|
|
78
|
+
target[key] = mergedValue
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isArray(value)) {
|
|
84
|
+
target[key] = value
|
|
85
|
+
} else if (isObject(value)) {
|
|
86
|
+
if (!isObject(target[key])) {
|
|
87
|
+
target[key] = {}
|
|
41
88
|
}
|
|
42
|
-
target[key] = deepMerge(target[key], value)
|
|
89
|
+
target[key] = deepMerge(target[key], value, merger)
|
|
43
90
|
} else {
|
|
44
91
|
target[key] = value
|
|
45
92
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { expect, test } from "vitest"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { isArray } from "./array.js"
|
|
4
|
+
import { extend, extendWith, merge, mergeWith } from "./objects.js"
|
|
4
5
|
|
|
5
6
|
test("it should extend an object with another, producing a new object as a result", () => {
|
|
6
7
|
const obj1 = {
|
|
@@ -32,13 +33,13 @@ test("it should extend an object with another, producing a new object as a resul
|
|
|
32
33
|
primitiveMerged: 3,
|
|
33
34
|
primitiveAdded: 4,
|
|
34
35
|
primitiveArrayKept: [1, 2],
|
|
35
|
-
primitiveArrayMerged: [5
|
|
36
|
+
primitiveArrayMerged: [5],
|
|
36
37
|
primitiveArrayAdded: [6, 7],
|
|
37
38
|
primitiveObjectKept: { a: 1 },
|
|
38
39
|
primitiveObjectMerged: { b: 2, c: 4 },
|
|
39
40
|
primitiveObjectAdded: { d: 4 },
|
|
40
41
|
nestedArrayKept: [{ a: 1 }],
|
|
41
|
-
nestedArrayMerged: [{
|
|
42
|
+
nestedArrayMerged: [{ d: 4 }],
|
|
42
43
|
nestedArrayAdded: [{ e: 5 }],
|
|
43
44
|
nestedObjectKept: { a: { b: 2 } },
|
|
44
45
|
nestedObjectMerged: { c: { d: 4, e: 5 } },
|
|
@@ -80,13 +81,13 @@ test("it should deep merge an two objects, changing the first object in place",
|
|
|
80
81
|
primitiveMerged: 3,
|
|
81
82
|
primitiveAdded: 4,
|
|
82
83
|
primitiveArrayKept: [1, 2],
|
|
83
|
-
primitiveArrayMerged: [5
|
|
84
|
+
primitiveArrayMerged: [5],
|
|
84
85
|
primitiveArrayAdded: [6, 7],
|
|
85
86
|
primitiveObjectKept: { a: 1 },
|
|
86
87
|
primitiveObjectMerged: { b: 2, c: 4 },
|
|
87
88
|
primitiveObjectAdded: { d: 4 },
|
|
88
89
|
nestedArrayKept: [{ a: 1 }],
|
|
89
|
-
nestedArrayMerged: [{
|
|
90
|
+
nestedArrayMerged: [{ d: 4 }],
|
|
90
91
|
nestedArrayAdded: [{ e: 5 }],
|
|
91
92
|
nestedObjectKept: { a: { b: 2 } },
|
|
92
93
|
nestedObjectMerged: { c: { d: 4, e: 5 } },
|
|
@@ -97,3 +98,99 @@ test("it should deep merge an two objects, changing the first object in place",
|
|
|
97
98
|
expect(result).toStrictEqual(expectedResult)
|
|
98
99
|
expect(result).toBe(obj1)
|
|
99
100
|
})
|
|
101
|
+
|
|
102
|
+
test("it should extend an object with a custom merger, producing a new object", () => {
|
|
103
|
+
const obj1 = {
|
|
104
|
+
primitiveArrayMerged: [3, 4],
|
|
105
|
+
nestedArrayMerged: [{ b: 2, c: 3 }],
|
|
106
|
+
nestedObjectMerged: { c: { d: 4 } },
|
|
107
|
+
}
|
|
108
|
+
const obj2 = {
|
|
109
|
+
primitiveArrayMerged: [5],
|
|
110
|
+
nestedArrayMerged: [{ d: 4 }],
|
|
111
|
+
nestedObjectMerged: { c: { e: 5 } },
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const merger = (targetValue, sourceValue) => {
|
|
115
|
+
if (isArray(targetValue) && isArray(sourceValue)) {
|
|
116
|
+
return targetValue.concat(sourceValue)
|
|
117
|
+
}
|
|
118
|
+
return undefined // Use default logic for non-arrays
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const expectedResult = {
|
|
122
|
+
primitiveArrayMerged: [3, 4, 5], // Concatenated
|
|
123
|
+
nestedArrayMerged: [{ b: 2, c: 3 }, { d: 4 }], // Concatenated
|
|
124
|
+
nestedObjectMerged: { c: { d: 4, e: 5 } }, // Default deep merge
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const result = extendWith(merger, obj1, obj2)
|
|
128
|
+
expect(result).toStrictEqual(expectedResult)
|
|
129
|
+
expect(result).not.toBe(obj1)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test("it should merge an object with a custom merger, changing the first object in place", () => {
|
|
133
|
+
const obj1 = {
|
|
134
|
+
primitiveArrayMerged: [3, 4],
|
|
135
|
+
nestedArrayMerged: [{ b: 2, c: 3 }],
|
|
136
|
+
nestedObjectMerged: { c: { d: 4 } },
|
|
137
|
+
}
|
|
138
|
+
const obj2 = {
|
|
139
|
+
primitiveArrayMerged: [5],
|
|
140
|
+
nestedArrayMerged: [{ d: 4 }],
|
|
141
|
+
nestedObjectMerged: { c: { e: 5 } },
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const merger = (targetValue, sourceValue) => {
|
|
145
|
+
if (isArray(targetValue) && isArray(sourceValue)) {
|
|
146
|
+
return targetValue.concat(sourceValue)
|
|
147
|
+
}
|
|
148
|
+
return undefined // Use default logic for non-arrays
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const expectedResult = {
|
|
152
|
+
primitiveArrayMerged: [3, 4, 5], // Concatenated
|
|
153
|
+
nestedArrayMerged: [{ b: 2, c: 3 }, { d: 4 }], // Concatenated
|
|
154
|
+
nestedObjectMerged: { c: { d: 4, e: 5 } }, // Default deep merge
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const result = mergeWith(merger, obj1, obj2)
|
|
158
|
+
expect(result).toStrictEqual(expectedResult)
|
|
159
|
+
expect(result).toBe(obj1)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test("extendWith without a merger should behave like extend", () => {
|
|
163
|
+
const obj1 = { a: 1, b: { c: 2 }, d: [3] }
|
|
164
|
+
const obj2 = { a: 10, b: { d: 20 }, d: [30] }
|
|
165
|
+
|
|
166
|
+
const expected = {
|
|
167
|
+
a: 10,
|
|
168
|
+
b: { c: 2, d: 20 },
|
|
169
|
+
d: [30],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const result = extendWith(undefined, obj1, obj2)
|
|
173
|
+
expect(result).toStrictEqual(expected)
|
|
174
|
+
expect(result).not.toBe(obj1)
|
|
175
|
+
|
|
176
|
+
const extendResult = extend(obj1, obj2)
|
|
177
|
+
expect(result).toStrictEqual(extendResult)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test("mergeWith without a merger should behave like merge", () => {
|
|
181
|
+
const obj1 = { a: 1, b: { c: 2 }, d: [3] }
|
|
182
|
+
const obj2 = { a: 10, b: { d: 20 }, d: [30] }
|
|
183
|
+
|
|
184
|
+
const expected = {
|
|
185
|
+
a: 10,
|
|
186
|
+
b: { c: 2, d: 20 },
|
|
187
|
+
d: [30],
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const result = mergeWith(undefined, obj1, obj2)
|
|
191
|
+
expect(result).toStrictEqual(expected)
|
|
192
|
+
expect(result).toBe(obj1)
|
|
193
|
+
|
|
194
|
+
const mergeResult = merge({ a: 1, b: { c: 2 }, d: [3] }, obj2)
|
|
195
|
+
expect(result).toStrictEqual(mergeResult)
|
|
196
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { isFunction } from "./function.js"
|
|
4
|
+
|
|
5
|
+
test("it should correctly identify a function", () => {
|
|
6
|
+
const func = () => {}
|
|
7
|
+
|
|
8
|
+
expect(isFunction(func)).toBe(true)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("it should correctly identify a non-function", () => {
|
|
12
|
+
const str = "hello"
|
|
13
|
+
|
|
14
|
+
expect(isFunction(str)).toBe(false)
|
|
15
|
+
})
|