fast-json-schema-patch
Version:
Ultra-fast, schema-aware JSON patch generation and human-readable diffing. A high-performance JSON patch library that leverages schema knowledge to generate efficient, semantic patches with tools for creating human-readable diffs suitable for frontend app
1,636 lines (1,625 loc) • 56.5 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
//#region node_modules/json-source-map/index.js
var require_json_source_map = __commonJS({ "node_modules/json-source-map/index.js"(exports) {
var escapedChars = {
"b": "\b",
"f": "\f",
"n": "\n",
"r": "\r",
"t": " ",
"\"": "\"",
"/": "/",
"\\": "\\"
};
var A_CODE = "a".charCodeAt();
exports.parse = function(source, _, options) {
var pointers = {};
var line = 0;
var column = 0;
var pos = 0;
var bigint = options && options.bigint && typeof BigInt != "undefined";
return {
data: _parse("", true),
pointers
};
function _parse(ptr, topLevel) {
whitespace();
var data;
map(ptr, "value");
var char = getChar();
switch (char) {
case "t":
read("rue");
data = true;
break;
case "f":
read("alse");
data = false;
break;
case "n":
read("ull");
data = null;
break;
case "\"":
data = parseString();
break;
case "[":
data = parseArray(ptr);
break;
case "{":
data = parseObject(ptr);
break;
default:
backChar();
if ("-0123456789".indexOf(char) >= 0) data = parseNumber();
else unexpectedToken();
}
map(ptr, "valueEnd");
whitespace();
if (topLevel && pos < source.length) unexpectedToken();
return data;
}
function whitespace() {
loop: while (pos < source.length) {
switch (source[pos]) {
case " ":
column++;
break;
case " ":
column += 4;
break;
case "\r":
column = 0;
break;
case "\n":
column = 0;
line++;
break;
default: break loop;
}
pos++;
}
}
function parseString() {
var str = "";
var char;
while (true) {
char = getChar();
if (char == "\"") break;
else if (char == "\\") {
char = getChar();
if (char in escapedChars) str += escapedChars[char];
else if (char == "u") str += getCharCode();
else wasUnexpectedToken();
} else str += char;
}
return str;
}
function parseNumber() {
var numStr = "";
var integer = true;
if (source[pos] == "-") numStr += getChar();
numStr += source[pos] == "0" ? getChar() : getDigits();
if (source[pos] == ".") {
numStr += getChar() + getDigits();
integer = false;
}
if (source[pos] == "e" || source[pos] == "E") {
numStr += getChar();
if (source[pos] == "+" || source[pos] == "-") numStr += getChar();
numStr += getDigits();
integer = false;
}
var result = +numStr;
return bigint && integer && (result > Number.MAX_SAFE_INTEGER || result < Number.MIN_SAFE_INTEGER) ? BigInt(numStr) : result;
}
function parseArray(ptr) {
whitespace();
var arr = [];
var i = 0;
if (getChar() == "]") return arr;
backChar();
while (true) {
var itemPtr = ptr + "/" + i;
arr.push(_parse(itemPtr));
whitespace();
var char = getChar();
if (char == "]") break;
if (char != ",") wasUnexpectedToken();
whitespace();
i++;
}
return arr;
}
function parseObject(ptr) {
whitespace();
var obj = {};
if (getChar() == "}") return obj;
backChar();
while (true) {
var loc = getLoc();
if (getChar() != "\"") wasUnexpectedToken();
var key = parseString();
var propPtr = ptr + "/" + escapeJsonPointer(key);
mapLoc(propPtr, "key", loc);
map(propPtr, "keyEnd");
whitespace();
if (getChar() != ":") wasUnexpectedToken();
whitespace();
obj[key] = _parse(propPtr);
whitespace();
var char = getChar();
if (char == "}") break;
if (char != ",") wasUnexpectedToken();
whitespace();
}
return obj;
}
function read(str) {
for (var i = 0; i < str.length; i++) if (getChar() !== str[i]) wasUnexpectedToken();
}
function getChar() {
checkUnexpectedEnd();
var char = source[pos];
pos++;
column++;
return char;
}
function backChar() {
pos--;
column--;
}
function getCharCode() {
var count = 4;
var code = 0;
while (count--) {
code <<= 4;
var char = getChar().toLowerCase();
if (char >= "a" && char <= "f") code += char.charCodeAt() - A_CODE + 10;
else if (char >= "0" && char <= "9") code += +char;
else wasUnexpectedToken();
}
return String.fromCharCode(code);
}
function getDigits() {
var digits = "";
while (source[pos] >= "0" && source[pos] <= "9") digits += getChar();
if (digits.length) return digits;
checkUnexpectedEnd();
unexpectedToken();
}
function map(ptr, prop) {
mapLoc(ptr, prop, getLoc());
}
function mapLoc(ptr, prop, loc) {
pointers[ptr] = pointers[ptr] || {};
pointers[ptr][prop] = loc;
}
function getLoc() {
return {
line,
column,
pos
};
}
function unexpectedToken() {
throw new SyntaxError("Unexpected token " + source[pos] + " in JSON at position " + pos);
}
function wasUnexpectedToken() {
backChar();
unexpectedToken();
}
function checkUnexpectedEnd() {
if (pos >= source.length) throw new SyntaxError("Unexpected end of JSON input");
}
};
exports.stringify = function(data, _, options) {
if (!validType(data)) return;
var wsLine = 0;
var wsPos, wsColumn;
var whitespace = typeof options == "object" ? options.space : options;
switch (typeof whitespace) {
case "number":
var len = whitespace > 10 ? 10 : whitespace < 0 ? 0 : Math.floor(whitespace);
whitespace = len && repeat(len, " ");
wsPos = len;
wsColumn = len;
break;
case "string":
whitespace = whitespace.slice(0, 10);
wsPos = 0;
wsColumn = 0;
for (var j = 0; j < whitespace.length; j++) {
var char = whitespace[j];
switch (char) {
case " ":
wsColumn++;
break;
case " ":
wsColumn += 4;
break;
case "\r":
wsColumn = 0;
break;
case "\n":
wsColumn = 0;
wsLine++;
break;
default: throw new Error("whitespace characters not allowed in JSON");
}
wsPos++;
}
break;
default: whitespace = void 0;
}
var json = "";
var pointers = {};
var line = 0;
var column = 0;
var pos = 0;
var es6 = options && options.es6 && typeof Map == "function";
_stringify(data, 0, "");
return {
json,
pointers
};
function _stringify(_data, lvl, ptr) {
map(ptr, "value");
switch (typeof _data) {
case "number":
case "bigint":
case "boolean":
out("" + _data);
break;
case "string":
out(quoted(_data));
break;
case "object": if (_data === null) out("null");
else if (typeof _data.toJSON == "function") out(quoted(_data.toJSON()));
else if (Array.isArray(_data)) stringifyArray();
else if (es6) if (_data.constructor.BYTES_PER_ELEMENT) stringifyArray();
else if (_data instanceof Map) stringifyMapSet();
else if (_data instanceof Set) stringifyMapSet(true);
else stringifyObject();
else stringifyObject();
}
map(ptr, "valueEnd");
function stringifyArray() {
if (_data.length) {
out("[");
var itemLvl = lvl + 1;
for (var i = 0; i < _data.length; i++) {
if (i) out(",");
indent(itemLvl);
var item = validType(_data[i]) ? _data[i] : null;
var itemPtr = ptr + "/" + i;
_stringify(item, itemLvl, itemPtr);
}
indent(lvl);
out("]");
} else out("[]");
}
function stringifyObject() {
var keys = Object.keys(_data);
if (keys.length) {
out("{");
var propLvl = lvl + 1;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = _data[key];
if (validType(value)) {
if (i) out(",");
var propPtr = ptr + "/" + escapeJsonPointer(key);
indent(propLvl);
map(propPtr, "key");
out(quoted(key));
map(propPtr, "keyEnd");
out(":");
if (whitespace) out(" ");
_stringify(value, propLvl, propPtr);
}
}
indent(lvl);
out("}");
} else out("{}");
}
function stringifyMapSet(isSet) {
if (_data.size) {
out("{");
var propLvl = lvl + 1;
var first = true;
var entries = _data.entries();
var entry = entries.next();
while (!entry.done) {
var item = entry.value;
var key = item[0];
var value = isSet ? true : item[1];
if (validType(value)) {
if (!first) out(",");
first = false;
var propPtr = ptr + "/" + escapeJsonPointer(key);
indent(propLvl);
map(propPtr, "key");
out(quoted(key));
map(propPtr, "keyEnd");
out(":");
if (whitespace) out(" ");
_stringify(value, propLvl, propPtr);
}
entry = entries.next();
}
indent(lvl);
out("}");
} else out("{}");
}
}
function out(str) {
column += str.length;
pos += str.length;
json += str;
}
function indent(lvl) {
if (whitespace) {
json += "\n" + repeat(lvl, whitespace);
line++;
column = 0;
while (lvl--) {
if (wsLine) {
line += wsLine;
column = wsColumn;
} else column += wsColumn;
pos += wsPos;
}
pos += 1;
}
}
function map(ptr, prop) {
pointers[ptr] = pointers[ptr] || {};
pointers[ptr][prop] = {
line,
column,
pos
};
}
function repeat(n, str) {
return Array(n + 1).join(str);
}
};
var VALID_TYPES = [
"number",
"bigint",
"boolean",
"string",
"object"
];
function validType(data) {
return VALID_TYPES.indexOf(typeof data) >= 0;
}
var ESC_QUOTE = /"|\\/g;
var ESC_B = /[\b]/g;
var ESC_F = /\f/g;
var ESC_N = /\n/g;
var ESC_R = /\r/g;
var ESC_T = /\t/g;
function quoted(str) {
str = str.replace(ESC_QUOTE, "\\$&").replace(ESC_F, "\\f").replace(ESC_B, "\\b").replace(ESC_N, "\\n").replace(ESC_R, "\\r").replace(ESC_T, "\\t");
return "\"" + str + "\"";
}
var ESC_0 = /~/g;
var ESC_1 = /\//g;
function escapeJsonPointer(str) {
return str.replace(ESC_0, "~0").replace(ESC_1, "~1");
}
} });
//#endregion
//#region src/performance/cache.ts
var import_json_source_map = __toESM(require_json_source_map(), 1);
/**
* Cache for JSON.stringify results
* Using WeakMap with object identity as keys to avoid memory leaks
*/
const jsonStringCache = new WeakMap();
/**
* Cache for buildPathMap results
* Using WeakMap with object identity as keys to avoid memory leaks
*/
const pathMapCache = new WeakMap();
/**
* Cache for DiffFormatter instances
* Using a composite key approach for (original, new) pairs
*/
const formatterCache = new WeakMap();
/**
* Cached version of JSON.stringify with 2-space indentation
*/
function cachedJsonStringify(obj) {
if (typeof obj !== "object" || obj === null) return JSON.stringify(obj, null, 2);
if (jsonStringCache.has(obj)) return jsonStringCache.get(obj);
const result = JSON.stringify(obj, null, 2);
jsonStringCache.set(obj, result);
return result;
}
function cachedBuildPathMap(obj) {
if (typeof obj !== "object" || obj === null) return {};
if (pathMapCache.has(obj)) return pathMapCache.get(obj);
const jsonText = cachedJsonStringify(obj);
let pathMap;
try {
const { pointers } = (0, import_json_source_map.parse)(jsonText);
pathMap = pointers;
} catch (error) {
console.error("Error building path map:", error);
pathMap = {};
}
pathMapCache.set(obj, pathMap);
return pathMap;
}
function getCachedFormatter(originalObj, newObj, createFormatter) {
if (typeof originalObj !== "object" || originalObj === null || typeof newObj !== "object" || newObj === null) return createFormatter(originalObj, newObj);
let innerCache = formatterCache.get(originalObj);
if (!innerCache) {
innerCache = new WeakMap();
formatterCache.set(originalObj, innerCache);
}
if (innerCache.has(newObj)) return innerCache.get(newObj);
const formatter = createFormatter(originalObj, newObj);
innerCache.set(newObj, formatter);
return formatter;
}
//#endregion
//#region src/performance/fashHash.ts
/**
* A simple, non-cryptographic FNV-1a hash function.
*
* @param {string} str The string to hash.
* @returns {string} A 32-bit hash as a hex string.
*/
function fnv1aHash(str) {
let hash = 2166136261;
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i);
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
return (hash >>> 0).toString(16);
}
function fastHash(obj, fields) {
if (fields.length === 0) return "";
let combined = "";
for (let i = 0; i < fields.length; i++) {
const key = fields[i];
if (!key) continue;
const value = obj[key];
if (value !== void 0) {
const str = typeof value === "string" ? value : cachedJsonStringify(value);
combined += `${i}:${key}=${str}|`;
}
}
return combined ? fnv1aHash(combined) : "";
}
//#endregion
//#region src/performance/getEffectiveHashFields.ts
/**
* Utility to create hash fields from a plan or infer them from objects
*/
function getEffectiveHashFields(plan, obj1, obj2, fallbackFields = []) {
if (plan?.hashFields && plan.hashFields.length > 0) return plan.hashFields;
if (plan?.primaryKey) return [plan.primaryKey];
if (fallbackFields.length > 0) return fallbackFields;
if (obj1 && obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
const commonKeys = keys1.filter((k) => keys2.includes(k));
const idFields = commonKeys.filter((k) => k.includes("id") || k.includes("key") || k.includes("name"));
return idFields.length > 0 ? idFields.slice(0, 3) : commonKeys.slice(0, 3);
}
return [];
}
//#endregion
//#region src/performance/deepEqual.ts
function getPlanFingerprint(plan) {
if (!plan) return "default";
return `${plan.primaryKey || ""}-${plan.hashFields?.join(",") || ""}-${plan.strategy || ""}`;
}
function deepEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (obj1 && obj2 && typeof obj1 === "object" && typeof obj2 === "object") {
const arrA = Array.isArray(obj1);
const arrB = Array.isArray(obj2);
let i;
let length;
if (arrA && arrB) {
const arr1 = obj1;
const arr2 = obj2;
length = arr1.length;
if (length !== arr2.length) return false;
for (i = length; i-- !== 0;) if (!deepEqual(arr1[i], arr2[i])) return false;
return true;
}
if (arrA !== arrB) return false;
const keys = Object.keys(obj1);
length = keys.length;
if (length !== Object.keys(obj2).length) return false;
for (i = length; i-- !== 0;) {
const currentKey = keys[i];
if (currentKey !== void 0 && !Object.hasOwn(obj2, currentKey)) return false;
if (!deepEqual(obj1[currentKey], obj2[currentKey])) return false;
}
return true;
}
return Number.isNaN(obj1) && Number.isNaN(obj2);
}
const eqCache = new WeakMap();
const schemaEqCache = new WeakMap();
function deepEqualMemo(obj1, obj2, hotFields = []) {
if (obj1 === obj2) return true;
if (obj1 == null || obj2 == null) return obj1 === obj2;
const type1 = typeof obj1;
const type2 = typeof obj2;
if (type1 !== type2) return false;
if (type1 !== "object") return obj1 === obj2;
const a = obj1;
const b = obj2;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length === 0 && keysB.length === 0) return true;
if (keysA.length !== keysB.length) return false;
const shouldHash = hotFields.length > 0 && !Array.isArray(a) && !Array.isArray(b) && keysA.length > 3;
if (shouldHash) {
const keyCount = keysA.length + keysB.length;
const estimatedSize = keyCount * 24;
if (estimatedSize >= 64 && keyCount > hotFields.length * 2) {
const h1 = fastHash(a, hotFields);
const h2 = fastHash(b, hotFields);
if (h1 !== h2) return false;
}
}
let inner = eqCache.get(a);
if (inner?.has(b)) return inner.get(b) ?? false;
const result = deepEqual(a, b);
if (!inner) {
inner = new WeakMap();
eqCache.set(a, inner);
}
inner.set(b, result);
return result;
}
/**
* Schema-aware deep equality that prioritizes comparison of significant fields first
* Uses plan information to optimize equality checks
*/
function deepEqualSchemaAware(obj1, obj2, plan, hotFields) {
if (obj1 === obj2) return true;
if (obj1 == null || obj2 == null) return obj1 === obj2;
const type1 = typeof obj1;
const type2 = typeof obj2;
if (type1 !== type2) return false;
if (type1 !== "object") return obj1 === obj2;
const a = obj1;
const b = obj2;
const effectiveHashFields = getEffectiveHashFields(plan, obj1, obj2, hotFields);
if (effectiveHashFields.length > 0 && !Array.isArray(a) && !Array.isArray(b)) {
const keyCount = Object.keys(a).length + Object.keys(b).length;
const estimatedSize = keyCount * 24;
if (estimatedSize >= 64) {
const h1 = fastHash(a, effectiveHashFields);
const h2 = fastHash(b, effectiveHashFields);
if (h1 !== h2) return false;
}
}
const planFingerprint = getPlanFingerprint(plan);
let planCache = schemaEqCache.get(a);
if (planCache?.has(b)) {
const cached = planCache.get(b)?.get(planFingerprint);
if (cached !== void 0) return cached;
}
if (plan?.requiredFields && plan.requiredFields.size > 0) {
for (const field of plan.requiredFields) if (!deepEqual(a[field], b[field])) {
if (!planCache) {
planCache = new WeakMap();
schemaEqCache.set(a, planCache);
}
if (!planCache.has(b)) planCache.set(b, new Map());
planCache.get(b)?.set(planFingerprint, false);
return false;
}
}
if (plan?.primaryKey && plan.primaryKey in a && plan.primaryKey in b) {
const primaryKey = plan.primaryKey;
const keyEqual = deepEqual(a[primaryKey], b[primaryKey]);
if (!keyEqual) {
if (!planCache) {
planCache = new WeakMap();
schemaEqCache.set(a, planCache);
}
if (!planCache.has(b)) planCache.set(b, new Map());
planCache.get(b)?.set(planFingerprint, false);
return false;
}
}
const result = deepEqual(a, b);
if (!planCache) {
planCache = new WeakMap();
schemaEqCache.set(a, planCache);
}
if (!planCache.has(b)) planCache.set(b, new Map());
planCache.get(b)?.set(planFingerprint, result);
return result;
}
//#endregion
//#region src/core/arrayDiffAlgorithms.ts
function diffArrayByPrimaryKey(arr1, arr2, primaryKey, path, patches, onModification, hashFields, plan) {
const effectiveHashFields = getEffectiveHashFields(plan, void 0, void 0, hashFields || []);
const hashFieldsLength = effectiveHashFields.length;
const hasHashFields = hashFieldsLength > 0;
const arr1Length = arr1.length;
const arr2Length = arr2.length;
const keyToIndex = new Map();
const itemsByIndex = new Array(arr1Length);
const pathPrefix = path + "/";
for (let i = 0; i < arr1Length; i++) {
const item = arr1[i];
if (typeof item === "object" && item !== null) {
const keyValue = item[primaryKey];
if (keyValue !== void 0 && keyValue !== null) {
const keyType = typeof keyValue;
if (keyType === "string" || keyType === "number") {
keyToIndex.set(keyValue, i);
itemsByIndex[i] = item;
}
}
}
}
const modificationPatches = [];
const additionPatches = [];
for (let i = 0; i < arr2Length; i++) {
const newItem = arr2[i];
if (typeof newItem !== "object" || newItem === null) continue;
const keyValue = newItem[primaryKey];
if (keyValue === void 0) continue;
const keyType = typeof keyValue;
if (keyType !== "string" && keyType !== "number") continue;
const oldIndex = keyToIndex.get(keyValue);
if (oldIndex !== void 0) {
keyToIndex.delete(keyValue);
const oldItem = itemsByIndex[oldIndex];
let needsDiff = false;
if (hasHashFields) {
const oldItemObj = oldItem;
const newItemObj = newItem;
for (let j = 0; j < hashFieldsLength; j++) {
const field = effectiveHashFields[j];
if (field && oldItemObj[field] !== newItemObj[field]) {
needsDiff = true;
break;
}
}
if (!needsDiff && oldItem !== newItem) needsDiff = !deepEqual(oldItem, newItem);
} else if (plan) needsDiff = !deepEqualSchemaAware(oldItem, newItem, plan, effectiveHashFields);
else needsDiff = oldItem !== newItem && !deepEqual(oldItem, newItem);
if (needsDiff) {
const itemPath = pathPrefix + oldIndex;
onModification(oldItem, newItem, itemPath, modificationPatches, true);
}
} else additionPatches.push({
op: "add",
path: pathPrefix + "-",
value: newItem
});
}
const removalIndices = Array.from(keyToIndex.values());
removalIndices.sort((a, b) => b - a);
const removalPatches = new Array(removalIndices.length);
for (let i = 0; i < removalIndices.length; i++) {
const index = removalIndices[i];
removalPatches[i] = {
op: "remove",
path: pathPrefix + index,
oldValue: itemsByIndex[index]
};
}
const totalPatches = modificationPatches.length + removalPatches.length + additionPatches.length;
if (totalPatches > 0) patches.push(...modificationPatches, ...removalPatches, ...additionPatches);
}
function diffArrayLCS(arr1, arr2, path, patches, onModification, hashFields, plan) {
const effectiveHashFields = getEffectiveHashFields(plan, void 0, void 0, hashFields || []);
const n = arr1.length;
const m = arr2.length;
if (n === 0) {
const prefixPath$1 = path === "" ? "/" : path + "/";
for (let i = 0; i < m; i++) patches.push({
op: "add",
path: prefixPath$1 + i,
value: arr2[i]
});
return;
}
if (m === 0) {
for (let i = n - 1; i >= 0; i--) patches.push({
op: "remove",
path: path === "" ? "/" : path + "/" + i
});
return;
}
const max = n + m;
const offset = max;
const bufSize = 2 * max + 1;
const buffer1 = new Int32Array(bufSize);
const buffer2 = new Int32Array(bufSize);
buffer1.fill(-1);
buffer2.fill(-1);
let vPrev = buffer1;
let vCurr = buffer2;
vPrev[offset + 1] = 0;
const trace = new Array(max + 1);
let traceLen = 0;
let endD = -1;
const equalCache = new Map();
const cacheKey = (x$1, y$1) => x$1 << 16 | y$1;
const equalAt = (x$1, y$1) => {
const key = cacheKey(x$1, y$1);
let result = equalCache.get(key);
if (result !== void 0) return result;
result = plan ? deepEqualSchemaAware(arr1[x$1], arr2[y$1], plan, effectiveHashFields) : deepEqualMemo(arr1[x$1], arr2[y$1], effectiveHashFields);
equalCache.set(key, result);
return result;
};
const prefixPath = path === "" ? "/" : path + "/";
outer: for (let d = 0; d <= max; d++) {
const traceCopy = new Int32Array(bufSize);
traceCopy.set(vPrev);
trace[traceLen++] = traceCopy;
const dMin = -d;
const dMax = d;
for (let k = dMin; k <= dMax; k += 2) {
const kOffset = k + offset;
const vLeft = kOffset > 0 ? vPrev[kOffset - 1] : -1;
const vRight = kOffset < bufSize - 1 ? vPrev[kOffset + 1] : -1;
const down = k === dMin || k !== dMax && vLeft < vRight;
let x$1 = down ? vRight : vLeft + 1;
let y$1 = x$1 - k;
while (x$1 < n && y$1 < m && equalAt(x$1, y$1)) {
x$1++;
y$1++;
}
vCurr[kOffset] = x$1;
if (x$1 >= n && y$1 >= m) {
const finalCopy = new Int32Array(bufSize);
finalCopy.set(vCurr);
trace[traceLen++] = finalCopy;
endD = d;
break outer;
}
}
const tmp = vPrev;
vPrev = vCurr;
vCurr = tmp;
vCurr.fill(-1);
}
if (endD === -1) return;
const editScript = [];
let x = n;
let y = m;
for (let d = endD; d > 0; d--) {
const vRow = trace[d];
const k = x - y;
const kOffset = k + offset;
const vLeft = kOffset > 0 ? vRow[kOffset - 1] : -1;
const vRight = kOffset < bufSize - 1 ? vRow[kOffset + 1] : -1;
const down = k === -d || k !== d && vLeft < vRight;
const prevK = down ? k + 1 : k - 1;
const prevX = vRow[prevK + offset];
const prevY = prevX - prevK;
while (x > prevX && y > prevY) {
x--;
y--;
editScript.push({
op: "common",
ai: x,
bi: y
});
}
if (down) {
y--;
editScript.push({
op: "add",
bi: y
});
} else {
x--;
editScript.push({
op: "remove",
ai: x
});
}
}
while (x > 0 && y > 0) {
x--;
y--;
editScript.push({
op: "common",
ai: x,
bi: y
});
}
editScript.reverse();
const optimizedScript = [];
for (let i = 0; i < editScript.length; i++) {
const current = editScript[i];
const next = editScript[i + 1];
if (current && current.op === "remove" && next && next.op === "add" && current.ai !== void 0 && next.bi !== void 0) {
optimizedScript.push({
op: "replace",
ai: current.ai,
bi: next.bi
});
i++;
} else if (current) optimizedScript.push(current);
}
let currentIndex = 0;
for (const operation of optimizedScript) switch (operation.op) {
case "common": {
const v1 = arr1[operation.ai];
const v2 = arr2[operation.bi];
if (typeof v1 === "object" && v1 !== null && typeof v2 === "object" && v2 !== null) onModification(v1, v2, prefixPath + currentIndex, patches, false);
currentIndex++;
break;
}
case "replace": {
patches.push({
op: "replace",
path: prefixPath + currentIndex,
value: arr2[operation.bi],
oldValue: arr1[operation.ai]
});
currentIndex++;
break;
}
case "remove": {
patches.push({
op: "remove",
path: prefixPath + currentIndex,
oldValue: arr1[operation.ai]
});
break;
}
case "add": {
patches.push({
op: "add",
path: prefixPath + currentIndex,
value: arr2[operation.bi]
});
currentIndex++;
break;
}
}
}
function diffArrayUnique(arr1, arr2, path, patches) {
const n = arr1.length;
const m = arr2.length;
const pathPrefix = path + "/";
const patches_temp = [];
if (n === 0 && m === 0) return;
if (n === 0) {
for (let i = 0; i < m; i++) patches_temp.push({
op: "add",
path: pathPrefix + "-",
value: arr2[i]
});
patches.push(...patches_temp);
return;
}
if (m === 0) {
for (let i = n - 1; i >= 0; i--) patches_temp.push({
op: "remove",
path: pathPrefix + i,
oldValue: arr1[i]
});
patches.push(...patches_temp);
return;
}
const arr1Map = new Map();
const arr2Map = new Map();
for (let i = 0; i < n; i++) arr1Map.set(arr1[i], i);
for (let i = 0; i < m; i++) arr2Map.set(arr2[i], i);
const minLength = Math.min(n, m);
const replacedItems = new Set();
for (let i = 0; i < minLength; i++) {
const val1 = arr1[i];
const val2 = arr2[i];
if (val1 !== val2) {
patches_temp.push({
op: "replace",
path: pathPrefix + i,
value: val2,
oldValue: val1
});
replacedItems.add(val2);
}
}
const removalIndices = [];
for (let i = n - 1; i >= 0; i--) {
const item = arr1[i];
if (i < minLength && arr1[i] !== arr2[i]) continue;
if (!arr2Map.has(item)) removalIndices.push(i);
}
for (const index of removalIndices) patches_temp.push({
op: "remove",
path: pathPrefix + index,
oldValue: arr1[index]
});
for (let i = 0; i < m; i++) {
const item = arr2[i];
if (i < minLength && arr1[i] !== arr2[i]) continue;
if (!arr1Map.has(item)) patches_temp.push({
op: "add",
path: pathPrefix + "-",
value: item
});
}
patches.push(...patches_temp);
}
function checkArraysUnique(arr1, arr2) {
const len1 = arr1.length;
const len2 = arr2.length;
if (len1 !== len2) return false;
const seen1 = new Set();
const seen2 = new Set();
for (let i = 0; i < len1; i++) {
const val1 = arr1[i];
const val2 = arr2[i];
if (seen1.has(val1) || seen2.has(val2)) return false;
seen1.add(val1);
seen2.add(val2);
}
return true;
}
//#endregion
//#region src/utils/pathUtils.ts
/**
* Cache for path resolution results to avoid repeated computations
*/
const pathResolutionCache = new Map();
/**
* Cache for normalized paths to avoid repeated regex operations
*/
const normalizedPathCache = new Map();
/**
* Resolves a JSON Pointer path to get a value from an object
* Handles JSON Pointer escaping (~0 for ~, ~1 for /)
*/
function getValueByPath(obj, path) {
if (path === "") return obj;
let objCache = pathResolutionCache.get(path);
if (!objCache) {
objCache = new WeakMap();
pathResolutionCache.set(path, objCache);
}
if (typeof obj === "object" && obj !== null && objCache.has(obj)) return objCache.get(obj);
const parts = path.split("/").slice(1);
let current = obj;
for (const part of parts) {
if (typeof current !== "object" || current === null) {
if (typeof obj === "object" && obj !== null) objCache.set(obj, void 0);
return void 0;
}
const key = unescapeJsonPointer(part);
if (Array.isArray(current)) {
const index = Number.parseInt(key, 10);
if (Number.isNaN(index) || index < 0 || index >= current.length) {
if (typeof obj === "object" && obj !== null) objCache.set(obj, void 0);
return void 0;
}
current = current[index];
} else {
const objCurrent = current;
if (!Object.hasOwn(objCurrent, key)) {
if (typeof obj === "object" && obj !== null) objCache.set(obj, void 0);
return void 0;
}
current = objCurrent[key];
}
}
if (typeof obj === "object" && obj !== null) objCache.set(obj, current);
return current;
}
/**
* Resolves a patch path, handling special cases like "/-" for array append operations
*/
function resolvePatchPath(path, jsonObj, isForNewVersion = false) {
if (path.endsWith("/-")) {
const parentPath = path.slice(0, -2);
if (parentPath === "") {
if (Array.isArray(jsonObj)) return isForNewVersion ? `/${jsonObj.length - 1}` : `/${jsonObj.length}`;
return null;
}
const parentValue = getValueByPath(jsonObj, parentPath);
if (Array.isArray(parentValue)) return isForNewVersion ? `${parentPath}/${parentValue.length - 1}` : parentPath;
}
return path;
}
/**
* Normalizes a path by removing array indices (e.g., /items/0/name -> /items/name)
* Optimized to avoid regex for simple cases
*/
function normalizePath(path) {
if (normalizedPathCache.has(path)) return normalizedPathCache.get(path);
if (!/\d/.test(path)) {
normalizedPathCache.set(path, path);
return path;
}
const normalized = path.replace(/\/\d+/g, "");
normalizedPathCache.set(path, normalized);
return normalized;
}
/**
* Gets the parent path and generates a wildcard version
*/
function getWildcardPath(path) {
const normalizedPath = normalizePath(path);
const lastSlash = normalizedPath.lastIndexOf("/");
if (lastSlash >= 0) return `${normalizedPath.substring(0, lastSlash)}/*`;
return null;
}
/**
* Unescapes JSON Pointer special characters
* ~1 becomes /, ~0 becomes ~
*/
function unescapeJsonPointer(part) {
return part.replace(/~1/g, "/").replace(/~0/g, "~");
}
//#endregion
//#region src/core/buildPlan.ts
function _resolveRef(ref, schema) {
if (!ref.startsWith("#/")) {
console.warn(`Unsupported reference: ${ref}`);
return null;
}
const path = ref.substring(2).split("/");
let current = schema;
for (const part of path) {
if (typeof current !== "object" || current === null || !Object.hasOwn(current, part)) return null;
current = current[part];
}
return current;
}
function _traverseSchema(subSchema, docPath, plan, schema, visited = new Set(), options) {
if (!subSchema || typeof subSchema !== "object" || visited.has(subSchema)) return;
visited.add(subSchema);
if (subSchema.$ref) {
const resolved = _resolveRef(subSchema.$ref, schema);
if (resolved) _traverseSchema(resolved, docPath, plan, schema, visited, options);
visited.delete(subSchema);
return;
}
for (const keyword of [
"anyOf",
"oneOf",
"allOf"
]) {
const schemas = subSchema[keyword];
if (schemas && Array.isArray(schemas)) {
const seenFingerprints = new Set();
for (const s of schemas) {
const fp = stableStringify(s);
if (seenFingerprints.has(fp)) continue;
seenFingerprints.add(fp);
_traverseSchema(s, docPath, plan, schema, visited, options);
}
}
}
if (subSchema.type === "object") {
if (subSchema.properties) for (const key in subSchema.properties) _traverseSchema(subSchema.properties[key], `${docPath}/${key}`, plan, schema, visited, options);
if (typeof subSchema.additionalProperties === "object" && subSchema.additionalProperties) _traverseSchema(subSchema.additionalProperties, `${docPath}/*`, plan, schema, visited, options);
}
if (subSchema.type === "array" && subSchema.items) {
const arrayPlan = {
primaryKey: null,
strategy: "lcs"
};
let itemsSchema = subSchema.items;
if (itemsSchema.$ref) itemsSchema = _resolveRef(itemsSchema.$ref, schema) || itemsSchema;
arrayPlan.itemSchema = itemsSchema;
const isPrimitive = itemsSchema && (itemsSchema.type === "string" || itemsSchema.type === "number" || itemsSchema.type === "boolean");
if (isPrimitive) arrayPlan.strategy = "unique";
const customKey = options?.primaryKeyMap?.[docPath];
if (customKey) {
arrayPlan.primaryKey = customKey;
arrayPlan.strategy = "primaryKey";
} else if (!isPrimitive) {
const findMetadata = (s) => {
let currentSchema = s;
if (!currentSchema || typeof currentSchema !== "object") return null;
if (currentSchema.$ref) {
const resolved = _resolveRef(currentSchema.$ref, schema);
if (!resolved) return null;
currentSchema = resolved;
}
if (!currentSchema || currentSchema.type !== "object" || !currentSchema.properties) return null;
const props = currentSchema.properties;
const required = new Set(currentSchema.required || []);
const hashFields = [];
for (const key of required) {
const prop = props[key];
if (prop && (prop.type === "string" || prop.type === "number")) hashFields.push(key);
}
const potentialKeys = [
"id",
"name",
"port"
];
for (const key of potentialKeys) if (required.has(key)) {
const prop = props[key];
if (prop && (prop.type === "string" || prop.type === "number")) return {
primaryKey: key,
requiredFields: required,
hashFields
};
}
return null;
};
const schemas = itemsSchema.anyOf || itemsSchema.oneOf;
let metadata = null;
if (schemas) for (const s of schemas) {
metadata = findMetadata(s);
if (metadata?.primaryKey) break;
}
else metadata = findMetadata(itemsSchema);
if (metadata?.primaryKey) {
arrayPlan.primaryKey = metadata.primaryKey;
arrayPlan.requiredFields = metadata.requiredFields;
arrayPlan.hashFields = metadata.hashFields;
arrayPlan.strategy = "primaryKey";
}
}
if (options?.basePath && !docPath.startsWith(options.basePath)) {} else {
const targetPath = options?.basePath ? docPath.replace(options.basePath, "") : docPath;
const existingPlan = plan.get(targetPath);
if (!existingPlan) plan.set(targetPath, arrayPlan);
else if (isBetterPlan(arrayPlan, existingPlan)) {
mergePlanMetadata(arrayPlan, existingPlan);
plan.set(targetPath, arrayPlan);
} else mergePlanMetadata(existingPlan, arrayPlan);
}
_traverseSchema(subSchema.items, docPath, plan, schema, visited, options);
}
visited.delete(subSchema);
}
function buildPlan(options) {
const plan = new Map();
const { schema,...rest } = options;
_traverseSchema(schema, "", plan, schema, new Set(), rest);
return plan;
}
function stableStringify(obj) {
const seen = new WeakSet();
const stringify = (value) => {
if (value && typeof value === "object") {
if (seen.has(value)) return void 0;
seen.add(value);
const keys = Object.keys(value).sort();
const result = {};
for (const k of keys) result[k] = stringify(value[k]);
return result;
}
return value;
};
return JSON.stringify(stringify(obj));
}
const STRATEGY_RANK = {
primaryKey: 3,
unique: 2,
lcs: 1
};
function isBetterPlan(candidate, current) {
const rankA = STRATEGY_RANK[candidate.strategy ?? "lcs"];
const rankB = STRATEGY_RANK[current.strategy ?? "lcs"];
if (rankA !== rankB) return rankA > rankB;
if (candidate.primaryKey && !current.primaryKey) return true;
if (!candidate.primaryKey && current.primaryKey) return false;
const lenA = candidate.hashFields?.length ?? 0;
const lenB = current.hashFields?.length ?? 0;
return lenA > lenB;
}
function mergePlanMetadata(dst, src) {
if (!dst.hashFields && src.hashFields) dst.hashFields = [...src.hashFields];
if (dst.hashFields && src.hashFields) {
const merged = new Set([...dst.hashFields, ...src.hashFields]);
dst.hashFields = Array.from(merged);
}
if (!dst.requiredFields && src.requiredFields) dst.requiredFields = new Set(src.requiredFields);
}
//#endregion
//#region src/formatting/DiffFormatter.ts
const diffFormatterCache = new Map();
function getPathLineRange(pathMap, path, jsonObj, isForNewVersion = false) {
const resolvedPath = resolvePatchPath(path, jsonObj, isForNewVersion);
if (!resolvedPath) return null;
const info = pathMap[resolvedPath];
if (info?.value && info.valueEnd) return {
start: info.value.line + 1,
end: info.valueEnd.line + 1
};
const pathParts = resolvedPath.split("/").filter((p) => p !== "");
for (let i = pathParts.length; i > 0; i--) {
const parentPath = `/${pathParts.slice(0, i).join("/")}`;
const parentInfo = pathMap[parentPath];
if (parentInfo?.value && parentInfo.valueEnd) return {
start: parentInfo.value.line + 1,
end: parentInfo.valueEnd.line + 1
};
}
return null;
}
var DiffFormatter = class {
originalJson;
newJson;
originalPathMap;
newPathMap;
plan;
constructor(originalJson, newJson, plan) {
this.originalJson = originalJson;
this.newJson = newJson;
this.originalPathMap = cachedBuildPathMap(originalJson);
this.newPathMap = cachedBuildPathMap(newJson);
this.plan = plan;
}
getSampleContent(json) {
const str = cachedJsonStringify(json);
if (str.length <= 300) return str;
return str.substring(0, 100) + str.substring(str.length / 2 - 50, str.length / 2 + 50) + str.substring(str.length - 100);
}
format(patches) {
const patchesKey = this.createPatchesKey(patches);
const planKey = this.plan ? getPlanFingerprint(this.plan) : "default";
const contentHash = fastHash({
originalSize: cachedJsonStringify(this.originalJson).length,
newSize: cachedJsonStringify(this.newJson).length,
originalSample: this.getSampleContent(this.originalJson),
newSample: this.getSampleContent(this.newJson)
}, [
"originalSize",
"newSize",
"originalSample",
"newSample"
]);
const cacheKey = `${contentHash}-${patchesKey}-${planKey}`;
const cached = diffFormatterCache.get(cacheKey);
if (cached) return cached;
const result = this.generateDiff(patches);
if (diffFormatterCache.size > 1e3) {
const keys = Array.from(diffFormatterCache.keys());
for (let i = 0; i < keys.length / 2; i++) diffFormatterCache.delete(keys[i]);
}
diffFormatterCache.set(cacheKey, result);
return result;
}
createPatchesKey(patches) {
if (patches.length === 0) return "empty";
const patchData = {
count: patches.length,
operations: patches.map((p) => `${p.op}:${p.path}`).join(","),
sample: patches.slice(0, 3).map((p) => p.op).join("")
};
return fastHash(patchData, [
"count",
"operations",
"sample"
]);
}
generateDiff(patches) {
const originalAffectedLines = new Set();
const newAffectedLines = new Set();
for (const op of patches) {
if (op.op === "remove" || op.op === "replace") {
const range = getPathLineRange(this.originalPathMap, op.path, this.originalJson, false);
if (range) for (let i = range.start; i <= range.end; i++) originalAffectedLines.add(i);
}
if (op.op === "add" || op.op === "replace") {
const range = getPathLineRange(this.newPathMap, op.path, this.newJson, true);
if (range) for (let i = range.start; i <= range.end; i++) newAffectedLines.add(i);
}
}
const originalFormatted = cachedJsonStringify(this.originalJson);
const newFormatted = cachedJsonStringify(this.newJson);
const originalLines = originalFormatted.split("\n");
const newLines = newFormatted.split("\n");
const originalDiffLines = originalLines.map((line, index) => ({
lineNumber: index + 1,
content: line,
type: originalAffectedLines.has(index + 1) ? "removed" : "unchanged"
}));
const newDiffLines = newLines.map((line, index) => ({
lineNumber: index + 1,
content: line,
type: newAffectedLines.has(index + 1) ? "added" : "unchanged"
}));
const unified = this.generateUnifiedDiff(originalDiffLines, newDiffLines);
return {
originalLines: originalDiffLines,
newLines: newDiffLines,
unifiedDiffLines: unified
};
}
generateUnifiedDiff(originalDiffLines, newDiffLines) {
const unified = [];
let i = 0;
let j = 0;
while (i < originalDiffLines.length && j < newDiffLines.length) {
const iLine = originalDiffLines[i];
const jLine = newDiffLines[j];
if (iLine?.type === "unchanged" && jLine?.type === "unchanged") {
if (iLine && jLine) unified.push({
type: "unchanged",
content: iLine.content,
oldLineNumber: iLine.lineNumber,
newLineNumber: jLine.lineNumber,
key: `unchanged-${iLine.lineNumber}-${jLine.lineNumber}`
});
i++;
j++;
} else {
while (i < originalDiffLines.length && originalDiffLines[i]?.type === "removed") {
const line = originalDiffLines[i];
if (line) unified.push({
type: "removed",
content: line.content,
oldLineNumber: line.lineNumber,
key: `removed-${line.lineNumber}`
});
i++;
}
while (j < newDiffLines.length && newDiffLines[j]?.type === "added") {
const line = newDiffLines[j];
if (line) unified.push({
type: "added",
content: line.content,
newLineNumber: line.lineNumber,
key: `added-${line.lineNumber}`
});
j++;
}
}
}
while (i < originalDiffLines.length) {
const line = originalDiffLines[i];
if (line) unified.push({
type: "removed",
content: line.content,
oldLineNumber: line.lineNumber,
key: `removed-${line.lineNumber}`
});
i++;
}
while (j < newDiffLines.length) {
const line = newDiffLines[j];
if (line) unified.push({
type: "added",
content: line.content,
newLineNumber: line.lineNumber,
key: `added-${line.lineNumber}`
});
j++;
}
return unified;
}
};
//#endregion
//#region src/aggregators/StructuredDiff.ts
function countChangedLines(diff) {
const addCount = diff.newLines.filter((line) => line.type === "added").length;
const removeCount = diff.originalLines.filter((line) => line.type === "removed").length;
return {
addCount,
removeCount
};
}
var StructuredDiff = class {
plan;
constructor(options) {
this.plan = options.plan;
}
getIdKeyForPath(pathPrefix) {
const arrayPlan = this.getArrayPlanForPath(pathPrefix, this.plan);
if (arrayPlan?.primaryKey) return arrayPlan.primaryKey;
return "id";
}
supportsAggregation(pathPrefix) {
const hasSchemaKey = this.getArrayPlanForPath(pathPrefix, this.plan)?.primaryKey;
return Boolean(hasSchemaKey);
}
isArrayPath(pathPrefix, config) {
if (this.plan) {
const arrayPlan = this.getArrayPlanForPath(pathPrefix, this.plan);
if (arrayPlan) return true;
}
const originalValue = getValueByPath(config.original, pathPrefix);
const newValue = getValueByPath(config.modified, pathPrefix);
return Array.isArray(originalValue) || Array.isArray(newValue);
}
getArrayPlanForPath(path, plan) {
const normalizedPath = path.replace(/\/\d+/g, "");
const hasLeadingSlash = path.startsWith("/");
const candidatePaths = new Set([
path,
normalizedPath,
hasLeadingSlash ? path.substring(1) : `/${path}`,
hasLeadingSlash ? normalizedPath.substring(1) : `/${normalizedPath}`
]);
for (const candidate of candidatePaths) {
const arrayPlan = plan.get(candidate);
if (arrayPlan) return arrayPlan;
}
return void 0;
}
aggregateWithoutChildSeparation(patches, config) {
const { pathPrefix } = config;
const originalParent = this.getAndStripChildArray(config.original, pathPrefix);
const newParent = this.getAndStripChildArray(config.modified, pathPrefix);
const parentFormatter = getCachedFormatter(originalParent, newParent, (orig, newVal) => new DiffFormatter(orig, newVal));
const parentDiffLines = parentFormatter.format(patches);
const parentLineCounts = countChangedLines(parentDiffLines);
return {
parentDiff: {
original: originalParent,
new: newParent,
patches,
diffLines: parentDiffLines.unifiedDiffLines,
...parentLineCounts
},
childDiffs: {}
};
}
compareObjects(obj1, obj2, plan) {
if (obj1 === obj2) return true;
if (!obj1 || !obj2) return false;
if (plan) return deepEqualSchemaAware(obj1, obj2, plan);
const hashFields = getEffectiveHashFields(plan, obj1, obj2);
if (hashFields.length > 0) {
const h1 = fastHash(obj1, hashFields);
const h2 = fastHash(obj2, hashFields);
if (h1 !== h2) return false;
}
return cachedJsonStringify(obj1) === cachedJsonStringify(obj2);
}
execute(config) {
const { pathPrefix } = config;
if (!this.isArrayPath(pathPrefix, config)) throw new Error(`Path ${pathPrefix} does not represent an array in the schema or data`);
const patches = config.patches || new JsonSchemaPatcher({ plan: this.plan }).execute({
original: config.original,
modified: config.modified
});
if (!this.supportsAggregation(pathPrefix)) return this.aggregateWithoutChildSeparation(patches, config);
const idKey = this.getIdKeyForPath(pathPrefix);
const parentPatches = [];
const childPatchesById = {};
const originalChildren = getValueByPath(config.original, pathPrefix) || [];
const originalChildIdsByIndex = originalChildren.map((child) => child[idKey]);
for (const patch of patches) {
if (!patch.path.startsWith(pathPrefix)) {
parentPatches.push(patch);
continue;
}
const relativePath = patch.path.substring(pathPrefix.length);
const match = relativePath.match(/^\/(\d+|-)$/);
const matchIndex = match?.[1];
let childId;
if (matchIndex) {
if (matchIndex === "-" && patch.op === "add") childId = patch.value?.[idKey];
else if (matchIndex !== "-") {
const index = Number.parseInt(matchIndex, 10);
if (patch.op === "add") childId = patch.value?.[idKey];
else childId = originalChildIdsByIndex[index];
}
} else {
const nestedMatch = relativePath.match(/^\/(\d+)/);
const nestedIndex = nestedMatch?.[1];
if (nestedIndex) {
const index = Number.parseInt(nestedIndex, 10);
childId = originalChildIdsByIndex[index];
}
}
if (childId) {
if (!(childId in childPatchesById)) childPatchesById[childId] = [];
childPatchesById[childId]?.push(patch);
} else parentPatches.push(patch);
}
const parentPath = pathPrefix.substring(0, pathPrefix.lastIndexOf("/"));
const originalParent = this.getAndStripChildArray(config.original, pathPrefix);
const newParent = this.getAndStripChildArray(config.modified, pathPrefix);
const parentFormatter = getCachedFormatter(originalParent, newParent, (orig, newVal) => new DiffFormatter(orig, newVal));
const transformedParentPatches = parentPatches.map((p) => {
if (p.path.startsWith(parentPath)) return {
...p,
path: p.path.substring(parentPath.length)
};
return p;
});
const parentDiffLines = parentFormatter.format(transformedParentPatches);
const parentLineCounts = countChangedLines(parentDiffLines);
const childDiffs = {};
const newChildren = getValueByPath(config.modified, pathPrefix) || [];
const originalChildrenById = new Map(originalChildren.map((c) => [c[idKey], c]));
const newChildrenById = new Map(newChildren.map((c) => [c[idKey], c]));
const allChildIds = new Set([...originalChildrenById.keys(), ...newChildrenById.keys()]);
for (const childId of allChildIds) {
const originalChild = originalChildrenById.get(childId) || null;
const newChild = newChildrenById.get(childId) || null;
const patchesForChild = childPatchesById[childId] || [];
const transformedPatches = patchesForChild.map((p) => {
const originalIndex = originalChildren.findIndex((c) => c[idKey] === childId);
if (originalIndex >= 0) {
const childPathPrefix = `${pathPrefix}/${originalIndex}`;
return {
...p,
path: p.path.substring(childPathPrefix.length)
};
}
if (p.op === "add") return {
...p,
path: ""
};
const pathMatch = p.path.match(new RegExp(`^${pathPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/(\d+)`));
const pathIndex = pathMatch?.[1];
if (pathIndex) {
const index = Number.parseInt(pathIndex, 10);
const childPathPrefix = `${pathPrefix}/${index}`;
return {
...p,
path: p.path.substring(childPathPrefix.length)
};
}
return p;
});
const formatter = getCachedFormatter(originalChild, newChild, (orig, newVal) => new DiffFormatter(orig, newVal));
let diffLines;
let lineCounts;
if (originalChild && !newChild) {
const originalFormatted = cachedJsonStringify(originalChild);
const originalLines = originalFormatted.split("\n");
diffLines = {
originalLines: originalLines.map((line, index) => ({
lineNumber: index + 1,
content: line,
type: "removed"
})),
newLines: [{
lineNumber: 1,
content: "null",
type: "unchanged"
}],
unifiedDiffLines: originalLines.map((line, index) => ({
type: "removed",
content: line,
oldLineNumber: index + 1,
key: `removed-${index + 1}`
}))
};
lineCounts = {
addCount: 0,
removeCount: originalLines.length
};
} else if (!or