@peter.naydenov/walk-async 6.1.0 → 7.0.1

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 CHANGED
@@ -2,10 +2,43 @@
2
2
 
3
3
 
4
4
 
5
+ ### 7.0.1 (2026-10-16)
6
+ - [x] Dependency updates. Ask-for-promise to version 3.2.1;
7
+
8
+
9
+
10
+ ### 7.0.0 (2026-10-05)
11
+ - [x] Docs: Update the skill;
12
+ - [x] Feature: Added private `FINISH()` / `FINISH(value)` instructions to both callbacks. Omit and stop immediately without an argument; key callbacks include supplied values directly, while object callbacks select the final branch for key callbacks with `isFinished:true` and suppress further object callbacks. Promised payloads and explicit settlement are awaited; ordinary key callbacks receive `isFinished:false`;
13
+ - [x] Tests: Ported 73 FINISH cases and 832 deterministic generated tests, covering 768 transformation settings cases and 384 early-finish combinations against an independent recursive reference. Added async FINISH settlement, payload, failure, timeout, distribution, and documentation checks;
14
+ - [x] Benchmarks: Added fresh-process async memory measurements separating sampled extra heap, retained and released heap after GC, and process RSS high-water marks. Recorded actual async samples and methodology; excluded benchmarks from package contents;
15
+ - [x] Docs: Added FINISH semantics, generated test methodology, measured memory use, and historical timing provenance. Updated the helper skill and regenerated FINISH declarations and distributions;
16
+ - [x] Feature: Callbacks now accept ordinary returns and awaited promises, matching the synchronous API. Added callable `IGNORE()` to both callbacks; change synchronous `return IGNORE` examples to `return IGNORE()`. Existing async `resolve`/`reject` callbacks remain supported; reading either field selects explicit settlement;
17
+ - [x] Feature: Added optional `settings`. Callback arguments `breadcrumbs` and `parentPath` are enabled by default; only literal `false` disables their preparation. Walks without callbacks skip path preparation automatically;
18
+ - [x] Feature: Added `settings.copy:false` to walk without building a result. Callback order, paths, replacements, `IGNORE()`, and `PASS()` keep their traversal behaviour; the walk resolves `undefined`;
19
+ - [x] Feature: Added read-only `parentPath` arrays to both callbacks. Paths exclude the current key, preserve property boundaries, and use input array indexes;
20
+ - [x] Feature: Added the `PASS` function to `objectCallback`. Return `PASS()` to copy the current value, or `PASS(modifiedValue)` to use a replacement, without immediate key callbacks. Replacement promises are awaited. Nested objects and arrays resume normal callbacks; `IGNORE()` removes the entire branch;
21
+ - [x] Packaging: Added `"types":"./types/main.d.ts"` before runtime conditions in `exports["."]` so modern TypeScript consumers resolve declarations through package-name imports;
22
+ - [x] Fix: Read each property once before finding its type, so getters are not executed twice;
23
+ - [x] Fix: Recognize arrays across JavaScript contexts and preserve numeric-looking non-index properties under their original names;
24
+ - [x] Breaking: Callback execution now awaits each sibling in `Object.keys` order instead of starting siblings concurrently. Deferred container contents remain in scheduling order;
25
+ - [x] Fix: Thrown callback errors, rejected callback promises, rejected resolve values, and nested-walk failures propagate to the walk promise. Awaited finite nested walks have independent state;
26
+ - [x] Feature: Retained timeout diagnostics for explicit settlement and unresolved returned promises; timers are cleared on completion or failure;
27
+ - [x] Performance: Replaced one-use generator wrappers with a work queue. Processed entries are cleared to release container and path references earlier; callback order is preserved;
28
+ - [x] Cleanup: Native promises replace the runtime use of `ask-for-promise`; the declared dependency remains unchanged for the maintainer. Split source helpers to match the synchronous project and regenerated all three distributions and declarations; removed stale `types/src` output;
29
+ - [x] Fix: Detect circular references through the current branch's ancestors and link them to the matching ancestor copy. Without copying, stop at the circular edge. Callbacks can still ignore or replace the edge; shared containers on separate branches are copied independently;
30
+ - [x] Feature: Added `settings.detectCycles`, enabled by default. Only literal `false` skips circular-reference checks and all ancestor bookkeeping. Visited data and callback replacements must then have no circles, or callbacks must prune cyclic branches;
31
+ - [x] Docs: Rewrote the README around readable traversal rules, transformations during copying, deep forEach, branch control, paths, and reference behaviour. Updated the skill and removed the outdated restriction on finite nested walk calls;
32
+ - [x] Tests: Ported all 208 synchronous tests, retained all 66 existing async tests, and added awaited instruction, callback order, rejection, and nested-walk tests. Enforced 100% coverage thresholds;
33
+ - [x] Tests: Added regression coverage for settings, parent paths, local `PASS`, traversal without copying, circular references, getters, and array recognition and properties;
34
+ - [x] Verification: Added generated-format smoke checks, executable README examples, strict TypeScript consumption checks, and a repeatable benchmark for cycle detection, copying, and paths. Recorded actual async measurements without importing synchronous timing claims;
35
+
36
+
37
+
5
38
  ### 6.1.0 (2026-09-01)
6
39
  - [x] Feature: Added a skill at `.agents/skills/git-walk-async/SKILL.md`;
7
- - [x] Packaging: Added `.agents/skills` to the `"files"` allow-list in `package.json` so the Mavis skill ships with the npm package. AI-agent users can discover the walk-async skill from `node_modules` directly, without needing access to the source repo. The same allow-list also drops the dev-only `rollup.config.js`, `tsconfig.json`, and `vitest.config.js` that were leaking under the previous `.npmignore`-only packaging;
8
- - [x] Docs: Prefilled `Migration.guide.md` for every major release: sync-`@peter.naydenov/walk` → `walk-async` (the README's "Migrating from `@peter.naydenov/walk`" table, folded in), v3.x → v6.x (callback signatures tightened with exported `Resolve` / `Reject` typedefs), v3.0.x → v3.1.x (the `timeout` option, the built-in-type pass-by-reference fix from v3.1.2, and the v3.0.6/v3.0.7 root-callback bug fixes), the existing v2.x → v3.x root-callback entry, v1.x → v2.x (ES module + `dist` + `exports`), and v1.0.0 → v1.x (the early null/undefined, DOM-node, and collection-container fixes). The README still has a copy of the sync-`walk` → `walk-async` table — flag for a follow-up if you want the README trimmed to a one-liner pointer so the docs aren't split across two files;
40
+ - [x] Packaging: Added `.agents/skills` to the `"files"` allow-list in `package.json` so the skill ships with the npm package. AI-agent users can discover the walk-async skill from `node_modules` directly, without needing access to the source repo. The same allow-list also drops the dev-only `rollup.config.js`, `tsconfig.json`, and `vitest.config.js` that were leaking under the previous `.npmignore`-only packaging;
41
+ - [x] Docs: Prefilled `Migration.guide.md` for every major release: sync-`@peter.naydenov/walk` → `walk-async` (the README's "Migrating from `@peter.naydenov/walk`" table, folded in), v3.x → v6.x (callback signatures tightened with exported `Resolve` / `Reject` typedefs), v3.0.x → v3.1.x (the `timeout` option, the built-in-type pass-by-reference fix from v3.1.2, and the v3.0.6/v3.0.7 root-callback bug fixes), the existing v2.x → v3.x root-callback entry, v1.x → v2.x (ES module + `dist` + `exports`), and v1.0.0 → v1.x (the early null/undefined, DOM-node, and collection-container fixes);
9
42
 
10
43
 
11
44
 
@@ -148,5 +181,3 @@
148
181
  - [x] Test package;
149
182
  - [x] Documentation;
150
183
  - [ ] Bug: Deep copy is not working.
151
-
152
-
@@ -1,43 +1,197 @@
1
1
  # Migration Guides
2
2
 
3
- Upgrade notes for non-trivial `walk-async` releases. For the full per-release list of changes, see the [Changelog](Changelog.md); this guide focuses on what *you* have to change in your code.
3
+ Upgrade notes for non-trivial `walk-async` releases. For the full per-release list of changes, see the [Changelog](Changelog.md).
4
4
 
5
- Most common first, then version history newest-first.
5
+ ## From v.6.x.x - v.7.x.x (Unreleased)
6
6
 
7
+ ### Agent skill
7
8
 
9
+ The skill is now named `walk-async` and lives in `skills/walk-async/`, replacing
10
+ `.agents/skills/git-walk-async/`. Update installers or integrations that use
11
+ the old path, and include the full folder with all four linked references.
12
+ There is one canonical portable skill; local `.claude` and `.agents` settings
13
+ are not maintained copies. See [skill integration](skills/README.md).
8
14
 
9
- ## From `@peter.naydenov/walk` (sync sibling)
15
+ The current `package.json` `files` whitelist excludes the new folder. Replace
16
+ the obsolete `".agents/skills"` entry with `"skills"` to include the skill, all
17
+ references, and the integration README linked from the package documentation. This manifest update is left for
18
+ the maintainer; the skill/documentation move does not change package.json.
19
+ Verify the folder with `npm pack --dry-run --json` after making that change.
20
+
21
+ ### Return values and awaited callbacks
22
+
23
+ Callbacks now share the synchronous library's return-value API. Change `resolve(value)` to `return value`, and `reject()` to `return IGNORE()` when adopting it:
24
+
25
+ ```js
26
+ // version 6: still supported in version 7
27
+ keyCallback : ({ value, key, resolve, reject }) => {
28
+ if ( key === 'password' ) reject ()
29
+ else resolve ( value )
30
+ }
31
+
32
+ // version 7
33
+ keyCallback : async ({ value, key, IGNORE }) => {
34
+ if ( key === 'password' ) return IGNORE()
35
+ return await refresh ( value )
36
+ }
37
+ ```
38
+
39
+ Both plain returns and returned promises are supported. The walk itself still returns a Promise. Await it to get the copy, finish side effects, or catch an error. A thrown error or a rejected promise from either callback now rejects the walk instead of leaving internal tasks pending. Finite awaited nested walks are supported; each call has independent traversal and ancestor state.
40
+
41
+ Existing `resolve`/`reject` callbacks remain supported. Reading either field selects explicit settlement for that invocation, so settle every path with one of them. An async callback's returned promise is awaited even when it calls `resolve` early; `resolve` also awaits a supplied promise. Do not destructure or spread unused settlement fields when switching to returns. `reject()` continues to prune the current key or branch; use a throw or a rejected promise to fail the walk. The existing `timeout` option still reports pending callbacks and now covers unresolved returned promises too. It does not cancel callback I/O.
42
+
43
+ ### Callable IGNORE
44
+
45
+ `IGNORE` is a function in both callbacks. When moving code from the synchronous v6 API, replace `return IGNORE` with `return IGNORE()`:
46
+
47
+ ```js
48
+ // synchronous version 6
49
+ keyCallback : ({ value, key, IGNORE }) => key === 'password' ? IGNORE : value
50
+
51
+ // version 7, synchronous or asynchronous
52
+ keyCallback : ({ value, key, IGNORE }) => key === 'password' ? IGNORE() : value
53
+ ```
54
+
55
+ Its private token removes a leaf or an entire container branch, including descendants. Ignoring the root resolves `[]` for an original array root or `{}` for an original object root when copying; without copying it resolves `undefined`. Returning the helper function itself stores ordinary function data. Async v6 used `reject()` rather than a public IGNORE constant; those callbacks do not need this change. TypeScript now includes `IgnoreFunction`, `PassFunction`, `ObjectCallbackArgs`, and `Settings` alongside `Resolve` and `Reject`. Metadata fields are optional and parent paths are read-only.
56
+
57
+ ### Callback order
58
+
59
+ Sibling callbacks used to start concurrently. Version 7 awaits them one at a time in `Object.keys` order to match the synchronous library. The root object callback runs first; child object callbacks run when their properties are encountered, and those containers' contents are deferred until the current container finishes. Queued containers run in scheduling order, breadth-first over contents. There is no exit callback.
60
+
61
+ Code that depends on sibling callbacks overlapping must move that concurrency into application code. Array output, side effects, replacement handling, and ancestor lookup now follow the same defined order even when callbacks await different delays.
62
+
63
+ ### Getter and array fixes
64
+
65
+ Properties are read once before classification. Getters no longer run twice. `Array.isArray` recognizes arrays from other JavaScript contexts. Only canonical indexes from `0` through `2^32 - 2` become consecutive output elements. Sparse holes and removed indexes compact; numeric-looking non-index properties such as `'01'`, `'-1'`, and `'4294967295'` retain their original names. Own `'__proto__'` properties remain ordinary data without changing the result prototype.
66
+
67
+ ### Settings, parentPath and PASS
68
+
69
+ Existing calls do not need a `settings` object. Both callbacks now receive a read-only `parentPath` array by default, alongside `breadcrumbs`. The parent path excludes `key`; the root `objectCallback` receives `parentPath:[]` and `key:'root'`.
70
+
71
+ Set `settings.breadcrumbs` or `settings.parentPath` to literal `false` to skip preparing an unused argument. Omitted settings remain enabled. Disabled arguments are absent, so destructuring them gives `undefined`; TypeScript declares both metadata fields as optional.
72
+
73
+ ```js
74
+ let result = await walk ({
75
+ data
76
+ , settings : { breadcrumbs:false, parentPath:false }
77
+ , keyCallback : ({ value }) => value
78
+ })
79
+ ```
80
+
81
+ Default calls now prepare parent arrays when callbacks are present. Disable `parentPath` when the callback does not use it, particularly for deeply nested data. With no callbacks, both paths are skipped automatically.
82
+
83
+ `objectCallback` also receives the `PASS` function. Return `PASS()` to copy the current value and skip `keyCallback` only on its immediate properties, or `PASS(modifiedValue)` to use a replacement with the same rule. Nested objects and arrays still run their own callbacks normally. `IGNORE()` removes the entire branch and stops visiting its contents.
84
+
85
+ `PASS()` and `PASS(undefined)` have different meanings: the first keeps the current value; the second explicitly replaces it with `undefined`. A simple replacement is stored directly without `keyCallback`. Neither instruction appears in the result.
86
+
87
+ Walk Async also awaits promises supplied to `PASS(replacement)` before interpreting the replacement and checking its ancestors. Rejected replacements reject the walk; unresolved replacements remain in timeout diagnostics.
88
+
89
+ ### Finish the entire walk
90
+
91
+ Both callbacks now receive the `FINISH` function. Like `IGNORE()` and `PASS()`, return its instruction or pass it to `resolve`. Calling it without returning or resolving the instruction has no effect on traversal.
92
+
93
+ ```js
94
+ let result = await walk ({
95
+ data : [12,22,33,44,55,66]
96
+ , keyCallback : ({ value, FINISH }) => {
97
+ if ( value === 33 ) return FINISH ( value )
98
+ return value
99
+ }
100
+ })
101
+ // [12,22,33]
102
+ ```
103
+
104
+ `FINISH()` omits the current value or branch and stops immediately. From `keyCallback`, `FINISH(value)` includes the supplied value directly and stops immediately, without copying or visiting its contents. An explicit argument counts even when it is `undefined`.
105
+
106
+ From `objectCallback`, `FINISH(value)` selects the final branch. If a key callback exists, it processes the supplied value's leaves with the new boolean argument `isFinished:true`, including nested containers and containers returned by key callbacks. No further object callbacks run, and unrelated pending work is discarded. Ordinary key callbacks receive `isFinished:false`. Return `value` to preserve final leaves, `IGNORE()` to drop them while continuing through the final branch, or `FINISH()` / `FINISH(value)` to stop immediately at a final leaf. Dropping leaves can leave empty nested containers. Earlier `PASS()` instructions do not suppress key callbacks in the selected final branch.
107
+
108
+ Without a key callback, `FINISH(value)` from an object callback includes the value directly. Supplied objects and arrays then keep their references. With a key callback, the final branch is copied using the normal leaf return rules and cycle detection. A simple root replacement remains a direct return without a key callback, matching the existing root contract.
109
+
110
+ When copying, the result contains work completed before stopping. Unrelated containers already allocated but waiting for traversal can remain empty or partially populated. Finishing from the root object callback without a value resolves an empty object or array matching the input root; supplying a container follows the final-branch rule. Without copying, finishing still controls traversal and walk resolves `undefined`. Collect search results externally in that mode.
10
111
 
11
- `walk-async` is the async sibling of [`@peter.naydenov/walk`](https://github.com/PeterNaydenov/walk). They share the same callback parameter names (`value`, `key`, `breadcrumbs`) and the same three-outcome contract. The only mechanical change is how you express the return value:
112
+ Walk Async awaits callback completion and promises supplied to `FINISH(value)`, including through `resolve ( FINISH ( value ) )`. Rejected payloads reject the walk; unresolved payloads remain in timeout diagnostics.
12
113
 
13
- | `walk` (sync) | `walk-async` (async) |
14
- | -------------------------------- | ---------------------------- |
15
- | `return value` | `resolve(value)` |
16
- | `return newObject` | `resolve(newObject)` |
17
- | `return IGNORE` | `reject()` |
114
+ This is an optional addition; existing callbacks do not need changes. Finishing is local to each walk, including nested calls. TypeScript declares the shared helper as `FinishFunction` and the key callback arguments as `KeyCallbackArgs`, including `isFinished:boolean`.
18
115
 
19
- The options shape is identical: `{ data, keyCallback, objectCallback }`. `walk-async` adds an optional `timeout` (milliseconds). The walk call itself becomes an `await` (or `.then`).
116
+ ### Walk without a copy
117
+
118
+ Set `settings.copy` to literal `false` to run callbacks without creating result objects or arrays. Walk resolves `undefined` in this mode, including for simple, ignored, or replaced roots. Copying remains enabled when the setting is omitted or has any other value.
20
119
 
21
120
  ```js
22
- // walk
23
- const result = walk ({
24
- data: user,
25
- keyCallback: ({ value, key, IGNORE }) => key === 'password' ? IGNORE : value
26
- })
27
-
28
- // walk-async — same shape, just promise-based
29
- const result = await walk ({
30
- data: user,
31
- keyCallback: ({ value, key, resolve, reject }) => {
32
- if ( key === 'password' ) reject ()
33
- else resolve ( value )
34
- }
35
- })
121
+ let values = [];
122
+ await walk ({
123
+ data : { number:1, nested:{ number:2 } }
124
+ , settings : { copy:false }
125
+ , keyCallback : ({ value }) => { values.push ( value ) }
126
+ })
127
+ // values: [1,2]
128
+ ```
129
+
130
+ Callback order and path arguments stay the same. `IGNORE()` still prunes branches; `PASS()` and `PASS(value)` still skip immediate key callbacks while visiting nested objects normally. Returned replacement containers are visited without being assigned to the source.
131
+
132
+ An object callback must still return the current or replacement value to continue into it. A key callback used only for side effects may omit its return. Metadata settings are independent of copying; disable unused paths separately.
133
+
134
+ ### Circular references
135
+
136
+ Walk now detects references back to an ancestor instead of following them indefinitely. When copying, the reference points to that ancestor's copy: if `data.self === data`, then `result.self === result`. With `settings.copy:false`, traversal stops at that edge and resolves `undefined` as usual.
137
+
138
+ Object callbacks still receive circular properties and can ignore or replace them. The returned container is checked against the current branch's ancestors before visiting its contents. This also applies to containers returned by `keyCallback`. Repeated references on separate branches are still copied independently. No new setting or callback argument is required; detection works with both path settings disabled.
139
+
140
+ Detection defaults to true. Set `settings.detectCycles` to literal `false` to skip the checks and all ancestor bookkeeping when the visited data and callback replacements are known to be acyclic. Omitted settings and other values keep detection enabled. Copying and metadata settings remain independent.
141
+
142
+ ```js
143
+ let result = await walk ({
144
+ data : JSON.parse ( '{"nested":{"number":1}}' )
145
+ , settings : { detectCycles:false }
146
+ })
147
+ // { nested:{ number:1 } }
148
+ ```
149
+
150
+ With detection disabled, callbacks must prune cyclic branches before revisiting them; otherwise traversal can continue indefinitely. Callback order and return rules are unchanged.
151
+
152
+ ### Traversal performance
153
+
154
+ Nested containers now use a work queue instead of generator wrappers. Processed entries are cleared to release their container and path references earlier. Callback order and return behaviour stay the same; no changes to your callbacks are required for this optimization. Walk no longer uses generator functions, so a generator polyfill is no longer needed for the library itself.
155
+
156
+ Pending descendants keep links to their ancestor containers for circular-reference detection. Shallow branches use the short parent chain; deeper branches use an internal lookup containing only the current branch's ancestors. This avoids repeatedly scanning long chains. Detection adds bookkeeping even when path preparation is disabled.
157
+
158
+
159
+ ### Package declarations
160
+
161
+ The generated declarations describe the new settings, helpers, optional metadata, and Promise return contract. The package now includes a `types` condition before the runtime conditions in `package.json` for modern TypeScript resolution:
162
+
163
+ ```json
164
+ "exports": {
165
+ ".": {
166
+ "types": "./types/main.d.ts",
167
+ "import": "./dist/walk-async.esm.mjs",
168
+ "require": "./dist/walk-async.cjs",
169
+ "default": "./dist/walk-async.umd.js"
170
+ },
171
+ "./package.json": "./package.json",
172
+ "./dist/*": "./dist/*",
173
+ "./src/*": "./src/*"
174
+ }
36
175
  ```
37
176
 
38
- If your callbacks don't actually need async work, prefer the sync `walk` — it's noticeably faster because it avoids the promise machinery. Reach for `walk-async` only when you have real async work inside the callbacks (database lookups, network calls, file reads, etc.) and the cost of multiple `walk` calls would be unacceptable.
177
+ Strict package-name imports now resolve declarations under `NodeNext` and `Bundler`. The package exports were verified with strict ESM, CommonJS default-import, and bundler consumers.
178
+
39
179
 
40
180
 
181
+ ## From `@peter.naydenov/walk` (sync sibling)
182
+
183
+ For version 7 of both packages, keep the callbacks and settings, change the import, and await the walk:
184
+
185
+ ```js
186
+ import walk from '@peter.naydenov/walk-async'
187
+
188
+ let result = await walk ({
189
+ data : { name:'Peter', password:'secret' }
190
+ , keyCallback : ({ value, key, IGNORE }) => key === 'password' ? IGNORE() : value
191
+ })
192
+ ```
193
+
194
+ Async callbacks may await I/O and return their result. Both packages share paths, replacement rules, local `PASS`, callable `IGNORE`, no-copy traversal, and ancestor-cycle handling. Walk Async additionally retains explicit `resolve`/`reject` callbacks and `timeout` diagnostics. It awaits callback results; the synchronous library does not. For pre-v7 synchronous code, change `return IGNORE` to `return IGNORE()` too.
41
195
 
42
196
  ## From v.3.x.x - v.6.x.x
43
197