neotraverse
Version:
traverse and transform objects by visiting every node on a recursive walk
409 lines (408 loc) • 14.8 kB
JavaScript
import { A as paths, B as clone, C as forEachAsync, D as mapAsync, E as map, F as size, G as has_own_property, H as empty_null, I as skipWhere, J as object_keys, K as is_array, L as some, M as pruneDeep, N as reduce, O as mapBfs, P as sanitize, R as values, S as forEach, T as groupBy, U as getType, V as assert_within_depth, W as get_own_property_symbols, X as same_value_zero, Y as safe_set, _ as deleteWhere, a as has, b as filter, c as parseGlob, d as pointerPath, f as select, g as count, h as breadthFirst, i as getPath, j as prune, k as nodes, l as parseJsonPointer, m as setPath, n as findPaths, o as hasPath, p as set, q as is_boxed_primitive, r as get, s as parseDotPath, t as filterPaths, u as parsePath, v as entries, w as freeze, x as find, y as every, z as walk } from "./path-Bxt08XGL.js";
//#region packages/neotraverse/src/ops.ts
function mergeable_object_type(t) {
return t === "object" || t === "array" || t === "map";
}
function merge_pair(target, source, options, cloneOpts, depth, seen) {
if (source === void 0) return clone(target, cloneOpts);
const st = getType(source);
const tt = getType(target);
if (st === "null" || st === "primitive" || st === "function") return clone(source, cloneOpts);
if (!mergeable_object_type(st) || !mergeable_object_type(tt) || st !== tt) return clone(source, cloneOpts);
if (options.maxDepth !== void 0 && depth >= options.maxDepth) return clone(source, cloneOpts);
if (seen.has(source)) return clone(source, cloneOpts);
seen.add(source);
let out;
if (st === "array") if (options.array === "concat") {
out = new Array(target.length + source.length);
for (let i = 0; i < target.length; i++) out[i] = clone(target[i], cloneOpts);
for (let i = 0; i < source.length; i++) out[target.length + i] = clone(source[i], cloneOpts);
} else {
out = new Array(source.length);
for (let i = 0; i < source.length; i++) if (i < target.length && mergeable_object_type(getType(target[i])) && mergeable_object_type(getType(source[i]))) out[i] = merge_pair(target[i], source[i], options, cloneOpts, depth + 1, seen);
else out[i] = clone(source[i], cloneOpts);
}
else if (st === "map") {
out = /* @__PURE__ */ new Map();
for (const [k, v] of target) if (source.has(k)) {
const sv = source.get(k);
out.set(k, mergeable_object_type(getType(v)) && mergeable_object_type(getType(sv)) ? merge_pair(v, sv, options, cloneOpts, depth + 1, seen) : clone(sv, cloneOpts));
} else out.set(k, clone(v, cloneOpts));
for (const [k, v] of source) if (!target.has(k)) out.set(k, clone(v, cloneOpts));
} else {
out = {};
const tkeys = object_keys(target);
for (let i = 0; i < tkeys.length; i++) {
const k = tkeys[i];
if (has_own_property.call(source, k)) if (mergeable_object_type(getType(target[k])) && mergeable_object_type(getType(source[k]))) safe_set(out, k, merge_pair(target[k], source[k], options, cloneOpts, depth + 1, seen));
else safe_set(out, k, clone(source[k], cloneOpts));
else safe_set(out, k, clone(target[k], cloneOpts));
}
const skeys = object_keys(source);
for (let i = 0; i < skeys.length; i++) {
const k = skeys[i];
if (!has_own_property.call(target, k)) safe_set(out, k, clone(source[k], cloneOpts));
}
}
seen.delete(source);
return out;
}
/**
* Deep-merge `source` into a clone of `target` (does not mutate `target`).
* Plain objects and Map entries merge recursively; arrays replace index-by-index
* unless `array: 'concat'`. Other types are replaced from `source`.
*
* @example
* ```js
* import { merge } from 'neotraverse/modern';
* merge({ x: 1, nested: { a: 1 } }, { y: 2, nested: { b: 2 } });
* // => { x: 1, y: 2, nested: { a: 1, b: 2 } }
* ```
*
* @see https://neotraverse.puruvj.dev/guide/api/walk#t-merge
*/
function merge(target, source, options) {
const opts = options ?? empty_null;
return merge_pair(target, source, opts, opts.maxDepth !== void 0 ? {
...opts,
maxDepth: void 0
} : opts, 0, /* @__PURE__ */ new WeakSet());
}
function is_ref_object(node) {
return node !== null && typeof node === "object" && !is_array(node) && typeof node.$ref === "string" && object_keys(node).length === 1 && get_own_property_symbols(node).length === 0;
}
/**
* Resolve local JSON Pointer `$ref` objects (`"#/…"`) on a cloned tree.
* External / URL refs are left unchanged.
*
* @example
* ```js
* import { dereference } from 'neotraverse/modern';
* dereference({ defs: { Pet: { type: 'object' } }, pet: { $ref: '#/defs/Pet' } });
* // => { defs: { Pet: { type: 'object' } }, pet: { type: 'object' } }
* ```
*
* @see https://neotraverse.puruvj.dev/guide/api/walk#t-dereference
*/
const NOT_RESOLVED = Symbol("unresolved");
function dereference(obj, options) {
const localOnly = options?.localOnly !== false;
const targetCache = /* @__PURE__ */ new Map();
const resolveTarget = (ref0) => {
if (targetCache.has(ref0)) return targetCache.get(ref0);
const seen = /* @__PURE__ */ new Set();
const chain = [];
let ref = ref0;
let result = NOT_RESOLVED;
for (;;) {
if (targetCache.has(ref)) {
result = targetCache.get(ref);
break;
}
if (localOnly && !ref.startsWith("#")) break;
const pointer = ref.startsWith("#") ? ref.slice(1) : ref;
if (!pointer.startsWith("/")) break;
if (seen.has(pointer)) break;
seen.add(pointer);
chain.push(ref);
const target = get(obj, parseJsonPointer(pointer), options);
if (target === void 0) break;
if (is_ref_object(target)) {
ref = target.$ref;
continue;
}
result = target;
break;
}
for (let i = 0; i < chain.length; i++) targetCache.set(chain[i], result);
targetCache.set(ref0, result);
return result;
};
return map(obj, (ctx) => {
if (is_ref_object(ctx.node)) {
const target = resolveTarget(ctx.node.$ref);
if (target !== NOT_RESOLVED) {
ctx.update(clone(target, options));
ctx.block();
}
}
}, options);
}
/**
* Structural equality with an explicit per-type contract (not identical to `clone()`).
*
* @example
* ```js
* import { deepEqual } from 'neotraverse/modern';
* deepEqual({ a: [1] }, { a: [1] }); // => true
* deepEqual(new Date('2020-01-01'), new Date('2020-01-01')); // => true
* ```
*
* @see https://neotraverse.puruvj.dev/guide/api/structural#t-deep-equal
*/
function deepEqual(a, b, options) {
const seen = /* @__PURE__ */ new Map();
return deepEqualPair(a, b, options?.compareFn, seen, options?.maxDepth, 0);
}
function deepEqualPair(a, b, compareFn, seen, maxDepth, depth) {
if (a === b) return true;
assert_within_depth(depth, maxDepth);
if (compareFn) {
const custom = compareFn(a, b);
if (custom !== void 0) return custom;
}
if (typeof a !== typeof b) return false;
if (a === null || b === null) return a === b;
if (is_boxed_primitive(a) || is_boxed_primitive(b)) {
if (!is_boxed_primitive(a) || !is_boxed_primitive(b)) return false;
return same_value_zero(Object(a).valueOf(), Object(b).valueOf());
}
const ta = getType(a);
if (ta !== getType(b)) return false;
if (ta === "primitive" || ta === "null") return same_value_zero(a, b);
if (typeof a === "object" && typeof b === "object") {
let pairs = seen.get(a);
if (pairs?.has(b)) return true;
if (!pairs) {
pairs = /* @__PURE__ */ new Map();
seen.set(a, pairs);
}
pairs.set(b, true);
}
switch (ta) {
case "date": return a.getTime() === b.getTime();
case "regexp": return a.source === b.source && a.flags === b.flags;
case "error": return a.message === b.message && a.name === b.name;
case "arraybuffer": {
if (a.byteLength !== b.byteLength) return false;
const va = new Uint8Array(a);
const vb = new Uint8Array(b);
for (let i = 0; i < va.length; i++) if (va[i] !== vb[i]) return false;
return true;
}
case "dataview":
if (a.byteLength !== b.byteLength) return false;
for (let i = 0; i < a.byteLength; i++) if (a.getUint8(i) !== b.getUint8(i)) return false;
return true;
case "typed-array":
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (!same_value_zero(a[i], b[i])) return false;
return true;
case "map":
if (a.size !== b.size) return false;
for (const [k, v] of a) if (!b.has(k) || !deepEqualPair(v, b.get(k), compareFn, seen, maxDepth, depth + 1)) return false;
return true;
case "set": {
if (a.size !== b.size) return false;
let allPrimitive = true;
for (const v of a) if (v !== null && (typeof v === "object" || typeof v === "function")) {
allPrimitive = false;
break;
}
if (allPrimitive) {
for (const v of a) if (!b.has(v)) return false;
return true;
}
const used = /* @__PURE__ */ new Set();
for (const v of a) {
let matched = false;
let i = 0;
for (const w of b) {
if (used.has(i)) {
i++;
continue;
}
if (deepEqualPair(v, w, compareFn, seen, maxDepth, depth + 1)) {
used.add(i);
matched = true;
break;
}
i++;
}
if (!matched) return false;
}
return true;
}
case "weakmap":
if (a === b) return true;
return false;
case "weakset": return a === b;
case "function": return a === b;
case "array":
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (!deepEqualPair(a[i], b[i], compareFn, seen, maxDepth, depth + 1)) return false;
return true;
default: {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (let i = 0; i < keysA.length; i++) {
const k = keysA[i];
if (!has_own_property.call(b, k)) return false;
if (!deepEqualPair(a[k], b[k], compareFn, seen, maxDepth, depth + 1)) return false;
}
return true;
}
}
}
/**
* JSON.stringify after a walk; does not throw on circular references.
*
* @example
* ```js
* import { toJSON } from 'neotraverse/modern';
* const ring = {}; ring.self = ring;
* toJSON(ring); // => '{"self":null}'
* ```
*
* @see https://neotraverse.puruvj.dev/guide/api/structural#t-to-json
*/
function toJSON(obj, options) {
const cycle = options?.cycle ?? null;
const prepared = map(obj, (ctx) => {
if (ctx.circular) {
ctx.update(cycle);
ctx.block();
return;
}
const n = ctx.node;
if (typeof n === "function" || typeof n === "symbol") ctx.update(void 0);
else if (typeof n === "bigint") ctx.update(String(n));
}, options);
return JSON.stringify(prepared);
}
/**
* RFC 6902 subset (`add` / `remove` / `replace`). Circular graphs are not supported.
*
* @example
* ```js
* import { diff } from 'neotraverse/modern';
* diff({ a: 1 }, { a: 2, b: 3 });
* // => [
* // { op: 'replace', path: '/a', value: 2 },
* // { op: 'add', path: '/b', value: 3 },
* // ]
* ```
*
* @see https://neotraverse.puruvj.dev/guide/api/structural#t-diff
*/
function diff(a, b, options) {
const ops = [];
diffPair(a, b, [], ops, /* @__PURE__ */ new WeakSet(), options?.maxDepth, 0, /* @__PURE__ */ new Map());
return ops;
}
function diffPair(a, b, path, ops, stack, maxDepth, depth, equalCache) {
if (a === b) return;
assert_within_depth(depth, maxDepth);
const ta = getType(a);
if (ta !== getType(b) || ta === "primitive" || ta === "null") {
ops.push({
op: "replace",
path: pointerPath(path),
value: clone(b)
});
return;
}
const tracked = typeof a === "object" && a !== null;
if (tracked) {
if (stack.has(a)) return;
const bs = equalCache.get(a);
if (bs !== void 0 && bs.has(b)) return;
stack.add(a);
}
const opsBefore = ops.length;
if (ta === "array") {
const min = Math.min(a.length, b.length);
for (let i = 0; i < min; i++) diffPair(a[i], b[i], path.concat(i), ops, stack, maxDepth, depth + 1, equalCache);
for (let i = a.length; i < b.length; i++) ops.push({
op: "add",
path: pointerPath(path.concat(i)),
value: clone(b[i])
});
for (let i = a.length - 1; i >= b.length; i--) ops.push({
op: "remove",
path: pointerPath(path.concat(i))
});
} else if (ta === "map" || ta === "set" || ta === "weakmap" || ta === "weakset" || ta === "date" || ta === "regexp" || ta === "error" || ta === "typed-array" || ta === "arraybuffer" || ta === "dataview" || ta === "function") {
if (!deepEqual(a, b, { maxDepth })) ops.push({
op: "replace",
path: pointerPath(path),
value: clone(b)
});
} else {
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
for (const k of keys) {
const p = path.concat(k);
const ha = has_own_property.call(a, k);
const hb = has_own_property.call(b, k);
if (ha && !hb) ops.push({
op: "remove",
path: pointerPath(p)
});
else if (!ha && hb) ops.push({
op: "add",
path: pointerPath(p),
value: clone(b[k])
});
else if (ha && hb) diffPair(a[k], b[k], p, ops, stack, maxDepth, depth + 1, equalCache);
}
}
if (tracked) {
stack.delete(a);
if (ops.length === opsBefore) {
let bs = equalCache.get(a);
if (bs === void 0) {
bs = /* @__PURE__ */ new Set();
equalCache.set(a, bs);
}
bs.add(b);
}
}
}
/**
* @example
* ```js
* import { patch } from 'neotraverse/modern';
* patch({ a: 1 }, [{ op: 'replace', path: '/a', value: 2 }]);
* // => { a: 2 }
* ```
*
* @see https://neotraverse.puruvj.dev/guide/api/structural#t-diff
*/
function patch(obj, ops) {
let root = clone(obj);
for (let i = 0; i < ops.length; i++) {
const op = ops[i];
const keys = parsePath(op.path.startsWith("/") ? op.path : `/${op.path}`);
if (op.op === "remove") root = removeAt(root, keys);
else root = setAt(root, keys, op.value, op.op === "add");
}
return root;
}
function setAt(root, keys, value, insert) {
if (keys.length === 0) return value;
const parentKeys = keys.slice(0, -1);
const last = keys[keys.length - 1];
let parent = parentKeys.length ? get(root, parentKeys) : root;
if (parent === void 0 || parent === null) {
parent = typeof last === "number" ? [] : {};
if (parentKeys.length) root = set(root, parentKeys, parent);
else root = parent;
}
if (typeof last === "number" && is_array(parent)) if (insert) parent.splice(last, 0, value);
else parent[last] = value;
else safe_set(parent, last, value);
return root;
}
function removeAt(root, keys) {
if (keys.length === 0) return root;
const parentKeys = keys.slice(0, -1);
const last = keys[keys.length - 1];
const parent = parentKeys.length ? get(root, parentKeys) : root;
if (parent == null) return root;
if (is_array(parent) && typeof last === "number") parent.splice(last, 1);
else delete parent[last];
return root;
}
//#endregion
export { breadthFirst, clone, count, deepEqual, deleteWhere, dereference, diff, entries, every, filter, filterPaths, find, findPaths, forEach, forEachAsync, freeze, get, getPath, getType, groupBy, has, hasPath, map, mapAsync, mapBfs, merge, nodes, parseDotPath, parseGlob, parseJsonPointer, parsePath, patch, paths, pointerPath, prune, pruneDeep, reduce, sanitize, select, set, setPath, size, skipWhere, some, toJSON, values, walk };