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 getKeyWithCheck = (x, key) => x == null ? null : isMapKey(key) ? x.has(key.key) ? {value: x.get(key.key)} : null : x.hasOwnProperty(key) ? {value: x[key]} : null; export const deepGetOrNil = (...keyDescriptions) => { const keys = constructKeys(keyDescriptions); return obj => keys.reduce(getKey, obj); }; export const deget = deepGetOrNil; const chain = (list, stop, step) => (function worker (index) { return index >= list.length ? stop : step(list[index], worker(index + 1)); })(0); const copyOnModification = (obj, key, value, modifierFn) => { const modified = modifierFn(value); return value === modified ? obj : copyWith(obj, key, modified); }; export const deepCopyOnWrite = (...keyDescriptions) => { const keys = constructKeys(keyDescriptions); return val => chain(keys, () => val, (key, next) => x => copyOnModification(x, key, getKey(x, key), next) ); }; export const decow = deepCopyOnWrite; export const deepCopyOnMap = (...keyDescriptions) => { const keys = constructKeys(keyDescriptions); return fn => chain(keys, fn, (key, next) => x => { const valueWithCheck = getKeyWithCheck(x, key); if (valueWithCheck == null) return x; return copyOnModification(x, key, valueWithCheck.value, next); }); }; export const decomap = deepCopyOnMap; export const cowValueModel = (...keyDescriptions) => { const GET_SENTINEL = {}; return (obj, val = GET_SENTINEL) => (val === GET_SENTINEL ? deget(...keyDescriptions) : decow(...keyDescriptions)(val)) (obj); }; 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}; };