objwalk 0.1.0 → 1.0.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/LICENSE +15 -0
- package/README.md +17 -1
- package/dist/index.cjs +82 -45
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +81 -45
- package/package.json +12 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergey Khalilov
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
|
15
|
+
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install objwalk
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import { each, extract, map, pick, set } from 'objwalk'
|
|
16
|
+
import { each, extract, map, merge, pick, set } from 'objwalk'
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## pick
|
|
@@ -81,6 +81,21 @@ set(data, 'items[0].title', 'First')
|
|
|
81
81
|
|
|
82
82
|
`set` mutates the object passed to it and returns the same object.
|
|
83
83
|
|
|
84
|
+
## merge
|
|
85
|
+
|
|
86
|
+
Deeply merge plain objects into a target object.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const target = { user: { name: 'Ada', flags: { admin: false } } }
|
|
90
|
+
|
|
91
|
+
merge(target, { user: { email: 'ada@example.com', flags: { admin: true } } })
|
|
92
|
+
// {
|
|
93
|
+
// user: { name: 'Ada', email: 'ada@example.com', flags: { admin: true } },
|
|
94
|
+
// }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`merge` mutates and returns `target`. Sources are applied from left to right. Arrays and non-plain objects, such as `Date`, replace the existing value rather than being merged.
|
|
98
|
+
|
|
84
99
|
## extract
|
|
85
100
|
|
|
86
101
|
Create a new object by mapping destination paths to source paths.
|
|
@@ -189,4 +204,5 @@ set(data, path, value)
|
|
|
189
204
|
extract(data, mapping)
|
|
190
205
|
each(data, callback)
|
|
191
206
|
map(data, callback)
|
|
207
|
+
merge(target, ...sources)
|
|
192
208
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -23,21 +23,69 @@ __export(index_exports, {
|
|
|
23
23
|
each: () => each,
|
|
24
24
|
extract: () => extract,
|
|
25
25
|
map: () => map,
|
|
26
|
+
merge: () => merge,
|
|
26
27
|
pick: () => pick,
|
|
27
28
|
set: () => set
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(index_exports);
|
|
30
31
|
|
|
31
|
-
// src/
|
|
32
|
-
var
|
|
33
|
-
|
|
32
|
+
// src/helpers/getValueByPath.ts
|
|
33
|
+
var arraySegmentRegex = /^(\w+)\[(\d+)\]$/;
|
|
34
|
+
var getValueByPath = (obj, path) => {
|
|
35
|
+
if (!path) return obj;
|
|
36
|
+
return path.split(".").reduce((result, current) => {
|
|
37
|
+
if (result === null || result === void 0) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
const match = current.match(arraySegmentRegex);
|
|
41
|
+
if (match) {
|
|
42
|
+
const [, key, index] = match;
|
|
43
|
+
const array = result[key];
|
|
44
|
+
return Array.isArray(array) ? array[parseInt(index, 10)] : void 0;
|
|
45
|
+
}
|
|
46
|
+
return result[current];
|
|
47
|
+
}, obj);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/helpers/parsePathSelection.ts
|
|
51
|
+
var selectionRegex = /(.*)\{([^}]+)\}$/;
|
|
52
|
+
var normalizeSelectionPath = (path) => {
|
|
53
|
+
return path.endsWith(".") ? path.slice(0, -1) : path;
|
|
54
|
+
};
|
|
55
|
+
var parseSelectedKeys = (selection) => {
|
|
56
|
+
if (selection.trim() === "*") {
|
|
57
|
+
return "all";
|
|
58
|
+
}
|
|
59
|
+
return selection.split(",").map((key) => key.trim()).filter(Boolean);
|
|
34
60
|
};
|
|
61
|
+
var parsePathSelection = (path = "") => {
|
|
62
|
+
const matches = path.match(selectionRegex);
|
|
63
|
+
if (!matches) {
|
|
64
|
+
return {
|
|
65
|
+
cleanPath: path,
|
|
66
|
+
keys: []
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
cleanPath: normalizeSelectionPath(matches[1]),
|
|
71
|
+
keys: parseSelectedKeys(matches[2])
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/helpers/createPath.ts
|
|
35
76
|
var createPath = (parentPath, key, parentIsArray) => {
|
|
36
77
|
if (parentIsArray) {
|
|
37
78
|
return parentPath + "[" + key + "]";
|
|
38
79
|
}
|
|
39
80
|
return parentPath ? parentPath + "." + key : String(key);
|
|
40
81
|
};
|
|
82
|
+
|
|
83
|
+
// src/helpers/isWalkable.ts
|
|
84
|
+
var isWalkable = (value) => {
|
|
85
|
+
return value !== null && typeof value === "object";
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/each.ts
|
|
41
89
|
var each = (data, callback) => {
|
|
42
90
|
const walk = (value, path, key, parentIsArray) => {
|
|
43
91
|
const currentPath = path || createPath("", key, parentIsArray);
|
|
@@ -69,48 +117,6 @@ var each = (data, callback) => {
|
|
|
69
117
|
});
|
|
70
118
|
};
|
|
71
119
|
|
|
72
|
-
// src/helpers/getValueByPath.ts
|
|
73
|
-
var getValueByPath = (obj, path) => {
|
|
74
|
-
if (!path) return obj;
|
|
75
|
-
return path.split(".").reduce((result, current) => {
|
|
76
|
-
if (result !== null && result !== void 0) {
|
|
77
|
-
const match = current.match(/(w+)[(d+)]/);
|
|
78
|
-
if (match) {
|
|
79
|
-
const [, key, index] = match;
|
|
80
|
-
const array = result[key];
|
|
81
|
-
return array && Array.isArray(array) ? array[parseInt(index, 10)] : void 0;
|
|
82
|
-
}
|
|
83
|
-
return result[current];
|
|
84
|
-
}
|
|
85
|
-
return void 0;
|
|
86
|
-
}, obj);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// src/helpers/parsePathSelection.ts
|
|
90
|
-
var selectionRegex = /(.*){([^}]+)}$/;
|
|
91
|
-
var normalizeSelectionPath = (path) => {
|
|
92
|
-
return path.endsWith(".") ? path.slice(0, -1) : path;
|
|
93
|
-
};
|
|
94
|
-
var parseSelectedKeys = (selection) => {
|
|
95
|
-
if (selection.trim() === "*") {
|
|
96
|
-
return "all";
|
|
97
|
-
}
|
|
98
|
-
return selection.split(",").map((key) => key.trim()).filter(Boolean);
|
|
99
|
-
};
|
|
100
|
-
var parsePathSelection = (path = "") => {
|
|
101
|
-
const matches = path.match(selectionRegex);
|
|
102
|
-
if (!matches) {
|
|
103
|
-
return {
|
|
104
|
-
cleanPath: path,
|
|
105
|
-
keys: []
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
cleanPath: normalizeSelectionPath(matches[1]),
|
|
110
|
-
keys: parseSelectedKeys(matches[2])
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
|
|
114
120
|
// src/pick.ts
|
|
115
121
|
var pick = (data = {}, path = "") => {
|
|
116
122
|
const { cleanPath, keys } = parsePathSelection(path);
|
|
@@ -225,11 +231,42 @@ var map = (data, callback, store = {}) => {
|
|
|
225
231
|
});
|
|
226
232
|
return store;
|
|
227
233
|
};
|
|
234
|
+
|
|
235
|
+
// src/merge.ts
|
|
236
|
+
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
237
|
+
var isPlainObject = (value) => {
|
|
238
|
+
if (value === null || typeof value !== "object") {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
const prototype = Object.getPrototypeOf(value);
|
|
242
|
+
return prototype === Object.prototype || prototype === null;
|
|
243
|
+
};
|
|
244
|
+
var merge = (target, ...sources) => {
|
|
245
|
+
const targetRecord = target;
|
|
246
|
+
for (const source of sources) {
|
|
247
|
+
if (!isPlainObject(source)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
for (const [key, sourceValue] of Object.entries(source)) {
|
|
251
|
+
if (unsafeKeys.has(key)) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const targetValue = targetRecord[key];
|
|
255
|
+
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
256
|
+
merge(targetValue, sourceValue);
|
|
257
|
+
} else {
|
|
258
|
+
targetRecord[key] = sourceValue;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return target;
|
|
263
|
+
};
|
|
228
264
|
// Annotate the CommonJS export names for ESM import in node:
|
|
229
265
|
0 && (module.exports = {
|
|
230
266
|
each,
|
|
231
267
|
extract,
|
|
232
268
|
map,
|
|
269
|
+
merge,
|
|
233
270
|
pick,
|
|
234
271
|
set
|
|
235
272
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type WalkKey = string | number;
|
|
2
|
+
|
|
2
3
|
type EachCallback = (key: WalkKey, value: any, path: string) => void;
|
|
3
4
|
declare const each: (data: any, callback: EachCallback) => void;
|
|
4
5
|
|
|
@@ -11,7 +12,9 @@ declare const extract: (source: ObjwalkObject$1, mapping: ExtractMapping) => Obj
|
|
|
11
12
|
type MapCallback<TStore extends Record<string, any> = Record<string, any>> = (key: WalkKey, value: any, store: TStore, path: string) => void;
|
|
12
13
|
declare const map: <TStore extends Record<string, any> = Record<string, any>>(data: any, callback: MapCallback<TStore>, store?: TStore) => TStore;
|
|
13
14
|
|
|
15
|
+
declare const merge: <T extends ObjwalkObject$1>(target: T, ...sources: unknown[]) => T;
|
|
16
|
+
|
|
14
17
|
type ObjwalkObject = Record<string, any>;
|
|
15
18
|
declare const set: (data: ObjwalkObject | undefined, path: string, value: any) => ObjwalkObject;
|
|
16
19
|
|
|
17
|
-
export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type WalkKey, each, extract, map, pick, set };
|
|
20
|
+
export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type WalkKey, each, extract, map, merge, pick, set };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type WalkKey = string | number;
|
|
2
|
+
|
|
2
3
|
type EachCallback = (key: WalkKey, value: any, path: string) => void;
|
|
3
4
|
declare const each: (data: any, callback: EachCallback) => void;
|
|
4
5
|
|
|
@@ -11,7 +12,9 @@ declare const extract: (source: ObjwalkObject$1, mapping: ExtractMapping) => Obj
|
|
|
11
12
|
type MapCallback<TStore extends Record<string, any> = Record<string, any>> = (key: WalkKey, value: any, store: TStore, path: string) => void;
|
|
12
13
|
declare const map: <TStore extends Record<string, any> = Record<string, any>>(data: any, callback: MapCallback<TStore>, store?: TStore) => TStore;
|
|
13
14
|
|
|
15
|
+
declare const merge: <T extends ObjwalkObject$1>(target: T, ...sources: unknown[]) => T;
|
|
16
|
+
|
|
14
17
|
type ObjwalkObject = Record<string, any>;
|
|
15
18
|
declare const set: (data: ObjwalkObject | undefined, path: string, value: any) => ObjwalkObject;
|
|
16
19
|
|
|
17
|
-
export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type WalkKey, each, extract, map, pick, set };
|
|
20
|
+
export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type WalkKey, each, extract, map, merge, pick, set };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,60 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
var
|
|
3
|
-
|
|
1
|
+
// src/helpers/getValueByPath.ts
|
|
2
|
+
var arraySegmentRegex = /^(\w+)\[(\d+)\]$/;
|
|
3
|
+
var getValueByPath = (obj, path) => {
|
|
4
|
+
if (!path) return obj;
|
|
5
|
+
return path.split(".").reduce((result, current) => {
|
|
6
|
+
if (result === null || result === void 0) {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
const match = current.match(arraySegmentRegex);
|
|
10
|
+
if (match) {
|
|
11
|
+
const [, key, index] = match;
|
|
12
|
+
const array = result[key];
|
|
13
|
+
return Array.isArray(array) ? array[parseInt(index, 10)] : void 0;
|
|
14
|
+
}
|
|
15
|
+
return result[current];
|
|
16
|
+
}, obj);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/helpers/parsePathSelection.ts
|
|
20
|
+
var selectionRegex = /(.*)\{([^}]+)\}$/;
|
|
21
|
+
var normalizeSelectionPath = (path) => {
|
|
22
|
+
return path.endsWith(".") ? path.slice(0, -1) : path;
|
|
23
|
+
};
|
|
24
|
+
var parseSelectedKeys = (selection) => {
|
|
25
|
+
if (selection.trim() === "*") {
|
|
26
|
+
return "all";
|
|
27
|
+
}
|
|
28
|
+
return selection.split(",").map((key) => key.trim()).filter(Boolean);
|
|
4
29
|
};
|
|
30
|
+
var parsePathSelection = (path = "") => {
|
|
31
|
+
const matches = path.match(selectionRegex);
|
|
32
|
+
if (!matches) {
|
|
33
|
+
return {
|
|
34
|
+
cleanPath: path,
|
|
35
|
+
keys: []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
cleanPath: normalizeSelectionPath(matches[1]),
|
|
40
|
+
keys: parseSelectedKeys(matches[2])
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/helpers/createPath.ts
|
|
5
45
|
var createPath = (parentPath, key, parentIsArray) => {
|
|
6
46
|
if (parentIsArray) {
|
|
7
47
|
return parentPath + "[" + key + "]";
|
|
8
48
|
}
|
|
9
49
|
return parentPath ? parentPath + "." + key : String(key);
|
|
10
50
|
};
|
|
51
|
+
|
|
52
|
+
// src/helpers/isWalkable.ts
|
|
53
|
+
var isWalkable = (value) => {
|
|
54
|
+
return value !== null && typeof value === "object";
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/each.ts
|
|
11
58
|
var each = (data, callback) => {
|
|
12
59
|
const walk = (value, path, key, parentIsArray) => {
|
|
13
60
|
const currentPath = path || createPath("", key, parentIsArray);
|
|
@@ -39,48 +86,6 @@ var each = (data, callback) => {
|
|
|
39
86
|
});
|
|
40
87
|
};
|
|
41
88
|
|
|
42
|
-
// src/helpers/getValueByPath.ts
|
|
43
|
-
var getValueByPath = (obj, path) => {
|
|
44
|
-
if (!path) return obj;
|
|
45
|
-
return path.split(".").reduce((result, current) => {
|
|
46
|
-
if (result !== null && result !== void 0) {
|
|
47
|
-
const match = current.match(/(w+)[(d+)]/);
|
|
48
|
-
if (match) {
|
|
49
|
-
const [, key, index] = match;
|
|
50
|
-
const array = result[key];
|
|
51
|
-
return array && Array.isArray(array) ? array[parseInt(index, 10)] : void 0;
|
|
52
|
-
}
|
|
53
|
-
return result[current];
|
|
54
|
-
}
|
|
55
|
-
return void 0;
|
|
56
|
-
}, obj);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// src/helpers/parsePathSelection.ts
|
|
60
|
-
var selectionRegex = /(.*){([^}]+)}$/;
|
|
61
|
-
var normalizeSelectionPath = (path) => {
|
|
62
|
-
return path.endsWith(".") ? path.slice(0, -1) : path;
|
|
63
|
-
};
|
|
64
|
-
var parseSelectedKeys = (selection) => {
|
|
65
|
-
if (selection.trim() === "*") {
|
|
66
|
-
return "all";
|
|
67
|
-
}
|
|
68
|
-
return selection.split(",").map((key) => key.trim()).filter(Boolean);
|
|
69
|
-
};
|
|
70
|
-
var parsePathSelection = (path = "") => {
|
|
71
|
-
const matches = path.match(selectionRegex);
|
|
72
|
-
if (!matches) {
|
|
73
|
-
return {
|
|
74
|
-
cleanPath: path,
|
|
75
|
-
keys: []
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
cleanPath: normalizeSelectionPath(matches[1]),
|
|
80
|
-
keys: parseSelectedKeys(matches[2])
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
|
|
84
89
|
// src/pick.ts
|
|
85
90
|
var pick = (data = {}, path = "") => {
|
|
86
91
|
const { cleanPath, keys } = parsePathSelection(path);
|
|
@@ -195,10 +200,41 @@ var map = (data, callback, store = {}) => {
|
|
|
195
200
|
});
|
|
196
201
|
return store;
|
|
197
202
|
};
|
|
203
|
+
|
|
204
|
+
// src/merge.ts
|
|
205
|
+
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
206
|
+
var isPlainObject = (value) => {
|
|
207
|
+
if (value === null || typeof value !== "object") {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
const prototype = Object.getPrototypeOf(value);
|
|
211
|
+
return prototype === Object.prototype || prototype === null;
|
|
212
|
+
};
|
|
213
|
+
var merge = (target, ...sources) => {
|
|
214
|
+
const targetRecord = target;
|
|
215
|
+
for (const source of sources) {
|
|
216
|
+
if (!isPlainObject(source)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
for (const [key, sourceValue] of Object.entries(source)) {
|
|
220
|
+
if (unsafeKeys.has(key)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const targetValue = targetRecord[key];
|
|
224
|
+
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
225
|
+
merge(targetValue, sourceValue);
|
|
226
|
+
} else {
|
|
227
|
+
targetRecord[key] = sourceValue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return target;
|
|
232
|
+
};
|
|
198
233
|
export {
|
|
199
234
|
each,
|
|
200
235
|
extract,
|
|
201
236
|
map,
|
|
237
|
+
merge,
|
|
202
238
|
pick,
|
|
203
239
|
set
|
|
204
240
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "objwalk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Small utilities for reading, writing, walking, and reshaping nested JavaScript objects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"object",
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
],
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"author": "Sergey Khalilov <sergey.khalilov@gmail.com>",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/khalilov/objwalk.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/khalilov/objwalk/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/khalilov/objwalk#readme",
|
|
21
29
|
"type": "module",
|
|
22
30
|
"main": "./dist/index.cjs",
|
|
23
31
|
"module": "./dist/index.js",
|
|
@@ -35,11 +43,12 @@
|
|
|
35
43
|
],
|
|
36
44
|
"scripts": {
|
|
37
45
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
38
|
-
"test": "
|
|
46
|
+
"test": "vitest run",
|
|
39
47
|
"pack:check": "npm pack --dry-run"
|
|
40
48
|
},
|
|
41
49
|
"devDependencies": {
|
|
42
50
|
"tsup": "^8.5.1",
|
|
43
|
-
"typescript": "^5.9.3"
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"vitest": "^4.1.10"
|
|
44
53
|
}
|
|
45
54
|
}
|