objwalk 1.4.0 → 1.6.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/README.md +128 -9
- package/dist/index.cjs +102 -51
- package/dist/index.d.cts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.js +100 -51
- package/package.json +1 -1
- package/skills/objwalk/SKILL.md +61 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Small utilities for reading, writing, walking, and reshaping nested JavaScript objects.
|
|
4
4
|
|
|
5
|
-
`objwalk` is built around a compact path syntax, so the same mental model works for picking
|
|
5
|
+
`objwalk` is built around a compact path syntax, so the same mental model works for picking, setting, and deleting values, extracting a new object shape, and walking through nested data.
|
|
6
6
|
|
|
7
7
|
> Coding agent? Load the [objwalk skill](skills/objwalk/SKILL.md).
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ npm install objwalk
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import { each, equally, extract, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
18
|
+
import { diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## pick
|
|
@@ -96,6 +96,38 @@ set(data, 'items[0].title', 'First')
|
|
|
96
96
|
|
|
97
97
|
`set` mutates the object passed to it and returns the same object.
|
|
98
98
|
|
|
99
|
+
## flush
|
|
100
|
+
|
|
101
|
+
Delete a property by path using native `delete` semantics.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const data = { user: { name: 'Ada', role: 'admin' } }
|
|
105
|
+
|
|
106
|
+
flush(data, 'user.name')
|
|
107
|
+
// { user: { role: 'admin' } }
|
|
108
|
+
|
|
109
|
+
flush({}, 'path.to.key') // {}
|
|
110
|
+
flush({ user: null }, 'user.name') // { user: null }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`flush(data, path)` mutates and returns the same object. Missing paths, `null`,
|
|
114
|
+
`undefined`, and primitive parents are left unchanged. It does not create intermediate
|
|
115
|
+
containers or remove empty parents. An empty path leaves the object unchanged.
|
|
116
|
+
Only own properties are traversed and deleted; inherited properties are left unchanged.
|
|
117
|
+
|
|
118
|
+
Array indexes use the same explicit syntax as `pick` and `set`:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const data = { items: [{ title: 'First' }, { title: 'Second' }] }
|
|
122
|
+
|
|
123
|
+
flush(data, 'items[0].title') // { items: [{}, { title: 'Second' }] }
|
|
124
|
+
flush(data, 'items[1]') // { items: [{}, <empty item>] }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Deleting an array item leaves a hole, preserves its length, and does not shift later items.
|
|
128
|
+
Selection syntax (`{name,email}`, `{*}`) and `[n]` mappings are not supported by `flush`.
|
|
129
|
+
Errors from getters, proxies, or deleting non-configurable properties propagate to the caller.
|
|
130
|
+
|
|
99
131
|
## equally
|
|
100
132
|
|
|
101
133
|
Deeply compare two values for structural equality.
|
|
@@ -111,6 +143,55 @@ equally({ a: 1 }, { a: 1, b: 2 })
|
|
|
111
143
|
`equally` treats `NaN` as equal to `NaN`, compares `Date` by timestamp and `RegExp` by
|
|
112
144
|
source and flags, and follows circular references without looping.
|
|
113
145
|
|
|
146
|
+
## diff
|
|
147
|
+
|
|
148
|
+
Describe changes from `first` to `last` as a list of `set` and `delete` operations.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { diff, type DiffOperation } from 'objwalk'
|
|
152
|
+
|
|
153
|
+
const operations: DiffOperation[] = diff(
|
|
154
|
+
{ hp: 10, target: 'enemy', position: { x: 1, y: 2 } },
|
|
155
|
+
{ hp: null, position: { x: 3, y: 2 } }
|
|
156
|
+
)
|
|
157
|
+
// [
|
|
158
|
+
// { type: 'set', path: ['hp'], value: null },
|
|
159
|
+
// { type: 'set', path: ['position', 'x'], value: 3 },
|
|
160
|
+
// { type: 'delete', path: ['target'] },
|
|
161
|
+
// ]
|
|
162
|
+
|
|
163
|
+
diff({}, { a: undefined })
|
|
164
|
+
// [{ type: 'set', path: ['a'], value: undefined }]
|
|
165
|
+
|
|
166
|
+
diff({ a: 1 }, {})
|
|
167
|
+
// [{ type: 'delete', path: ['a'] }]
|
|
168
|
+
|
|
169
|
+
diff(1, null)
|
|
170
|
+
// [{ type: 'set', path: [], value: null }]
|
|
171
|
+
|
|
172
|
+
diff({ a: 1 }, { a: 1 }) // []
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- When both values are plain objects (with `Object.prototype` or a null prototype),
|
|
176
|
+
`diff` recursively compares own enumerable string keys. Symbol and non-enumerable
|
|
177
|
+
keys are ignored during that traversal, and prototype or descriptor changes are not reported.
|
|
178
|
+
- Arrays and other values use `equally`; changed values are replaced as a whole.
|
|
179
|
+
Newly added properties also use a single `set`, including when their value is an empty object.
|
|
180
|
+
- A `set` carries the new value, including `null` or `undefined`; a `delete` means
|
|
181
|
+
the key is absent from the final object's enumerable keys. Empty parents remain.
|
|
182
|
+
- Paths are arrays of literal string keys, so dots, brackets, and empty keys are unambiguous.
|
|
183
|
+
An empty path `[]` means replacement of the root. These are not the string paths
|
|
184
|
+
accepted by `pick`, `set`, or `flush`; joining segments with `.` can lose information.
|
|
185
|
+
- Operations follow the final object's key order, recursively, then report removed
|
|
186
|
+
keys in the first object's key order at each level. No changes return `[]`.
|
|
187
|
+
|
|
188
|
+
`diff(first, last): DiffOperation[]` does not mutate or clone either input. Values in `set`
|
|
189
|
+
operations retain references from `last`; this is not an independent snapshot.
|
|
190
|
+
Getter and proxy errors propagate. Inputs must be acyclic; cycle detection is not performed.
|
|
191
|
+
Shared references without cycles are supported. There is no options argument or patch applier.
|
|
192
|
+
Native JSON serialization drops an `undefined` operation value, so these operations are not
|
|
193
|
+
a lossless JSON transport format for arbitrary JavaScript values.
|
|
194
|
+
|
|
114
195
|
## merge
|
|
115
196
|
|
|
116
197
|
Deeply merge plain objects into a target object.
|
|
@@ -184,16 +265,52 @@ extract(source, {
|
|
|
184
265
|
Walk through an object and call a function for each value.
|
|
185
266
|
|
|
186
267
|
```ts
|
|
187
|
-
each(data, (key, value, path) => {
|
|
188
|
-
console.log(key, value, path)
|
|
268
|
+
each(data, (key, value, path, segments) => {
|
|
269
|
+
console.log(key, value, path, segments)
|
|
189
270
|
})
|
|
190
271
|
```
|
|
191
272
|
|
|
192
273
|
The callback receives:
|
|
193
274
|
|
|
194
|
-
- `key`: the current object key or array index
|
|
275
|
+
- `key`: the current object key or numeric array index; `undefined` for the root when enabled
|
|
195
276
|
- `value`: the current value
|
|
196
|
-
- `path`: the full path to the current value
|
|
277
|
+
- `path`: the full string path to the current value
|
|
278
|
+
- `segments`: literal path segments (`WalkKey[]`), with string object keys and numeric array indexes
|
|
279
|
+
|
|
280
|
+
For example, a literal key `'user.name'` has segments `['user.name']`, while a nested
|
|
281
|
+
`user.name` has `['user', 'name']`. `items[0].id` has `['items', 0, 'id']`.
|
|
282
|
+
The first three callback arguments keep their existing meaning; old callbacks still work.
|
|
283
|
+
Callback return values are ignored, including `false`.
|
|
284
|
+
|
|
285
|
+
Pass options as the third argument:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
each(data, (key, value, path, segments) => {
|
|
289
|
+
// The root is visited first with key undefined, path '', and segments [].
|
|
290
|
+
}, {
|
|
291
|
+
root: true,
|
|
292
|
+
// Visit arrays themselves, but skip their contents.
|
|
293
|
+
descend: ({ value }) => Array.isArray(value) === false,
|
|
294
|
+
})
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
| Option | Behavior | Default |
|
|
298
|
+
| --- | --- | --- |
|
|
299
|
+
| `root` | Visit the root, including primitive, nullish, or empty root values | `false` |
|
|
300
|
+
| `descend` | Receive `{ key, value, path, segments }` and decide whether to visit a container's children | Visit all children |
|
|
301
|
+
|
|
302
|
+
`descend` runs only for objects and arrays, after their callback. It also runs for the
|
|
303
|
+
root container when `root` is false, allowing the entire traversal to be skipped.
|
|
304
|
+
Returning `false` skips children, without evaluating their getters; the container itself
|
|
305
|
+
is still visited when applicable. Errors from callbacks, getters, and `descend` propagate.
|
|
306
|
+
Mutating the received `segments` arrays does not change traversal paths.
|
|
307
|
+
|
|
308
|
+
`EachCallback`, `EachEntry`, and `EachOptions` are public named type exports.
|
|
309
|
+
Without `root`, or with `root: false`, callback keys remain `WalkKey`. With `root: true`
|
|
310
|
+
or a dynamic `root` option, keys are `WalkKey | undefined`. An explicitly typed callback
|
|
311
|
+
can use `EachCallback<WalkKey | undefined>` when root visits are possible.
|
|
312
|
+
Traversal does not detect cycles. `diff` uses `each` with `root: true` and a `descend`
|
|
313
|
+
predicate that stops at arrays, non-plain objects, and branches replaced as a whole.
|
|
197
314
|
|
|
198
315
|
## map
|
|
199
316
|
|
|
@@ -304,8 +421,8 @@ The wrappers do not mutate inputs, undo callback side effects, log errors, or re
|
|
|
304
421
|
|
|
305
422
|
| Syntax | Description |
|
|
306
423
|
| --- | --- |
|
|
307
|
-
| `user.name` | Read or
|
|
308
|
-
| `items[0].title` | Read or
|
|
424
|
+
| `user.name` | Read, write, or delete a nested property. |
|
|
425
|
+
| `items[0].title` | Read, write, or delete an array item's property by index. |
|
|
309
426
|
| `user.{name,email}` | Pick several keys from one object. |
|
|
310
427
|
| `user.{*}` | List keys from one object. |
|
|
311
428
|
| `items[n].title` | Map every item in an array during `extract`. |
|
|
@@ -324,11 +441,13 @@ them without redundant wrappers that only repeat object checks.
|
|
|
324
441
|
```ts
|
|
325
442
|
pick(data, path)
|
|
326
443
|
set(data, path, value)
|
|
444
|
+
flush(data, path)
|
|
327
445
|
extract(data, mapping)
|
|
328
|
-
each(data, callback)
|
|
446
|
+
each(data, callback, options?)
|
|
329
447
|
map(data, callback)
|
|
330
448
|
merge(target, ...sources)
|
|
331
449
|
equally(a, b)
|
|
450
|
+
diff(first, last) // DiffOperation[]
|
|
332
451
|
parse(raw, options?) // unknown
|
|
333
452
|
stringify(value, options?) // string | undefined
|
|
334
453
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -21,9 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
clone: () => clone,
|
|
24
|
+
diff: () => diff,
|
|
24
25
|
each: () => each,
|
|
25
26
|
equally: () => equally,
|
|
26
27
|
extract: () => extract,
|
|
28
|
+
flush: () => flush,
|
|
27
29
|
map: () => map,
|
|
28
30
|
merge: () => merge,
|
|
29
31
|
parse: () => parse,
|
|
@@ -33,6 +35,40 @@ __export(index_exports, {
|
|
|
33
35
|
});
|
|
34
36
|
module.exports = __toCommonJS(index_exports);
|
|
35
37
|
|
|
38
|
+
// src/helpers/createPath.ts
|
|
39
|
+
var createPath = (parentPath, key, parentIsArray) => {
|
|
40
|
+
if (parentIsArray) {
|
|
41
|
+
return parentPath + "[" + key + "]";
|
|
42
|
+
}
|
|
43
|
+
return parentPath ? parentPath + "." + key : String(key);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/helpers/isWalkable.ts
|
|
47
|
+
var isWalkable = (value) => {
|
|
48
|
+
return value !== null && typeof value === "object";
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/each.ts
|
|
52
|
+
var each = (data, callback, { root = false, descend } = {}) => {
|
|
53
|
+
const walk = (value, path, segments, key) => {
|
|
54
|
+
if (segments.length > 0 || root) {
|
|
55
|
+
callback(key, value, path, [...segments]);
|
|
56
|
+
}
|
|
57
|
+
if (isWalkable(value) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
value.forEach((item, index) => {
|
|
60
|
+
walk(item, createPath(path, index, true), [...segments, index], index);
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
Object.entries(value).forEach(([childKey, item]) => {
|
|
64
|
+
walk(item, createPath(path, childKey, false), [...segments, childKey], childKey);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(data, "", [], void 0);
|
|
70
|
+
};
|
|
71
|
+
|
|
36
72
|
// src/helpers/getValueByKey.ts
|
|
37
73
|
var getValueByKey = (object, key, { inherited = true, ignore = [] } = {}) => {
|
|
38
74
|
if (object === null || object === void 0 || ignore.includes(key) || !inherited && !Object.hasOwn(object, key)) {
|
|
@@ -84,51 +120,6 @@ var parsePathSelection = (path = "") => {
|
|
|
84
120
|
};
|
|
85
121
|
};
|
|
86
122
|
|
|
87
|
-
// src/helpers/createPath.ts
|
|
88
|
-
var createPath = (parentPath, key, parentIsArray) => {
|
|
89
|
-
if (parentIsArray) {
|
|
90
|
-
return parentPath + "[" + key + "]";
|
|
91
|
-
}
|
|
92
|
-
return parentPath ? parentPath + "." + key : String(key);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// src/helpers/isWalkable.ts
|
|
96
|
-
var isWalkable = (value) => {
|
|
97
|
-
return value !== null && typeof value === "object";
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// src/each.ts
|
|
101
|
-
var each = (data, callback) => {
|
|
102
|
-
const walk = (value, path, key, parentIsArray) => {
|
|
103
|
-
const currentPath = path || createPath("", key, parentIsArray);
|
|
104
|
-
callback(key, value, currentPath);
|
|
105
|
-
if (!isWalkable(value)) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (Array.isArray(value)) {
|
|
109
|
-
value.forEach((item, index) => {
|
|
110
|
-
walk(item, createPath(currentPath, index, true), index, true);
|
|
111
|
-
});
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
Object.entries(value).forEach(([childKey, item]) => {
|
|
115
|
-
walk(item, createPath(currentPath, childKey, false), childKey, false);
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
if (!isWalkable(data)) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (Array.isArray(data)) {
|
|
122
|
-
data.forEach((item, index) => {
|
|
123
|
-
walk(item, createPath("", index, true), index, true);
|
|
124
|
-
});
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
128
|
-
walk(value, createPath("", key, false), key, false);
|
|
129
|
-
});
|
|
130
|
-
};
|
|
131
|
-
|
|
132
123
|
// src/pick.ts
|
|
133
124
|
var pick = (data = {}, path = "", options = {}) => {
|
|
134
125
|
const { cleanPath, keys } = parsePathSelection(path);
|
|
@@ -244,15 +235,17 @@ var map = (data, callback, store = {}) => {
|
|
|
244
235
|
return store;
|
|
245
236
|
};
|
|
246
237
|
|
|
247
|
-
// src/
|
|
248
|
-
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
238
|
+
// src/helpers/isPlainObject.ts
|
|
249
239
|
var isPlainObject = (value) => {
|
|
250
|
-
if (value
|
|
251
|
-
|
|
240
|
+
if (value !== null && typeof value === "object") {
|
|
241
|
+
const prototype = Object.getPrototypeOf(value);
|
|
242
|
+
return prototype === Object.prototype || prototype === null;
|
|
252
243
|
}
|
|
253
|
-
|
|
254
|
-
return prototype === Object.prototype || prototype === null;
|
|
244
|
+
return false;
|
|
255
245
|
};
|
|
246
|
+
|
|
247
|
+
// src/merge.ts
|
|
248
|
+
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
256
249
|
var merge = (target, ...sources) => {
|
|
257
250
|
const targetRecord = target;
|
|
258
251
|
for (const source of sources) {
|
|
@@ -274,6 +267,27 @@ var merge = (target, ...sources) => {
|
|
|
274
267
|
return target;
|
|
275
268
|
};
|
|
276
269
|
|
|
270
|
+
// src/flush.ts
|
|
271
|
+
var flush = (data = {}, path) => {
|
|
272
|
+
if (path.length > 0) {
|
|
273
|
+
const keys = path.split(".");
|
|
274
|
+
const lastKey = keys.pop() ?? "";
|
|
275
|
+
const match = lastKey.match(/^(\w+)\[(\d+)\]$/);
|
|
276
|
+
let parent = getValueByPath(data, keys.join("."), { inherited: false });
|
|
277
|
+
let key = lastKey;
|
|
278
|
+
if (match) {
|
|
279
|
+
const [, arrayKey, index] = match;
|
|
280
|
+
const array = getValueByKey(parent, arrayKey, { inherited: false });
|
|
281
|
+
parent = Array.isArray(array) ? array : void 0;
|
|
282
|
+
key = index;
|
|
283
|
+
}
|
|
284
|
+
if (parent !== null && typeof parent === "object" && Object.hasOwn(parent, key)) {
|
|
285
|
+
delete parent[key];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return data;
|
|
289
|
+
};
|
|
290
|
+
|
|
277
291
|
// src/deepEqual.ts
|
|
278
292
|
var deepEqual = (x, y, store) => {
|
|
279
293
|
if (Number.isNaN(x) && Number.isNaN(y)) {
|
|
@@ -331,6 +345,41 @@ var equalObjects = (x, y, store) => {
|
|
|
331
345
|
// src/equally.ts
|
|
332
346
|
var equally = (a, b) => deepEqual(a, b, /* @__PURE__ */ new WeakMap());
|
|
333
347
|
|
|
348
|
+
// src/diff.ts
|
|
349
|
+
var diff = (first, last) => {
|
|
350
|
+
const operations = [];
|
|
351
|
+
const frames = [];
|
|
352
|
+
const finish = (depth) => {
|
|
353
|
+
for (const { first: first2, last: last2, path } of frames.splice(depth).reverse()) {
|
|
354
|
+
for (const key of Object.keys(first2)) {
|
|
355
|
+
if (Object.prototype.propertyIsEnumerable.call(last2, key) === false) {
|
|
356
|
+
operations.push({ type: "delete", path: [...path, key] });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return frames.at(-1);
|
|
361
|
+
};
|
|
362
|
+
let descend = false;
|
|
363
|
+
each(last, (key, value, _path, segments) => {
|
|
364
|
+
const path = segments.map(String);
|
|
365
|
+
const frame = finish(segments.length);
|
|
366
|
+
const present = key === void 0 || frame !== void 0 && Object.prototype.propertyIsEnumerable.call(frame.first, key);
|
|
367
|
+
const previous = key === void 0 ? first : present ? frame?.first[key] : void 0;
|
|
368
|
+
descend = false;
|
|
369
|
+
if (present && isPlainObject(previous) && isPlainObject(value)) {
|
|
370
|
+
frames.push({ first: previous, last: value, path });
|
|
371
|
+
descend = true;
|
|
372
|
+
} else if (present === false || equally(previous, value) === false) {
|
|
373
|
+
operations.push({ type: "set", path, value });
|
|
374
|
+
}
|
|
375
|
+
}, {
|
|
376
|
+
root: true,
|
|
377
|
+
descend: () => descend
|
|
378
|
+
});
|
|
379
|
+
finish(0);
|
|
380
|
+
return operations;
|
|
381
|
+
};
|
|
382
|
+
|
|
334
383
|
// src/clone.ts
|
|
335
384
|
var clone = (value) => {
|
|
336
385
|
const store = /* @__PURE__ */ new WeakMap();
|
|
@@ -401,9 +450,11 @@ var stringify = (value, { fallback, onError, replacer, space } = {}) => {
|
|
|
401
450
|
// Annotate the CommonJS export names for ESM import in node:
|
|
402
451
|
0 && (module.exports = {
|
|
403
452
|
clone,
|
|
453
|
+
diff,
|
|
404
454
|
each,
|
|
405
455
|
equally,
|
|
406
456
|
extract,
|
|
457
|
+
flush,
|
|
407
458
|
map,
|
|
408
459
|
merge,
|
|
409
460
|
parse,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
type WalkKey = string | number;
|
|
2
|
+
type DiffOperation = {
|
|
3
|
+
type: 'set';
|
|
4
|
+
path: string[];
|
|
5
|
+
value: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'delete';
|
|
8
|
+
path: string[];
|
|
9
|
+
};
|
|
2
10
|
type PickOptions = {
|
|
3
11
|
inherited?: boolean;
|
|
4
12
|
ignore?: readonly string[];
|
|
@@ -15,8 +23,24 @@ type StringifyOptions = {
|
|
|
15
23
|
space?: string | number;
|
|
16
24
|
};
|
|
17
25
|
|
|
18
|
-
type EachCallback = (key:
|
|
19
|
-
|
|
26
|
+
type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (key: TKey, value: any, path: string, segments: WalkKey[]) => void;
|
|
27
|
+
type EachEntry = {
|
|
28
|
+
key: WalkKey | undefined;
|
|
29
|
+
value: any;
|
|
30
|
+
path: string;
|
|
31
|
+
segments: WalkKey[];
|
|
32
|
+
};
|
|
33
|
+
type EachOptions = {
|
|
34
|
+
root?: boolean;
|
|
35
|
+
descend?: (entry: EachEntry) => boolean;
|
|
36
|
+
};
|
|
37
|
+
type Each = {
|
|
38
|
+
(data: any, callback: EachCallback, options?: EachOptions & {
|
|
39
|
+
root?: false;
|
|
40
|
+
}): void;
|
|
41
|
+
(data: any, callback: EachCallback<WalkKey | undefined>, options: EachOptions): void;
|
|
42
|
+
};
|
|
43
|
+
declare const each: Each;
|
|
20
44
|
|
|
21
45
|
type ObjwalkObject$1 = Record<string, any>;
|
|
22
46
|
declare const pick: (data?: ObjwalkObject$1, path?: string, options?: PickOptions) => any;
|
|
@@ -32,12 +56,16 @@ declare const merge: <T extends ObjwalkObject$1>(target: T, ...sources: unknown[
|
|
|
32
56
|
type ObjwalkObject = Record<string, any>;
|
|
33
57
|
declare const set: (data: ObjwalkObject | undefined, path: string, value: any) => ObjwalkObject;
|
|
34
58
|
|
|
59
|
+
declare const flush: (data: ObjwalkObject$1 | undefined, path: string) => ObjwalkObject$1;
|
|
60
|
+
|
|
35
61
|
declare const equally: (a: unknown, b: unknown) => boolean;
|
|
36
62
|
|
|
63
|
+
declare const diff: (first: unknown, last: unknown) => DiffOperation[];
|
|
64
|
+
|
|
37
65
|
declare const clone: <T>(value: T) => T;
|
|
38
66
|
|
|
39
67
|
declare const parse: (raw: unknown, { fallback, onError, reviver }?: ParseOptions) => unknown;
|
|
40
68
|
|
|
41
69
|
declare const stringify: (value: unknown, { fallback, onError, replacer, space }?: StringifyOptions) => string | undefined;
|
|
42
70
|
|
|
43
|
-
export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, each, equally, extract, map, merge, parse, pick, set, stringify };
|
|
71
|
+
export { type DiffOperation, type EachCallback, type EachEntry, type EachOptions, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
type WalkKey = string | number;
|
|
2
|
+
type DiffOperation = {
|
|
3
|
+
type: 'set';
|
|
4
|
+
path: string[];
|
|
5
|
+
value: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'delete';
|
|
8
|
+
path: string[];
|
|
9
|
+
};
|
|
2
10
|
type PickOptions = {
|
|
3
11
|
inherited?: boolean;
|
|
4
12
|
ignore?: readonly string[];
|
|
@@ -15,8 +23,24 @@ type StringifyOptions = {
|
|
|
15
23
|
space?: string | number;
|
|
16
24
|
};
|
|
17
25
|
|
|
18
|
-
type EachCallback = (key:
|
|
19
|
-
|
|
26
|
+
type EachCallback<TKey extends WalkKey | undefined = WalkKey> = (key: TKey, value: any, path: string, segments: WalkKey[]) => void;
|
|
27
|
+
type EachEntry = {
|
|
28
|
+
key: WalkKey | undefined;
|
|
29
|
+
value: any;
|
|
30
|
+
path: string;
|
|
31
|
+
segments: WalkKey[];
|
|
32
|
+
};
|
|
33
|
+
type EachOptions = {
|
|
34
|
+
root?: boolean;
|
|
35
|
+
descend?: (entry: EachEntry) => boolean;
|
|
36
|
+
};
|
|
37
|
+
type Each = {
|
|
38
|
+
(data: any, callback: EachCallback, options?: EachOptions & {
|
|
39
|
+
root?: false;
|
|
40
|
+
}): void;
|
|
41
|
+
(data: any, callback: EachCallback<WalkKey | undefined>, options: EachOptions): void;
|
|
42
|
+
};
|
|
43
|
+
declare const each: Each;
|
|
20
44
|
|
|
21
45
|
type ObjwalkObject$1 = Record<string, any>;
|
|
22
46
|
declare const pick: (data?: ObjwalkObject$1, path?: string, options?: PickOptions) => any;
|
|
@@ -32,12 +56,16 @@ declare const merge: <T extends ObjwalkObject$1>(target: T, ...sources: unknown[
|
|
|
32
56
|
type ObjwalkObject = Record<string, any>;
|
|
33
57
|
declare const set: (data: ObjwalkObject | undefined, path: string, value: any) => ObjwalkObject;
|
|
34
58
|
|
|
59
|
+
declare const flush: (data: ObjwalkObject$1 | undefined, path: string) => ObjwalkObject$1;
|
|
60
|
+
|
|
35
61
|
declare const equally: (a: unknown, b: unknown) => boolean;
|
|
36
62
|
|
|
63
|
+
declare const diff: (first: unknown, last: unknown) => DiffOperation[];
|
|
64
|
+
|
|
37
65
|
declare const clone: <T>(value: T) => T;
|
|
38
66
|
|
|
39
67
|
declare const parse: (raw: unknown, { fallback, onError, reviver }?: ParseOptions) => unknown;
|
|
40
68
|
|
|
41
69
|
declare const stringify: (value: unknown, { fallback, onError, replacer, space }?: StringifyOptions) => string | undefined;
|
|
42
70
|
|
|
43
|
-
export { type EachCallback, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, each, equally, extract, map, merge, parse, pick, set, stringify };
|
|
71
|
+
export { type DiffOperation, type EachCallback, type EachEntry, type EachOptions, type ExtractMapping, type MapCallback, type ObjwalkObject$1 as ObjwalkObject, type ParseOptions, type PickOptions, type StringifyOptions, type WalkKey, clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
// src/helpers/createPath.ts
|
|
2
|
+
var createPath = (parentPath, key, parentIsArray) => {
|
|
3
|
+
if (parentIsArray) {
|
|
4
|
+
return parentPath + "[" + key + "]";
|
|
5
|
+
}
|
|
6
|
+
return parentPath ? parentPath + "." + key : String(key);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/helpers/isWalkable.ts
|
|
10
|
+
var isWalkable = (value) => {
|
|
11
|
+
return value !== null && typeof value === "object";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/each.ts
|
|
15
|
+
var each = (data, callback, { root = false, descend } = {}) => {
|
|
16
|
+
const walk = (value, path, segments, key) => {
|
|
17
|
+
if (segments.length > 0 || root) {
|
|
18
|
+
callback(key, value, path, [...segments]);
|
|
19
|
+
}
|
|
20
|
+
if (isWalkable(value) && (descend === void 0 || descend({ key, value, path, segments: [...segments] }))) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
value.forEach((item, index) => {
|
|
23
|
+
walk(item, createPath(path, index, true), [...segments, index], index);
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
Object.entries(value).forEach(([childKey, item]) => {
|
|
27
|
+
walk(item, createPath(path, childKey, false), [...segments, childKey], childKey);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
walk(data, "", [], void 0);
|
|
33
|
+
};
|
|
34
|
+
|
|
1
35
|
// src/helpers/getValueByKey.ts
|
|
2
36
|
var getValueByKey = (object, key, { inherited = true, ignore = [] } = {}) => {
|
|
3
37
|
if (object === null || object === void 0 || ignore.includes(key) || !inherited && !Object.hasOwn(object, key)) {
|
|
@@ -49,51 +83,6 @@ var parsePathSelection = (path = "") => {
|
|
|
49
83
|
};
|
|
50
84
|
};
|
|
51
85
|
|
|
52
|
-
// src/helpers/createPath.ts
|
|
53
|
-
var createPath = (parentPath, key, parentIsArray) => {
|
|
54
|
-
if (parentIsArray) {
|
|
55
|
-
return parentPath + "[" + key + "]";
|
|
56
|
-
}
|
|
57
|
-
return parentPath ? parentPath + "." + key : String(key);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// src/helpers/isWalkable.ts
|
|
61
|
-
var isWalkable = (value) => {
|
|
62
|
-
return value !== null && typeof value === "object";
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// src/each.ts
|
|
66
|
-
var each = (data, callback) => {
|
|
67
|
-
const walk = (value, path, key, parentIsArray) => {
|
|
68
|
-
const currentPath = path || createPath("", key, parentIsArray);
|
|
69
|
-
callback(key, value, currentPath);
|
|
70
|
-
if (!isWalkable(value)) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (Array.isArray(value)) {
|
|
74
|
-
value.forEach((item, index) => {
|
|
75
|
-
walk(item, createPath(currentPath, index, true), index, true);
|
|
76
|
-
});
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
Object.entries(value).forEach(([childKey, item]) => {
|
|
80
|
-
walk(item, createPath(currentPath, childKey, false), childKey, false);
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
if (!isWalkable(data)) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (Array.isArray(data)) {
|
|
87
|
-
data.forEach((item, index) => {
|
|
88
|
-
walk(item, createPath("", index, true), index, true);
|
|
89
|
-
});
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
93
|
-
walk(value, createPath("", key, false), key, false);
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
86
|
// src/pick.ts
|
|
98
87
|
var pick = (data = {}, path = "", options = {}) => {
|
|
99
88
|
const { cleanPath, keys } = parsePathSelection(path);
|
|
@@ -209,15 +198,17 @@ var map = (data, callback, store = {}) => {
|
|
|
209
198
|
return store;
|
|
210
199
|
};
|
|
211
200
|
|
|
212
|
-
// src/
|
|
213
|
-
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
201
|
+
// src/helpers/isPlainObject.ts
|
|
214
202
|
var isPlainObject = (value) => {
|
|
215
|
-
if (value
|
|
216
|
-
|
|
203
|
+
if (value !== null && typeof value === "object") {
|
|
204
|
+
const prototype = Object.getPrototypeOf(value);
|
|
205
|
+
return prototype === Object.prototype || prototype === null;
|
|
217
206
|
}
|
|
218
|
-
|
|
219
|
-
return prototype === Object.prototype || prototype === null;
|
|
207
|
+
return false;
|
|
220
208
|
};
|
|
209
|
+
|
|
210
|
+
// src/merge.ts
|
|
211
|
+
var unsafeKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
221
212
|
var merge = (target, ...sources) => {
|
|
222
213
|
const targetRecord = target;
|
|
223
214
|
for (const source of sources) {
|
|
@@ -239,6 +230,27 @@ var merge = (target, ...sources) => {
|
|
|
239
230
|
return target;
|
|
240
231
|
};
|
|
241
232
|
|
|
233
|
+
// src/flush.ts
|
|
234
|
+
var flush = (data = {}, path) => {
|
|
235
|
+
if (path.length > 0) {
|
|
236
|
+
const keys = path.split(".");
|
|
237
|
+
const lastKey = keys.pop() ?? "";
|
|
238
|
+
const match = lastKey.match(/^(\w+)\[(\d+)\]$/);
|
|
239
|
+
let parent = getValueByPath(data, keys.join("."), { inherited: false });
|
|
240
|
+
let key = lastKey;
|
|
241
|
+
if (match) {
|
|
242
|
+
const [, arrayKey, index] = match;
|
|
243
|
+
const array = getValueByKey(parent, arrayKey, { inherited: false });
|
|
244
|
+
parent = Array.isArray(array) ? array : void 0;
|
|
245
|
+
key = index;
|
|
246
|
+
}
|
|
247
|
+
if (parent !== null && typeof parent === "object" && Object.hasOwn(parent, key)) {
|
|
248
|
+
delete parent[key];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return data;
|
|
252
|
+
};
|
|
253
|
+
|
|
242
254
|
// src/deepEqual.ts
|
|
243
255
|
var deepEqual = (x, y, store) => {
|
|
244
256
|
if (Number.isNaN(x) && Number.isNaN(y)) {
|
|
@@ -296,6 +308,41 @@ var equalObjects = (x, y, store) => {
|
|
|
296
308
|
// src/equally.ts
|
|
297
309
|
var equally = (a, b) => deepEqual(a, b, /* @__PURE__ */ new WeakMap());
|
|
298
310
|
|
|
311
|
+
// src/diff.ts
|
|
312
|
+
var diff = (first, last) => {
|
|
313
|
+
const operations = [];
|
|
314
|
+
const frames = [];
|
|
315
|
+
const finish = (depth) => {
|
|
316
|
+
for (const { first: first2, last: last2, path } of frames.splice(depth).reverse()) {
|
|
317
|
+
for (const key of Object.keys(first2)) {
|
|
318
|
+
if (Object.prototype.propertyIsEnumerable.call(last2, key) === false) {
|
|
319
|
+
operations.push({ type: "delete", path: [...path, key] });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return frames.at(-1);
|
|
324
|
+
};
|
|
325
|
+
let descend = false;
|
|
326
|
+
each(last, (key, value, _path, segments) => {
|
|
327
|
+
const path = segments.map(String);
|
|
328
|
+
const frame = finish(segments.length);
|
|
329
|
+
const present = key === void 0 || frame !== void 0 && Object.prototype.propertyIsEnumerable.call(frame.first, key);
|
|
330
|
+
const previous = key === void 0 ? first : present ? frame?.first[key] : void 0;
|
|
331
|
+
descend = false;
|
|
332
|
+
if (present && isPlainObject(previous) && isPlainObject(value)) {
|
|
333
|
+
frames.push({ first: previous, last: value, path });
|
|
334
|
+
descend = true;
|
|
335
|
+
} else if (present === false || equally(previous, value) === false) {
|
|
336
|
+
operations.push({ type: "set", path, value });
|
|
337
|
+
}
|
|
338
|
+
}, {
|
|
339
|
+
root: true,
|
|
340
|
+
descend: () => descend
|
|
341
|
+
});
|
|
342
|
+
finish(0);
|
|
343
|
+
return operations;
|
|
344
|
+
};
|
|
345
|
+
|
|
299
346
|
// src/clone.ts
|
|
300
347
|
var clone = (value) => {
|
|
301
348
|
const store = /* @__PURE__ */ new WeakMap();
|
|
@@ -365,9 +412,11 @@ var stringify = (value, { fallback, onError, replacer, space } = {}) => {
|
|
|
365
412
|
};
|
|
366
413
|
export {
|
|
367
414
|
clone,
|
|
415
|
+
diff,
|
|
368
416
|
each,
|
|
369
417
|
equally,
|
|
370
418
|
extract,
|
|
419
|
+
flush,
|
|
371
420
|
map,
|
|
372
421
|
merge,
|
|
373
422
|
parse,
|
package/package.json
CHANGED
package/skills/objwalk/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: objwalk
|
|
3
|
-
description: Use when writing or editing JavaScript or TypeScript that imports `objwalk`, reads or
|
|
3
|
+
description: Use when writing or editing JavaScript or TypeScript that imports `objwalk`, reads, writes, or deletes nested object paths, computes differences, reshapes or walks objects, or uses objwalk JSON helpers.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# objwalk
|
|
@@ -19,7 +19,7 @@ them with type assertions or assume every method accepts arbitrary `unknown`.
|
|
|
19
19
|
Named imports only:
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { clone, each, equally, extract, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
22
|
+
import { clone, diff, each, equally, extract, flush, map, merge, parse, pick, set, stringify } from 'objwalk'
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Choose the existing operation
|
|
@@ -28,15 +28,17 @@ import { clone, each, equally, extract, map, merge, parse, pick, set, stringify
|
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| Read a nested value | `pick(data, path, options?)`; unresolved paths return `undefined`. |
|
|
30
30
|
| Write a nested value | `set(data, path, value)`; creates intermediate containers, mutates and returns `data`. |
|
|
31
|
+
| Delete a nested property | `flush(data, path)`; deletes an own property, mutates and returns `data`; missing paths are left unchanged. |
|
|
31
32
|
| Reshape an object | `extract(source, mapping)`; mapping keys are destination paths, values are source paths. |
|
|
32
|
-
| Visit nested values | `each(data, (key, value, path) => { ... })`;
|
|
33
|
+
| Visit nested values | `each(data, (key, value, path, segments) => { ... }, options?)`; root is excluded by default. |
|
|
33
34
|
| Build a store while walking | `map(data, (key, value, store, path) => { ... }, store?)`; returns the store, ignores callback return values. |
|
|
34
35
|
| Deep merge | `merge(target, ...sources)`; mutates and returns the target, merges plain objects, replaces arrays and non-plain values. |
|
|
35
36
|
| Deep copy | `clone(value)`; preserves `Date`, `RegExp`, symbol keys, and circular references. |
|
|
36
37
|
| Deep comparison | `equally(a, b)`; supports `NaN`, `Date`, `RegExp`, and circular references. |
|
|
38
|
+
| Describe changes | `diff(first, last): DiffOperation[]`; returns `set`/`delete` operations, or `[]` when unchanged. |
|
|
37
39
|
|
|
38
|
-
`set` and `merge` mutate their targets. Use `clone` first only when the task
|
|
39
|
-
requires an independent copy. `each` and `
|
|
40
|
+
`set`, `flush`, and `merge` mutate their targets. Use `clone` first only when the task
|
|
41
|
+
requires an independent copy. `each`, `map`, and `diff` do not support circular traversal;
|
|
40
42
|
do not infer that capability from `clone` or `equally`.
|
|
41
43
|
|
|
42
44
|
## Paths and reshaping
|
|
@@ -49,10 +51,18 @@ do not infer that capability from `clone` or `equally`.
|
|
|
49
51
|
| `user.{*}` | List object keys with `pick`. |
|
|
50
52
|
| `items[n].title` | Map array items in `extract`, with `[n]` in both source and destination paths. |
|
|
51
53
|
|
|
52
|
-
`[n]` is an `extract` mapping feature, not a general wildcard for `pick` or `
|
|
54
|
+
`[n]` is an `extract` mapping feature, not a general wildcard for `pick`, `set`, or `flush`.
|
|
53
55
|
An array of source paths in `extract` selects the first result that is not
|
|
54
56
|
`undefined`, preserving `null`, `false`, `0`, and empty strings.
|
|
55
57
|
|
|
58
|
+
Use `flush` to remove a property rather than `set(data, path, undefined)`, which
|
|
59
|
+
leaves the property present. `flush` traverses and deletes only own properties.
|
|
60
|
+
Missing, nullish, or primitive parents and empty paths are left unchanged; no
|
|
61
|
+
containers are created and empty parents remain. Explicit array indexes work,
|
|
62
|
+
but deleting an item leaves a hole without shifting indexes or changing length.
|
|
63
|
+
Selection syntax (`{name,email}`, `{*}`) is not supported. Errors from getters,
|
|
64
|
+
proxies, or deleting non-configurable properties propagate.
|
|
65
|
+
|
|
56
66
|
`pick` reads inherited properties by default. Use its existing options when own
|
|
57
67
|
properties or ignored keys are required:
|
|
58
68
|
|
|
@@ -63,6 +73,51 @@ pick(data, 'user.name', {
|
|
|
63
73
|
})
|
|
64
74
|
```
|
|
65
75
|
|
|
76
|
+
## Walking
|
|
77
|
+
|
|
78
|
+
`each` passes literal `segments: WalkKey[]` as its fourth callback argument.
|
|
79
|
+
Object keys are strings; array indexes are numbers. Prefer segments when keys may
|
|
80
|
+
contain dots, brackets, or empty strings; the string `path` can be ambiguous.
|
|
81
|
+
|
|
82
|
+
`EachOptions` accepts `root` (default `false`) and
|
|
83
|
+
`descend: ({ key, value, path, segments }) => boolean`.
|
|
84
|
+
With `root: true`, the root is visited first, including primitive/nullish values,
|
|
85
|
+
with key `undefined`, path `''`, and segments `[]`. Callback keys are then typed
|
|
86
|
+
`WalkKey | undefined`; legacy calls keep `WalkKey`. Use the public
|
|
87
|
+
`EachCallback<WalkKey | undefined>` for an explicitly typed root-aware callback.
|
|
88
|
+
`EachEntry` and `EachOptions` are also public named exports.
|
|
89
|
+
|
|
90
|
+
`descend` is called only for containers, after their callback, and returning false
|
|
91
|
+
skips their children. It also runs for a root container excluded from callbacks.
|
|
92
|
+
For example, `descend: ({ value }) => Array.isArray(value) === false` visits arrays
|
|
93
|
+
without traversing their elements. Callback return values remain ignored, including
|
|
94
|
+
false. Mutating received segments does not affect traversal. Callback/getter/descend
|
|
95
|
+
errors propagate. Cycles are not detected.
|
|
96
|
+
|
|
97
|
+
## Differences
|
|
98
|
+
|
|
99
|
+
`diff` recursively compares two plain objects by their own enumerable string keys;
|
|
100
|
+
symbol/non-enumerable keys, prototypes, and descriptors are not reported during that traversal.
|
|
101
|
+
Arrays and other values use `equally` and are replaced as a whole when changed.
|
|
102
|
+
New properties are also set as whole values. The public `DiffOperation` type is:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
type DiffOperation =
|
|
106
|
+
| { type: 'set'; path: string[]; value: unknown }
|
|
107
|
+
| { type: 'delete'; path: string[] }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Paths contain literal keys; `[]` replaces the root. Do not join them with `.` to
|
|
111
|
+
call `set` or `flush`: keys may contain dots or brackets. `merge` does not apply
|
|
112
|
+
these operations. There is no patch applier or options argument.
|
|
113
|
+
|
|
114
|
+
Deletion is distinct from assigning `null` or `undefined`, including adding an own
|
|
115
|
+
`undefined` property. Empty parent objects remain. Inputs are not mutated or cloned;
|
|
116
|
+
`set.value` retains references from `last`. Native JSON serialization loses an
|
|
117
|
+
`undefined` value, so do not assume lossless transport for arbitrary JS values.
|
|
118
|
+
Getter/proxy errors propagate. Cycles are outside the contract, without detection;
|
|
119
|
+
shared references without cycles work.
|
|
120
|
+
|
|
66
121
|
## JSON and ready values
|
|
67
122
|
|
|
68
123
|
Call `parse(raw, options?)` directly with `raw: unknown`. The library chooses
|