datum-merge
Version:
Simplified diff and merging for deeply nested objects
139 lines (138 loc) • 4.28 kB
JavaScript
import { concat, differenceWith, intersectionWith, unionWith, isEqual } from "lodash-es";
import { emptyValue, isArrayOfAny, isNullish, isPrimitive, typeOfValue } from "./type-utils";
import { areArraysEqual, deepClone } from "./datum-utils";
export const UpdateCode = {
T: "T",
C: "C",
N: "N",
Y: "Y",
B: "B",
U: "U",
H: "H",
I: "I",
D: "D",
XR: "XR",
XM: "XM",
XD: "XD",
XI: "XI",
XS: "XS",
XF: "XF",
};
export function mergeScalarField(target, source, label, mergeCode) {
const sourceHas = !isNullish(source[label]);
const targetKey = target.hasOwnProperty(label);
const targetHas = targetKey && !isNullish(target[label]);
if (!targetHas && !sourceHas) {
return false;
}
if (targetHas && sourceHas) {
if (typeOfValue(target[label]) !== typeOfValue(source[label])) {
throw new TypeError("scalar type mismatch for " + label);
}
if (target[label] === source[label]) {
return false;
}
}
let migrateVal = false;
switch (mergeCode) {
case UpdateCode.N:
return false;
case UpdateCode.Y:
target[label] = deepClone(source[label]);
return true;
case UpdateCode.B:
if (sourceHas)
migrateVal = true;
break;
case UpdateCode.U:
if (targetHas)
migrateVal = true;
break;
case UpdateCode.I:
if (!targetHas)
migrateVal = true;
break;
case UpdateCode.H:
if (targetHas && sourceHas)
migrateVal = true;
break;
case UpdateCode.D:
if (targetHas && !sourceHas)
migrateVal = true;
break;
case UpdateCode.XR:
case UpdateCode.XS:
case UpdateCode.XF:
case UpdateCode.XM:
case UpdateCode.XD:
case UpdateCode.XI:
return mergeVectorField(target, source, label, mergeCode);
}
if (!migrateVal) {
return false;
}
target[label] = !sourceHas || isPrimitive(source[label])
? source[label]
: deepClone(source[label]);
if (emptyValue(target[label])) {
delete target[label];
return targetKey;
}
return true;
}
;
export function mergeVectorField(target, source, label, mergeCode) {
var _a, _b;
let sourceVals = source[label];
if (isNullish(sourceVals)) {
return false;
}
let sourceVec = isArrayOfAny(sourceVals);
const targetKey = target.hasOwnProperty(label);
const targetHas = targetKey && !isNullish(target[label]);
const targetVec = isArrayOfAny(target[label]);
if (targetHas && !targetVec) {
throw new TypeError("type change to vector for " + label);
}
if (!sourceVec) {
sourceVals = [sourceVals];
sourceVec = true;
}
let targetVals;
switch (mergeCode) {
case UpdateCode.N:
return false;
case UpdateCode.Y:
target[label] = deepClone(sourceVals);
return true;
case UpdateCode.XR:
targetVals = sourceVals;
break;
case UpdateCode.XS:
targetVals = concat((_a = target[label]) !== null && _a !== void 0 ? _a : [], sourceVals);
break;
case UpdateCode.XF:
targetVals = concat(sourceVals, (_b = target[label]) !== null && _b !== void 0 ? _b : []);
break;
case UpdateCode.B:
case UpdateCode.XM:
targetVals = unionWith(target[label], sourceVals, isEqual);
break;
case UpdateCode.XD:
targetVals = differenceWith(target[label], sourceVals, isEqual);
break;
case UpdateCode.XI:
targetVals = intersectionWith(target[label], sourceVals, isEqual);
break;
default:
return false;
}
if (!(targetVals === null || targetVals === void 0 ? void 0 : targetVals.length)) {
delete target[label];
return targetKey;
}
const changed = !areArraysEqual(targetVals, target[label]);
target[label] = !changed ? targetVals : deepClone(targetVals);
return changed;
}
;