@peter.naydenov/walk-async 7.0.0 → 7.0.2

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.
@@ -0,0 +1,108 @@
1
+ # Walk Async skill
2
+
3
+ The distributable skill is [walk-async/SKILL.md](walk-async/SKILL.md). Its name is
4
+ `walk-async`, matching the library. Install the entire `walk-async` folder, including
5
+ `references`; installing only `SKILL.md` loses the detailed guidance.
6
+
7
+ ## Agent integration
8
+
9
+ Keep the canonical skill in `skills/walk-async/`, independent of any agent vendor.
10
+ Agents can read it at that path or install the same folder using their skill
11
+ installer. Discovery locations differ between agents; top-level `skills/`
12
+ does not imply automatic discovery by every tool. For example, Claude Code
13
+ documents separate project, personal, and plugin locations in its
14
+ [skill documentation](https://code.claude.com/docs/en/skills).
15
+
16
+ Do not duplicate the skill under `.claude` or change personal agent
17
+ configuration as part of maintaining this directory.
18
+
19
+ ## Authoring sources
20
+
21
+ Guidance checked on October 4, 2026:
22
+
23
+ - [Anthropic skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices): concise instructions, precise descriptions, progressive disclosure, direct references, and evaluation-driven iteration.
24
+ - [Claude Code skills](https://code.claude.com/docs/en/skills): discovery locations, invocation, frontmatter, and supporting files.
25
+
26
+ Follow Anthropic's recommendation to keep the `SKILL.md` body under 500
27
+ lines. This is an upper limit, not a target. Keep essential rules and routing
28
+ in the entrypoint, with conditional details in references. All consumer
29
+ references are linked directly from it; references longer than 100 lines
30
+ have a contents list. Read supporting files only as needed.
31
+
32
+ Keep the skill focused on using Walk Async. Generic programming lessons, copied
33
+ manuals, promotional claims, and maintainer procedures do not belong in the
34
+ consumer entrypoint. Keep examples consistent with the library's code style.
35
+
36
+ ## Quality checks
37
+
38
+ After API changes, verify the skill against `src/main.js`, `src/runCallback.js`,
39
+ the instruction helpers, callback creation, and traversal loop. Run the
40
+ runnable examples with return, promise, and explicit-settlement callbacks
41
+ against the implementation, checking outputs, source preservation, callback
42
+ visits, and reference identity rather than only matching text.
43
+
44
+ Evaluate selection and answers separately. Use these realistic prompts:
45
+
46
+ | Prompt | Expected selection and behaviour |
47
+ | --- | --- |
48
+ | “Use Walk Async to copy an API response and remove passwords at every depth.” | Select `walk-async`; await the walk; leaf callback returns `IGNORE()`; preserve source |
49
+ | “Skip every metadata branch while scanning; I don't need a copy.” | Select `walk-async`; await the walk; `copy:false`; object callback returns `IGNORE()` for metadata and `value` otherwise |
50
+ | “Keep a branch's immediate values unchanged but transform nested values.” | Read control-flow reference; await the walk; local `PASS()`; nested callbacks continue |
51
+ | “Replace a branch, skipping only its immediate key callbacks.” | Return `PASS(replacement)`; replacement remains data, not an instruction in the output |
52
+ | “Find the first matching leaf and stop without cloning.” | Collect externally with `copy:false`; return `FINISH()`; walk resolves `undefined` |
53
+ | “Finish on one object, then choose its final leaves.” | Read final-branch rules; `FINISH(value)` plus key callback's `isFinished`; no further object callbacks |
54
+ | “A key contains a slash; give me its full path as an array.” | Use `[ ...parentPath, key ]`; never split breadcrumbs |
55
+ | “Copy a self-referencing object and two shared branches.” | Ancestor cycle closes inside copy; separate shared branches become distinct copies |
56
+ | “Deep-copy nested data while awaiting a lookup for every userId.” | Select `walk-async`; await the walk and lookup; returned containers are visited |
57
+ | “Await an HTTP request for each leaf.” | Select `walk-async`; return or await callback promises; process siblings sequentially |
58
+ | “Use resolve/reject callbacks to drop failed values.” | Settle every path; `reject()` prunes; a throw or rejected promise fails the walk |
59
+ | “Resolve FINISH early, then await more work in that callback.” | Await callback completion and the supplied payload before processing the instruction |
60
+ | “A callback never settles; limit the walk to one second.” | Use top-level `timeout:1000`; explain diagnostics and that timeout does not cancel I/O |
61
+ | “Transform a JSON object using only immediate arithmetic.” | Route to `@peter.naydenov/walk` when a synchronous API meets the request |
62
+ | “Independently clone the contents of a Map.” | Do not select this skill for built-in cloning; explain Walk's reference contract if asked |
63
+
64
+ For selection evaluation, expose only the skill name and description first.
65
+ For answer evaluation, expose the skill and let the agent choose references.
66
+ Record which files it reads and check the generated code's observable result.
67
+ Run these prompts with the target agents/models when available. Executable
68
+ examples verify API accuracy but do not prove automatic selection quality.
69
+
70
+ Do not change `package.json`, commit, or publish as part of maintaining the
71
+ skill unless separately requested.
72
+
73
+ ## Package integration
74
+
75
+ The canonical folder is available from this repository. The current npm
76
+ `files` whitelist still names `.agents/skills` and excludes the top-level
77
+ `skills` folder.
78
+ The manifest is intentionally unchanged for this documentation update.
79
+ To ship the portable skill, manually replace the obsolete `.agents/skills`
80
+ entry in the `package.json` `files` array with this exact entry:
81
+
82
+ ```json
83
+ "skills"
84
+ ```
85
+
86
+ This includes `skills/README.md`, `walk-async/SKILL.md`, and all four reference
87
+ files. Listing only the skill subfolder would omit the integration README
88
+ linked from the package documentation.
89
+ Verify the complete folder with `npm pack --dry-run --json` after changing
90
+ the manifest. Local `/.claude` and `/.agents` settings are listed in
91
+ `.npmignore`. An explicit `files` entry can override that exclusion, so the
92
+ obsolete `.agents/skills` entry also needs replacing. Neither location
93
+ contains a maintained duplicate of this skill.
94
+
95
+ ## Validation scope
96
+
97
+ Run `npx vitest run test/14-skill.test.js` to verify entrypoint structure,
98
+ relative links, portable references, and examples against the async source.
99
+ The cases check outputs, source preservation, reference identity, sequential
100
+ callback order, and final-branch flags across return, promise, and resolve
101
+ modes. They also execute the explicit-settlement examples as written.
102
+
103
+ The built-in Python skill validator requires PyYAML, which is unavailable in
104
+ this environment. Frontmatter can instead be parsed with the available Ruby
105
+ YAML parser and checked against the same name, description, and scaffold
106
+ rules. Do not install dependencies solely for this documentation update.
107
+ No automatic skill-selection or target-agent answer evaluation has been run;
108
+ the prompts above are evaluation scenarios, not evidence of agent selection.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: walk-async
3
+ description: >-
4
+ Uses @peter.naydenov/walk-async to copy, transform, filter, or inspect nested
5
+ JavaScript data with awaited callbacks. Use for deep copies with transformations,
6
+ masking or removing nested fields, pruning branches, deep forEach, and
7
+ early-stop searches. Applies to the async Walk API, not synchronous
8
+ traversal or independent cloning of built-in objects such as Map and Date.
9
+ ---
10
+
11
+ # Walk Async
12
+
13
+ Write traversal rules with `@peter.naydenov/walk-async`. Walk handles the nesting
14
+ and builds an editable copy by default. This skill covers the upcoming version
15
+ 7 API in this repository.
16
+ Await the walk to obtain the result or finish its side effects.
17
+
18
+ ## Choose the call
19
+
20
+ | Intent | Use |
21
+ | --- | --- |
22
+ | Copy nested objects and arrays | `await walk ({ data })` |
23
+ | Transform, mask, or remove leaves | `keyCallback` |
24
+ | Await a lookup while copying | An async callback that returns its awaited result |
25
+ | Replace a container or remove a branch | `objectCallback` |
26
+ | Inspect without building a copy | `settings:{ copy:false }` |
27
+ | Skip immediate key callbacks but visit nested containers | Return `PASS()` or `PASS(value)` from `objectCallback` |
28
+ | Stop at a match | Return `FINISH()` or `FINISH(value)` |
29
+
30
+ For example, mask passwords at any depth:
31
+
32
+ ```js
33
+ import walk from '@peter.naydenov/walk-async'
34
+
35
+ let data = { user:{ password:'secret', name:'Peter' } };
36
+ let result = await walk ({
37
+ data
38
+ , keyCallback : ({ key, value }) => key === 'password' ? '[hidden]' : value
39
+ })
40
+ // { user:{ password:'[hidden]', name:'Peter' } }
41
+ ```
42
+
43
+ ## Essential rules
44
+
45
+ - Return the intended value when copying. `null` and `undefined` are values,
46
+ not instructions to remove a property.
47
+ - Call and return helper instructions: `return IGNORE()`, `return PASS()`,
48
+ or `return FINISH()`, or pass the instruction to `resolve`. Merely calling
49
+ a helper has no traversal effect. Promised replacement payloads are awaited.
50
+ - An object callback must return or resolve a container to continue into it,
51
+ including when `copy:false`. A leaf callback used only for side effects
52
+ may omit its return in that mode. Walk then resolves `undefined`.
53
+ - Return new containers to avoid mutating source data. Built-in objects and
54
+ functions are leaves preserved by reference; this is not a full graph clone.
55
+ - Each callback completes before the next starts; sibling callbacks do not
56
+ run concurrently. Throws and rejected promises reject the walk.
57
+ - Existing `resolve`/`reject` callbacks remain supported. Reading either field
58
+ selects explicit settlement; settle every path and leave unused fields alone.
59
+ `reject()` prunes the current value; throw to fail the walk.
60
+ - Use `@peter.naydenov/walk` for work that completes synchronously.
61
+ Do not invent methods such as `walk.pick()`.
62
+
63
+ ## Read the relevant reference
64
+
65
+ Read only the files needed for the request. Resolve these links relative to
66
+ this skill folder; they remain usable when the folder is installed elsewhere.
67
+
68
+ - [Callbacks and copying](references/callbacks.md): callback arguments,
69
+ return values, explicit settlement, errors, timeout, and queued visit order.
70
+ - [Traversal controls](references/control-flow.md): `IGNORE`, local `PASS`,
71
+ both `FINISH` forms, `isFinished`, and partially completed results.
72
+ - [Paths, settings, and cycles](references/settings.md): `parentPath`,
73
+ breadcrumbs, performance switches, circular links, and shared references.
74
+ - [Runnable examples](references/examples.md): input and output examples
75
+ for filtering, pruning, local skipping, searching, and final branches.
76
+
77
+ ## Deliver the solution
78
+
79
+ Provide focused code in the developer's existing style, using ESM by default.
80
+ Explain the selected callback or setting and any limitation relevant to the
81
+ task. Prefer one walk with combined rules when it satisfies the request;
82
+ finite awaited nested walks are supported when needed. Explain speed through
83
+ work avoided, without claiming a universal advantage over other libraries.
@@ -0,0 +1,135 @@
1
+ # Callbacks and copying
2
+
3
+ ## Contents
4
+
5
+ - [Import and signature](#import-and-signature)
6
+ - [Return values](#return-values)
7
+ - [What is copied](#what-is-copied)
8
+ - [Visit order](#visit-order)
9
+ - [Explicit settlement](#explicit-settlement)
10
+ - [Errors and timeout](#errors-and-timeout)
11
+
12
+ ## Import and signature
13
+
14
+ ```js
15
+ import walk from '@peter.naydenov/walk-async'
16
+ // CommonJS: const walk = require ( '@peter.naydenov/walk-async' )
17
+
18
+ let result = await walk ({ data, keyCallback, objectCallback, settings, timeout }, ...args)
19
+ ```
20
+
21
+ The signature is `walk(options, ...args)` and it returns a Promise. Only
22
+ `data` is required. Await the call to obtain the copy or finish side effects.
23
+ Extra positional arguments are forwarded to both callbacks after their
24
+ arguments object, in the same order.
25
+
26
+ | Callback | Receives | Additional fields |
27
+ | --- | --- | --- |
28
+ | `keyCallback` | Leaves: primitives, functions, and supported built-in values | `isFinished` |
29
+ | `objectCallback` | Object and array containers, including the root | `PASS` |
30
+
31
+ Both receive `value`, string `key`, `IGNORE`, `FINISH`, and the async-only
32
+ `resolve` and `reject` settlement fields. `breadcrumbs` and read-only
33
+ `parentPath` are present unless disabled. The root object
34
+ callback's key is `'root'`. Helpers are supplied to callbacks, not imported
35
+ as named exports. `isFinished` is normally `false` in key callbacks.
36
+
37
+ ## Return values
38
+
39
+ Both plain returns and returned promises are awaited before interpretation.
40
+ When copying, return the value to keep. Returning `null` or `undefined`
41
+ stores it. Return `IGNORE()` to remove the current property or branch.
42
+
43
+ - A plain object or array returned by either callback is traversed and
44
+ copied. Its children receive callbacks. A container returned by
45
+ `keyCallback` does not itself receive another `objectCallback` call.
46
+ - A simple replacement returned by `objectCallback` goes to `keyCallback`
47
+ when key callbacks are active for its parent. A `PASS` instruction skips
48
+ that call. Simple root replacements resolve directly.
49
+ - Built-in replacements remain leaves and are stored by reference.
50
+ - In no-copy mode, returns still control traversal but are never assigned
51
+ to the source. Leaf callbacks used for side effects may omit their return.
52
+ Object callbacks must return a container to continue into it.
53
+ - A primitive or built-in root resolves directly without callbacks;
54
+ with `copy:false`, the walk always resolves `undefined`.
55
+ - Root `IGNORE()` produces an empty object or array matching the input
56
+ root when copying, or `undefined` without copying.
57
+
58
+ `IGNORE`, `PASS`, and `FINISH` must be called and their results returned or
59
+ passed to `resolve`.
60
+ Returning the function itself is an ordinary function value. When moving
61
+ from synchronous v6, change `return IGNORE` to `return IGNORE()`; async v6
62
+ used `reject()`, which remains supported.
63
+
64
+ ## What is copied
65
+
66
+ Walk copies own enumerable string-keyed properties into new editable object
67
+ and array containers. It does not preserve prototypes, property descriptors,
68
+ non-enumerable properties, or symbol-keyed properties. Enumerable getters
69
+ are read once per visited property.
70
+
71
+ `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, `ArrayBuffer`,
72
+ `DataView`, typed arrays, DOM nodes, and functions remain the same references.
73
+ Their internal contents are not visited. Use an appropriate independent
74
+ clone operation when the task requires cloning those built-in contents.
75
+
76
+ Callbacks receive source or replacement values. Mutating them may mutate
77
+ the original data; return a replacement instead when preservation matters.
78
+ Walk does not freeze the result.
79
+
80
+ Array traversal uses input index strings. Removed items and sparse holes
81
+ are compacted in the result; non-index enumerable properties keep their
82
+ names. Own `__proto__` keys are copied as data properties.
83
+
84
+ ## Visit order
85
+
86
+ Within a container, properties follow `Object.keys` order. Object callbacks
87
+ run before the corresponding container's contents. Contents are queued and
88
+ processed later in scheduling order; this is not recursive depth-first order.
89
+
90
+ For `{ first:{ a:1 }, second:{ b:2 } }`, ordinary callback order is:
91
+
92
+ ```text
93
+ object root
94
+ object first
95
+ object second
96
+ key a
97
+ key b
98
+ ```
99
+
100
+ Do not treat the next object callback as a signal that the previous
101
+ container's keys are complete. Callback values are input or replacement
102
+ values, not completed transformed subtrees. Walk has no post-order callback.
103
+
104
+ Await each callback before starting the next; siblings do not overlap.
105
+ Returned containers follow the same queue order. Finite awaited nested walks
106
+ have their own queue, ancestors, and finish state. Walk has no exit callback.
107
+ Use `@peter.naydenov/walk` when callbacks do not need asynchronous work.
108
+
109
+ ## Explicit settlement
110
+
111
+ Version 6 callbacks can still use `resolve(value)` or `reject()`. Reading or
112
+ destructuring either field selects explicit settlement for that invocation.
113
+ Settle every path with one of them; the callback's normal return is then
114
+ ignored. Spreading the entire arguments object also reads these getters and
115
+ selects explicit settlement. Do not read unused fields in return-based code.
116
+
117
+ `resolve` awaits supplied promises and consumes helper instructions, including
118
+ `resolve ( IGNORE() )`, `resolve ( PASS(value) )` in object callbacks, and
119
+ `resolve ( FINISH(value) )`. Callback completion is still awaited when an async
120
+ callback calls `resolve` early. `reject()` drops the current key or branch,
121
+ equivalent to `IGNORE()`; its optional reason does not reject the walk.
122
+
123
+ ## Errors and timeout
124
+
125
+ Throwing or returning a rejected promise rejects the walk, including failures
126
+ from awaited nested walks or promised instruction payloads. Catch only when
127
+ recovery or pruning is intended; `reject()` is traversal control, not failure.
128
+
129
+ `timeout` is an optional number of milliseconds for the whole walk, supplied
130
+ beside `data`, not inside `settings`. If time expires, the walk rejects with
131
+ pending callback paths, or keys when breadcrumbs are disabled. It covers
132
+ forgotten explicit settlement, unresolved callback returns, and unresolved
133
+ PASS or FINISH payloads. The timer is cleared after success or failure.
134
+ Timeout does not cancel callback I/O or stop the underlying traversal; arrange
135
+ cancellation in application code when required. No timeout is set by default.
@@ -0,0 +1,94 @@
1
+ # Traversal controls
2
+
3
+ All helpers produce instructions that must be returned from the callback
4
+ or passed to `resolve`. Returned promises settle before interpretation.
5
+ Calling a helper and then returning an ordinary value does not issue the
6
+ instruction. Ordinary objects and arrays are data, not instructions.
7
+
8
+ ## IGNORE
9
+
10
+ `IGNORE()` is available to both callbacks:
11
+
12
+ - From `keyCallback`, remove this leaf and continue the walk.
13
+ - From `objectCallback`, remove this container and skip its whole branch.
14
+ - From the root object callback, stop without visiting children. Copying
15
+ resolves `{}` or `[]` matching the original root; no-copy resolves `undefined`.
16
+
17
+ `IGNORE()` takes no arguments. Returning `undefined` is not removal.
18
+
19
+ ## PASS
20
+
21
+ `PASS` is available only to `objectCallback`:
22
+
23
+ - `PASS()` keeps the current value.
24
+ - `PASS(replacement)` uses the supplied replacement.
25
+ - Both skip `keyCallback` only for this container's immediate leaves.
26
+ Nested containers still run their object and key callbacks normally.
27
+ - Simple replacements are stored directly without a key callback.
28
+ - `PASS(undefined)` supplies an explicit replacement; it differs from
29
+ `PASS()`, which keeps the current value.
30
+
31
+ This is local callback skipping, not pruning or retaining an entire branch
32
+ by reference. Object and array containers are still traversed and copied
33
+ when copying is enabled. In no-copy mode, `PASS` applies the same visit rules.
34
+
35
+ ## FINISH without a value
36
+
37
+ Both callbacks receive `FINISH`. `return FINISH()` omits the current value
38
+ or branch and immediately stops the entire walk, including pending work.
39
+ It behaves like removal followed by stopping. Root `FINISH()` resolves an
40
+ empty object or array matching the original root when copying.
41
+
42
+ ## FINISH with a value
43
+
44
+ From `keyCallback`, `return FINISH(value)` includes the supplied value
45
+ directly and immediately stops. Supplied containers are not traversed or
46
+ copied; their references are retained.
47
+
48
+ From `objectCallback`, `return FINISH(value)` selects the final branch:
49
+
50
+ - Without `keyCallback`, include the supplied value directly and stop.
51
+ Supplied object and array references are retained.
52
+ - With `keyCallback`, visit the final branch's leaves, including nested
53
+ containers and containers returned by key callbacks. Each key callback
54
+ receives `isFinished:true`. No further object callbacks run.
55
+ - In that final branch, return `value` to keep a leaf, `IGNORE()` to drop
56
+ it and continue, or either `FINISH` form to stop immediately at that leaf.
57
+ - Ordinary key callbacks receive `isFinished:false`. Earlier `PASS`
58
+ instructions do not suppress the selected branch's final key callbacks.
59
+ - A simple replacement from the root object callback remains a direct
60
+ resolution, without a key callback, as in ordinary root replacement.
61
+
62
+ An explicit argument counts even if it is `undefined`:
63
+ `FINISH(undefined)` includes an undefined value; `FINISH()` omits it.
64
+ An object callback that wants to finish with its current value must return
65
+ `FINISH(value)`, not the zero-argument form.
66
+
67
+ ## Results and search side effects
68
+
69
+ Copying resolves to the work completed before stopping. Unrelated containers
70
+ allocated earlier but still awaiting traversal can remain empty or partially
71
+ populated. Dropping all final leaves may leave empty nested containers.
72
+ Final-branch traversal uses the normal leaf return rules and cycle detection.
73
+
74
+ With `settings.copy:false`, every walk resolves `undefined`, including a
75
+ walk that returns `FINISH(value)` from a callback. Store a search result in
76
+ external state before returning the instruction. Replacements are visited
77
+ without being assigned to the source.
78
+
79
+ ## Awaited instructions
80
+
81
+ Walk awaits promises supplied to `PASS(replacement)` or `FINISH(value)` before
82
+ classifying the payload and checking ancestors. This applies to ordinary
83
+ returns and explicit `resolve` settlement. Rejected payloads reject the walk;
84
+ unresolved payloads stay in timeout diagnostics. An explicit promised
85
+ undefined counts as a supplied value, unlike a zero-argument instruction.
86
+
87
+ A key callback's FINISH container payload is included by reference after it
88
+ settles. An object callback's FINISH payload with a key callback selects a
89
+ copied final branch with the same cycle and leaf-return rules. `isFinished`
90
+ is provided only to key callbacks; object callbacks do not receive it.
91
+
92
+ Finishing is local to each walk; a finite awaited nested walk has its own
93
+ control state. A FINISH instruction issued through early `resolve` is
94
+ interpreted only after the callback's own returned promise completes.
@@ -0,0 +1,252 @@
1
+ # Runnable examples
2
+
3
+ ## Contents
4
+
5
+ - [Remove leaf keys](#remove-leaf-keys)
6
+ - [Prune a whole branch](#prune-a-whole-branch)
7
+ - [PASS with a replacement](#pass-with-a-replacement)
8
+ - [Inspect with a parent path](#inspect-with-a-parent-path)
9
+ - [Stop at a matching leaf](#stop-at-a-matching-leaf)
10
+ - [Search without copying](#search-without-copying)
11
+ - [Process a final branch](#process-a-final-branch)
12
+ - [Copy circular and shared references](#copy-circular-and-shared-references)
13
+ - [Await a lookup](#await-a-lookup)
14
+ - [Use resolve and reject](#use-resolve-and-reject)
15
+ - [Settle a final branch payload](#settle-a-final-branch-payload)
16
+ - [Observe sequential callbacks](#observe-sequential-callbacks)
17
+
18
+ Each block is independent and uses the version 7 API. Expected results are
19
+ shown below the calls. Preserve the developer's style when adapting them.
20
+
21
+ ## Remove leaf keys
22
+
23
+ ```js
24
+ import walk from '@peter.naydenov/walk-async'
25
+
26
+ let data = { name:'Peter', password:'secret', nested:{ token:'abc', count:2 } };
27
+ let result = await walk ({
28
+ data
29
+ , keyCallback : ({ key, value, IGNORE }) => {
30
+ if ( key === 'password' || key === 'token' ) return IGNORE()
31
+ return value
32
+ }
33
+ })
34
+ // { name:'Peter', nested:{ count:2 } }
35
+ // data still contains password and nested.token.
36
+ ```
37
+
38
+ ## Prune a whole branch
39
+
40
+ ```js
41
+ import walk from '@peter.naydenov/walk-async'
42
+
43
+ let data = { profile:{ name:'Peter' }, metadata:{ events:[1,2,3] } };
44
+ let result = await walk ({
45
+ data
46
+ , objectCallback : ({ key, value, IGNORE }) => key === 'metadata' ? IGNORE() : value
47
+ })
48
+ // { profile:{ name:'Peter' } }
49
+ // Nothing inside metadata is visited.
50
+ ```
51
+
52
+ ## PASS with a replacement
53
+
54
+ ```js
55
+ import walk from '@peter.naydenov/walk-async'
56
+
57
+ let data = { branch:{ count:2, nested:{ count:3 } } };
58
+ let result = await walk ({
59
+ data
60
+ , objectCallback : ({ key, value, PASS }) => {
61
+ if ( key === 'branch' ) return PASS ({ ...value, count:100 })
62
+ return value
63
+ }
64
+ , keyCallback : ({ value }) => typeof value === 'number' ? value * 2 : value
65
+ })
66
+ // { branch:{ count:100, nested:{ count:6 } } }
67
+ // PASS() instead keeps branch.count at 2; nested.count still becomes 6.
68
+ ```
69
+
70
+ ## Inspect with a parent path
71
+
72
+ ```js
73
+ import walk from '@peter.naydenov/walk-async'
74
+
75
+ let data = { profile:{ age:42 } };
76
+ let paths = [];
77
+ let result = await walk ({
78
+ data
79
+ , settings : { copy:false, breadcrumbs:false }
80
+ , objectCallback : ({ value }) => value
81
+ , keyCallback : ({ parentPath, key }) => {
82
+ paths.push ([ ...parentPath, key ])
83
+ }
84
+ })
85
+ // paths: [['root','profile','age']]
86
+ // result: undefined
87
+ ```
88
+
89
+ ## Stop at a matching leaf
90
+
91
+ ```js
92
+ import walk from '@peter.naydenov/walk-async'
93
+
94
+ let data = [12,22,33,44,55,66];
95
+ let result = await walk ({
96
+ data
97
+ , keyCallback : ({ value, FINISH }) => {
98
+ if ( value === 33 ) return FINISH ( value )
99
+ return value
100
+ }
101
+ })
102
+ // [12,22,33]
103
+ // Return FINISH() instead to produce [12,22].
104
+ ```
105
+
106
+ ## Search without copying
107
+
108
+ ```js
109
+ import walk from '@peter.naydenov/walk-async'
110
+
111
+ let data = { a:12, nested:{ b:33, c:44 } };
112
+ let found;
113
+ let result = await walk ({
114
+ data
115
+ , settings : { copy:false, breadcrumbs:false, parentPath:false }
116
+ , keyCallback : ({ value, FINISH }) => {
117
+ if ( value !== 33 ) return
118
+ found = value
119
+ return FINISH()
120
+ }
121
+ })
122
+ // found: 33
123
+ // result: undefined
124
+ ```
125
+
126
+ ## Process a final branch
127
+
128
+ ```js
129
+ import walk from '@peter.naydenov/walk-async'
130
+
131
+ let data = { a:12, c:{ internal:44, in1:'bbb', in2:'ccc' }, later:99 };
132
+ let result = await walk ({
133
+ data
134
+ , objectCallback : ({ key, value, FINISH }) => key === 'c' ? FINISH ( value ) : value
135
+ , keyCallback : ({ key, value, isFinished, IGNORE }) => {
136
+ if ( isFinished && key !== 'internal' ) return IGNORE()
137
+ return value
138
+ }
139
+ })
140
+ // { a:12, c:{ internal:44 } }
141
+ // Key callbacks in c receive isFinished:true; later is not visited.
142
+ ```
143
+
144
+ To select just the first final leaf and stop, return `FINISH(value)` from
145
+ that final key callback. Its order follows `Object.keys`, so select by key
146
+ when a particular property is required. Nested containers in the selected
147
+ branch still have their leaves visited, without further object callbacks.
148
+
149
+ ## Copy circular and shared references
150
+
151
+ ```js
152
+ import walk from '@peter.naydenov/walk-async'
153
+
154
+ let shared = { count:1 };
155
+ let data = { a:shared, b:shared };
156
+ data.self = data
157
+
158
+ let result = await walk ({ data })
159
+ // result !== data
160
+ // result.self === result
161
+ // result.a !== result.b
162
+ // result.a and result.b both contain { count:1 }.
163
+ ```
164
+
165
+ ## Await a lookup
166
+
167
+ ```js
168
+ import walk from '@peter.naydenov/walk-async'
169
+
170
+ async function lookupUser ( id ) {
171
+ return await Promise.resolve ({ id, active:true })
172
+ } // lookupUser func.
173
+
174
+ let data = { userId:42, nested:{ userId:7 } };
175
+ let result = await walk ({
176
+ data
177
+ , keyCallback : async ({ key, value }) => {
178
+ if ( key === 'userId' ) return await lookupUser ( value )
179
+ return value
180
+ }
181
+ })
182
+ // { userId:{ id:42, active:true }, nested:{ userId:{ id:7, active:true } } }
183
+ ```
184
+
185
+ The local lookup stands in for an awaited service call. Returned containers
186
+ are visited; only keys named userId start lookups, so their generated leaves
187
+ do not trigger the lookup again. Errors propagate to the caller.
188
+
189
+ ## Use resolve and reject
190
+
191
+ ```js
192
+ import walk from '@peter.naydenov/walk-async'
193
+
194
+ let data = { number:2, password:'secret', nested:{ number:3 } };
195
+ let result = await walk ({
196
+ data
197
+ , keyCallback : ({ key, value, resolve, reject }) => {
198
+ if ( key === 'password' ) reject()
199
+ else resolve ( Promise.resolve ( value * 2 ) )
200
+ }
201
+ })
202
+ // { number:4, nested:{ number:6 } }
203
+ ```
204
+
205
+ These fields select explicit settlement, so every callback path settles.
206
+ `reject()` prunes a leaf. Throw or return a rejected promise to fail the walk.
207
+ Return-based callbacks should leave these fields unread.
208
+
209
+ ## Settle a final branch payload
210
+
211
+ ```js
212
+ import walk from '@peter.naydenov/walk-async'
213
+
214
+ let data = { final:{ original:1 }, later:99 };
215
+ let flags = [];
216
+ let result = await walk ({
217
+ data
218
+ , objectCallback : ({ key, value, FINISH, resolve }) => {
219
+ resolve ( key === 'final' ? FINISH ( Promise.resolve ({ first:2, nested:{ number:3 } }) ) : value )
220
+ }
221
+ , keyCallback : async ({ value, isFinished }) => {
222
+ flags.push ( isFinished )
223
+ return await Promise.resolve ( value * 10 )
224
+ }
225
+ })
226
+ // { final:{ first:20, nested:{ number:30 } } }
227
+ // flags: [true,true]; later and final-branch object callbacks do not run.
228
+ ```
229
+
230
+ ## Observe sequential callbacks
231
+
232
+ ```js
233
+ import walk from '@peter.naydenov/walk-async'
234
+
235
+ let data = { first:{ a:1 }, second:{ b:2 } };
236
+ let visits = [];
237
+ let result = await walk ({
238
+ data
239
+ , objectCallback : async ({ key, value }) => {
240
+ visits.push ( 'object ' + key )
241
+ await Promise.resolve()
242
+ return value
243
+ }
244
+ , keyCallback : async ({ key, value }) => {
245
+ visits.push ( 'key ' + key )
246
+ await Promise.resolve()
247
+ return value
248
+ }
249
+ })
250
+ // visits: ['object root','object first','object second','key a','key b']
251
+ // result: { first:{ a:1 }, second:{ b:2 } }
252
+ ```