@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 +35 -4
- package/Migration.guide.md +179 -25
- package/README.md +501 -311
- package/dist/walk-async.cjs +1 -1
- package/dist/walk-async.esm.mjs +1 -1
- package/dist/walk-async.umd.js +1 -1
- package/package.json +7 -6
- package/src/copyObject.js +145 -0
- package/src/createCallbackArgs.js +15 -0
- package/src/findType.js +28 -0
- package/src/finish.js +38 -0
- package/src/ignore.js +24 -0
- package/src/main.js +230 -285
- package/src/pass.js +36 -0
- package/src/runCallback.js +55 -0
- package/src/setKey.js +12 -0
- package/src/validateForInsertion.js +12 -0
- package/types/copyObject.d.ts +8 -0
- package/types/copyObject.d.ts.map +1 -0
- package/types/createCallbackArgs.d.ts +32 -0
- package/types/createCallbackArgs.d.ts.map +1 -0
- package/types/findType.d.ts +3 -0
- package/types/findType.d.ts.map +1 -0
- package/types/finish.d.ts +25 -0
- package/types/finish.d.ts.map +1 -0
- package/types/ignore.d.ts +10 -0
- package/types/ignore.d.ts.map +1 -0
- package/types/main.d.ts +147 -61
- package/types/main.d.ts.map +1 -1
- package/types/pass.d.ts +23 -0
- package/types/pass.d.ts.map +1 -0
- package/types/runCallback.d.ts +3 -0
- package/types/runCallback.d.ts.map +1 -0
- package/types/setKey.d.ts +3 -0
- package/types/setKey.d.ts.map +1 -0
- package/types/validateForInsertion.d.ts +3 -0
- package/types/validateForInsertion.d.ts.map +1 -0
- package/.agents/skills/git-walk-async/SKILL.md +0 -173
- package/types/src/main.d.ts +0 -35
- package/types/src/main.d.ts.map +0 -1
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: git-walk-async
|
|
3
|
-
description: |
|
|
4
|
-
Help developers use `@peter.naydenov/walk-async` (the async `walk` from
|
|
5
|
-
the git-walk-async project, v6.0.0): write a deep copy with `await
|
|
6
|
-
walk({ data, keyCallback?, objectCallback?, timeout? })`, use the
|
|
7
|
-
`resolve` / `reject` pair inside callbacks to store or drop values, and
|
|
8
|
-
leverage the `timeout` option as a debugging safety net. Use when the
|
|
9
|
-
developer asks for a deep copy with simultaneous async transforms, a
|
|
10
|
-
deep `forEach` that can `await` work and drop or rewrite values, a
|
|
11
|
-
single-pass async clone that filters keys by name, or anything phrased
|
|
12
|
-
as "async deep copy / async deep clone / async deep walk / async deep
|
|
13
|
-
forEach" where the transforms do real async I/O. Do NOT use for: sync
|
|
14
|
-
walks (point to the sibling `@peter.naydenov/walk` instead),
|
|
15
|
-
deep-copying a `Map` / `Set` / `Date` / typed array (use
|
|
16
|
-
`structuredClone` for those), retry / abort semantics, or fixing bugs
|
|
17
|
-
in the library itself.
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
# git-walk-async helper
|
|
21
|
-
|
|
22
|
-
A single-pass deep walk over a JavaScript data structure that runs two
|
|
23
|
-
optional async callbacks during the visit. Each callback can `await`
|
|
24
|
-
work and reports back via `resolve(value)` or `reject()`. A plain deep
|
|
25
|
-
copy is the no-callback case.
|
|
26
|
-
|
|
27
|
-
This is the **async sibling** of `@peter.naydenov/walk`. If your
|
|
28
|
-
callbacks don't actually need `await`, prefer the sync version — it's
|
|
29
|
-
noticeably faster because it avoids the promise machinery.
|
|
30
|
-
|
|
31
|
-
Source of truth:
|
|
32
|
-
- `src/main.js` — JSDoc on every public function and typedef (`walk`, `keyCallback`, `objectCallback`, `CallbackArgs`, `Resolve`, `Reject`, `Options`)
|
|
33
|
-
- `test/01-deepCopy.test.js` — plain deep copy
|
|
34
|
-
- `test/02-keyCallback.test.js` — primitive transforms
|
|
35
|
-
- `test/03-objectCallback.test.js` — object/array transforms
|
|
36
|
-
- `test/04-async.test.js` — real async work in callbacks
|
|
37
|
-
- `test/05-bothCallbacks.test.js` — interaction between the two
|
|
38
|
-
- `test/06-builtins.test.js` — Date / Map / Set / typed arrays / DOM nodes
|
|
39
|
-
- `README.md` — narrative docs (lead paragraph, "When to use `walk-async` vs `structuredClone`" callout, "Migrating from `@peter.naydenov/walk`" table, "Timeout" section, "Limitations")
|
|
40
|
-
|
|
41
|
-
## Procedure
|
|
42
|
-
|
|
43
|
-
1. **Map the developer's intent to the right shape of `await walk({ data, ... })` call**:
|
|
44
|
-
- "Just deep-copy this" / "give me an immutable copy" → `await walk({ data })` (no callbacks)
|
|
45
|
-
- "Drop a few keys while copying" → `await walk({ data, keyCallback })`; call `reject()` for the keys to drop
|
|
46
|
-
- "Mask a value" / "rewrite a value" → `await walk({ data, keyCallback })`; call `resolve(newValue)`
|
|
47
|
-
- "Skip a whole subtree" → `await walk({ data, objectCallback })`; call `reject()` at the right key
|
|
48
|
-
- "Await something inside the walk" (DB lookup, fetch, fs.readFile) → `await walk({ data, keyCallback: async ({ resolve, value, key }) => { const fresh = await fetch(value); resolve(fresh) } })`
|
|
49
|
-
- "Run a function on every leaf" (deep `forEach` with side effects) → `await walk({ data, keyCallback })`; the callback must still call `resolve(value)` (see gotcha below)
|
|
50
|
-
|
|
51
|
-
2. **Generate code that follows the real API contract**:
|
|
52
|
-
- ESM import: `import walk from '@peter.naydenov/walk-async'` (CJS: `require('@peter.naydenov/walk-async')`)
|
|
53
|
-
- **The call returns a Promise** — `await` it, or chain `.then`. Forgetting this is the #1 mistake.
|
|
54
|
-
- Options shape: `{ data, keyCallback?, objectCallback?, timeout? }`. Same names as the sync `walk`, plus `timeout`.
|
|
55
|
-
- Callback signature is `({ value, key, breadcrumbs, resolve, reject }, ...args)`. Always destructure the five fields from the first arg.
|
|
56
|
-
- Extra positional args passed to `walk` are forwarded to both callbacks as `...args`. Use this for context the callbacks need but you don't want to close over.
|
|
57
|
-
- **There is no `IGNORE` symbol.** The way to drop a key is `reject()` — that's it. The internal `IGNORE = Symbol('ignore___')` is private implementation detail; do not import, construct, or return it from user code. Returning it from a callback is a bug (it would be treated as a value to store, not a drop signal).
|
|
58
|
-
- The callback MUST call `resolve(...)` or `reject()` on every code path. If it doesn't, the walk promise never settles. The `timeout` option is the recommended way to catch this in development.
|
|
59
|
-
- `resolve` accepts anything a sync `walk` would accept: a primitive, a built-in like `Date` / `Map` / `Set`, or a plain object/array. A plain object/array is walked into (deferred). A `Date` / `Map` / `Set` is stored by reference.
|
|
60
|
-
|
|
61
|
-
3. **Apply the order-of-execution rules**:
|
|
62
|
-
- Within one level, keys are visited in `Object.keys` order on the current object/array.
|
|
63
|
-
- A level finishes before any nested walk starts. The new walk into a returned object/array is deferred.
|
|
64
|
-
- For the same value, `objectCallback` runs before `keyCallback`. If `objectCallback` resolves a new object, that object is what `keyCallback` sees when it processes the children.
|
|
65
|
-
- The root goes through `objectCallback` first (if defined), then its children are walked. Rejecting from the root `objectCallback` short-circuits the whole walk to `[]` (for array roots) or `{}` (for object roots).
|
|
66
|
-
- **Within a level, all keys are started concurrently** — they don't wait for each other to resolve before the next one is fired. They just have to finish before the next level begins. (This is the part where the async version earns its keep — `await` work in callbacks runs in parallel for free.)
|
|
67
|
-
|
|
68
|
-
4. **Surface only the relevant gotcha proactively** — pick at most one from the list below that applies to the current example, and only if the user is unlikely to know it:
|
|
69
|
-
- **The biggest trap: `walk-async` is `await`able.** A no-`await` call silently drops the result. `walk(...)` alone returns a Promise, not the copy.
|
|
70
|
-
- **Don't return values from callbacks.** Returning `value` or `IGNORE` from a callback is a sync-API leak — the walk ignores the return. The right call is `resolve(value)` or `reject()`. If a user just ported from sync `walk` and writes `return value` in their callback, their result will be `{}` (the empty container, because nothing was ever stored).
|
|
71
|
-
- **Callbacks must call `resolve` or `reject` on every code path.** A path that returns early or `throw`s without calling either will leave the walk promise pending forever. Either wire every path explicitly, or set `timeout` during development to surface these as a clear rejection with breadcrumbs of the stuck callbacks.
|
|
72
|
-
- **Don't `await walk-async` from inside a callback.** That's the first item on the Limitations list in the README — it corrupts the in-progress walk. If you need recursion, let the library handle it by `resolve`-ing a plain object/array from the callback.
|
|
73
|
-
- **Built-in types are not walked.** `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, `ArrayBuffer`, `DataView`, typed arrays, DOM nodes, and functions are passed by reference. If the user wants a deep clone of a `Map`/`Set`/typed array, recommend `structuredClone` for that subtree, or do it themselves before calling `walk-async`.
|
|
74
|
-
- **`'simple'` is the storage type, not the return type.** A plain object or array resolved from `keyCallback` is re-typed and walked into (deferred). A `Date` resolved from `keyCallback` is `'simple'` and stored by reference. This is the v6.0.0 contract.
|
|
75
|
-
|
|
76
|
-
5. **If the request is "I just need a deep clone with no transforms"** (or the user doesn't actually need async), point them at `structuredClone` first (built into the platform, no dependency, no promise), and mention the sync `walk` as a fallback. Only use `walk-async` when real async I/O is happening inside the callbacks.
|
|
77
|
-
|
|
78
|
-
6. **If the request is for a sync walk**, route to `@peter.naydenov/walk` (separate package, similar interface, no promise machinery). The two are kept in sync semantically — same callback args, same three-outcome contract — but the sync version is faster when you don't need `await`.
|
|
79
|
-
|
|
80
|
-
## Output contract
|
|
81
|
-
|
|
82
|
-
- One focused code snippet, ESM by default (CJS if asked)
|
|
83
|
-
- The snippet must `await` the `walk` call (or chain `.then`); never produce a snippet that calls `walk` and ignores the returned Promise
|
|
84
|
-
- One line of context explaining which option(s) are used and why
|
|
85
|
-
- A pointer to the relevant source/test section if the developer wants to dig deeper
|
|
86
|
-
- Surface at most one relevant gotcha proactively, only if it applies to the example
|
|
87
|
-
- Never include a code example that uses `return value` / `return IGNORE` from a callback (those are the sync API)
|
|
88
|
-
- Never include a code example that `await`s `walk-async` from inside one of its own callbacks
|
|
89
|
-
|
|
90
|
-
## Failure handling
|
|
91
|
-
|
|
92
|
-
- The developer's use case genuinely ambiguous (e.g., "deep clone this") → point them at `structuredClone` for the no-deps no-callback case, then `walk-async` only if they want callbacks that `await`
|
|
93
|
-
- Developer reports a bug or unexpected behavior in `walk-async` itself → do NOT try to fix from this skill; route to the project source or maintainer
|
|
94
|
-
- Developer wants a walk that never settles, or hits the timeout, or sees the "callbacks still pending" error → point at the `Timeout` section in the README and the callback "every code path resolves" rule
|
|
95
|
-
- Developer asks for an API `walk-async` doesn't have (e.g., `walk.pick(...)`, `walk.omit(...)`) → do not invent it; point at the callback-factory pattern in the README ("Why one callback, not a list of methods" section) where such helpers belong outside the library
|
|
96
|
-
|
|
97
|
-
## Examples
|
|
98
|
-
|
|
99
|
-
**"Fetch fresh data for every URL string in the object, drop the rest"**
|
|
100
|
-
|
|
101
|
-
```js
|
|
102
|
-
import walk from '@peter.naydenov/walk-async'
|
|
103
|
-
|
|
104
|
-
const result = await walk({
|
|
105
|
-
data: source,
|
|
106
|
-
keyCallback: async ({ value, resolve, reject }) => {
|
|
107
|
-
if (typeof value !== 'string' || !/^https?:\/\//.test(value)) {
|
|
108
|
-
reject() // drop non-URL primitives
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
const r = await fetch(value)
|
|
113
|
-
resolve(await r.json())
|
|
114
|
-
} catch (e) {
|
|
115
|
-
reject() // drop on network failure
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
timeout: 5000, // surfaces forgotten resolve/reject with breadcrumbs
|
|
119
|
-
})
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
`await` is mandatory — without it you have a Promise, not a result. `reject()` (no argument) is the only way to drop a key. The `timeout` is a debugging safety net; remove or raise it for production. See `test/04-async.test.js` for the executable pattern.
|
|
123
|
-
|
|
124
|
-
**"Drop a whole `metadata` subtree"**
|
|
125
|
-
|
|
126
|
-
```js
|
|
127
|
-
import walk from '@peter.naydenov/walk-async'
|
|
128
|
-
|
|
129
|
-
const result = await walk({
|
|
130
|
-
data: user,
|
|
131
|
-
objectCallback: ({ key, value, resolve, reject }) =>
|
|
132
|
-
key === 'metadata' ? reject() : resolve(value)
|
|
133
|
-
})
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Rejecting from `objectCallback` drops the entire subtree (not just the immediate property). `keyCallback` is not used here, so primitive keys inside `metadata` are never visited. See `test/03-objectCallback.test.js`.
|
|
137
|
-
|
|
138
|
-
**"Deep `forEach` — log every leaf"**
|
|
139
|
-
|
|
140
|
-
```js
|
|
141
|
-
import walk from '@peter.naydenov/walk-async'
|
|
142
|
-
|
|
143
|
-
await walk({
|
|
144
|
-
data: tree,
|
|
145
|
-
keyCallback: ({ value, key, breadcrumbs, resolve }) => {
|
|
146
|
-
console.log(`${breadcrumbs}/${key} = ${value}`)
|
|
147
|
-
resolve(value) // pass-through — required, otherwise this key is dropped
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
The callback MUST call `resolve` or `reject` on every code path. A missing call leaves the walk pending — if you suspect this in your own code, set `timeout: 1000` during dev and look at which callback the rejection names. The `breadcrumbs` argument is a slash-delimited path starting with `'root'`, e.g. `'root/props/age'`. See "Deep 'forEach'" in `README.md`.
|
|
153
|
-
|
|
154
|
-
**"Port a sync `walk` call to `walk-async`"**
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
// walk (sync)
|
|
158
|
-
const result = walk ({
|
|
159
|
-
data: user,
|
|
160
|
-
keyCallback: ({ value, key, IGNORE }) => key === 'password' ? IGNORE : value
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
// walk-async — same shape, just promise-based
|
|
164
|
-
const result = await walk ({
|
|
165
|
-
data: user,
|
|
166
|
-
keyCallback: ({ value, key, resolve, reject }) => {
|
|
167
|
-
if ( key === 'password' ) reject () // was: return IGNORE
|
|
168
|
-
else resolve ( value ) // was: return value
|
|
169
|
-
}
|
|
170
|
-
})
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
The mechanical translation: `return X` → `resolve(X)`, `return IGNORE` → `reject()`. Add `await` to the call. Add `timeout` during dev to surface any path that forgets to resolve/reject. The "Migrating from `@peter.naydenov/walk`" section in `README.md` has the full table.
|
package/types/src/main.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export default walk;
|
|
2
|
-
export type Options = {
|
|
3
|
-
/**
|
|
4
|
-
* - The data structure to be copied.
|
|
5
|
-
*/
|
|
6
|
-
data: Object;
|
|
7
|
-
/**
|
|
8
|
-
* - A function called for every object in the data structure.
|
|
9
|
-
*/
|
|
10
|
-
objectCallback: Function | null;
|
|
11
|
-
/**
|
|
12
|
-
* - A function called for every property in the data structure.
|
|
13
|
-
*/
|
|
14
|
-
keyCallback: Function | null;
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {Object} Options
|
|
18
|
-
* @property {Object} data - The data structure to be copied.
|
|
19
|
-
* @property {Function|null} objectCallback - A function called for every object in the data structure.
|
|
20
|
-
* @property {Function|null} keyCallback - A function called for every property in the data structure.
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* Walk-async is a deep copy function that executes callback functions on every object and property.
|
|
24
|
-
* Callbacks are async functions.
|
|
25
|
-
*
|
|
26
|
-
* @param {Options} options - Object with following properties:
|
|
27
|
-
* - data - The data structure to be copied.
|
|
28
|
-
* - objectCallback - A function that will be called for every object in the data structure.
|
|
29
|
-
* - keyCallback - A function that will be called for every property in the data structure.
|
|
30
|
-
* @param {...any} args - Any additional arguments will be passed to the callback functions.
|
|
31
|
-
*
|
|
32
|
-
* @returns {Promise} - A promise that resolves to the immutable copy of the data structure.
|
|
33
|
-
*/
|
|
34
|
-
declare function walk({ data: origin, objectCallback, keyCallback }: Options, ...args: any[]): Promise<any>;
|
|
35
|
-
//# sourceMappingURL=main.d.ts.map
|
package/types/src/main.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";;;;;UAoBc,MAAM;;;;oBACN,WAAS,IAAI;;;;iBACb,WAAS,IAAI;;AAJ3B;;;;;GAKG;AAKH;;;;;;;;;;;GAWG;AACH,qEARW,OAAO,WAIJ,GAAG,EAAA,gBAmChB"}
|