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/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
|
-
|
|
55
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
354
|
-
|
|
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
|
-
|
|
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[]) =>
|
|
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[]) =>
|
|
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
|
|