import { isArrayOfAny, emptyObject, isObject, isString } from "./type-utils"; import { deepClone, getObjectKeys } from "./datum-utils"; import { MergeCode, UpdateCode, mergeScalarField, mergeVectorField } from "./merge-low"; import { deepDiffTyped } from "./diff-high"; import { PatchResult, deepPatchLog } from "./patch-low"; export type MergePerms = { enable: boolean; insert?: boolean; update?: boolean; unset?: boolean; }; export function updateCodeInfo( mergeCode: MergeCode, ): MergePerms { if (!isString(mergeCode)) { return { enable: false }; } const allowUnset = ([ UpdateCode.Y, UpdateCode.D, UpdateCode.U, UpdateCode.XR, UpdateCode.XD, UpdateCode.XI] as string[] ).includes(mergeCode); const allowInsert = ([ UpdateCode.Y, UpdateCode.I, UpdateCode.B, UpdateCode.XR, UpdateCode.XM, UpdateCode.XS, UpdateCode.XF] as string[] ).includes(mergeCode); const allowUpdate = ([ UpdateCode.Y, UpdateCode.H, UpdateCode.U, UpdateCode.B, UpdateCode.XR, UpdateCode.XS, UpdateCode.XF, UpdateCode.XM, UpdateCode.XI, UpdateCode.XD] as string[] ).includes(mergeCode); return { insert: allowInsert, update: allowUpdate, unset: allowUnset, enable: allowInsert || allowUpdate || allowUnset, }; } //----------------------------------------------------------------------------- /** * merges every top-level key present in source * C, T not supported here * @returns if target was changed */ export function shallowMerge( target: any, source: any, scalarCode: MergeCode, vectorCode?: MergeCode, excludeKeys?: string[], includeKeys?: string[], //for delete ): boolean { const sourceKeys = getObjectKeys(source, excludeKeys, includeKeys); if (!sourceKeys?.length) { return false; } let changed = false; for (const label of sourceKeys) { if (isArrayOfAny(target[label]) || isArrayOfAny(source[label])) { //fails if target is not array if (mergeVectorField(target, source, label, vectorCode ?? scalarCode)) { changed = true; } continue; } if (mergeScalarField(target, source, label, scalarCode)) { changed = true; } } return changed; } /** * shallow merge but into a clone of target * @returns new object with merged result */ export function immutableMerge( target: any, source: any, scalarCode: MergeCode, vectorCode?: MergeCode, ): any { const targetCopy = deepClone(target); shallowMerge(targetCopy, source, scalarCode, vectorCode); return targetCopy; }; //----------------------------------------------------------------------------- /** * recursively merges any unstructured datum * nesting can be disabled or treated as scalar * C, T not supported here * @returns if target was changed */ export function deepMerge( target: { [key: string]: any }, source: { [key: string]: any }, scalarCode: MergeCode, vectorCode: MergeCode, nestedCode: MergeCode, ): boolean { const sourceKeys = getObjectKeys(source); if (!sourceKeys?.length) { return false; } let changed = false; for (const label of sourceKeys) { if (isArrayOfAny(target[label]) || isArrayOfAny(source[label])) { changed = mergeVectorField(target, source, label, vectorCode) || changed; continue; } //recursive call for objects //todo should nest for empty target if (isObject(target[label]) && isObject(source[label])) { if (nestedCode === UpdateCode.N) continue; if (nestedCode === UpdateCode.Y) { changed = mergeScalarField(target, source, label, scalarCode) || changed; continue; } changed = deepMerge(target[label], source[label], nestedCode.startsWith("X") ? scalarCode : nestedCode, nestedCode.startsWith("X") ? nestedCode : vectorCode, nestedCode) || changed; continue; } changed = mergeScalarField(target, source, label, scalarCode) || changed; } return changed; } /** * deep merge but into a clone of target * @returns new object with merged result */ export function immutableDeepMerge( target: { [key: string]: any }, source: { [key: string]: any }, scalarCode: MergeCode, vectorCode?: MergeCode, nestedCode?: MergeCode, ): any { const targetCopy = deepClone(target); deepMerge(targetCopy, source, scalarCode, vectorCode ?? scalarCode, nestedCode ?? scalarCode, ); return targetCopy; }; /** * simulate diff generated by deep merge * target object is not modified * deletes are lost in the delta object * @returns diff or false if no changes */ export function diffFromMerge( target: { [key: string]: any }, source: { [key: string]: any }, scalarCode: MergeCode, vectorCode?: MergeCode, nestedCode?: MergeCode, orderInd: boolean = false, ): any | false { const targetCopy = immutableDeepMerge(target, source, scalarCode, vectorCode, nestedCode); const delta = deepDiffTyped(target, targetCopy, orderInd); return emptyObject(delta) ? false : delta; } /** * simulate patch generated by deep merge * target object is not modified * @returns patch array or false if no changes */ export function patchFromMerge( target: { [key: string]: any }, source: { [key: string]: any }, scalarCode: MergeCode, vectorCode?: MergeCode, nestedCode?: MergeCode, orderInd: boolean = false, ): PatchResult[] | false { const targetCopy = immutableDeepMerge(target, source, scalarCode, vectorCode, nestedCode); const patch = deepPatchLog(target, targetCopy, orderInd, true); return !patch?.length ? false : patch; }