function copyArrayWith (obj, key, value) { const result = obj == null ? [] : [...obj]; result[key] = value; return result; } function copyMapWith (obj, key, value) { const result = obj == null ? new Map() : new Map(obj); result.set(key, value); return result; } function copyObjectWith (obj, key, value) { const result = obj == null ? {} : {...obj}; result[key] = value; return result; } const isMapKey = key => typeof key === 'object' && key.isKeyInMap === true; export const keyInMap = key => ({isKeyInMap: true, key}); export const kim = keyInMap; const copyWith = (obj, key, value) => typeof key === 'number' ? copyArrayWith(obj, key, value) : isMapKey(key) ? copyMapWith(obj, key.key, value) : copyObjectWith(obj, key, value); const constructKeys = function (keyDescriptions) { const keys = []; function fillKeys (keyDescriptions) { keyDescriptions.forEach(each => { if (typeof each === 'number' || isMapKey(each)) keys.push(each); else if (Array.isArray(each)) fillKeys(each); else keys.push(...each.toString().split('.')); }); } fillKeys(keyDescriptions); return keys; }; const getKey = (x, key) => x == null ? undefined : isMapKey(key) ? x.get(key.key) : x[key]; const deepGet = (keys, obj) => keys.reduce(getKey, obj); const deepPut = (keys, obj, val) => { function setVal (x, index) { if (index >= keys.length) return val; const key = keys[index], value = getKey(x, key), modified = setVal(value, index + 1); return value === modified ? x : copyWith(x, key, modified); } return setVal(obj, 0); }; export const deepGetOrNil = (...keyDescriptions) => { const keys = constructKeys(keyDescriptions); return obj => deepGet(keys, obj); }; export const deget = deepGetOrNil; export const deepCopyOnWrite = (...keyDescriptions) => { const keys = constructKeys(keyDescriptions); return val => obj => deepPut(keys, obj, val); }; export const decow = deepCopyOnWrite; export const cowValueModel = (...keyDescriptions) => { const keys = constructKeys(keyDescriptions); const GET_SENTINEL = {}; return (obj, val = GET_SENTINEL) => val === GET_SENTINEL ? deepGet(keys, obj) : deepPut(keys, obj, val); }; export const cowWorkshop = (keys, fn = x => x) => (obj, {result = obj, resultKeys = keys, diff} = {}) => { keys.forEach((key, index) => { const value = fn(deget(key)(obj)); if (typeof value === "undefined") return; const modifier = decow(resultKeys[index])(value); const oldResult = result; result = modifier(oldResult); if (result !== oldResult) { diff = modifier(diff); } }); return {result, diff}; };