objwalk 1.5.0 → 1.6.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 +92 -6
- package/dist/index.cjs +79 -51
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +78 -51
- package/package.json +1 -1
- package/skills/objwalk/SKILL.md +50 -4
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npm install objwalk
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import { each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
18
|
+
import { diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## pick
|
|
@@ -143,6 +143,55 @@ equally({ a: 1 }, { a: 1, b: 2 })
|
|
|
143
143
|
`equally` treats `NaN` as equal to `NaN`, compares `Date` by timestamp and `RegExp` by
|
|
144
144
|
source and flags, and follows circular references without looping.
|
|
145
145
|
|
|
146
|
+
## diff
|
|
147
|
+
|
|
148
|
+
Describe changes from `first` to `last` as a list of `set` and `delete` operations.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { diff, type DiffOperation } from 'objwalk'
|
|
152
|
+
|
|
153
|
+
const operations: DiffOperation[] = diff(
|
|
154
|
+
{ hp: 10, target: 'enemy', position: { x: 1, y: 2 } },
|
|
155
|
+
{ hp: null, position: { x: 3, y: 2 } }
|
|
156
|
+
)
|
|
157
|
+
// [
|
|
158
|
+
// { type: 'set', path: ['hp'], value: null },
|
|
159
|
+
// { type: 'set', path: ['position', 'x'], value: 3 },
|
|
160
|
+
// { type: 'delete', path: ['target'] },
|
|
161
|
+
// ]
|
|
162
|
+
|
|
163
|
+
diff({}, { a: undefined })
|
|
164
|
+
// [{ type: 'set', path: ['a'], value: undefined }]
|
|
165
|
+
|
|
166
|
+
diff({ a: 1 }, {})
|
|
167
|
+
// [{ type: 'delete', path: ['a'] }]
|
|
168
|
+
|
|
169
|
+
diff(1, null)
|
|
170
|
+
// [{ type: 'set', path: [], value: null }]
|
|
171
|
+
|
|
172
|
+
diff({ a: 1 }, { a: 1 }) // []
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- When both values are plain objects (with `Object.prototype` or a null prototype),
|
|
176
|
+
`diff` recursively compares own enumerable string keys. Symbol and non-enumerable
|
|
177
|
+
keys are ignored during that traversal, and prototype or descriptor changes are not reported.
|
|
178
|
+
- Arrays and other values use `equally`; changed values are replaced as a whole.
|
|
179
|
+
Newly added properties also use a single `set`, including when their value is an empty object.
|
|
180
|
+
- A `set` carries the new value, including `null` or `undefined`; a `delete` means
|
|
181
|
+
the key is absent from the final object's enumerable keys. Empty parents remain.
|
|
182
|
+
- Paths are arrays of literal string keys, so dots, brackets, and empty keys are unambiguous.
|
|
183
|
+
An empty path `[]` means replacement of the root. These are not the string paths
|
|
184
|
+
accepted by `pick`, `set`, or `flush`; joining segments with `.` can lose information.
|
|
185
|
+
- Operations follow the final object's key order, recursively, then report removed
|
|
186
|
+
keys in the first object's key order at each level. No changes return `[]`.
|
|
187
|
+
|
|
188
|
+
`diff(first, last): DiffOperation[]` does not mutate or clone either input. Values in `set`
|
|
189
|
+
operations retain references from `last`; this is not an independent snapshot.
|
|
190
|
+
Getter and proxy errors propagate. Inputs must be acyclic; cycle detection is not performed.
|
|
191
|
+
Shared references without cycles are supported. There is no options argument or patch applier.
|
|
192
|
+
Native JSON serialization drops an `undefined` operation value, so these operations are not
|
|
193
|
+
a lossless JSON transport format for arbitrary JavaScript values.
|
|
194
|
+
|
|
146
195
|
## merge
|
|
147
196
|
|
|
148
197
|
Deeply merge plain objects into a target object.
|
|
@@ -216,16 +265,52 @@ extract(source, {
|
|
|
216
265
|
Walk through an object and call a function for each value.
|
|
217
266
|
|
|
218
267
|
```ts
|
|
219
|
-
each(data, (key, value, path) => {
|
|
220
|
-
console.log(key, value, path)
|
|
268
|
+
each(data, (key, value, path, segments) => {
|
|
269
|
+
console.log(key, value, path, segments)
|
|
221
270
|
})
|
|
222
271
|
```
|
|
223
272
|
|
|
224
273
|
The callback receives:
|
|
225
274
|
|
|
226
|
-
- `key`: the current object key or array index
|
|
275
|
+
- `key`: the current object key or numeric array index; `undefined` for the root when enabled
|
|
227
276
|
- `value`: the current value
|
|
228
|
-
- `path`: the full path to the current value
|
|
277
|
+
- `path`: the full string path to the current value
|
|
278
|
+
- `segments`: literal path segments (`WalkKey[]`), with string object keys and numeric array indexes
|
|
279
|
+
|
|
280
|
+
For example, a literal key `'user.name'` has segments `['user.name']`, while a nested
|
|
281
|
+
`user.name` has `['user', 'name']`. `items[0].id` has `['items', 0, 'id']`.
|
|
282
|
+
The first three callback arguments keep their existing meaning; old callbacks still work.
|
|
283
|
+
Callback return values are ignored, including `false`.
|
|
284
|
+
|
|
285
|
+
Pass options as the third argument:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
each(data, (key, value, path, segments) => {
|
|
289
|
+
// The root is visited first with key undefined, path '', and segments [].
|
|
290
|
+
}, {
|
|
291
|
+
root: true,
|
|
292
|
+
// Visit arrays themselves, but skip their contents.
|
|
293
|
+
descend: ({ value }) => Array.isArray(value) === false,
|
|
294
|
+
})
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
| Option | Behavior | Default |
|
|
298
|
+
| --- | --- | --- |
|
|
299
|
+
| `root` | Visit the root, including primitive, nullish, or empty root values | `false` |
|
|
300
|
+
| `descend` | Receive `{ key, value, path, segments }` and decide whether to visit a container's children | Visit all children |
|
|
301
|
+
|
|
302
|
+
`descend` runs only for objects and arrays, after their callback. It also runs for the
|
|
303
|
+
root container when `root` is false, allowing the entire traversal to be skipped.
|
|
304
|
+
Returning `false` skips children, without evaluating their getters; the container itself
|
|
305
|
+
is still visited when applicable. Errors from callbacks, getters, and `descend` propagate.
|
|
306
|
+
Mutating the received `segments` arrays does not change traversal paths.
|
|
307
|
+
|
|
308
|
+
`EachCallback`, `EachEntry`, and `EachOptions` are public named type exports.
|
|
309
|
+
Without `root`, or with `root: false`, callback keys remain `WalkKey`. With `root: true`
|
|
310
|
+
or a dynamic `root` option, keys are `WalkKey | undefined`. An explicitly typed callback
|
|
311
|
+
can use `EachCallback<WalkKey | undefined>` when root visits are possible.
|
|
312
|
+
Traversal does not detect cycles. `diff` uses `each` with `root: true` and a `descend`
|
|
313
|
+
predicate that stops at arrays, non-plain objects, and branches replaced as a whole.
|
|
229
314
|
|
|
230
315
|
## map
|
|
231
316
|
|
|
@@ -358,10 +443,11 @@ pick(data, path)
|
|
|
358
443
|
set(data, path, value)
|
|
359
444
|
flush(data, path)
|
|
360
445
|
extract(data, mapping)
|
|
361
|
-
each(data, callback)
|
|
446
|
+
each(data, callback, options?)
|
|
362
447
|
map(data, callback)
|
|
363
448
|
merge(target, ...sources)
|
|
364
449
|
equally(a, b)
|
|
450
|
+
diff(first, last) // DiffOperation[]
|
|
365
451
|
parse(raw, options?) // unknown
|
|
366
452
|
stringify(value, options?) // string | undefined
|
|
367
453
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
clone: () => clone,
|
|
24
|
+
diff: () => diff,
|
|
24
25
|
each: () => each,
|
|
25
26
|
equally: () => equally,
|
|
26
27
|
extract: () => extract,
|
|
@@ -34,6 +35,40 @@ __export(index_exports, {
|
|
|
34
35
|
});
|
|
35
36
|
module.exports = __toCommonJS(index_exports);
|
|
36
37
|
|
|
38
|
+
// src/helpers/createPath.ts
|
|
39
|
+
var createPath = (parentPath, key, parentIsArray) => {
|
|
40
|
+
if (parentIsArray) {
|
|
41
|
+
return parentPath + "[" + key + "]";
|
|
42
|
+
}
|
|
43
|
+
return parentPath ? parentPath + "." + key : String(key);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/helpers/isWalkable.ts
|
|
47
|
+
var isWalkable = (value) => {
|
|
48
|
+
return value !== null && typeof value === "object";
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/each.ts
|
|
52
|
+
var each = (data, callback, { root = false, descend } = {}) => {
|
|
53
|
+
const walk = (value, path, segments, key) => {
|
|
54
|
+
if (segments.length > 0 || root) {
|
|
55
|
+
callback(key, value, path, [...segments]);
|
|
56
|
+
}
|
|
57
|
+
if (isWalkable(value) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
value.forEach((item, index) => {
|
|
60
|
+
walk(item, createPath(path, index, true), [...segments, index], index);
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
Object.entries(value).forEach(([childKey, item]) => {
|
|
64
|
+
walk(item, createPath(path, childKey, false), [...segments, childKey], childKey);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(data, "", [], void 0);
|
|
70
|
+
};
|
|
71
|
+
|
|
37
72
|
// src/helpers/getValueByKey.ts
|
|
38
73
|
var getValueByKey = (object, key, { inherited = true, ignore = [] } = {}) => {
|
|
39
74
|
if (object === null || object === void 0 || ignore.includes(key) || !inherited && !Object.hasOwn(object, key)) {
|
|
@@ -85,51 +120,6 @@ var parsePathSelection = (path = "") => {
|
|
|
85
120
|
};
|
|
86
121
|
};
|
|
87
122
|
|
|
88
|
-
// src/helpers/createPath.ts
|
|
89
|
-
var createPath = (parentPath, key, parentIsArray) => {
|
|
90
|
-
if (parentIsArray) {
|
|
91
|
-
return parentPath + "[" + key + "]";
|
|
92
|
-
}
|
|
93
|
-
return parentPath ? parentPath + "." + key : String(key);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// src/helpers/isWalkable.ts
|
|
97
|
-
var isWalkable = (value) => {
|
|
98
|
-
return value !== null && typeof value === "object";
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// src/each.ts
|
|
102
|
-
var each = (data, callback) => {
|
|
103
|
-
const walk = (value, path, key, parentIsArray) => {
|
|
104
|
-
const currentPath = path || createPath("", key, parentIsArray);
|
|
105
|
-
callback(key, value, currentPath);
|
|
106
|
-
if (!isWalkable(value)) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (Array.isArray(value)) {
|
|
110
|
-
value.forEach((item, index) => {
|
|
111
|
-
walk(item, createPath(currentPath, index, true), index, true);
|
|
112
|
-
});
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
Object.entries(value).forEach(([childKey, item]) => {
|
|
116
|
-
walk(item, createPath(currentPath, childKey, false), childKey, false);
|
|
117
|
-
});
|
|
118
|
-
};
|
|
119
|
-
if (!isWalkable(data)) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (Array.isArray(data)) {
|
|
123
|
-
data.forEach((item, index) => {
|
|
124
|
-
walk(item, createPath("", index, true), index, true);
|
|
125
|
-
});
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
129
|
-
walk(value, createPath("", key, false), key, false);
|
|
130
|
-
});
|
|
131
|
-
};
|
|
132
|
-
|
|
133
123
|
// src/pick.ts
|
|
134
124
|
var pick = (data = {}, path = "", options = {}) => {
|
|
135
125
|
const { cleanPath, keys } = parsePathSelection(path);
|
|
@@ -245,15 +235,17 @@ var map = (data, callback, store = {}) => {
|
|
|
245
235
|
return store;
|
|
246
236
|
};
|
|
247
237
|
|
|
248
|
-
// src/
|
|
249
|
-
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
238
|
+
// src/helpers/isPlainObject.ts
|
|
250
239
|
var isPlainObject = (value) => {
|
|
251
|
-
if (value
|
|
252
|
-
|
|
240
|
+
if (value !== null && typeof value === "object") {
|
|
241
|
+
const prototype = Object.getPrototypeOf(value);
|
|
242
|
+
return prototype === Object.prototype || prototype === null;
|
|
253
243
|
}
|
|
254
|
-
|
|
255
|
-
return prototype === Object.prototype || prototype === null;
|
|
244
|
+
return false;
|
|
256
245
|
};
|
|
246
|
+
|
|
247
|
+
// src/merge.ts
|
|
248
|
+
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
257
249
|
var merge = (target, ...sources) => {
|
|
258
250
|
const targetRecord = target;
|
|
259
251
|
for (const source of sources) {
|
|
@@ -353,6 +345,41 @@ var equalObjects = (x, y, store) => {
|
|
|
353
345
|
// src/equally.ts
|
|
354
346
|
var equally = (a, b) => deepEqual(a, b, /* @__PURE__ */ new WeakMap());
|
|
355
347
|
|
|
348
|
+
// src/diff.ts
|
|
349
|
+
var diff = (first, last) => {
|
|
350
|
+
const operations = [];
|
|
351
|
+
const frames = [];
|
|
352
|
+
const finish = (depth) => {
|
|
353
|
+
for (const { first: first2, last: last2, path } of frames.splice(depth).reverse()) {
|
|
354
|
+
for (const key of Object.keys(first2)) {
|
|
355
|
+
if (Object.prototype.propertyIsEnumerable.call(last2, key) === false) {
|
|
356
|
+
operations.push({ type: "delete", path: [...path, key] });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return frames.at(-1);
|
|
361
|
+
};
|
|
362
|
+
let descend = false;
|
|
363
|
+
each(last, (key, value, _path, segments) => {
|
|
364
|
+
const path = segments.map(String);
|
|
365
|
+
const frame = finish(segments.length);
|
|
366
|
+
const present = key === void 0 || frame !== void 0 && Object.prototype.propertyIsEnumerable.call(frame.first, key);
|
|
367
|
+
const previous = key === void 0 ? first : present ? frame?.first[key] : void 0;
|
|
368
|
+
descend = false;
|
|
369
|
+
if (present && isPlainObject(previous) && isPlainObject(value)) {
|
|
370
|
+
frames.push({ first: previous, last: value, path });
|
|
371
|
+
descend = true;
|
|
372
|
+
} else if (present === false || equally(previous, value) === false) {
|
|
373
|
+
operations.push({ type: "set", path, value });
|
|
374
|
+
}
|
|
375
|
+
}, {
|
|
376
|
+
root: true,
|
|
377
|
+
descend: () => descend
|
|
378
|
+
});
|
|
379
|
+
finish(0);
|
|
380
|
+
return operations;
|
|
381
|
+
};
|
|
382
|
+
|
|
356
383
|
// src/clone.ts
|
|
357
384
|
var clone = (value) => {
|
|
358
385
|
const store = /* @__PURE__ */ new WeakMap();
|
|
@@ -423,6 +450,7 @@ var stringify = (value, { fallback, onError, replacer, space } = {}) => {
|
|
|
423
450
|
// Annotate the CommonJS export names for ESM import in node:
|
|
424
451
|
0 && (module.exports = {
|
|
425
452
|
clone,
|
|
453
|
+
diff,
|
|
426
454
|
each,
|
|
427
455
|
equally,
|
|
428
456
|
extract,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
type WalkKey = string | number;
|
|
2
|
+
type DiffOperation = {
|
|
3
|
+
type: 'set';
|
|
4
|
+
path: string[];
|
|
5
|
+
value: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'delete';
|
|
8
|
+
path: string[];
|
|
9
|
+
};
|
|
2
10
|
type PickOptions = {
|
|
3
11
|
inherited?: boolean;
|
|
4
12
|
ignore?: readonly string[];
|
|
@@ -15,8 +23,24 @@ type StringifyOptions = {
|
|
|
15
23
|
space?: string | number;
|
|
16
24
|
};
|
|
17
25
|
|
|
18
|
-
type EachCallback = (key:
|
|
19
|
-
|
|
26
|
+
type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (key: TKey, value: any, path: string, segments: WalkKey[]) => void;
|
|
27
|
+
type EachEntry = {
|
|
28
|
+
key: WalkKey | undefined;
|
|
29
|
+
value: any;
|
|
30
|
+
path: string;
|
|
31
|
+
segments: WalkKey[];
|
|
32
|
+
};
|
|
33
|
+
type EachOptions = {
|
|
34
|
+
root?: boolean;
|
|
35
|
+
descend?: (entry: EachEntry) => boolean;
|
|
36
|
+
};
|
|
37
|
+
type Each = {
|
|
38
|
+
(data: any, callback: EachCallback, options?: EachOptions & {
|
|
39
|
+
root?: false;
|
|
40
|
+
}): void;
|
|
41
|
+
(data: any, callback: EachCallback<WalkKey | undefined>, options: EachOptions): void;
|
|
42
|
+
};
|
|
43
|
+
declare const each: Each;
|
|
20
44
|
|
|
21
45
|
type ObjwalkObject$1 = Record<string, any>;
|
|
22
46
|
declare const pick: (data?: ObjwalkObject$1, path?: string, options?: PickOptions) => any;
|
|
@@ -36,10 +60,12 @@ declare const flush: (data: ObjwalkObject$1 | undefined, path: string) => Objwal
|
|
|
36
60
|
|
|
37
61
|
declare const equally: (a: unknown, b: unknown) => boolean;
|
|
38
62
|
|
|
63
|
+
declare const diff: (first: unknown, last: unknown) => DiffOperation[];
|
|
64
|
+
|
|
39
65
|
declare const clone: <T>(value: T) => T;
|
|
40
66
|
|
|
41
67
|
declare const parse: (raw: unknown, { fallback, onError, reviver }?: ParseOptions) => unknown;
|
|
42
68
|
|
|
43
69
|
declare const stringify: (value: unknown, { fallback, onError, replacer, space }?: StringifyOptions) => string | undefined;
|
|
44
70
|
|
|
45
|
-
export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
|
|
71
|
+
export { type DiffOperation, type EachCallback, type EachEntry, type EachOptions, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
type WalkKey = string | number;
|
|
2
|
+
type DiffOperation = {
|
|
3
|
+
type: 'set';
|
|
4
|
+
path: string[];
|
|
5
|
+
value: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'delete';
|
|
8
|
+
path: string[];
|
|
9
|
+
};
|
|
2
10
|
type PickOptions = {
|
|
3
11
|
inherited?: boolean;
|
|
4
12
|
ignore?: readonly string[];
|
|
@@ -15,8 +23,24 @@ type StringifyOptions = {
|
|
|
15
23
|
space?: string | number;
|
|
16
24
|
};
|
|
17
25
|
|
|
18
|
-
type EachCallback = (key:
|
|
19
|
-
|
|
26
|
+
type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (key: TKey, value: any, path: string, segments: WalkKey[]) => void;
|
|
27
|
+
type EachEntry = {
|
|
28
|
+
key: WalkKey | undefined;
|
|
29
|
+
value: any;
|
|
30
|
+
path: string;
|
|
31
|
+
segments: WalkKey[];
|
|
32
|
+
};
|
|
33
|
+
type EachOptions = {
|
|
34
|
+
root?: boolean;
|
|
35
|
+
descend?: (entry: EachEntry) => boolean;
|
|
36
|
+
};
|
|
37
|
+
type Each = {
|
|
38
|
+
(data: any, callback: EachCallback, options?: EachOptions & {
|
|
39
|
+
root?: false;
|
|
40
|
+
}): void;
|
|
41
|
+
(data: any, callback: EachCallback<WalkKey | undefined>, options: EachOptions): void;
|
|
42
|
+
};
|
|
43
|
+
declare const each: Each;
|
|
20
44
|
|
|
21
45
|
type ObjwalkObject$1 = Record<string, any>;
|
|
22
46
|
declare const pick: (data?: ObjwalkObject$1, path?: string, options?: PickOptions) => any;
|
|
@@ -36,10 +60,12 @@ declare const flush: (data: ObjwalkObject$1 | undefined, path: string) => Objwal
|
|
|
36
60
|
|
|
37
61
|
declare const equally: (a: unknown, b: unknown) => boolean;
|
|
38
62
|
|
|
63
|
+
declare const diff: (first: unknown, last: unknown) => DiffOperation[];
|
|
64
|
+
|
|
39
65
|
declare const clone: <T>(value: T) => T;
|
|
40
66
|
|
|
41
67
|
declare const parse: (raw: unknown, { fallback, onError, reviver }?: ParseOptions) => unknown;
|
|
42
68
|
|
|
43
69
|
declare const stringify: (value: unknown, { fallback, onError, replacer, space }?: StringifyOptions) => string | undefined;
|
|
44
70
|
|
|
45
|
-
export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
|
|
71
|
+
export { type DiffOperation, type EachCallback, type EachEntry, type EachOptions, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
// src/helpers/createPath.ts
|
|
2
|
+
var createPath = (parentPath, key, parentIsArray) => {
|
|
3
|
+
if (parentIsArray) {
|
|
4
|
+
return parentPath + "[" + key + "]";
|
|
5
|
+
}
|
|
6
|
+
return parentPath ? parentPath + "." + key : String(key);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/helpers/isWalkable.ts
|
|
10
|
+
var isWalkable = (value) => {
|
|
11
|
+
return value !== null && typeof value === "object";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/each.ts
|
|
15
|
+
var each = (data, callback, { root = false, descend } = {}) => {
|
|
16
|
+
const walk = (value, path, segments, key) => {
|
|
17
|
+
if (segments.length > 0 || root) {
|
|
18
|
+
callback(key, value, path, [...segments]);
|
|
19
|
+
}
|
|
20
|
+
if (isWalkable(value) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
value.forEach((item, index) => {
|
|
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
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
walk(data, "", [], void 0);
|
|
33
|
+
};
|
|
34
|
+
|
|
1
35
|
// src/helpers/getValueByKey.ts
|
|
2
36
|
var getValueByKey = (object, key, { inherited = true, ignore = [] } = {}) => {
|
|
3
37
|
if (object === null || object === void 0 || ignore.includes(key) || !inherited && !Object.hasOwn(object, key)) {
|
|
@@ -49,51 +83,6 @@ var parsePathSelection = (path = "") => {
|
|
|
49
83
|
};
|
|
50
84
|
};
|
|
51
85
|
|
|
52
|
-
// src/helpers/createPath.ts
|
|
53
|
-
var createPath = (parentPath, key, parentIsArray) => {
|
|
54
|
-
if (parentIsArray) {
|
|
55
|
-
return parentPath + "[" + key + "]";
|
|
56
|
-
}
|
|
57
|
-
return parentPath ? parentPath + "." + key : String(key);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// src/helpers/isWalkable.ts
|
|
61
|
-
var isWalkable = (value) => {
|
|
62
|
-
return value !== null && typeof value === "object";
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// src/each.ts
|
|
66
|
-
var each = (data, callback) => {
|
|
67
|
-
const walk = (value, path, key, parentIsArray) => {
|
|
68
|
-
const currentPath = path || createPath("", key, parentIsArray);
|
|
69
|
-
callback(key, value, currentPath);
|
|
70
|
-
if (!isWalkable(value)) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (Array.isArray(value)) {
|
|
74
|
-
value.forEach((item, index) => {
|
|
75
|
-
walk(item, createPath(currentPath, index, true), index, true);
|
|
76
|
-
});
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
Object.entries(value).forEach(([childKey, item]) => {
|
|
80
|
-
walk(item, createPath(currentPath, childKey, false), childKey, false);
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
if (!isWalkable(data)) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (Array.isArray(data)) {
|
|
87
|
-
data.forEach((item, index) => {
|
|
88
|
-
walk(item, createPath("", index, true), index, true);
|
|
89
|
-
});
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
93
|
-
walk(value, createPath("", key, false), key, false);
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
86
|
// src/pick.ts
|
|
98
87
|
var pick = (data = {}, path = "", options = {}) => {
|
|
99
88
|
const { cleanPath, keys } = parsePathSelection(path);
|
|
@@ -209,15 +198,17 @@ var map = (data, callback, store = {}) => {
|
|
|
209
198
|
return store;
|
|
210
199
|
};
|
|
211
200
|
|
|
212
|
-
// src/
|
|
213
|
-
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
201
|
+
// src/helpers/isPlainObject.ts
|
|
214
202
|
var isPlainObject = (value) => {
|
|
215
|
-
if (value
|
|
216
|
-
|
|
203
|
+
if (value !== null && typeof value === "object") {
|
|
204
|
+
const prototype = Object.getPrototypeOf(value);
|
|
205
|
+
return prototype === Object.prototype || prototype === null;
|
|
217
206
|
}
|
|
218
|
-
|
|
219
|
-
return prototype === Object.prototype || prototype === null;
|
|
207
|
+
return false;
|
|
220
208
|
};
|
|
209
|
+
|
|
210
|
+
// src/merge.ts
|
|
211
|
+
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
221
212
|
var merge = (target, ...sources) => {
|
|
222
213
|
const targetRecord = target;
|
|
223
214
|
for (const source of sources) {
|
|
@@ -317,6 +308,41 @@ var equalObjects = (x, y, store) => {
|
|
|
317
308
|
// src/equally.ts
|
|
318
309
|
var equally = (a, b) => deepEqual(a, b, /* @__PURE__ */ new WeakMap());
|
|
319
310
|
|
|
311
|
+
// src/diff.ts
|
|
312
|
+
var diff = (first, last) => {
|
|
313
|
+
const operations = [];
|
|
314
|
+
const frames = [];
|
|
315
|
+
const finish = (depth) => {
|
|
316
|
+
for (const { first: first2, last: last2, path } of frames.splice(depth).reverse()) {
|
|
317
|
+
for (const key of Object.keys(first2)) {
|
|
318
|
+
if (Object.prototype.propertyIsEnumerable.call(last2, key) === false) {
|
|
319
|
+
operations.push({ type: "delete", path: [...path, key] });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return frames.at(-1);
|
|
324
|
+
};
|
|
325
|
+
let descend = false;
|
|
326
|
+
each(last, (key, value, _path, segments) => {
|
|
327
|
+
const path = segments.map(String);
|
|
328
|
+
const frame = finish(segments.length);
|
|
329
|
+
const present = key === void 0 || frame !== void 0 && Object.prototype.propertyIsEnumerable.call(frame.first, key);
|
|
330
|
+
const previous = key === void 0 ? first : present ? frame?.first[key] : void 0;
|
|
331
|
+
descend = false;
|
|
332
|
+
if (present && isPlainObject(previous) && isPlainObject(value)) {
|
|
333
|
+
frames.push({ first: previous, last: value, path });
|
|
334
|
+
descend = true;
|
|
335
|
+
} else if (present === false || equally(previous, value) === false) {
|
|
336
|
+
operations.push({ type: "set", path, value });
|
|
337
|
+
}
|
|
338
|
+
}, {
|
|
339
|
+
root: true,
|
|
340
|
+
descend: () => descend
|
|
341
|
+
});
|
|
342
|
+
finish(0);
|
|
343
|
+
return operations;
|
|
344
|
+
};
|
|
345
|
+
|
|
320
346
|
// src/clone.ts
|
|
321
347
|
var clone = (value) => {
|
|
322
348
|
const store = /* @__PURE__ */ new WeakMap();
|
|
@@ -386,6 +412,7 @@ var stringify = (value, { fallback, onError, replacer, space } = {}) => {
|
|
|
386
412
|
};
|
|
387
413
|
export {
|
|
388
414
|
clone,
|
|
415
|
+
diff,
|
|
389
416
|
each,
|
|
390
417
|
equally,
|
|
391
418
|
extract,
|
package/package.json
CHANGED
package/skills/objwalk/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: objwalk
|
|
3
|
-
description: Use when writing or editing JavaScript or TypeScript that imports `objwalk`, reads, writes, or deletes nested object paths, reshapes or walks objects, or uses objwalk JSON helpers.
|
|
3
|
+
description: Use when writing or editing JavaScript or TypeScript that imports `objwalk`, reads, writes, or deletes nested object paths, computes differences, reshapes or walks objects, or uses objwalk JSON helpers.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# objwalk
|
|
@@ -19,7 +19,7 @@ them with type assertions or assume every method accepts arbitrary `unknown`.
|
|
|
19
19
|
Named imports only:
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { clone, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
22
|
+
import { clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Choose the existing operation
|
|
@@ -30,14 +30,15 @@ import { clone, each, equally, extract, flush, map, merge, parse, pick, set, str
|
|
|
30
30
|
| Write a nested value | `set(data, path, value)`; creates intermediate containers, mutates and returns `data`. |
|
|
31
31
|
| Delete a nested property | `flush(data, path)`; deletes an own property, 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) => { ... })`;
|
|
33
|
+
| Visit nested values | `each(data, (key, value, path, segments) => { ... }, options?)`; root is excluded by default. |
|
|
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
37
|
| Deep comparison | `equally(a, b)`; supports `NaN`, `Date`, `RegExp`, and circular references. |
|
|
38
|
+
| Describe changes | `diff(first, last): DiffOperation[]`; returns `set`/`delete` operations, or `[]` when unchanged. |
|
|
38
39
|
|
|
39
40
|
`set`, `flush`, and `merge` mutate their targets. Use `clone` first only when the task
|
|
40
|
-
requires an independent copy. `each` and `
|
|
41
|
+
requires an independent copy. `each`, `map`, and `diff` do not support circular traversal;
|
|
41
42
|
do not infer that capability from `clone` or `equally`.
|
|
42
43
|
|
|
43
44
|
## Paths and reshaping
|
|
@@ -72,6 +73,51 @@ pick(data, 'user.name', {
|
|
|
72
73
|
})
|
|
73
74
|
```
|
|
74
75
|
|
|
76
|
+
## Walking
|
|
77
|
+
|
|
78
|
+
`each` passes literal `segments: WalkKey[]` as its fourth callback argument.
|
|
79
|
+
Object keys are strings; array indexes are numbers. Prefer segments when keys may
|
|
80
|
+
contain dots, brackets, or empty strings; the string `path` can be ambiguous.
|
|
81
|
+
|
|
82
|
+
`EachOptions` accepts `root` (default `false`) and
|
|
83
|
+
`descend: ({ key, value, path, segments }) => boolean`.
|
|
84
|
+
With `root: true`, the root is visited first, including primitive/nullish values,
|
|
85
|
+
with key `undefined`, path `''`, and segments `[]`. Callback keys are then typed
|
|
86
|
+
`WalkKey | undefined`; legacy calls keep `WalkKey`. Use the public
|
|
87
|
+
`EachCallback<WalkKey | undefined>` for an explicitly typed root-aware callback.
|
|
88
|
+
`EachEntry` and `EachOptions` are also public named exports.
|
|
89
|
+
|
|
90
|
+
`descend` is called only for containers, after their callback, and returning false
|
|
91
|
+
skips their children. It also runs for a root container excluded from callbacks.
|
|
92
|
+
For example, `descend: ({ value }) => Array.isArray(value) === false` visits arrays
|
|
93
|
+
without traversing their elements. Callback return values remain ignored, including
|
|
94
|
+
false. Mutating received segments does not affect traversal. Callback/getter/descend
|
|
95
|
+
errors propagate. Cycles are not detected.
|
|
96
|
+
|
|
97
|
+
## Differences
|
|
98
|
+
|
|
99
|
+
`diff` recursively compares two plain objects by their own enumerable string keys;
|
|
100
|
+
symbol/non-enumerable keys, prototypes, and descriptors are not reported during that traversal.
|
|
101
|
+
Arrays and other values use `equally` and are replaced as a whole when changed.
|
|
102
|
+
New properties are also set as whole values. The public `DiffOperation` type is:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
type DiffOperation =
|
|
106
|
+
| { type: 'set'; path: string[]; value: unknown }
|
|
107
|
+
| { type: 'delete'; path: string[] }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Paths contain literal keys; `[]` replaces the root. Do not join them with `.` to
|
|
111
|
+
call `set` or `flush`: keys may contain dots or brackets. `merge` does not apply
|
|
112
|
+
these operations. There is no patch applier or options argument.
|
|
113
|
+
|
|
114
|
+
Deletion is distinct from assigning `null` or `undefined`, including adding an own
|
|
115
|
+
`undefined` property. Empty parent objects remain. Inputs are not mutated or cloned;
|
|
116
|
+
`set.value` retains references from `last`. Native JSON serialization loses an
|
|
117
|
+
`undefined` value, so do not assume lossless transport for arbitrary JS values.
|
|
118
|
+
Getter/proxy errors propagate. Cycles are outside the contract, without detection;
|
|
119
|
+
shared references without cycles work.
|
|
120
|
+
|
|
75
121
|
## JSON and ready values
|
|
76
122
|
|
|
77
123
|
Call `parse(raw, options?)` directly with `raw: unknown`. The library chooses
|