objwalk 1.6.0 → 1.8.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 CHANGED
@@ -1,10 +1,15 @@
1
1
  # objwalk
2
2
 
3
- Small utilities for reading, writing, walking, and reshaping nested JavaScript objects.
3
+ Work with nested JavaScript data without writing the same traversal code again.
4
+ Read and update values by path, reshape API responses, walk objects and arrays,
5
+ and see exactly what changed between two states.
4
6
 
5
- `objwalk` is built around a compact path syntax, so the same mental model works for picking, setting, and deleting values, extracting a new object shape, and walking through nested data.
7
+ - **One path syntax** for nested properties and array indexes.
8
+ - **Tools that work together:** pick, set, reshape, walk, copy, merge, and compare.
9
+ - **No runtime dependencies**, with TypeScript declarations and ESM/CommonJS builds.
6
10
 
7
- > Coding agent? Load the [objwalk skill](skills/objwalk/SKILL.md).
11
+ > 🤖 Coding agent? Load the [objwalk skill](https://github.com/khalilov/objwalk/blob/main/skills/objwalk/SKILL.md).
12
+ > Need every edge case? Open the [full specification](https://github.com/khalilov/objwalk/blob/main/SPEC.md).
8
13
 
9
14
  ## Install
10
15
 
@@ -14,440 +19,203 @@ npm install objwalk
14
19
 
15
20
  ## Usage
16
21
 
17
- ```ts
18
- import { diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
19
- ```
20
-
21
- ## pick
22
-
23
- Read a value from an object by path.
24
-
25
- ```ts
26
- const data = {
27
- user: {
28
- name: 'Natalia Romanoff',
29
- email: 'black.widow@example.com',
30
- },
31
- }
32
-
33
- pick(data, 'user.name')
34
- // 'Natalia Romanoff'
35
- ```
36
-
37
- Pick several keys from the same nested object.
38
-
39
- ```ts
40
- pick(data, 'user.{name,email}')
41
- // { name: 'Natalia Romanoff', email: 'black.widow@example.com' }
42
- ```
43
-
44
- List keys from a nested object.
22
+ Import the tools you need and use paths to reach nested values:
45
23
 
46
24
  ```ts
47
- pick(data, 'user.{*}')
48
- // ['name', 'email']
49
- ```
25
+ import { pick, set, flush } from 'objwalk'
50
26
 
51
- Read from arrays with an explicit index.
52
-
53
- ```ts
54
27
  const data = {
55
- items: [{ title: 'First' }, { title: 'Second' }],
28
+ user: { name: 'Natalia Romanoff' },
29
+ items: [{ title: 'Keyboard', internalId: 'draft-1' }],
56
30
  }
57
31
 
58
- pick(data, 'items[1].title')
59
- // 'Second'
60
- ```
61
-
62
- If the path cannot be resolved, `pick` returns `undefined`.
63
-
64
- By default, `pick` keeps its permissive property lookup behavior. Pass options to restrict
65
- lookups to own properties or ignore specific keys.
66
-
67
- ```ts
68
- pick(data, path, {
69
- inherited: false,
70
- ignore: ['__proto__', 'prototype', 'constructor'],
71
- })
72
- ```
73
-
74
- The options apply to every path segment, array index, and selected key. With `{*}`, ignored
75
- keys are omitted from the returned list.
76
-
77
- ## set
78
-
79
- Set a value inside an object by path.
80
-
81
- ```ts
82
- const data = {}
83
-
84
- set(data, 'user.name', 'Natalia')
85
- // { user: { name: 'Natalia' } }
32
+ pick(data, 'items[0].title') // 'Keyboard'
33
+ set(data, 'user.email', 'black.widow@example.com')
34
+ flush(data, ['user.name', 'items[0].internalId'])
35
+ // data is now:
36
+ // {
37
+ // user: { email: 'black.widow@example.com' },
38
+ // items: [{ title: 'Keyboard' }],
39
+ // }
86
40
  ```
87
41
 
88
- Set values inside arrays with an explicit index.
42
+ `set`, `flush`, and `merge` update their target and return it. Use `clone` first
43
+ when you need an independent copy of supported data.
89
44
 
90
- ```ts
91
- const data = {}
45
+ ## Paths at a glance
92
46
 
93
- set(data, 'items[0].title', 'First')
94
- // { items: [{ title: 'Romanoff' }] }
95
- ```
47
+ | Path | What it does |
48
+ | --- | --- |
49
+ | `user.name` | Access a nested property |
50
+ | `items[0].title` | Access an array item's property |
51
+ | `user.{name,email}` | Select several keys with `pick` |
52
+ | `user.{*}` | List keys with `pick` |
53
+ | `items[n].title` | Map array items with `extract` |
96
54
 
97
- `set` mutates the object passed to it and returns the same object.
55
+ A missing path returns `undefined` with `pick`. Selection and array mapping have
56
+ specific uses; see the [path rules](SPEC.md#paths) for the full syntax.
98
57
 
99
- ## flush
58
+ ## Reshape an API response
100
59
 
101
- Delete a property by path using native `delete` semantics.
60
+ Describe the shape you want instead of assembling it field by field.
61
+ Use a list of source paths for fallbacks and `[n]` to map array items:
102
62
 
103
63
  ```ts
104
- const data = { user: { name: 'Ada', role: 'admin' } }
105
-
106
- flush(data, 'user.name')
107
- // { user: { role: 'admin' } }
108
-
109
- flush({}, 'path.to.key') // {}
110
- flush({ user: null }, 'user.name') // { user: null }
111
- ```
64
+ import { extract } from 'objwalk'
112
65
 
113
- `flush(data, path)` mutates and returns the same object. Missing paths, `null`,
114
- `undefined`, and primitive parents are left unchanged. It does not create intermediate
115
- containers or remove empty parents. An empty path leaves the object unchanged.
116
- Only own properties are traversed and deleted; inherited properties are left unchanged.
117
-
118
- Array indexes use the same explicit syntax as `pick` and `set`:
119
-
120
- ```ts
121
- const data = { items: [{ title: 'First' }, { title: 'Second' }] }
66
+ const response = {
67
+ profile: { fullName: 'Natalia Romanoff' },
68
+ products: [{ name: 'Keyboard' }, { name: 'Mouse' }],
69
+ }
122
70
 
123
- flush(data, 'items[0].title') // { items: [{}, { title: 'Second' }] }
124
- flush(data, 'items[1]') // { items: [{}, <empty item>] }
71
+ const result = extract(response, {
72
+ 'user.name': ['profile.displayName', 'profile.fullName'],
73
+ 'items[n].title': 'products[n].name',
74
+ })
75
+ // {
76
+ // user: { name: 'Natalia Romanoff' },
77
+ // items: [{ title: 'Keyboard' }, { title: 'Mouse' }],
78
+ // }
125
79
  ```
126
80
 
127
- Deleting an array item leaves a hole, preserves its length, and does not shift later items.
128
- Selection syntax (`{name,email}`, `{*}`) and `[n]` mappings are not supported by `flush`.
129
- Errors from getters, proxies, or deleting non-configurable properties propagate to the caller.
81
+ `extract` builds a new result object. Selected object values keep their references;
82
+ use `clone` when you also need to copy those values.
130
83
 
131
- ## equally
84
+ ## Compare values and track changes
132
85
 
133
- Deeply compare two values for structural equality.
86
+ Compare a whole state, check only the fields that matter, or get a list of changes:
134
87
 
135
88
  ```ts
136
- equally({ a: { b: [1, 2] } }, { a: { b: [1, 2] } })
137
- // true
138
-
139
- equally({ a: 1 }, { a: 1, b: 2 })
140
- // false
141
- ```
142
-
143
- `equally` treats `NaN` as equal to `NaN`, compares `Date` by timestamp and `RegExp` by
144
- source and flags, and follows circular references without looping.
89
+ import { equally, diff } from 'objwalk'
145
90
 
146
- ## diff
91
+ const before = { user: { name: 'Natalia' }, hp: 10, target: 'enemy' }
92
+ const after = { user: { name: 'Natalia' }, hp: 8 }
147
93
 
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'
94
+ equally(before, after) // false
95
+ equally(before, after, ['user.name']) // true
152
96
 
153
- const operations: DiffOperation[] = diff(
154
- { hp: 10, target: 'enemy', position: { x: 1, y: 2 } },
155
- { hp: null, position: { x: 3, y: 2 } }
156
- )
97
+ diff(before, after)
157
98
  // [
158
- // { type: 'set', path: ['hp'], value: null },
159
- // { type: 'set', path: ['position', 'x'], value: 3 },
99
+ // { type: 'set', path: ['hp'], value: 8 },
160
100
  // { type: 'delete', path: ['target'] },
161
101
  // ]
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
-
195
- ## merge
196
-
197
- Deeply merge plain objects into a target object.
198
-
199
- ```ts
200
- const target = { user: { name: 'Ada', flags: { admin: false } } }
201
-
202
- merge(target, { user: { email: 'ada@example.com', flags: { admin: true } } })
203
- // {
204
- // user: { name: 'Ada', email: 'ada@example.com', flags: { admin: true } },
205
- // }
206
102
  ```
207
103
 
208
- `merge` mutates and returns `target`. Sources are applied from left to right. Arrays and non-plain objects, such as `Date`, replace the existing value rather than being merged.
104
+ `equally` supports `NaN`, `Date`, `RegExp`, and circular references. `diff` returns
105
+ changes without applying them; its paths are arrays of literal keys, and changed
106
+ arrays are replaced as a whole. Its inputs must be acyclic.
209
107
 
210
- ## extract
108
+ ## Walk objects and arrays
211
109
 
212
- Create a new object by mapping destination paths to source paths.
110
+ `each` visits nested values and tells you where they live:
213
111
 
214
112
  ```ts
215
- const source = {
216
- profile: {
217
- fullName: 'Natalia Romanoff',
218
- email: 'black.widow@example.com',
219
- },
220
- }
113
+ import { each } from 'objwalk'
221
114
 
222
- extract(source, {
223
- 'user.name': 'profile.fullName',
224
- 'user.email': 'profile.email',
115
+ each({ user: { name: 'Natalia' }, tags: ['ts'] }, (_key, value, path) => {
116
+ if (typeof value === 'string') {
117
+ console.log(path, value)
118
+ }
225
119
  })
226
- // {
227
- // user: {
228
- // name: 'Natalia Romanoff',
229
- // email: 'black.widow@example.com',
230
- // },
231
- // }
120
+ // user.name Natalia
121
+ // tags[0] ts
232
122
  ```
233
123
 
234
- Use an array of source paths to provide fallbacks. The first resolved value is used.
124
+ Return `false` to stop the entire walk. For more control, `each` also accepts
125
+ `root` and `descend` options and provides literal path segments as a fourth
126
+ callback argument. [Traversal details](SPEC.md#each).
235
127
 
236
- ```ts
237
- extract(source, {
238
- 'user.name': ['profile.displayName', 'profile.fullName'],
239
- })
240
- ```
241
-
242
- Use `[n]` to map over arrays.
128
+ Use `map` to transform values while keeping the object and array structure:
243
129
 
244
130
  ```ts
245
- const source = {
246
- products: [
247
- { name: 'Keyboard' },
248
- { name: 'Mouse' },
249
- ],
250
- }
131
+ import { map } from 'objwalk'
251
132
 
252
- extract(source, {
253
- 'items[n].title': 'products[n].name',
254
- })
255
- // {
256
- // items: [
257
- // { title: 'Keyboard' },
258
- // { title: 'Mouse' },
259
- // ],
260
- // }
133
+ const cleaned = map({ name: ' Natalia ', tags: [' ts '] }, (_key, value) =>
134
+ typeof value === 'string' ? value.trim() : value
135
+ )
136
+ // { name: 'Natalia', tags: ['ts'] }
261
137
  ```
262
138
 
263
- ## each
139
+ Each callback return becomes the value at that key or index in the new result.
140
+ The input is not modified by `map`. Both walkers expect acyclic data unless
141
+ traversal options prune the cycles.
264
142
 
265
- Walk through an object and call a function for each value.
143
+ When your callback handles each entry as a whole, use a shallow walk:
266
144
 
267
145
  ```ts
268
- each(data, (key, value, path, segments) => {
269
- console.log(key, value, path, segments)
270
- })
271
- ```
272
-
273
- The callback receives:
146
+ import { map } from 'objwalk'
274
147
 
275
- - `key`: the current object key or numeric array index; `undefined` for the root when enabled
276
- - `value`: 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
- })
148
+ const counts = map(
149
+ { editors: ['Natalia'], readers: ['Ada', 'Grace'] },
150
+ (_key, value) => value.length,
151
+ { shallow: true }
152
+ )
153
+ // { editors: 1, readers: 2 }
295
154
  ```
296
155
 
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.
156
+ Shallow dictionary keys are typed as `string`; shallow array indexes are `number`.
157
+ For selective traversal, pass `descend` in the third argument.
158
+ [Mapping options](SPEC.md#map).
307
159
 
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.
160
+ ## Merge defaults or make a copy
314
161
 
315
- ## map
316
-
317
- Walk through an object and build a new result.
162
+ Combine nested settings while keeping the original defaults:
318
163
 
319
164
  ```ts
320
- const result = map(data, (key, value, store, path) => {
321
- if (typeof value === 'string') {
322
- set(store, path, value.trim())
323
- }
165
+ import { clone, merge } from 'objwalk'
166
+
167
+ const defaults = { theme: { mode: 'light', contrast: 1 }, tags: ['default'] }
168
+ const settings = merge(clone(defaults), {
169
+ theme: { mode: 'dark' },
170
+ tags: ['custom'],
324
171
  })
172
+ // { theme: { mode: 'dark', contrast: 1 }, tags: ['custom'] }
325
173
  ```
326
174
 
327
- The callback receives:
328
-
329
- - `key`: the current object key or array index
330
- - `value`: the current value
331
- - `store`: the result object being built
332
- - `path`: the full path to the current value
333
-
334
- `map` returns `store`.
335
-
336
- ## parse and stringify
175
+ `merge` combines plain objects and replaces arrays. `clone` copies nested objects
176
+ and arrays, including circular references, with support for `Date` and `RegExp`.
177
+ See the [copying contract](SPEC.md#clone) for supported types.
337
178
 
338
- Parse JSON or accept a ready value, and serialize values with optional error handling.
339
- Exceptions return `undefined`, unless a fallback is provided or `onError` throws.
179
+ ## Handle JSON at the boundary
340
180
 
341
- ```ts
342
- import { parse, stringify, type ParseOptions, type StringifyOptions } from 'objwalk'
343
-
344
- // Signatures:
345
- // parse(raw: unknown, options?: ParseOptions): unknown
346
- // stringify(value: unknown, options?: StringifyOptions): string | undefined
347
-
348
- const value = parse('{"a":1}') // { a: 1 }, typed as unknown
349
- const valueOrNull = parse('{', { fallback: null }) // null
350
- const text = stringify(value, { fallback: 'null', space: 2 })
351
- const object = { a: 1 }
352
- const array = [1, 2]
353
-
354
- parse(object) === object // true
355
- parse(array) === array // true
356
-
357
- parse('[]') // []
358
- stringify({}) // '{}'
359
- stringify([]) // '[]'
360
- stringify(null) // 'null'
361
- stringify('{}') // '"{}"'
362
- ```
363
-
364
- Root values can be objects, arrays, strings, numbers, booleans, or `null`;
365
- neither function requires an expected shape or restricts values to objects.
366
- `parse` returns `unknown`; successful parsing does not validate the expected data structure.
367
- Validate the result before using it as a specific type. There is no `parse<T>()` overload.
368
-
369
- `parse` accepts `unknown`. Plain objects (with `Object.prototype` or a `null` prototype) and
370
- arrays are returned by reference, without cloning, serialization, mutation, or calling `reviver`.
371
- Other inputs use `JSON.parse(typeof raw === 'string' ? raw : String(raw), reviver)`.
372
- ArrayBuffer, DataView, and typed-array views use this text path; there is no binary decoder.
373
- For example, a Node.js Buffer containing JSON text can be parsed through its native string conversion.
374
- Errors during input recognition or string conversion follow the same `onError`/`fallback` contract.
375
-
376
- | Option | `ParseOptions` | `StringifyOptions` |
377
- | --- | --- | --- |
378
- | `fallback` | `unknown`, default `undefined` | `string`, default `undefined` |
379
- | `onError` | `(error: unknown) => void` | `(error: unknown) => void` |
380
- | `reviver` | Native `JSON.parse` callback, only for the text path | — |
381
- | `replacer` | — | Native callback or array of strings/numbers (also accepts `null`) |
382
- | `space` | — | `string` or `number` for native JSON formatting |
383
-
384
- Callbacks retain native `this` binding. For example, a reviver can transform or delete properties,
385
- and a replacer can select or transform values:
181
+ `parse` accepts JSON text or a ready object or array. `stringify` serializes a value.
182
+ Use a fallback when an operation fails:
386
183
 
387
184
  ```ts
388
- parse('{"count":2,"secret":true}', {
389
- reviver: (key, value) => key === 'secret' ? undefined : value,
390
- }) // { count: 2 }
185
+ import { parse, stringify } from 'objwalk'
391
186
 
392
- stringify({ name: 'Ada', secret: true }, { replacer: ['name'], space: 2 })
393
- // '{\n "name": "Ada"\n}'
394
- ```
395
-
396
- On an exception, `onError` receives the original error once, then the function returns `fallback`.
397
- The callback's return value is ignored. Exceptions from `reviver`, `replacer`, `toJSON`, or getters
398
- follow the same rule. If `onError` throws, its exception propagates to the caller:
399
-
400
- ```ts
401
- const requiredText = stringify(value, {
402
- onError: (error) => {
403
- throw error
404
- },
405
- }) // still string | undefined
406
-
407
- stringify(1n, { fallback: 'null' }) // 'null' (BigInt throws natively)
408
- stringify(undefined, { fallback: 'null' }) // undefined (no exception)
409
- parse('false', { fallback: true }) // false
410
- parse('null', { fallback: {} }) // null
187
+ parse('{"enabled":true}') // { enabled: true }
188
+ parse({ enabled: true }) // { enabled: true }, same object
189
+ parse('invalid JSON', { fallback: null }) // null
190
+ stringify({ enabled: true }) // '{"enabled":true}'
411
191
  ```
412
192
 
413
- Fallbacks apply only to exceptions: valid `0`, `false`, `''`, and `null` are preserved.
414
- Likewise, a reviver returning `undefined` for the root does not trigger a fallback.
415
- Top-level `undefined`, functions, and symbols serialize to native `undefined` without calling
416
- `onError`. Native handling of these values inside objects/arrays, `NaN`, `Infinity`, and `toJSON`
417
- is preserved. Circular references throw natively and use the same error handling as BigInt.
418
- The wrappers do not mutate inputs, undo callback side effects, log errors, or repair invalid JSON.
193
+ Without a fallback, errors return `undefined`. Native `reviver`, `replacer`,
194
+ formatting, and an `onError` callback are available through options.
195
+ `parse` returns `unknown`; validate the result before using it as a domain type.
196
+ [JSON options and error handling](SPEC.md#parse-and-stringify).
419
197
 
420
- ## Path syntax
198
+ ## API
421
199
 
422
- | Syntax | Description |
200
+ | Task | Function |
423
201
  | --- | --- |
424
- | `user.name` | Read, write, or delete a nested property. |
425
- | `items[0].title` | Read, write, or delete an array item's property by index. |
426
- | `user.{name,email}` | Pick several keys from one object. |
427
- | `user.{*}` | List keys from one object. |
428
- | `items[n].title` | Map every item in an array during `extract`. |
202
+ | Read a value or select keys | [`pick(data, path?, options?)`](SPEC.md#pick) |
203
+ | Write a value by path | [`set(data, path, value)`](SPEC.md#set) |
204
+ | Delete one or several paths | [`flush(data, path)`](SPEC.md#flush) |
205
+ | Reshape data with a mapping | [`extract(source, mapping)`](SPEC.md#extract) |
206
+ | Visit nested values | [`each(data, callback, options?)`](SPEC.md#each) |
207
+ | Transform values while keeping keys | [`map(data, callback, options?)`](SPEC.md#map) |
208
+ | Merge plain objects | [`merge(target, ...sources)`](SPEC.md#merge) |
209
+ | Copy supported values | [`clone(value)`](SPEC.md#clone) |
210
+ | Compare values or selected paths | [`equally(a, b, paths?)`](SPEC.md#equally) |
211
+ | Describe changes between values | [`diff(first, last)`](SPEC.md#diff) |
212
+ | Parse JSON or accept a ready value | [`parse(raw, options?)`](SPEC.md#parse-and-stringify) |
213
+ | Serialize a value as JSON | [`stringify(value, options?)`](SPEC.md#parse-and-stringify) |
214
+
215
+ For exact types, mutation rules, and edge cases, use the [full specification](SPEC.md).
429
216
 
430
217
  ## Agent skill
431
218
 
432
- The package includes an [objwalk skill](skills/objwalk/SKILL.md) in `skills/objwalk/`.
433
- After installation, point your agent at `node_modules/objwalk/skills/objwalk/SKILL.md`.
434
- If your agent uses a skill directory, link the entire `skills/objwalk` folder, not just `SKILL.md`.
435
-
436
- The skill explains the existing object utilities and JSON helpers, and directs agents to use
437
- them without redundant wrappers that only repeat object checks.
438
-
439
- ## API
440
-
441
- ```ts
442
- pick(data, path)
443
- set(data, path, value)
444
- flush(data, path)
445
- extract(data, mapping)
446
- each(data, callback, options?)
447
- map(data, callback)
448
- merge(target, ...sources)
449
- equally(a, b)
450
- diff(first, last) // DiffOperation[]
451
- parse(raw, options?) // unknown
452
- stringify(value, options?) // string | undefined
453
- ```
219
+ The package includes an [objwalk skill](skills/objwalk/SKILL.md). Point your agent at
220
+ `node_modules/objwalk/skills/objwalk/SKILL.md`, or link the entire `skills/objwalk`
221
+ folder into its skill directory.