UNPKG

datum-merge

Version:

Simplified diff and merging for deeply nested objects

148 lines (147 loc) 5.98 kB
import { isArrayOfAny, emptyObject, isNullish, isObject, isString } from "./type-utils"; import { deepClone, fastGlobMatch, getObjectKeys } from "./datum-utils"; import { deepDiffTyped } from "./diff-high"; import { UpdateCode, mergeScalarField, mergeVectorField } from "./merge-low"; import { updateCodeInfo } from "./merge-high"; export function detailMerge(target, source, mergeCodes) { if (!isObject(mergeCodes) || emptyObject(mergeCodes)) { return false; } let changed = false; const mergeKeys = getObjectKeys(mergeCodes); for (const label of mergeKeys) { const mergeCode = mergeCodes[label]; if (!isString(mergeCode)) { if (isObject(target[label]) && isObject(source[label])) { changed = detailMerge(target[label], source[label], mergeCode) || changed; continue; } if (!isObject(target[label]) && isObject(source[label])) { const targetVal = {}; detailMerge(targetVal, source[label], mergeCode); changed = mergeScalarField(target, { [label]: targetVal }, label, UpdateCode.I) || changed; continue; } changed = mergeScalarField(target, source, label, UpdateCode.I) || changed; continue; } if (mergeCode.toString().startsWith("X") ? mergeVectorField(target, source, label, mergeCode) : mergeScalarField(target, source, label, mergeCode)) { changed = true; } } return changed; } export function immutableDetailMerge(target, source, mergeCodes) { const targetCopy = deepClone(target); detailMerge(targetCopy, source, mergeCodes); return targetCopy; } export function getDetailKeys(obj, mergeCodes, excludeKeys) { const includeKeys = []; if (!obj || !mergeCodes) { return includeKeys; } for (const label of getObjectKeys(obj, excludeKeys)) { const labelConf = mergeCodes[label]; if (isString(labelConf) && updateCodeInfo(labelConf).enable) includeKeys.push(label); if (isObject(labelConf) && !emptyObject(labelConf)) includeKeys.push(label); } return includeKeys; } export function customMerge(target, source, mergeConf, excludeKeys) { switch (mergeConf) { case UpdateCode.C: return false; case UpdateCode.T: return true; case UpdateCode.N: return false; case UpdateCode.Y: Object.assign(target, Object.assign({}, source)); return true; } const mergeCodes = fillUpdateCodes(source, mergeConf, false, excludeKeys); if (emptyObject(mergeCodes)) { return false; } const changed = detailMerge(target, source, mergeCodes); return changed; } export function customMergeDiff(target, source, mergeConf, excludeKeys) { const targetBkp = deepClone(target); const changed = customMerge(target, source, mergeConf, excludeKeys); const delta = deepDiffTyped(targetBkp, target, true); return changed || !emptyObject(delta) ? delta : false; } export function immutableCustomMerge(target, source, mergeConf, skipFill = false) { const mergeCodes = !skipFill ? fillUpdateCodes(source, mergeConf) : mergeConf; const targetCopy = deepClone(target); detailMerge(targetCopy, source, mergeCodes); return targetCopy; } export const fillUpdateCodes = (source, mergeConf, blockUnset = false, excludeKeys) => { if (isString(mergeConf)) { mergeConf = { scalar: mergeConf }; } const deepConf = { scalar: (mergeConf === null || mergeConf === void 0 ? void 0 : mergeConf.scalar) || UpdateCode.B, vector: (mergeConf === null || mergeConf === void 0 ? void 0 : mergeConf.vector) || UpdateCode.XS, nested: (mergeConf === null || mergeConf === void 0 ? void 0 : mergeConf.nested) || UpdateCode.N, }; const globKeys = getObjectKeys(mergeConf) .filter((s) => s.includes("*")) .sort((s1, s2) => s2.length - s1.length); const mergeCodes = {}; const sourceKeys = getObjectKeys(source); for (const srcLabel of sourceKeys) { const srcValue = source[srcLabel]; const labelConf = mergeConf === null || mergeConf === void 0 ? void 0 : mergeConf[srcLabel]; if (excludeKeys === null || excludeKeys === void 0 ? void 0 : excludeKeys.includes(srcLabel)) { mergeCodes[srcLabel] = UpdateCode.N; continue; } if (isNullish(srcValue) && blockUnset) { mergeCodes[srcLabel] = UpdateCode.N; continue; } if (isString(labelConf)) { mergeCodes[srcLabel] = labelConf; continue; } if (isObject(labelConf) && isObject(srcValue)) { mergeCodes[srcLabel] = fillUpdateCodes(srcValue, Object.assign(Object.assign({}, deepConf), labelConf), blockUnset); continue; } const globIndex = globKeys.findIndex((g) => fastGlobMatch(g, srcLabel)); if (globIndex >= 0) { const globConf = mergeConf[globKeys[globIndex]]; if (isString(globConf)) { mergeCodes[srcLabel] = globConf; continue; } if (isObject(globConf) && isObject(srcValue)) { mergeCodes[srcLabel] = fillUpdateCodes(srcValue, Object.assign(Object.assign({}, deepConf), globConf), blockUnset); continue; } } if (isObject(srcValue)) { mergeCodes[srcLabel] = deepConf.nested; } else if (isArrayOfAny(srcValue)) { mergeCodes[srcLabel] = deepConf.vector; } else { mergeCodes[srcLabel] = deepConf.scalar; } } if (getObjectKeys(mergeCodes).length !== sourceKeys.length) { throw new Error("illegal merge config state"); } return mergeCodes; };