@peter.naydenov/walk-async 7.0.1 → 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.
- package/Changelog.md +11 -0
- package/README.md +3 -0
- package/dist/walk-async.cjs +1 -1
- package/dist/walk-async.esm.mjs +396 -1
- package/dist/walk-async.umd.js +1 -1
- package/package.json +5 -9
- package/skills/README.md +108 -0
- package/skills/walk-async/SKILL.md +83 -0
- package/skills/walk-async/references/callbacks.md +135 -0
- package/skills/walk-async/references/control-flow.md +94 -0
- package/skills/walk-async/references/examples.md +252 -0
- package/skills/walk-async/references/settings.md +60 -0
- package/src/copyObject.js +7 -3
- package/src/main.js +21 -13
- package/src/runCallback.js +14 -7
- package/src/runTask.js +34 -0
- package/types/copyObject.d.ts +1 -6
- package/types/copyObject.d.ts.map +1 -1
- package/types/main.d.ts.map +1 -1
- package/types/runCallback.d.ts.map +1 -1
- package/types/runTask.d.ts +3 -0
- package/types/runTask.d.ts.map +1 -0
package/skills/README.md
ADDED
|
@@ -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
|
+
```
|