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/SPEC.md ADDED
@@ -0,0 +1,383 @@
1
+ # objwalk specification
2
+
3
+ This document describes the public API in the current source tree, including changes
4
+ not yet released. [README.md](README.md) provides examples; [tests](tests/) exercise
5
+ the contracts.
6
+
7
+ ## Public API and shared types
8
+
9
+ All functions and types below are named exports from `objwalk`.
10
+
11
+ ```ts
12
+ type ObjwalkObject = Record<string, any>
13
+ type WalkKey = string | number
14
+ type ExtractMapping = Record<string, string | string[]>
15
+
16
+ type PickOptions = {
17
+ inherited?: boolean
18
+ ignore?: readonly string[]
19
+ }
20
+
21
+ type DiffOperation =
22
+ | { type: 'set'; path: string[]; value: unknown }
23
+ | { type: 'delete'; path: string[] }
24
+ ```
25
+
26
+ | Function | Return value | Mutation and identity |
27
+ | --- | --- | --- |
28
+ | `pick(data, path?, options?)` | Selected value, selection object, key list, or `undefined` | Selected values retain their references |
29
+ | `set(data, path, value)` | Input object | Mutates the input; assigned values are not cloned |
30
+ | `flush(data, path)` | Input object | Mutates the input |
31
+ | `extract(source, mapping)` | New result object | Selected values are not cloned |
32
+ | `each(data, callback, options?)` | `undefined` | Passes original values to callbacks |
33
+ | `map(data, callback, store?)` | Supplied store or a new object | Callback builds or mutates the store |
34
+ | `merge(target, ...sources)` | Target object | Mutates the target; replacement values are not cloned |
35
+ | `clone(value)` | Value of the same declared generic type | Recursively copies supported objects and arrays |
36
+ | `equally(a, b, paths?)` | `boolean` | Does not assign to the inputs |
37
+ | `diff(first, last)` | `DiffOperation[]` | Does not assign to inputs; operation values retain references |
38
+ | `parse(raw, options?)` | `unknown` | Ready objects and arrays retain their references |
39
+ | `stringify(value, options?)` | `string \| undefined` | Uses native JSON serialization |
40
+
41
+ `pick`, `set`, and `flush` use `{}` when their `data` argument is `undefined`.
42
+ `set` and `flush` still require the path argument. `pick` defaults its path to `''`.
43
+ Their declared data contract is `ObjwalkObject`; this is not a guarantee that all
44
+ functions accept every possible JavaScript value. `each` and `map` accept `any`;
45
+ `clone` is generic; `equally`, `diff`, `parse`, and `stringify` accept `unknown` inputs.
46
+ `extract` requires an object and a mapping; `merge` requires an object target.
47
+
48
+ Except for the documented JSON error handling, exceptions from callbacks, getters,
49
+ setters, proxies, or native property operations propagate. Mutations already made
50
+ before an exception are not rolled back. Functions are synchronous and do not await
51
+ callback results. User callbacks and property accessors can have their own side effects.
52
+
53
+ ## Paths
54
+
55
+ Paths are strings, not arrays of literal segments. A dot separates object properties.
56
+ An explicit array segment has the form `items[0]`; its parser matches
57
+ `^(\w+)\[(\d+)\]$`. Use canonical non-negative decimal indexes and a word-character
58
+ property name for this syntax. A bracket segment resolves only when its named value
59
+ is an array. Ordinary segments use property lookup.
60
+
61
+ Supported examples are `user.name`, `items[0].title`, and
62
+ `groups[0].items[1].id`. Quoted brackets, escaping dots, negative indexes, and nested
63
+ brackets such as `matrix[0][1]` are not supported path operators. Unsupported syntax
64
+ is not automatically rejected: a segment can instead be treated as a literal key.
65
+ There is no general path validator.
66
+
67
+ | Syntax | Interpretation |
68
+ | --- | --- |
69
+ | `user.name` | Property traversal |
70
+ | `items[0].title` | Explicit array index followed by property traversal |
71
+ | `user.{name,email}` | Terminal key selection in `pick` |
72
+ | `user.{*}` | Terminal key listing in `pick` |
73
+ | `items[n].title` | Array mapping in `extract`, paired with a source mapping |
74
+
75
+ Key selection is interpreted by `pick`, including source paths passed through
76
+ `pick` by `extract`. It is not interpreted by `set`, `flush`, or `equally`.
77
+ `[n]` is not a wildcard for the other functions.
78
+
79
+ Empty paths have operation-specific meanings:
80
+
81
+ - `pick(data, '')` returns the root value.
82
+ - `equally(a, b, [''])` compares the whole input values.
83
+ - `flush(data, '')` does nothing.
84
+ - `set(data, '', value)` assigns the property whose key is the empty string.
85
+
86
+ Traversal paths from `each` and `map` are descriptive strings, not a lossless encoding
87
+ of arbitrary keys. Literal dots, brackets, and empty keys can make them ambiguous.
88
+ A root array produces paths such as `[0]`, which do not have the named-array-segment
89
+ form above. Use `each`'s literal `segments` or `diff`'s operation paths when exact keys matter.
90
+
91
+ ## pick
92
+
93
+ `pick(data, path?, options?)` reads a path without creating intermediate containers.
94
+ Missing or nullish intermediate values produce `undefined`. Valid falsy values are
95
+ preserved. An explicit array index outside the array resolves to `undefined`, unless
96
+ a value is available through the configured property lookup.
97
+
98
+ `PickOptions` defaults to `{ inherited: true, ignore: [] }`. `inherited: false`
99
+ restricts every property and array-index lookup to own properties. `ignore` matches
100
+ exact string keys, including array indexes represented as strings, at every lookup.
101
+
102
+ Terminal selection trims whitespace around comma-separated keys and removes empty
103
+ entries. One selected key returns its value. Multiple selected keys return a new
104
+ object containing every selected key, including keys whose values are `undefined`.
105
+ Selection requires an object value; an unresolved or primitive selection base returns
106
+ `undefined`. The selection operation does not clone selected values.
107
+
108
+ `{*}` returns own enumerable string keys, filtered by `ignore`, in `Object.keys`
109
+ order. It returns `[]` for arrays, primitives, nullish values, or unresolved paths;
110
+ it does not list inherited keys even when inherited lookup is enabled.
111
+
112
+ ## set
113
+
114
+ `set(data, path, value)` mutates and returns the same input object. It uses direct
115
+ property lookup and assignment, without `PickOptions` or the key filter from `merge`.
116
+
117
+ For an ordinary intermediate segment, missing, nullish, and primitive values are
118
+ replaced with `{}`. Existing object values, including arrays, are reused.
119
+ For an array segment, a non-array named value is replaced with `[]`. A later index
120
+ extends the array, leaving holes before that index. An intermediate indexed value
121
+ is initialized to `{}` only when it is `undefined`; existing null or primitive
122
+ indexed values are not normalized into objects for further traversal.
123
+
124
+ The final property or array slot is assigned even when `value` is `undefined`.
125
+ Assignment is distinct from deletion. No empty-parent pruning or rollback occurs.
126
+
127
+ ## flush
128
+
129
+ `flush(data, path)` accepts `path: string | string[]`. Each path deletes an own
130
+ property using native `delete`, and the function returns the same input object.
131
+
132
+ - Path arrays are processed in order and are not modified. Holes are skipped.
133
+ - Empty lists, empty paths, missing paths, and nullish or primitive parents are no-ops.
134
+ - Traversal and deletion use only own properties, including explicit array indexes.
135
+ - Missing containers are not created; empty parents are not removed.
136
+ - Overlapping and repeated paths are processed against the state left by earlier paths.
137
+ - Deleting an array slot leaves a hole; length and later indexes are unchanged.
138
+ - An exception stops processing immediately. Earlier deletions remain applied and
139
+ later paths are not processed.
140
+
141
+ ## extract
142
+
143
+ `extract(source, mapping)` creates a result root. Mapping keys are destination paths;
144
+ values are source paths or ordered arrays of fallback source paths.
145
+
146
+ Mappings are processed in `Object.entries(mapping)` order. A direct source path is
147
+ read with `pick` and written with `set`, even when its value is `undefined`.
148
+ A fallback array selects the first value that is not `undefined`; `null`, `false`,
149
+ `0`, and `''` all count as resolved. Holes are skipped and later paths are not read
150
+ after a match. If none resolve, that mapping performs no assignment.
151
+
152
+ For array mapping, use one `[n].` marker in both the destination path and its string
153
+ source path, for example `'items[n].title': 'products[n].name'`. The source prefix
154
+ must resolve to an array. Each present source item is read using the source suffix
155
+ and assigned at the corresponding destination index. A non-array source produces
156
+ no assignments. Multiple mapping markers and fallback arrays of array mappings
157
+ are outside this mapping contract.
158
+
159
+ The result root is new, but selected objects are assigned by reference. Overlapping
160
+ destination writes can therefore modify objects shared with the source. `extract`
161
+ does not provide an independent deep snapshot.
162
+
163
+ ## each
164
+
165
+ ```ts
166
+ type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (
167
+ key: TKey,
168
+ value: any,
169
+ path: string,
170
+ segments: WalkKey[]
171
+ ) => unknown
172
+
173
+ type EachEntry = {
174
+ key: WalkKey | undefined
175
+ value: any
176
+ path: string
177
+ segments: WalkKey[]
178
+ }
179
+
180
+ type EachOptions = {
181
+ root?: boolean
182
+ descend?: (entry: EachEntry) => boolean
183
+ }
184
+ ```
185
+
186
+ `each(data, callback, options?)` walks objects and arrays depth first, visiting a
187
+ value before its children. Objects enumerate own enumerable string keys in
188
+ `Object.keys` order; symbols and non-enumerable properties are not enumerated.
189
+ Arrays visit numeric indexes in ascending order, using native `every` iteration:
190
+ absent slots are skipped, inherited indexed values can be visited, and non-index
191
+ properties are not traversed. Any non-null object can be a container; functions
192
+ are leaves, and built-in collection internals are not traversed.
193
+
194
+ `root` defaults to `false`. When enabled, the root is visited first with key
195
+ `undefined`, path `''`, and segments `[]`, even for primitive, nullish, or empty
196
+ inputs. Otherwise a primitive or nullish root has no visits. Object keys are
197
+ strings and array indexes are numbers. Without root visits, callback keys are
198
+ typed as `WalkKey`; enabled or dynamic root options use `WalkKey | undefined`.
199
+
200
+ Returning exactly `false` from the callback stops the entire traversal, including
201
+ siblings and remaining ancestor branches. Other return values, including other
202
+ falsy values, are ignored. A stopped container does not invoke `descend` or visit
203
+ children; subsequent values are not read. The function returns `undefined` after
204
+ both completion and early stopping.
205
+
206
+ `descend` is evaluated only for containers, after their callback. It also runs for
207
+ an excluded root container. Returning `false` skips that container's children and
208
+ continues with siblings. It does not stop the entire traversal.
209
+
210
+ Object keys are collected when entering a container; values are read as visited.
211
+ For arrays, native iteration captures the starting length: appended slots beyond
212
+ that length are not visited. Callback and `descend` receive separate copies of
213
+ `segments`; modifying those copies does not change traversal paths. `value` remains
214
+ the original value. Cycles are not detected. Separate and nested invocations do
215
+ not share stop state.
216
+
217
+ ## map
218
+
219
+ ```ts
220
+ type MapCallback<TStore extends Record<string, any> = Record<string, any>> = (
221
+ key: WalkKey,
222
+ value: any,
223
+ store: TStore,
224
+ path: string
225
+ ) => void
226
+ ```
227
+
228
+ `map(data, callback, store?)` uses the default `each` traversal for objects and arrays,
229
+ excluding the root. The callback receives the original value and the same store
230
+ on every visit. A supplied store is returned by identity; otherwise the store is
231
+ a new `{}`. The generic store type is preserved in the return type.
232
+
233
+ All callback return values, including `false`, are ignored. `map` exposes neither
234
+ root/descend options nor literal segments. Errors propagate and completed store
235
+ mutations remain applied. Cycles are not detected.
236
+
237
+ ## merge
238
+
239
+ `merge<T extends ObjwalkObject>(target: T, ...sources: unknown[]): T` mutates and
240
+ returns the target. Sources are applied left to right. A source is processed only
241
+ when its prototype is `Object.prototype` or `null`; other root sources are ignored.
242
+
243
+ Each source uses its own enumerable string entries. Entry values are read by
244
+ `Object.entries` before that source's assignments begin. Keys `__proto__`,
245
+ `constructor`, and `prototype` are skipped at every recursive merge level.
246
+ Symbols and non-enumerable source keys are not merged.
247
+
248
+ When the current target value and source value are both plain objects, they are
249
+ merged recursively. Otherwise the source value replaces the target value by
250
+ reference, including arrays, `Date`, `null`, and `undefined`. Replacement objects
251
+ are not traversed just because they have been assigned. The operation does not
252
+ guarantee cycle handling or preserve a snapshot of source values beyond each
253
+ source's entry collection.
254
+
255
+ ## clone
256
+
257
+ `clone<T>(value: T): T` returns primitive values and functions unchanged. Arrays
258
+ and ordinary object properties are recursively copied with a reference store,
259
+ preserving their shared references and circular links within the copied graph.
260
+
261
+ - Arrays copy indexed values up to their length into a new array. Holes become
262
+ explicit `undefined` entries; extra properties and symbol keys are not copied.
263
+ - Other objects use `Reflect.ownKeys`, including symbols and non-enumerable keys,
264
+ to read values and assign them to a new ordinary object. Custom prototypes and
265
+ property descriptors are not preserved; accessors are evaluated rather than copied.
266
+ - `Date` uses a new instance with the same timestamp; `RegExp` uses its source and
267
+ flags. Their custom properties are not copied, `lastIndex` is not retained, and
268
+ repeated references to these special values are not deduplicated by the store.
269
+ - There is no specialized copying of `Map`, `Set`, typed-array internal state, or
270
+ class invariants. The generic return type does not guarantee preservation of
271
+ those runtime capabilities.
272
+
273
+ Property access errors propagate. The implementation does not assign to the source.
274
+
275
+ ## equally
276
+
277
+ `equally(a: unknown, b: unknown, paths?: string[]): boolean` performs deep comparison.
278
+ Omitting `paths`, or passing `undefined`, compares the whole values. Providing a
279
+ path list compares only the selected values; all must match. An empty list returns
280
+ `true`, holes are skipped, and comparison stops at the first mismatch. Unselected
281
+ properties and later paths are not read.
282
+
283
+ Path reads use ordinary dot and explicit array-index lookup with inherited
284
+ properties enabled. Missing paths resolve to `undefined`, so a missing value and
285
+ an explicit `undefined` value compare equally. `''` selects the root. Selection
286
+ syntax and `[n]` mappings are not interpreted.
287
+
288
+ For whole values or each selected value:
289
+
290
+ - Strictly identical values compare equal, including `0` and `-0`.
291
+ - `NaN` compares equal to `NaN`. Other primitive values use strict equality.
292
+ - Two `Date` instances compare timestamps; two `RegExp` instances compare source
293
+ and flags. Distinct invalid dates have unequal timestamps under strict equality.
294
+ A `Date` or `RegExp` cannot equal a value of the other type or an ordinary object.
295
+ - An array and a non-array compare unequal. Arrays compare lengths and indexed
296
+ values in both dense and sparse arrays. A hole reads as `undefined`, including
297
+ when compared against an explicit `undefined` slot. Inherited indexed values
298
+ follow ordinary property lookup. Comparison is independent of argument order
299
+ for inputs without access side effects. Values are read once per compared index,
300
+ and reading stops at the first mismatch. Extra string and symbol properties
301
+ are not compared.
302
+ - Other objects compare all own string and symbol keys, including non-enumerable
303
+ keys, then recursively compare values. Key order, prototypes, and descriptors
304
+ are not compared. Accessors are evaluated.
305
+ - Previously visited object pairs allow circular graphs to terminate; alias
306
+ identity across otherwise equal object graphs is not required.
307
+
308
+ There is no specialized comparison of collection internal state such as `Map`
309
+ or `Set` entries. The same rules apply to whole-value and selected-path comparisons.
310
+
311
+ ## diff
312
+
313
+ `diff(first: unknown, last: unknown): DiffOperation[]` describes changes from the
314
+ first value to the last. Equal inputs produce `[]`.
315
+
316
+ When both values at a location are plain objects, comparison recurses through own
317
+ enumerable string keys. Inherited, symbol, and non-enumerable properties are not
318
+ part of that recursion. Other values, including arrays, use `equally` and are
319
+ replaced as a whole when unequal. Newly added values are also set as a whole.
320
+
321
+ Paths are arrays of literal string keys. `[]` identifies a root replacement.
322
+ They are not string-path arguments for `pick`, `set`, or `flush`, and must not be
323
+ joined with dots when keys can contain path punctuation.
324
+
325
+ Changed and added values are visited in the last object's key order. Removals
326
+ are emitted when leaving their containing branch, in the first object's key
327
+ order, before moving to the next branch. An own `undefined` value is distinct
328
+ from deletion. Empty parents remain; prototype and descriptor changes are not
329
+ reported during plain-object recursion.
330
+
331
+ Operations contain original references from `last`, without cloning. No patch
332
+ applier, options argument, or cycle detection is provided. Acyclic shared references
333
+ are supported. JSON serialization can drop an `undefined` operation value, so the
334
+ operation list is not a lossless JSON transport for every JavaScript value.
335
+
336
+ ## parse and stringify
337
+
338
+ ```ts
339
+ type ParseOptions = {
340
+ fallback?: unknown
341
+ onError?: (error: unknown) => void
342
+ reviver?: Parameters<typeof JSON.parse>[1]
343
+ }
344
+
345
+ type StringifyOptions = {
346
+ fallback?: string
347
+ onError?: (error: unknown) => void
348
+ replacer?: ParseOptions['reviver'] | Parameters<typeof JSON.stringify>[1]
349
+ space?: string | number
350
+ }
351
+ ```
352
+
353
+ `parse(raw: unknown, options?): unknown` returns ready arrays and plain objects
354
+ by reference without walking them or calling `reviver`. Plain objects include
355
+ null-prototype objects. Other values use
356
+ `JSON.parse(typeof raw === 'string' ? raw : String(raw), reviver)`.
357
+ `ArrayBuffer` and array-buffer views always use the text path; there is no binary
358
+ decoder. The result can be any JSON root type, or the arbitrary result of a
359
+ reviver. There is no generic `parse<T>()` or object-only mode.
360
+
361
+ `stringify(value: unknown, options?): string | undefined` always calls native
362
+ `JSON.stringify` with `replacer` and `space`. Strings are serialized as strings,
363
+ not treated as already serialized JSON. Native omission, array-null replacement,
364
+ formatting, `toJSON`, and callback `this` binding are preserved.
365
+
366
+ Both functions catch exceptions from their normal operation, including recognition,
367
+ conversion, getters, proxies, and JSON callbacks. On an exception, `onError`, when
368
+ provided, receives the original thrown value once, then the function returns
369
+ `fallback`, which defaults to `undefined`. A throwing `onError` propagates its
370
+ exception without being called again. Its return value is ignored.
371
+
372
+ Fallback applies only to exceptions. Valid `null`, `false`, `0`, and `''` are
373
+ preserved. A root reviver result of `undefined`, or native `stringify(undefined)`,
374
+ does not invoke error handling or use the fallback. A string fallback or throwing
375
+ handler does not narrow stringify's public `string | undefined` return type.
376
+ Callback side effects are not rolled back. There is no logging, JSON repair, or
377
+ domain-shape validation.
378
+
379
+ ## Verification
380
+
381
+ `npm test` type-checks source and tests through `tsconfig.test.json`, then runs
382
+ Vitest. This includes compile-time `expectTypeOf` assertions. `npm run build` generates ESM,
383
+ CommonJS, and declarations; `npm run pack:check` inspects the npm package contents.
package/dist/index.cjs CHANGED
@@ -51,20 +51,18 @@ var isWalkable = (value) => {
51
51
  // src/each.ts
52
52
  var each = (data, callback, { root = false, descend } = {}) => {
53
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] }))) {
54
+ const proceed = segments.length > 0 || root ? callback(key, value, path, [...segments]) !== false : true;
55
+ if (proceed && isWalkable(value) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
58
56
  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
- });
57
+ return value.every(
58
+ (item, index) => walk(item, createPath(path, index, true), [...segments, index], index)
59
+ );
66
60
  }
61
+ return Object.keys(value).every(
62
+ (childKey) => walk(value[childKey], createPath(path, childKey, false), [...segments, childKey], childKey)
63
+ );
67
64
  }
65
+ return proceed;
68
66
  };
69
67
  walk(data, "", [], void 0);
70
68
  };
@@ -213,13 +211,13 @@ var extract = (source, mapping) => {
213
211
  return;
214
212
  }
215
213
  if (Array.isArray(sourcePath)) {
216
- for (const item of sourcePath) {
214
+ each(sourcePath, (_key, item) => {
217
215
  const value = pick(source, item);
218
216
  if (value !== void 0) {
219
217
  set(result, destinationPath, value);
220
- break;
218
+ return false;
221
219
  }
222
- }
220
+ });
223
221
  return;
224
222
  }
225
223
  set(result, destinationPath, pick(source, sourcePath));
@@ -248,115 +246,108 @@ var isPlainObject = (value) => {
248
246
  var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
249
247
  var merge = (target, ...sources) => {
250
248
  const targetRecord = target;
251
- for (const source of sources) {
252
- if (!isPlainObject(source)) {
253
- continue;
254
- }
255
- for (const [key, sourceValue] of Object.entries(source)) {
256
- if (unsafeKeys.has(key)) {
257
- continue;
258
- }
259
- const targetValue = targetRecord[key];
260
- if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
261
- merge(targetValue, sourceValue);
262
- } else {
263
- targetRecord[key] = sourceValue;
264
- }
249
+ each(sources, (_index, source) => {
250
+ if (isPlainObject(source)) {
251
+ each(Object.entries(source), (_index2, [key, sourceValue]) => {
252
+ if (unsafeKeys.has(key) === false) {
253
+ const targetValue = targetRecord[key];
254
+ if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
255
+ merge(targetValue, sourceValue);
256
+ } else {
257
+ targetRecord[key] = sourceValue;
258
+ }
259
+ }
260
+ }, { descend: ({ segments }) => segments.length === 0 });
265
261
  }
266
- }
262
+ }, { descend: ({ segments }) => segments.length === 0 });
267
263
  return target;
268
264
  };
269
265
 
270
266
  // src/flush.ts
271
267
  var flush = (data = {}, path) => {
272
- if (path.length > 0) {
273
- const keys = path.split(".");
274
- const lastKey = keys.pop() ?? "";
275
- const match = lastKey.match(/^(\w+)\[(\d+)\]$/);
276
- let parent = getValueByPath(data, keys.join("."), { inherited: false });
277
- let key = lastKey;
278
- if (match) {
279
- const [, arrayKey, index] = match;
280
- const array = getValueByKey(parent, arrayKey, { inherited: false });
281
- parent = Array.isArray(array) ? array : void 0;
282
- key = index;
283
- }
284
- if (parent !== null && typeof parent === "object" && Object.hasOwn(parent, key)) {
285
- delete parent[key];
268
+ const paths = Array.isArray(path) ? path : [path];
269
+ each(paths, (_key, path2) => {
270
+ if (path2.length > 0) {
271
+ const keys = path2.split(".");
272
+ const lastKey = keys.pop() ?? "";
273
+ const match = lastKey.match(/^(\w+)\[(\d+)\]$/);
274
+ let parent = getValueByPath(data, keys.join("."), { inherited: false });
275
+ let key = lastKey;
276
+ if (match) {
277
+ const [, arrayKey, index] = match;
278
+ const array = getValueByKey(parent, arrayKey, { inherited: false });
279
+ parent = Array.isArray(array) ? array : void 0;
280
+ key = index;
281
+ }
282
+ if (parent !== null && typeof parent === "object" && Object.hasOwn(parent, key)) {
283
+ delete parent[key];
284
+ }
286
285
  }
287
- }
286
+ });
288
287
  return data;
289
288
  };
290
289
 
291
290
  // src/deepEqual.ts
292
291
  var deepEqual = (x, y, store) => {
293
- if (Number.isNaN(x) && Number.isNaN(y)) {
294
- return true;
295
- }
296
- if (x === y) {
292
+ if (x === y || Number.isNaN(x) && Number.isNaN(y)) {
297
293
  return true;
298
294
  }
299
- if (x == null || y == null) {
300
- return false;
301
- }
302
- if (typeof x !== "object" || typeof y !== "object") {
303
- return false;
304
- }
305
- let seen = store.get(x);
306
- if (seen !== void 0 && seen.has(y)) {
307
- return true;
308
- }
309
- if (seen === void 0) {
310
- seen = /* @__PURE__ */ new WeakSet();
311
- store.set(x, seen);
312
- }
313
- seen.add(y);
314
- if (x instanceof Date && y instanceof Date) {
315
- return x.getTime() === y.getTime();
316
- }
317
- if (x instanceof RegExp && y instanceof RegExp) {
318
- return x.toString() === y.toString();
319
- }
320
- const isArrayX = Array.isArray(x);
321
- const isArrayY = Array.isArray(y);
322
- if (isArrayX !== isArrayY) {
323
- return false;
324
- }
325
- if (isArrayX) {
326
- return equalArrays(x, y, store);
327
- }
328
- return equalObjects(x, y, store);
329
- };
330
- var equalArrays = (x, y, store) => x.length === y.length && x.every((value, index) => deepEqual(value, y[index], store));
331
- var equalObjects = (x, y, store) => {
332
- const keysX = Reflect.ownKeys(x);
333
- const keysY = Reflect.ownKeys(y);
334
- if (keysX.length !== keysY.length) {
335
- return false;
336
- }
337
- return keysX.every((key) => {
338
- if (!Object.hasOwn(y, key)) {
339
- return false;
295
+ if (x !== null && y !== null && typeof x === "object" && typeof y === "object") {
296
+ let seen = store.get(x);
297
+ if (seen !== void 0 && seen.has(y)) {
298
+ return true;
340
299
  }
341
- return deepEqual(x[key], y[key], store);
342
- });
300
+ if (seen === void 0) {
301
+ seen = /* @__PURE__ */ new WeakSet();
302
+ store.set(x, seen);
303
+ }
304
+ seen.add(y);
305
+ if (x instanceof Date || y instanceof Date) {
306
+ return x instanceof Date && y instanceof Date && x.getTime() === y.getTime();
307
+ }
308
+ if (x instanceof RegExp || y instanceof RegExp) {
309
+ return x instanceof RegExp && y instanceof RegExp && x.toString() === y.toString();
310
+ }
311
+ if (Array.isArray(x) && Array.isArray(y)) {
312
+ const { length } = x;
313
+ let equal = length === y.length;
314
+ let index = 0;
315
+ while (equal && index < length) {
316
+ equal = deepEqual(x[index], y[index], store);
317
+ index += 1;
318
+ }
319
+ return equal;
320
+ } else if (Array.isArray(x) === false && Array.isArray(y) === false) {
321
+ const keysX = Reflect.ownKeys(x);
322
+ const keysY = Reflect.ownKeys(y);
323
+ return keysX.length === keysY.length && keysX.every(
324
+ (key) => Object.hasOwn(y, key) && deepEqual(x[key], y[key], store)
325
+ );
326
+ }
327
+ }
328
+ return false;
343
329
  };
344
330
 
345
331
  // src/equally.ts
346
- var equally = (a, b) => deepEqual(a, b, /* @__PURE__ */ new WeakMap());
332
+ var equally = (a, b, paths) => {
333
+ if (paths !== void 0) {
334
+ return paths.every((path) => equally(getValueByPath(a, path), getValueByPath(b, path)));
335
+ }
336
+ return deepEqual(a, b, /* @__PURE__ */ new WeakMap());
337
+ };
347
338
 
348
339
  // src/diff.ts
349
340
  var diff = (first, last) => {
350
341
  const operations = [];
351
342
  const frames = [];
352
343
  const finish = (depth) => {
353
- for (const { first: first2, last: last2, path } of frames.splice(depth).reverse()) {
354
- for (const key of Object.keys(first2)) {
344
+ each(frames.splice(depth).reverse(), (_index, { first: first2, last: last2, path }) => {
345
+ each(Object.keys(first2), (_index2, key) => {
355
346
  if (Object.prototype.propertyIsEnumerable.call(last2, key) === false) {
356
347
  operations.push({ type: "delete", path: [...path, key] });
357
348
  }
358
- }
359
- }
349
+ });
350
+ }, { descend: ({ segments }) => segments.length === 0 });
360
351
  return frames.at(-1);
361
352
  };
362
353
  let descend = false;
@@ -407,9 +398,9 @@ var clone = (value) => {
407
398
  }
408
399
  const result = {};
409
400
  store.set(obj, result);
410
- for (const key of Reflect.ownKeys(obj)) {
401
+ each(Reflect.ownKeys(obj), (_index, key) => {
411
402
  result[key] = cloneValue(obj[key]);
412
- }
403
+ });
413
404
  return result;
414
405
  };
415
406
  return cloneValue(value);
package/dist/index.d.cts CHANGED
@@ -23,7 +23,7 @@ type StringifyOptions = {
23
23
  space?: string | number;
24
24
  };
25
25
 
26
- type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (key: TKey, value: any, path: string, segments: WalkKey[]) => void;
26
+ type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (key: TKey, value: any, path: string, segments: WalkKey[]) => unknown;
27
27
  type EachEntry = {
28
28
  key: WalkKey | undefined;
29
29
  value: any;
@@ -56,9 +56,9 @@ declare const merge: <T extends ObjwalkObject$1>(target: T, ...sources: unknown[
56
56
  type ObjwalkObject = Record<string, any>;
57
57
  declare const set: (data: ObjwalkObject | undefined, path: string, value: any) => ObjwalkObject;
58
58
 
59
- declare const flush: (data: ObjwalkObject$1 | undefined, path: string) => ObjwalkObject$1;
59
+ declare const flush: (data: ObjwalkObject$1 | undefined, path: string | string[]) => ObjwalkObject$1;
60
60
 
61
- declare const equally: (a: unknown, b: unknown) => boolean;
61
+ declare const equally: (a: unknown, b: unknown, paths?: string[]) => boolean;
62
62
 
63
63
  declare const diff: (first: unknown, last: unknown) => DiffOperation[];
64
64
 
package/dist/index.d.ts CHANGED
@@ -23,7 +23,7 @@ type StringifyOptions = {
23
23
  space?: string | number;
24
24
  };
25
25
 
26
- type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (key: TKey, value: any, path: string, segments: WalkKey[]) => void;
26
+ type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (key: TKey, value: any, path: string, segments: WalkKey[]) => unknown;
27
27
  type EachEntry = {
28
28
  key: WalkKey | undefined;
29
29
  value: any;
@@ -56,9 +56,9 @@ declare const merge: <T extends ObjwalkObject$1>(target: T, ...sources: unknown[
56
56
  type ObjwalkObject = Record<string, any>;
57
57
  declare const set: (data: ObjwalkObject | undefined, path: string, value: any) => ObjwalkObject;
58
58
 
59
- declare const flush: (data: ObjwalkObject$1 | undefined, path: string) => ObjwalkObject$1;
59
+ declare const flush: (data: ObjwalkObject$1 | undefined, path: string | string[]) => ObjwalkObject$1;
60
60
 
61
- declare const equally: (a: unknown, b: unknown) => boolean;
61
+ declare const equally: (a: unknown, b: unknown, paths?: string[]) => boolean;
62
62
 
63
63
  declare const diff: (first: unknown, last: unknown) => DiffOperation[];
64
64