const copyArrayWith = (key, value) => obj => { const result = obj == null ? [] : [...obj]; result[key] = value; return result; }; const copyMapWith = (key, value) => obj => { const result = obj == null ? new Map() : new Map(obj); result.set(key, value); return result; }; const copyObjectWith = (key, value) => obj => { 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 = (key, value) => typeof key === 'number' ? copyArrayWith(key, value) : isMapKey(key) ? copyMapWith(key.key, value) : copyObjectWith(key, value); function parseKeysInto (keyDescriptions, keys) { 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('.')); }); 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; const chain = list => step => stop => (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(key, modified)(obj); }; const atPathBeginningWith = prefix => (...keyDescriptions) => { const keys = parseKeysInto(keyDescriptions, [...prefix]), keyChain = chain(keys), putChain = keyChain((key, next) => x => copyOnModification(x, key, getKey(x, key), next) ), mapChain = keyChain((key, next) => x => { const valueWithCheck = getKeyWithCheck(x, key); if (valueWithCheck == null) return x; return copyOnModification(x, key, valueWithCheck.value, next); }); return Object.assign(atPathBeginningWith(keys), { get: obj => keys.reduce(getKey, obj), put: val => putChain(() => val), map: mapChain }); }; export const atPath = atPathBeginningWith([]); 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}; };