objwalk 1.6.0 → 1.7.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/README.md +125 -373
- package/SPEC.md +383 -0
- package/dist/index.cjs +89 -98
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +89 -98
- package/package.json +3 -2
- package/skills/objwalk/SKILL.md +28 -8
package/dist/index.js
CHANGED
|
@@ -14,20 +14,18 @@ var isWalkable = (value) => {
|
|
|
14
14
|
// src/each.ts
|
|
15
15
|
var each = (data, callback, { root = false, descend } = {}) => {
|
|
16
16
|
const walk = (value, path, segments, key) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
if (isWalkable(value) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
|
|
17
|
+
const proceed = segments.length > 0 || root ? callback(key, value, path, [...segments]) !== false : true;
|
|
18
|
+
if (proceed && isWalkable(value) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
|
|
21
19
|
if (Array.isArray(value)) {
|
|
22
|
-
value.
|
|
23
|
-
walk(item, createPath(path, index, true), [...segments, index], index)
|
|
24
|
-
|
|
25
|
-
} else {
|
|
26
|
-
Object.entries(value).forEach(([childKey, item]) => {
|
|
27
|
-
walk(item, createPath(path, childKey, false), [...segments, childKey], childKey);
|
|
28
|
-
});
|
|
20
|
+
return value.every(
|
|
21
|
+
(item, index) => walk(item, createPath(path, index, true), [...segments, index], index)
|
|
22
|
+
);
|
|
29
23
|
}
|
|
24
|
+
return Object.keys(value).every(
|
|
25
|
+
(childKey) => walk(value[childKey], createPath(path, childKey, false), [...segments, childKey], childKey)
|
|
26
|
+
);
|
|
30
27
|
}
|
|
28
|
+
return proceed;
|
|
31
29
|
};
|
|
32
30
|
walk(data, "", [], void 0);
|
|
33
31
|
};
|
|
@@ -176,13 +174,13 @@ var extract = (source, mapping) => {
|
|
|
176
174
|
return;
|
|
177
175
|
}
|
|
178
176
|
if (Array.isArray(sourcePath)) {
|
|
179
|
-
|
|
177
|
+
each(sourcePath, (_key, item) => {
|
|
180
178
|
const value = pick(source, item);
|
|
181
179
|
if (value !== void 0) {
|
|
182
180
|
set(result, destinationPath, value);
|
|
183
|
-
|
|
181
|
+
return false;
|
|
184
182
|
}
|
|
185
|
-
}
|
|
183
|
+
});
|
|
186
184
|
return;
|
|
187
185
|
}
|
|
188
186
|
set(result, destinationPath, pick(source, sourcePath));
|
|
@@ -211,115 +209,108 @@ var isPlainObject = (value) => {
|
|
|
211
209
|
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
212
210
|
var merge = (target, ...sources) => {
|
|
213
211
|
const targetRecord = target;
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
targetRecord[key] = sourceValue;
|
|
227
|
-
}
|
|
212
|
+
each(sources, (_index, source) => {
|
|
213
|
+
if (isPlainObject(source)) {
|
|
214
|
+
each(Object.entries(source), (_index2, [key, sourceValue]) => {
|
|
215
|
+
if (unsafeKeys.has(key) === false) {
|
|
216
|
+
const targetValue = targetRecord[key];
|
|
217
|
+
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
218
|
+
merge(targetValue, sourceValue);
|
|
219
|
+
} else {
|
|
220
|
+
targetRecord[key] = sourceValue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}, { descend: ({ segments }) => segments.length === 0 });
|
|
228
224
|
}
|
|
229
|
-
}
|
|
225
|
+
}, { descend: ({ segments }) => segments.length === 0 });
|
|
230
226
|
return target;
|
|
231
227
|
};
|
|
232
228
|
|
|
233
229
|
// src/flush.ts
|
|
234
230
|
var flush = (data = {}, path) => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
231
|
+
const paths = Array.isArray(path) ? path : [path];
|
|
232
|
+
each(paths, (_key, path2) => {
|
|
233
|
+
if (path2.length > 0) {
|
|
234
|
+
const keys = path2.split(".");
|
|
235
|
+
const lastKey = keys.pop() ?? "";
|
|
236
|
+
const match = lastKey.match(/^(\w+)\[(\d+)\]$/);
|
|
237
|
+
let parent = getValueByPath(data, keys.join("."), { inherited: false });
|
|
238
|
+
let key = lastKey;
|
|
239
|
+
if (match) {
|
|
240
|
+
const [, arrayKey, index] = match;
|
|
241
|
+
const array = getValueByKey(parent, arrayKey, { inherited: false });
|
|
242
|
+
parent = Array.isArray(array) ? array : void 0;
|
|
243
|
+
key = index;
|
|
244
|
+
}
|
|
245
|
+
if (parent !== null && typeof parent === "object" && Object.hasOwn(parent, key)) {
|
|
246
|
+
delete parent[key];
|
|
247
|
+
}
|
|
249
248
|
}
|
|
250
|
-
}
|
|
249
|
+
});
|
|
251
250
|
return data;
|
|
252
251
|
};
|
|
253
252
|
|
|
254
253
|
// src/deepEqual.ts
|
|
255
254
|
var deepEqual = (x, y, store) => {
|
|
256
|
-
if (Number.isNaN(x) && Number.isNaN(y)) {
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
if (x === y) {
|
|
255
|
+
if (x === y || Number.isNaN(x) && Number.isNaN(y)) {
|
|
260
256
|
return true;
|
|
261
257
|
}
|
|
262
|
-
if (x
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
let seen = store.get(x);
|
|
269
|
-
if (seen !== void 0 && seen.has(y)) {
|
|
270
|
-
return true;
|
|
271
|
-
}
|
|
272
|
-
if (seen === void 0) {
|
|
273
|
-
seen = /* @__PURE__ */ new WeakSet();
|
|
274
|
-
store.set(x, seen);
|
|
275
|
-
}
|
|
276
|
-
seen.add(y);
|
|
277
|
-
if (x instanceof Date && y instanceof Date) {
|
|
278
|
-
return x.getTime() === y.getTime();
|
|
279
|
-
}
|
|
280
|
-
if (x instanceof RegExp && y instanceof RegExp) {
|
|
281
|
-
return x.toString() === y.toString();
|
|
282
|
-
}
|
|
283
|
-
const isArrayX = Array.isArray(x);
|
|
284
|
-
const isArrayY = Array.isArray(y);
|
|
285
|
-
if (isArrayX !== isArrayY) {
|
|
286
|
-
return false;
|
|
287
|
-
}
|
|
288
|
-
if (isArrayX) {
|
|
289
|
-
return equalArrays(x, y, store);
|
|
290
|
-
}
|
|
291
|
-
return equalObjects(x, y, store);
|
|
292
|
-
};
|
|
293
|
-
var equalArrays = (x, y, store) => x.length === y.length && x.every((value, index) => deepEqual(value, y[index], store));
|
|
294
|
-
var equalObjects = (x, y, store) => {
|
|
295
|
-
const keysX = Reflect.ownKeys(x);
|
|
296
|
-
const keysY = Reflect.ownKeys(y);
|
|
297
|
-
if (keysX.length !== keysY.length) {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
return keysX.every((key) => {
|
|
301
|
-
if (!Object.hasOwn(y, key)) {
|
|
302
|
-
return false;
|
|
258
|
+
if (x !== null && y !== null && typeof x === "object" && typeof y === "object") {
|
|
259
|
+
let seen = store.get(x);
|
|
260
|
+
if (seen !== void 0 && seen.has(y)) {
|
|
261
|
+
return true;
|
|
303
262
|
}
|
|
304
|
-
|
|
305
|
-
|
|
263
|
+
if (seen === void 0) {
|
|
264
|
+
seen = /* @__PURE__ */ new WeakSet();
|
|
265
|
+
store.set(x, seen);
|
|
266
|
+
}
|
|
267
|
+
seen.add(y);
|
|
268
|
+
if (x instanceof Date || y instanceof Date) {
|
|
269
|
+
return x instanceof Date && y instanceof Date && x.getTime() === y.getTime();
|
|
270
|
+
}
|
|
271
|
+
if (x instanceof RegExp || y instanceof RegExp) {
|
|
272
|
+
return x instanceof RegExp && y instanceof RegExp && x.toString() === y.toString();
|
|
273
|
+
}
|
|
274
|
+
if (Array.isArray(x) && Array.isArray(y)) {
|
|
275
|
+
const { length } = x;
|
|
276
|
+
let equal = length === y.length;
|
|
277
|
+
let index = 0;
|
|
278
|
+
while (equal && index < length) {
|
|
279
|
+
equal = deepEqual(x[index], y[index], store);
|
|
280
|
+
index += 1;
|
|
281
|
+
}
|
|
282
|
+
return equal;
|
|
283
|
+
} else if (Array.isArray(x) === false && Array.isArray(y) === false) {
|
|
284
|
+
const keysX = Reflect.ownKeys(x);
|
|
285
|
+
const keysY = Reflect.ownKeys(y);
|
|
286
|
+
return keysX.length === keysY.length && keysX.every(
|
|
287
|
+
(key) => Object.hasOwn(y, key) && deepEqual(x[key], y[key], store)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return false;
|
|
306
292
|
};
|
|
307
293
|
|
|
308
294
|
// src/equally.ts
|
|
309
|
-
var equally = (a, b) =>
|
|
295
|
+
var equally = (a, b, paths) => {
|
|
296
|
+
if (paths !== void 0) {
|
|
297
|
+
return paths.every((path) => equally(getValueByPath(a, path), getValueByPath(b, path)));
|
|
298
|
+
}
|
|
299
|
+
return deepEqual(a, b, /* @__PURE__ */ new WeakMap());
|
|
300
|
+
};
|
|
310
301
|
|
|
311
302
|
// src/diff.ts
|
|
312
303
|
var diff = (first, last) => {
|
|
313
304
|
const operations = [];
|
|
314
305
|
const frames = [];
|
|
315
306
|
const finish = (depth) => {
|
|
316
|
-
|
|
317
|
-
|
|
307
|
+
each(frames.splice(depth).reverse(), (_index, { first: first2, last: last2, path }) => {
|
|
308
|
+
each(Object.keys(first2), (_index2, key) => {
|
|
318
309
|
if (Object.prototype.propertyIsEnumerable.call(last2, key) === false) {
|
|
319
310
|
operations.push({ type: "delete", path: [...path, key] });
|
|
320
311
|
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
312
|
+
});
|
|
313
|
+
}, { descend: ({ segments }) => segments.length === 0 });
|
|
323
314
|
return frames.at(-1);
|
|
324
315
|
};
|
|
325
316
|
let descend = false;
|
|
@@ -370,9 +361,9 @@ var clone = (value) => {
|
|
|
370
361
|
}
|
|
371
362
|
const result = {};
|
|
372
363
|
store.set(obj, result);
|
|
373
|
-
|
|
364
|
+
each(Reflect.ownKeys(obj), (_index, key) => {
|
|
374
365
|
result[key] = cloneValue(obj[key]);
|
|
375
|
-
}
|
|
366
|
+
});
|
|
376
367
|
return result;
|
|
377
368
|
};
|
|
378
369
|
return cloneValue(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "objwalk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Small utilities for reading, writing, walking, and reshaping nested JavaScript objects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"object",
|
|
@@ -40,11 +40,12 @@
|
|
|
40
40
|
"files": [
|
|
41
41
|
"dist",
|
|
42
42
|
"README.md",
|
|
43
|
+
"SPEC.md",
|
|
43
44
|
"skills"
|
|
44
45
|
],
|
|
45
46
|
"scripts": {
|
|
46
47
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
47
|
-
"test": "vitest run",
|
|
48
|
+
"test": "tsc --project tsconfig.test.json && vitest run",
|
|
48
49
|
"pack:check": "npm pack --dry-run"
|
|
49
50
|
},
|
|
50
51
|
"prepublishOnly": "npm run build",
|
package/skills/objwalk/SKILL.md
CHANGED
|
@@ -28,13 +28,13 @@ import { clone, diff, each, equally, extract, flush, map, merge, parse, pick, se
|
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| Read a nested value | `pick(data, path, options?)`; unresolved paths return `undefined`. |
|
|
30
30
|
| Write a nested value | `set(data, path, value)`; creates intermediate containers, mutates and returns `data`. |
|
|
31
|
-
| Delete
|
|
31
|
+
| Delete nested properties | `flush(data, path)`; accepts one path or `string[]`, deletes own properties, mutates and returns `data`; missing paths are left unchanged. |
|
|
32
32
|
| Reshape an object | `extract(source, mapping)`; mapping keys are destination paths, values are source paths. |
|
|
33
|
-
| Visit nested values | `each(data, (key, value, path, segments) => { ... }, options?)`; root is excluded by default. |
|
|
33
|
+
| Visit nested values | `each(data, (key, value, path, segments) => { ... }, options?)`; supports objects and arrays, root is excluded by default; returning `false` stops the entire traversal. |
|
|
34
34
|
| Build a store while walking | `map(data, (key, value, store, path) => { ... }, store?)`; returns the store, ignores callback return values. |
|
|
35
35
|
| Deep merge | `merge(target, ...sources)`; mutates and returns the target, merges plain objects, replaces arrays and non-plain values. |
|
|
36
36
|
| Deep copy | `clone(value)`; preserves `Date`, `RegExp`, symbol keys, and circular references. |
|
|
37
|
-
| Deep comparison | `equally(a, b)`; supports `NaN`, `Date`, `RegExp`, and circular references. |
|
|
37
|
+
| Deep comparison | `equally(a, b, paths?)`; supports `NaN`, `Date`, `RegExp`, and circular references; optional `string[]` compares only selected paths. |
|
|
38
38
|
| Describe changes | `diff(first, last): DiffOperation[]`; returns `set`/`delete` operations, or `[]` when unchanged. |
|
|
39
39
|
|
|
40
40
|
`set`, `flush`, and `merge` mutate their targets. Use `clone` first only when the task
|
|
@@ -53,7 +53,8 @@ do not infer that capability from `clone` or `equally`.
|
|
|
53
53
|
|
|
54
54
|
`[n]` is an `extract` mapping feature, not a general wildcard for `pick`, `set`, or `flush`.
|
|
55
55
|
An array of source paths in `extract` selects the first result that is not
|
|
56
|
-
`undefined`, preserving `null`, `false`, `0`, and empty strings
|
|
56
|
+
`undefined`, preserving `null`, `false`, `0`, and empty strings; later paths are
|
|
57
|
+
not read. Sparse path arrays in `flush` and fallback arrays in `extract` skip holes.
|
|
57
58
|
|
|
58
59
|
Use `flush` to remove a property rather than `set(data, path, undefined)`, which
|
|
59
60
|
leaves the property present. `flush` traverses and deletes only own properties.
|
|
@@ -63,6 +64,18 @@ but deleting an item leaves a hole without shifting indexes or changing length.
|
|
|
63
64
|
Selection syntax (`{name,email}`, `{*}`) is not supported. Errors from getters,
|
|
64
65
|
proxies, or deleting non-configurable properties propagate.
|
|
65
66
|
|
|
67
|
+
`flush` processes a path array in order; `[]` leaves the target unchanged.
|
|
68
|
+
`equally` without `paths` (or with `undefined`) compares whole values. With `paths`,
|
|
69
|
+
every selected value must match; `[]` returns true and `''` selects the whole input.
|
|
70
|
+
It uses dot notation and explicit array indexes, reading inherited properties like
|
|
71
|
+
`pick`; unresolved paths compare as `undefined`, including against an explicit
|
|
72
|
+
`undefined` value. Selection syntax and `[n]` mappings are not supported.
|
|
73
|
+
Getter/proxy errors propagate.
|
|
74
|
+
|
|
75
|
+
`equally` compares `Date` and `RegExp` only with their respective types. Arrays
|
|
76
|
+
compare every index, including holes, which read as `undefined`; holes match
|
|
77
|
+
explicit `undefined` slots but not other values.
|
|
78
|
+
|
|
66
79
|
`pick` reads inherited properties by default. Use its existing options when own
|
|
67
80
|
properties or ignored keys are required:
|
|
68
81
|
|
|
@@ -90,9 +103,15 @@ with key `undefined`, path `''`, and segments `[]`. Callback keys are then typed
|
|
|
90
103
|
`descend` is called only for containers, after their callback, and returning false
|
|
91
104
|
skips their children. It also runs for a root container excluded from callbacks.
|
|
92
105
|
For example, `descend: ({ value }) => Array.isArray(value) === false` visits arrays
|
|
93
|
-
without traversing their elements.
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
without traversing their elements. Returning exactly `false` from the callback stops
|
|
107
|
+
the entire traversal, including siblings and ancestor branches; other return values
|
|
108
|
+
are ignored. The stopped container's `descend` is not called, and its children and
|
|
109
|
+
subsequent values are not read. Object keys are collected on entering a container;
|
|
110
|
+
values are read as visited. `each` still returns `undefined`. Existing callbacks
|
|
111
|
+
that returned `false` must omit that return to keep walking. `map` continues to
|
|
112
|
+
ignore callback return values for both objects and arrays.
|
|
113
|
+
Mutating received segments does not affect traversal. Callback/getter/descend errors
|
|
114
|
+
propagate. Cycles are not detected.
|
|
96
115
|
|
|
97
116
|
## Differences
|
|
98
117
|
|
|
@@ -147,5 +166,6 @@ Fallback applies only to exceptions. Preserve valid falsy results and native
|
|
|
147
166
|
`undefined`. Neither fallback nor a throwing `onError` changes stringify's
|
|
148
167
|
return type of `string | undefined`.
|
|
149
168
|
|
|
150
|
-
Full usage: [README](../../README.md).
|
|
169
|
+
Full usage: [README](../../README.md). Edge cases: [specification](../../SPEC.md).
|
|
170
|
+
In a consumer project, read the installed
|
|
151
171
|
package's README and declarations when checking the available version's API.
|