@peter.naydenov/walk-async 3.1.3 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Changelog.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
 
4
4
 
5
+ ### 6.0.0 (2026-08-08)
6
+ - [x] Allign version numbers with `@peter.naydenov/walk`. We jumping from 3.1.3 to 6.0.0;
7
+ - [x] Types: Tighten callback signatures in `types/main.d.ts`. `keyCallback` and `objectCallback` are now typed as `KeyCallback` / `ObjectCallback` over a `CallbackArgs` shape (`value`, `key`, `breadcrumbs`, `resolve`, `reject`); `resolve` and `reject` are exported as `Resolve` / `Reject` types so TypeScript users get autocomplete. Driven from JSDoc in `src/main.js`; regenerate with `npm run build`;
8
+ - [x] Docs: Reframe the lead paragraph so the deep copy reads as a side-effect and the callback-driven modifications as the headline;
9
+ - [x] Docs: Add a "When to use `walk-async` vs `structuredClone`" callout that points to the sync sibling `@peter.naydenov/walk` for the no-async case;
10
+ - [x] Docs: Add a "Built-in types" subsection that documents how `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, `ArrayBuffer`, `DataView`, typed arrays, DOM nodes, and functions are passed by reference;
11
+ - [x] Docs: Tighten the `objectCallback` section to enumerate the three return-value outcomes (resolve with object/array, resolve with primitive, `reject()`);
12
+ - [x] Docs: Rewrite the "keyCallback" section with the three-outcome return contract, replacing the misleading "value: Only primitives" comment;
13
+ - [x] Docs: Add a "Skip a branch" subsection that documents calling `reject()` from `objectCallback` to drop an entire subtree;
14
+ - [x] Docs: Reframe the `keyCallback` intro so "forEach" is the central concept, not a secondary use case;
15
+ - [x] Docs: Add a "Why one callback, not a list of methods" section that explains the single-pass architecture (matters more for async — extra passes multiply awaited I/O cost) and points users toward callback factories;
16
+ - [x] Docs: Add an "Order of execution" section that makes the key invariants visible up front (level-internal key order, deferred nested walks, `objectCallback` before `keyCallback`, root behavior, concurrent key starts);
17
+ - [x] Docs: Add a "Migrating from `@peter.naydenov/walk`" section with a side-by-side mapping table (`return value` → `resolve(value)`, `return IGNORE` → `reject()`, etc.) so the sync→async move is mechanical;
18
+ - [x] Docs: Align the section ordering with `@peter.naydenov/walk` (Order of execution → callbacks → Why one callback → Installation → How to use it → Migrating → Timeout → Limitations → See also);
19
+ - [x] Docs: Add a "See also" block that explicitly positions `walk-async` next to its sync sibling;
20
+ - [x] Tests: Add 9 tests in `test/02-keyCallback.test.js` covering plain-object resolution, array resolution, `Date` / `Map` passed by reference, `reject()` drops, order preservation, primitive leaf, and full nested walk with arrays;
21
+
22
+
23
+
5
24
  ## 3.1.3 (2026-07-31)
6
25
  - [x] Dependencies updates. Ask-for-promise to version 3.2.0;
7
26
 
package/README.md CHANGED
@@ -5,59 +5,61 @@
5
5
  ![GitHub issues](https://img.shields.io/github/issues/peterNaydenov/walk-async)
6
6
  ![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40peter.naydenov%2Fwalk-async)
7
7
 
8
- Creates an immutable copies of javascript data structures(objects, arrays or mixed). Can execute callback functions on every object property(objectCallback) and/or every primitive property(keyCallback). Callbacks can modify result object during the walk process. Mask, filter or substitute values during the copy process.
8
+ `walk-async` visits every member of a JavaScript data structure **once**, in a single pass. Two callbacks run during the visit:
9
+
10
+ - `objectCallback` fires on every object or array (including the root)
11
+ - `keyCallback` fires on every primitive property
12
+
13
+ Both callbacks can `resolve` a new value (mask, filter, or substitute) or `reject` to drop the key. Callbacks are async — they can `await` work, talk to the network, read files, anything — and the walk is built so a single pass collects all the transformations you need. The deep copy of the original data is mostly a side-effect: the real power of `walk-async` is the ability to do all your modifications in one pass, with full freedom inside a single callback.
9
14
 
10
15
  ```js
11
16
 
12
17
  walk ({
13
- data // (required) Any JS object structure
14
- , objectCallback // (optional) Function executed on each object property
15
- , keyCallback // (optional) Function executed on each primitive property
16
- , timeout // (optional) Milliseconds. Reject the promise if callbacks do not resolve in time
18
+ data // (required) Any JS data structure;
19
+ , objectCallback // (optional) Function executed on each object/array property;
20
+ , keyCallback // (optional) Function executed on each primitive property;
21
+ , timeout // (optional) Milliseconds. Reject the promise if callbacks do not resolve in time;
17
22
  })
18
23
  .then ( result => {
19
- // Result will become a exact deep copy of "data"
20
- // - if callbacks are not defined
21
- // - if callbacks are resolved with "value" without modification
24
+ // `result` is an immutable copy of `data` if callbacks are absent
25
+ // or resolve with the original `value` unchanged.
22
26
  })
23
27
  ```
24
28
 
25
- It is very simular to [`@peter.naydenov/walk`](https://github.com/PeterNaydenov/walk) but there are some differences:
26
- - `walk-async` returns a result as a promise;
27
- - `walk-async` can execute async operations inside callback methods;
28
- - `walk-async` keyCallback can return as a result object, array or primitives.
29
- - `walk` keyCallback can return only primitives;
30
- - `walk` can not execute another walk call inside callback functions;
29
+ > **When to use `walk-async` vs `structuredClone`.**
30
+ > `structuredClone` is built into modern browsers and Node 17+ and is the right tool when you just need a deep copy. Reach for `walk-async` when you want to **modify particular properties, set values conditionally, or skip whole branches** during the copy — all in a single pass over the data — and your callbacks need to do async work. If you don't need callbacks, or you don't need async, `walk-async` is overkill: use `structuredClone` for the simple case, or use the sibling [`@peter.naydenov/walk`](https://github.com/PeterNaydenov/walk) for sync callbacks.
31
+
31
32
 
33
+ ## Order of execution
32
34
 
33
- Data structure values must be one of the following data types:
34
- - string;
35
- - number;
36
- - bigint;
37
- - boolean;
38
- - symbol;
39
- - null;
40
- - undefined;
41
- - array;
42
- - object(data only);
43
- - function;
35
+ A few invariants to keep in mind — they shape the order in which your callbacks fire and are easier to reason about up front than to discover in the source:
44
36
 
45
- Other data types can compromise the results;
37
+ - **Within one level, keys are visited in `Object.keys` order.** That's the order of own enumerable string keys on the current object/array.
38
+ - **A level finishes before any nested walk starts.** When `walk-async` hits an object/array value (original or returned by a callback), it allocates the result container and defers the nested walk. The nested walk runs after the current level's iteration completes. New objects/arrays returned by `keyCallback` or `objectCallback` are scheduled the same way, so the iteration order of the current level is preserved.
39
+ - **`objectCallback` runs before `keyCallback` on the same value.** For every object/array, `objectCallback` fires first; if it resolves with a new object, that object is what gets walked; its primitive children are then handed to `keyCallback`.
40
+ - **The root is treated as a normal object/array.** When `data` is an object/array, the root goes through `objectCallback` first (if defined), then its children are walked. Rejecting from the root `objectCallback` short-circuits the whole walk to an empty result.
46
41
 
42
+ Because callbacks are async, the same level's keys are started concurrently in `Object.keys` order — they don't have to wait for each other to resolve before the next one is fired. They just have to finish before the next level begins.
47
43
 
48
44
 
49
- ## кeyCallback
50
- Function **keyCallback** of the `walk-async` could be used also as a deep '**forEach**' method no matter of the type of the object(objects, array or mixed).
45
+ ## keyCallback
46
+ `walk-async` visits each member of the data once. `keyCallback` fires on every primitive property — types: *string, number, bigint, boolean, symbol, null, undefined, function*. Object and array values are visited by `objectCallback` instead (see below). Built-in types like `Date`, `Map`, `Set`, and typed arrays arrive at `keyCallback` as primitives and are passed through by reference — see [Built-in types](#built-in-types-date-regexp-map-set-typed-arrays-etc) below.
47
+
48
+ The value you `resolve` becomes the new value at that key:
49
+
50
+ - **Resolve a primitive (or a built-in like `Date` / `Map` / `Set`)** → stored as-is. Walk does not descend into it;
51
+ - **Resolve a plain object or array** → walk continues into it with `objectCallback` and `keyCallback` applied to its children (same as any original nested object/array). The new walk is deferred via the `extend` mechanism, so the iteration order of the current level is preserved;
52
+ - **`reject()`** → that key is dropped from the result.
51
53
 
52
54
  ```js
53
55
  function keyCallbackFn ({ value, key, breadcrumbs, resolve, reject }) {
54
- // value: value of the property. Only primitives;
55
- // key: key of the property;
56
- // breadcrumbs: location of the property;
57
- // resolve: function that will resolve the callback promise. Provide the result as argument;
58
- // reject: function that can cancel the copy of that property;
59
- // Important: key callback should be resolved or rejected on every code path,
60
- // otherwise the walk promise never settles. See section "Timeout".
56
+ // value: value of the property;
57
+ // key: key of the property;
58
+ // breadcrumbs: location of the property;
59
+ // resolve: function. Call it with the new value to store at that key;
60
+ // reject: function. Call it to drop the key from the result;
61
+ // Important: key callback should be resolved or rejected on every code path,
62
+ // otherwise the walk promise never settles. See section "Timeout".
61
63
  }
62
64
 
63
65
  const result = await walk ({ data, keyCallback : keyCallbackFn }); // It's the short way to provide only key-callback. Callback functions are optional.
@@ -65,25 +67,29 @@ const result = await walk ({ data, keyCallback : keyCallbackFn }); // It's the
65
67
  ```
66
68
 
67
69
 
68
- ## Object-callback
70
+ ## objectCallback
69
71
 
70
- Optional callback function that is started on each object property. Function should resolve or reject. Rejection of the property will remove it from the result.
72
+ Optional callback function that is started on each object or array property, including the root. The value you `resolve` becomes the new value at that key:
73
+
74
+ - **Resolve an object or array** → walk continues into it with the other callbacks applied to its children;
75
+ - **Resolve a primitive** (string, number, `null`, etc.) → it is stored at that key as-is, and walk does not descend into it (primitives have no children to walk);
76
+ - **`reject()`** → that key is dropped from the result.
71
77
 
72
78
  ```js
73
79
  function objectCallbackFn ({ value, key, breadcrumbs, resolve, reject }) {
74
- // value: each object property during the walk;
75
- // key: key of the object property;
80
+ // value: each object/array during the walk;
81
+ // key: key of the object/array;
76
82
  // breadcrumbs: location of the object;
77
- // resolve: function that will resolve the callback promise. Provide the result as argument;
78
- // reject: function that can cancel the copy of that property;
83
+ // resolve: function. Call it with the new value to store at that key;
84
+ // reject: function. Call it to drop the key from the result;
79
85
  // Important: Object callback should be resolved or rejected on every code path,
80
86
  // otherwise the walk promise never settles. See section "Timeout".
81
87
  }
82
88
 
83
- walk ({
89
+ walk ({
84
90
  data
85
91
  , keyCallback: keyCallbackFn
86
- , objectCallback : objectCallbackFn
92
+ , objectCallback : objectCallbackFn
87
93
  })
88
94
  .then ( resultOfWalk => {
89
95
  // do something with the result of walk
@@ -92,37 +98,38 @@ walk ({
92
98
 
93
99
  **IMPORTANT: Object-callbacks are executed always before key-callbacks. If we have both callbacks, then key-callbacks will be executed on the result of object-callback.**
94
100
 
95
- Skip key-callbacks by not providing a keyCallback function.
101
+ Skip key-callbacks by not providing a keyCallback function:
96
102
  ```js
97
103
  let result = await walk ({ data }) // ignore key-callbacks
98
104
  ```
99
105
 
100
106
 
101
- ## Timeout
107
+ ## Why one callback, not a list of methods
102
108
 
103
- Every callback must call `resolve` or `reject` on every code path. If some path forgets to do it, the promise returned by `walk` will never settle — and by default there is no error and no hint which callback is the reason. The optional `timeout` property (milliseconds) turns that silent hang into a rejection with diagnostics:
109
+ `walk-async` is built around a single pass over the data — every member is visited exactly once, no matter how many transformations the callbacks perform. This is the whole reason the API exposes a callback (or two, for objects vs primitives) and not a bag of pre-built methods like `omit`, `pick`, or `set`.
110
+
111
+ Each pre-built method would be another pass over the data: dropping one key, then renaming another, then masking a third — that's three O(n) cycles where one would do. With async callbacks the cost is even worse, because each extra pass pays the full round-trip of every awaited I/O operation. The cost compounds with every method you chain, and on large data it gets expensive fast.
112
+
113
+ A single `keyCallback` (and optionally `objectCallback`) lets you do every transformation you need in the same pass — drop a key, mask a value, rename another, conditionally remove a subtree, await an API call to enrich a field — all together, no extra cycles.
114
+
115
+ If you find yourself wanting a named transformation you can reuse, the natural place for it is a **callback factory**: a function that returns a `keyCallback` / `objectCallback`. Such factories can live in a separate package; they don't need to extend `walk-async` itself.
104
116
 
105
117
  ```js
106
- function objectCallbackFn ({ value, key, breadcrumbs, resolve, reject }) {
107
- if ( breadcrumbs === 'root/props' ) return // BUG: this path never resolves
108
- resolve ( value )
118
+ // Example callback factory — not part of walk-async
119
+ function omitKeys (...keysToDrop) {
120
+ const set = new Set ( keysToDrop )
121
+ return ({ key, value, resolve, reject }) => {
122
+ if ( set.has ( key ) ) reject ()
123
+ else resolve ( value )
109
124
  }
125
+ }
110
126
 
111
- walk ({ data, objectCallback: objectCallbackFn, timeout: 5000 })
112
- .catch ( err => {
113
- console.error ( err.message )
114
- // walk-async: timed out after 5000ms; callbacks still pending:
115
- // - objectCallback at 'root/props'
116
- })
127
+ const result = await walk ({
128
+ data: user
129
+ , keyCallback: omitKeys ( 'password', 'token' )
130
+ })
117
131
  ```
118
132
 
119
- The error message lists the breadcrumbs of every callback that was started but never resolved or rejected, so the broken code path can be found directly.
120
-
121
- Notes:
122
- - `timeout` is disabled by default. Without it the behavior is exactly as before;
123
- - The limit applies to the whole walk, not to a single callback. Set it well above the worst-case duration of the legitimate async work inside the callbacks — it is a debugging safety net, not a scheduler;
124
- - When callbacks finish in time, the timer is cleared and the result is delivered as usual.
125
-
126
133
 
127
134
  ## Installation
128
135
 
@@ -146,13 +153,12 @@ import walk from '@peter.naydenov/walk-async'
146
153
  **Installation for browsers**: Get the file `"dist/walk-async.min.js"` and put it inside the project. Request the file from HTML page. Global variable 'walk' is available for use.
147
154
 
148
155
  Note:
149
- Library is using 'generator functions'. If support for old browsers
156
+ Library is using 'generator functions'. If support for old browsers
150
157
  is required, add a polyfill for 'generators'.
151
158
 
152
159
 
153
160
 
154
161
 
155
-
156
162
  ## How to use it
157
163
 
158
164
  ### Deep copy
@@ -161,8 +167,30 @@ const myCopy = await walk ({ data:x }) // where x is some javascript data stru
161
167
  ```
162
168
 
163
169
 
170
+ ### Built-in types (Date, RegExp, Map, Set, typed arrays, etc.)
171
+ `walk-async` operates on the own-enumerable-string-key model. Values whose data lives outside that model are **preserved by reference** — the same instance appears in the result:
172
+
173
+ | Type | Behavior |
174
+ | ---------------------------------------- | ----------------------------------------- |
175
+ | `Date`, `RegExp` | Passed by reference |
176
+ | `Map`, `Set`, `WeakMap`, `WeakSet` | Passed by reference |
177
+ | `ArrayBuffer`, `DataView`, typed arrays | Passed by reference |
178
+ | DOM nodes (`HTMLElement`, etc.) | Passed by reference |
179
+ | Functions | Passed by reference |
180
+
181
+ ```js
182
+ const x = { when: new Date ( '2024-01-15' ), tags: new Set ([ 'js' ]) }
183
+ const r = await walk ({ data: x })
184
+ r.when === x.when // true — same Date instance
185
+ r.tags === x.tags // true — same Set instance
186
+ ```
187
+
188
+ This is the same contract used by `function` values and DOM nodes. If you need a deep copy of a `Map`/`Set`/typed array, do it yourself before calling `walk-async`, or use the platform `structuredClone` for those particular subtrees.
189
+
164
190
 
165
191
  ### Deep 'forEach'
192
+ `keyCallback` can be used as a deep `forEach` over every primitive property of the data — no matter how deeply nested. Unlike a plain `forEach`, **the callback must call `resolve` or `reject` on every code path**: that resolution is what tells `walk-async` the per-key work is done. To walk without changing anything, `resolve(value)`.
193
+
166
194
  ```js
167
195
  let x = {
168
196
  ls : [ 1,2,3 ]
@@ -175,11 +203,11 @@ let x = {
175
203
  }
176
204
  };
177
205
 
178
- function keyFn ({value,key, breadcrumbs, resolve}) {
206
+ function keyFn ({ value, key, breadcrumbs, resolve }) {
179
207
  console.log (`${key} ----> ${value}`) // Show each each primitive couples key->value
180
208
  console.log ( `Property location >> ${breadcrumbs}`)
181
209
  // example for breadcrumbs: 'age' will looks like this : 'root/props/age'
182
- resolve ( value )
210
+ resolve ( value ) // pass-through — required, otherwise the result loses this key
183
211
  }
184
212
 
185
213
  walk ({ data:x, keyCallback: keyFn })
@@ -188,6 +216,48 @@ walk ({ data:x, keyCallback: keyFn })
188
216
  })
189
217
  ```
190
218
 
219
+ `breadcrumbs` is a slash-delimited path string starting with `root` (e.g. `"root/props/age"`). Use it to know where you are in the structure; you can `.split('/')` it if you need a path array.
220
+
221
+ > Built-in types (`Date`, `Map`, `Set`, typed arrays, etc.) are reached by `keyCallback` as-is — see [Built-in types](#built-in-types-date-regexp-map-set-typed-arrays-etc) above.
222
+
223
+
224
+ ### Skip a branch
225
+ Calling `reject()` from `objectCallback` drops the **entire subtree** at that key — not just the immediate property. Use it when you want to cut a whole section out of the result without having to walk into it and reject it key by key.
226
+
227
+ ```js
228
+ let x = {
229
+ name : 'Peter'
230
+ , password : 'secret'
231
+ , metadata : {
232
+ ip : '1.2.3.4'
233
+ , session : 'abc-123'
234
+ , device : { os: 'mac', browser: 'safari' }
235
+ }
236
+ };
237
+
238
+ // Drop a single primitive (use keyCallback)
239
+ const r1 = await walk ({
240
+ data: x
241
+ , keyCallback: ({ key, value, resolve, reject }) => {
242
+ if ( key === 'password' ) reject ()
243
+ else resolve ( value )
244
+ }
245
+ })
246
+ // r1.metadata is still fully present; only r1.password is gone.
247
+
248
+ // Drop an entire subtree (use objectCallback)
249
+ const r2 = await walk ({
250
+ data: x
251
+ , objectCallback: ({ key, value, resolve, reject }) => {
252
+ if ( key === 'metadata' ) reject ()
253
+ else resolve ( value )
254
+ }
255
+ })
256
+ // r2.password is still present; the whole r2.metadata subtree is gone.
257
+ ```
258
+
259
+ `objectCallback` fires on the root too, so this also works at the top level (e.g. to short-circuit a `walk-async` by rejecting from the root call).
260
+
191
261
 
192
262
  ### Ignore a key
193
263
 
@@ -231,9 +301,9 @@ let x = {
231
301
  , sizes : [12,33,12,21]
232
302
  }
233
303
  };
234
- walk ({
304
+ walk ({
235
305
  data:x
236
- , keyCallback : ({resolve}) => resolve('xxx')
306
+ , keyCallback : ({resolve}) => resolve('xxx')
237
307
  })
238
308
  .then ( result => {
239
309
  // 'result' will have the same structure as 'x' but all values are 'xxx'
@@ -246,7 +316,7 @@ walk ({
246
316
  // , height : 'xxx'
247
317
  // , sizes : ['xxx','xxx','xxx','xxx']
248
318
  // }
249
- // }
319
+ // }
250
320
  })
251
321
  ```
252
322
 
@@ -273,9 +343,9 @@ function objectCallback ({ value:obj, key, resolve }) {
273
343
  resolve ( obj )
274
344
  }
275
345
 
276
- walk ({
346
+ walk ({
277
347
  data: x
278
- , objectCallback
348
+ , objectCallback
279
349
  })
280
350
  .then ( result => {
281
351
  // 'result.props' will have only 'age' and 'height' properties.
@@ -286,15 +356,80 @@ walk ({
286
356
  // age : 48
287
357
  // , height : 176
288
358
  // }
289
- // }
359
+ // }
290
360
  })
291
361
  ```
292
362
 
293
363
 
364
+ ## Migrating from `@peter.naydenov/walk`
365
+
366
+ `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:
367
+
368
+ | `walk` (sync) | `walk-async` (async) |
369
+ | -------------------------------- | ---------------------------- |
370
+ | `return value` | `resolve(value)` |
371
+ | `return newObject` | `resolve(newObject)` |
372
+ | `return IGNORE` | `reject()` |
294
373
 
295
- ## Links
374
+ The options shape is identical: `{ data, keyCallback, objectCallback }`. `walk-async` adds an optional `timeout` (milliseconds). The walk call itself becomes an `await` (or `.then`).
375
+
376
+ ```js
377
+ // walk
378
+ const result = walk ({
379
+ data: user,
380
+ keyCallback: ({ value, key, IGNORE }) => key === 'password' ? IGNORE : value
381
+ })
382
+
383
+ // walk-async — same shape, just promise-based
384
+ const result = await walk ({
385
+ data: user,
386
+ keyCallback: ({ value, key, resolve, reject }) => {
387
+ if ( key === 'password' ) reject ()
388
+ else resolve ( value )
389
+ }
390
+ })
391
+ ```
392
+
393
+ 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.
394
+
395
+
396
+ ## Timeout
397
+
398
+ Every callback must call `resolve` or `reject` on every code path. If some path forgets to do it, the promise returned by `walk` will never settle — and by default there is no error and no hint which callback is the reason. The optional `timeout` property (milliseconds) turns that silent hang into a rejection with diagnostics:
399
+
400
+ ```js
401
+ function objectCallbackFn ({ value, key, breadcrumbs, resolve, reject }) {
402
+ if ( breadcrumbs === 'root/props' ) return // BUG: this path never resolves
403
+ resolve ( value )
404
+ }
405
+
406
+ walk ({ data, objectCallback: objectCallbackFn, timeout: 5000 })
407
+ .catch ( err => {
408
+ console.error ( err.message )
409
+ // walk-async: timed out after 5000ms; callbacks still pending:
410
+ // - objectCallback at 'root/props'
411
+ })
412
+ ```
413
+
414
+ The error message lists the breadcrumbs of every callback that was started but never resolved or rejected, so the broken code path can be found directly.
415
+
416
+ Notes:
417
+ - `timeout` is disabled by default. Without it the behavior is exactly as before;
418
+ - The limit applies to the whole walk, not to a single callback. Set it well above the worst-case duration of the legitimate async work inside the callbacks — it is a debugging safety net, not a scheduler;
419
+ - When callbacks finish in time, the timer is cleared and the result is delivered as usual.
420
+
421
+
422
+ ## Limitations
423
+ - `walk-async` does not descend into built-in types (`Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, `ArrayBuffer`, `DataView`, typed arrays, DOM nodes, functions) — they are passed by reference, whether they appear in the input or are returned by a callback;
424
+ - `walk-async` can not execute another `walk-async` from inside the callbacks;
425
+ - The walk promise does not settle until every callback has resolved or rejected. Use the `timeout` option to turn a forgotten `resolve`/`reject` into a rejection with diagnostics.
426
+
427
+
428
+ ## See also
429
+ - [`@peter.naydenov/walk`](https://github.com/PeterNaydenov/walk) — the sync sibling. Same callback shape, same three-outcome contract, no promise machinery. Use it when your callbacks don't need async work.
296
430
  - [Release history](Changelog.md)
297
431
 
432
+
298
433
  ## Credits
299
434
  '@peter.naydenov/walk-async' was created and supported by Peter Naydenov.
300
435
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peter.naydenov/walk-async",
3
- "version": "3.1.3",
3
+ "version": "6.0.0",
4
4
  "description": "Deep copy with fine control during the process. Can be used also as a deep forEach function",
5
5
  "main": ".dist/walk-async.umd.js",
6
6
  "type": "module",
package/src/main.js CHANGED
@@ -1,44 +1,114 @@
1
1
 
2
2
  /**
3
3
  * walk-async
4
- *
5
- * Alternative of deep-copy that provides much better control during creation of immutable
6
- * copies of javascript data structures.
7
- * Library is using 'generator functions'. If support for old browsers is required,
4
+ *
5
+ * Alternative of deep-copy that provides much better control during creation of immutable
6
+ * copies of javascript data structures.
7
+ * Library is using 'generator functions'. If support for old browsers is required,
8
8
  * add a polyfill for 'generators'.
9
- *
9
+ *
10
10
  * History notes:
11
11
  * - Walk-async was born on September 18th, 2022
12
12
  * - Converted to ES module on January 1st, 2024
13
- *
13
+ *
14
14
  */
15
15
 
16
16
 
17
17
  import askForPromise from "ask-for-promise"
18
18
 
19
19
  /**
20
- * @typedef {Object} Options
21
- * @property {Object} data - The data structure to be copied.
22
- * @property {Function|null} objectCallback - A function called for every object in the data structure.
23
- * @property {Function|null} keyCallback - A function called for every property in the data structure.
24
- * @property {number|null} [timeout] - Milliseconds. When set, the promise is rejected if callbacks do not resolve in time. Error lists the pending callbacks.
20
+ * Resolve the callback with the new value to store at the current key:
21
+ * - resolving a primitive (or a built-in like `Date` / `Map` / `Set`) → stored as-is by reference;
22
+ * - resolving a plain object or array → walk continues into it with the other callback applied to its children;
23
+ *
24
+ * You must call `resolve` or `reject` on every code path — otherwise the walk promise never settles
25
+ * (use the `timeout` option to turn that silent hang into a rejection with diagnostics).
26
+ *
27
+ * @callback Resolve
28
+ * @param {*} value
29
+ * @returns {void}
30
+ */
31
+
32
+ /**
33
+ * Reject the callback. The current key is dropped from the result.
34
+ *
35
+ * @callback Reject
36
+ * @param {*} [reason]
37
+ * @returns {void}
38
+ */
39
+
40
+ /**
41
+ * Arguments object received by both `keyCallback` and `objectCallback`.
42
+ *
43
+ * @typedef {object} CallbackArgs
44
+ * @property {*} value - The current value being processed.
45
+ * @property {string} key - Property key as a string.
46
+ * @property {string} breadcrumbs - Slash-delimited path to the current key, starting with `root` (e.g. `"root/props/age"`).
47
+ * @property {Resolve} resolve - Resolve the callback with the new value (see `Resolve` typedef).
48
+ * @property {Reject} reject - Reject the callback to drop the current key (see `Reject` typedef).
49
+ */
50
+
51
+ /**
52
+ * Called once per primitive property (string, number, bigint, boolean,
53
+ * symbol, null, undefined, function, Date, RegExp, Map, Set, WeakMap,
54
+ * WeakSet, ArrayBuffer, DataView, typed arrays, DOM nodes).
55
+ *
56
+ * Resolve with the new value to store, or reject to drop the key:
57
+ * - resolve a primitive (or a built-in like `Date` / `Map` / `Set`) → stored as-is by reference;
58
+ * - resolve a plain object or array → walk continues into it with the other callback applied to its children;
59
+ * - reject → that key is dropped from the result.
60
+ *
61
+ * @callback KeyCallback
62
+ * @param {CallbackArgs} args
63
+ * @param {...*} rest - Any extra arguments passed to `walk()` are forwarded to the callback.
64
+ * @returns {void}
25
65
  */
26
66
 
67
+ /**
68
+ * Called once per object or array property, including the root.
69
+ * The resolved value becomes the new value at that key:
70
+ * - resolve an object or array → walk continues into it with the other callbacks applied to its children;
71
+ * - resolve a primitive → it is stored at that key as-is, and walk does not descend into it (primitives have no children to walk);
72
+ * - reject → the key is dropped from the result.
73
+ *
74
+ * @callback ObjectCallback
75
+ * @param {CallbackArgs} args
76
+ * @param {...*} rest
77
+ * @returns {void}
78
+ */
27
79
 
80
+ /**
81
+ * @typedef {object} Options
82
+ * @property {*} data - Required. Any JS data structure that will be copied.
83
+ * @property {KeyCallback} [keyCallback] - Optional. Executed on each primitive property.
84
+ * @property {ObjectCallback} [objectCallback] - Optional. Executed on each object/array property, including the root.
85
+ * @property {number} [timeout] - Optional. Milliseconds. When set, the promise is rejected if callbacks do not resolve in time. Error lists the pending callbacks.
86
+ */
28
87
 
29
88
 
30
89
  /**
31
- * Walk-async is a deep copy function that executes callback functions on every object and property.
32
- * Callbacks are async functions.
90
+ * Walk-async
91
+ *
92
+ * Async sibling of `@peter.naydenov/walk`. Visits every member of a deep
93
+ * JavaScript data structure once, in a single pass, and runs two optional
94
+ * callbacks during the visit that can mask, filter, or substitute values
95
+ * as the result is built.
33
96
  *
34
- * @param {Options} options - Object with following properties:
35
- * - data - The data structure to be copied.
36
- * - objectCallback - A function that will be called for every object in the data structure.
37
- * - keyCallback - A function that will be called for every property in the data structure.
38
- * - timeout - Milliseconds. When set, the promise is rejected if callbacks do not resolve in time.
39
- * @param {...any} args - Any additional arguments will be passed to the callback functions.
97
+ * The deep copy of the original data is mostly a side-effect: the real
98
+ * power is the ability to do all your modifications in one pass, with full
99
+ * freedom inside a single callback — now with async work, awaits, and
100
+ * timeouts supported.
40
101
  *
41
- * @returns {Promise} - A promise that resolves to the immutable copy of the data structure.
102
+ * @function walk
103
+ * @param {Options} options - Required. Object with required `data` property, two optional callback functions (`keyCallback`, `objectCallback`), and an optional `timeout`.
104
+ * @param {...*} args - Optional. Additional arguments forwarded to both callbacks.
105
+ * @returns {Promise<*>} - A promise that resolves to the immutable copy of `options.data` (with the callbacks' transformations applied).
106
+ * @example
107
+ * const result = await walk ({
108
+ * data: someData,
109
+ * keyCallback: keyCallbackFn,
110
+ * objectCallback: objectCallbackFn
111
+ * })
42
112
  */
43
113
  function walk ({
44
114
  data:origin
package/types/main.d.ts CHANGED
@@ -11,43 +11,136 @@
11
11
  * - Converted to ES module on January 1st, 2024
12
12
  *
13
13
  */
14
+ export type Resolve = (value: any) => void;
15
+ export type Reject = (reason?: any) => void;
16
+ export type CallbackArgs = {
17
+ /**
18
+ * - The current value being processed.
19
+ */
20
+ value: any;
21
+ /**
22
+ * - Property key as a string.
23
+ */
24
+ key: string;
25
+ /**
26
+ * - Slash-delimited path to the current key, starting with `root` (e.g. `"root/props/age"`).
27
+ */
28
+ breadcrumbs: string;
29
+ /**
30
+ * - Resolve the callback with the new value (see `Resolve` typedef).
31
+ */
32
+ resolve: Resolve;
33
+ /**
34
+ * - Reject the callback to drop the current key (see `Reject` typedef).
35
+ */
36
+ reject: Reject;
37
+ };
38
+ export type KeyCallback = (args: CallbackArgs, ...rest: any) => void;
39
+ export type ObjectCallback = (args: CallbackArgs, ...rest: any) => void;
14
40
  export type Options = {
15
41
  /**
16
- * - The data structure to be copied.
42
+ * - Required. Any JS data structure that will be copied.
17
43
  */
18
- data: Object;
44
+ data: any;
19
45
  /**
20
- * - A function called for every object in the data structure.
46
+ * - Optional. Executed on each primitive property.
21
47
  */
22
- objectCallback: Function | null;
48
+ keyCallback?: KeyCallback;
23
49
  /**
24
- * - A function called for every property in the data structure.
50
+ * - Optional. Executed on each object/array property, including the root.
25
51
  */
26
- keyCallback: Function | null;
52
+ objectCallback?: ObjectCallback;
27
53
  /**
28
- * - Milliseconds. When set, the promise is rejected if callbacks do not resolve in time. Error lists the pending callbacks.
54
+ * - Optional. Milliseconds. When set, the promise is rejected if callbacks do not resolve in time. Error lists the pending callbacks.
29
55
  */
30
- timeout?: number | null;
56
+ timeout?: number;
31
57
  };
32
58
  /**
33
- * @typedef {Object} Options
34
- * @property {Object} data - The data structure to be copied.
35
- * @property {Function|null} objectCallback - A function called for every object in the data structure.
36
- * @property {Function|null} keyCallback - A function called for every property in the data structure.
37
- * @property {number|null} [timeout] - Milliseconds. When set, the promise is rejected if callbacks do not resolve in time. Error lists the pending callbacks.
59
+ * Resolve the callback with the new value to store at the current key:
60
+ * - resolving a primitive (or a built-in like `Date` / `Map` / `Set`) → stored as-is by reference;
61
+ * - resolving a plain object or array → walk continues into it with the other callback applied to its children;
62
+ *
63
+ * You must call `resolve` or `reject` on every code path — otherwise the walk promise never settles
64
+ * (use the `timeout` option to turn that silent hang into a rejection with diagnostics).
65
+ *
66
+ * @callback Resolve
67
+ * @param {*} value
68
+ * @returns {void}
38
69
  */
39
70
  /**
40
- * Walk-async is a deep copy function that executes callback functions on every object and property.
41
- * Callbacks are async functions.
71
+ * Reject the callback. The current key is dropped from the result.
72
+ *
73
+ * @callback Reject
74
+ * @param {*} [reason]
75
+ * @returns {void}
76
+ */
77
+ /**
78
+ * Arguments object received by both `keyCallback` and `objectCallback`.
79
+ *
80
+ * @typedef {object} CallbackArgs
81
+ * @property {*} value - The current value being processed.
82
+ * @property {string} key - Property key as a string.
83
+ * @property {string} breadcrumbs - Slash-delimited path to the current key, starting with `root` (e.g. `"root/props/age"`).
84
+ * @property {Resolve} resolve - Resolve the callback with the new value (see `Resolve` typedef).
85
+ * @property {Reject} reject - Reject the callback to drop the current key (see `Reject` typedef).
86
+ */
87
+ /**
88
+ * Called once per primitive property (string, number, bigint, boolean,
89
+ * symbol, null, undefined, function, Date, RegExp, Map, Set, WeakMap,
90
+ * WeakSet, ArrayBuffer, DataView, typed arrays, DOM nodes).
91
+ *
92
+ * Resolve with the new value to store, or reject to drop the key:
93
+ * - resolve a primitive (or a built-in like `Date` / `Map` / `Set`) → stored as-is by reference;
94
+ * - resolve a plain object or array → walk continues into it with the other callback applied to its children;
95
+ * - reject → that key is dropped from the result.
96
+ *
97
+ * @callback KeyCallback
98
+ * @param {CallbackArgs} args
99
+ * @param {...*} rest - Any extra arguments passed to `walk()` are forwarded to the callback.
100
+ * @returns {void}
101
+ */
102
+ /**
103
+ * Called once per object or array property, including the root.
104
+ * The resolved value becomes the new value at that key:
105
+ * - resolve an object or array → walk continues into it with the other callbacks applied to its children;
106
+ * - resolve a primitive → it is stored at that key as-is, and walk does not descend into it (primitives have no children to walk);
107
+ * - reject → the key is dropped from the result.
108
+ *
109
+ * @callback ObjectCallback
110
+ * @param {CallbackArgs} args
111
+ * @param {...*} rest
112
+ * @returns {void}
113
+ */
114
+ /**
115
+ * @typedef {object} Options
116
+ * @property {*} data - Required. Any JS data structure that will be copied.
117
+ * @property {KeyCallback} [keyCallback] - Optional. Executed on each primitive property.
118
+ * @property {ObjectCallback} [objectCallback] - Optional. Executed on each object/array property, including the root.
119
+ * @property {number} [timeout] - Optional. Milliseconds. When set, the promise is rejected if callbacks do not resolve in time. Error lists the pending callbacks.
120
+ */
121
+ /**
122
+ * Walk-async
123
+ *
124
+ * Async sibling of `@peter.naydenov/walk`. Visits every member of a deep
125
+ * JavaScript data structure once, in a single pass, and runs two optional
126
+ * callbacks during the visit that can mask, filter, or substitute values
127
+ * as the result is built.
42
128
  *
43
- * @param {Options} options - Object with following properties:
44
- * - data - The data structure to be copied.
45
- * - objectCallback - A function that will be called for every object in the data structure.
46
- * - keyCallback - A function that will be called for every property in the data structure.
47
- * - timeout - Milliseconds. When set, the promise is rejected if callbacks do not resolve in time.
48
- * @param {...any} args - Any additional arguments will be passed to the callback functions.
129
+ * The deep copy of the original data is mostly a side-effect: the real
130
+ * power is the ability to do all your modifications in one pass, with full
131
+ * freedom inside a single callback — now with async work, awaits, and
132
+ * timeouts supported.
49
133
  *
50
- * @returns {Promise} - A promise that resolves to the immutable copy of the data structure.
134
+ * @function walk
135
+ * @param {Options} options - Required. Object with required `data` property, two optional callback functions (`keyCallback`, `objectCallback`), and an optional `timeout`.
136
+ * @param {...*} args - Optional. Additional arguments forwarded to both callbacks.
137
+ * @returns {Promise<*>} - A promise that resolves to the immutable copy of `options.data` (with the callbacks' transformations applied).
138
+ * @example
139
+ * const result = await walk ({
140
+ * data: someData,
141
+ * keyCallback: keyCallbackFn,
142
+ * objectCallback: objectCallbackFn
143
+ * })
51
144
  */
52
145
  declare function walk({ data: origin, objectCallback, keyCallback, timeout }: Options, ...args: any[]): Promise<any>;
53
146
  export default walk;
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.js"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAMA,YAAkB,OAAO,GACzB;;;;IAAmB,IAAI,EAAZ,MAAM,CACjB;;;;IAA0B,cAAc,EAA7B,WAAS,IAAI,CACxB;;;;IAA0B,WAAW,EAA1B,WAAS,IAAI,CACxB;;;;IAAyB,OAAO,AAAhC,CACF,EADa,MAAM,GAAC,IAAI,CACxB;CAAA,CAAA;AAND;;;;;;GAMG;AAKH;;;;;;;;;;;;GAYG;AACH,iBAAS,IAAI,CAAE,EACG,IAAI,EAAC,MAAM,EACX,cAAqB,EACrB,WAAkB,EAClB,OAAc,EACnB,EAdF,OAcE,EAAE,GAAG,IAAI,EATR,GAAG,EASK,gBA8DrB;eA4Lc,IAAI"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.js"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAaC,YAAU,OAAO,GACjB,CAAW,KAAK,EAAR,GACR,KAAU,IACb,CAAA;AAKG,YAAU,MAAM,GAChB,CAAY,MAAM,AAAlB,CACA,EADQ,GACR,KAAU,IACb,CAAA;AAKG,YAAkB,YAAY,GAC9B;;;;IAAuB,KAAK,EAAjB,GAAC,CACZ;;;;IAAuB,GAAG,EAAf,MAAM,CACjB;;;;IAAuB,WAAW,EAAvB,MAAM,CACjB;;;;IAAuB,OAAO,EAAnB,OAAO,CAClB;;;;IAAuB,MAAM,EAAlB,MAAM,CACpB;CAAA,CAAA;AAYG,YAAU,WAAW,GACrB,CAAsB,IAAI,EAAlB,YACR,EAAA,GACA,AADsB,IAAI,EAAf,GACX,KAAU,IACb,CAAA;AASG,YAAU,cAAc,GACxB,CAAsB,IAAI,EAAlB,YACR,EAAA,GACA,AADsB,IAAI,EAAf,GACX,KAAU,IACb,CAAA;AAGG,YAAkB,OAAO,GACzB;;;;IAA0B,IAAI,EAAnB,GAAC,CACZ;;;;IAA4B,WAAW,AAAvC,CACA,EADW,WAAW,CACtB;;;;IAA4B,cAAc,AAA1C,CACA,EADW,cAAc,CACzB;;;;IAA4B,OAAO,AAAnC,CACH,EADc,MAAM,CACpB;CAAA,CAAA;AAnED;;;;;;;;;;;GAWG;AAEH;;;;;;GAMG;AAEH;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;GAWG;AAEH;;;;;;GAMG;AAGH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,iBAAS,IAAI,CAAE,EACG,IAAI,EAAC,MAAM,EACX,cAAqB,EACrB,WAAkB,EAClB,OAAc,EACnB,EAfD,OAeC,EAAE,GAAG,IAAI,EAdP,GAAC,EAcM,GAbR,OAAO,CAAC,GAAC,CAAC,CA2EvB;eA4Lc,IAAI"}