cow-value-model.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. function copyArrayWith (obj, key, value) {
  2. const result = obj == null ? [] : [...obj];
  3. result[key] = value;
  4. return result;
  5. }
  6. function copyMapWith (obj, key, value) {
  7. const result = obj == null ? new Map() : new Map(obj);
  8. result.set(key, value);
  9. return result;
  10. }
  11. function copyObjectWith (obj, key, value) {
  12. const result = obj == null ? {} : {...obj};
  13. result[key] = value;
  14. return result;
  15. }
  16. const isMapKey = key => typeof key === 'object' && key.isKeyInMap === true;
  17. export const keyInMap = key => ({isKeyInMap: true, key});
  18. export const kim = keyInMap;
  19. const copyWith = (obj, key, value) =>
  20. typeof key === 'number' ? copyArrayWith(obj, key, value) :
  21. isMapKey(key) ? copyMapWith(obj, key.key, value) :
  22. copyObjectWith(obj, key, value);
  23. const constructKeys = function (keyDescriptions) {
  24. const keys = [];
  25. function fillKeys (keyDescriptions) {
  26. keyDescriptions.forEach(each => {
  27. if (typeof each === 'number' || isMapKey(each)) keys.push(each);
  28. else if (Array.isArray(each)) fillKeys(each);
  29. else keys.push(...each.toString().split('.'));
  30. });
  31. }
  32. fillKeys(keyDescriptions);
  33. return keys;
  34. };
  35. const getKey = (x, key) => x == null ? undefined : isMapKey(key) ? x.get(key.key) : x[key];
  36. const getKeyWithCheck = (x, key) =>
  37. x == null ? null :
  38. isMapKey(key) ? x.has(key.key) ? {value: x.get(key.key)} : null :
  39. x.hasOwnProperty(key) ? {value: x[key]} : null;
  40. export const deepGetOrNil = (...keyDescriptions) => {
  41. const keys = constructKeys(keyDescriptions);
  42. return obj => keys.reduce(getKey, obj);
  43. };
  44. export const deget = deepGetOrNil;
  45. export const deepCopyOnWrite = (...keyDescriptions) => {
  46. const keys = constructKeys(keyDescriptions);
  47. function setVal (x, index, val) {
  48. if (index >= keys.length) return val;
  49. const key = keys[index],
  50. value = getKey(x, key),
  51. modified = setVal(value, index + 1, val);
  52. return value === modified ? x : copyWith(x, key, modified);
  53. }
  54. return val => obj => setVal(obj, 0, val);
  55. };
  56. export const decow = deepCopyOnWrite;
  57. export const deepCopyOnMap = (...keyDescriptions) => {
  58. const keys = constructKeys(keyDescriptions);
  59. function mapFn (x, index, fn) {
  60. if (index >= keys.length) return fn(x);
  61. const key = keys[index],
  62. value = getKeyWithCheck(x, key);
  63. if (value == null) return x;
  64. const modified = mapFn(value.value, index + 1, fn);
  65. return value.value === modified ? x : copyWith(x, key, modified);
  66. }
  67. return fn => obj => mapFn(obj, 0, fn);
  68. };
  69. export const decomap = deepCopyOnMap;
  70. export const cowValueModel = (...keyDescriptions) => {
  71. const GET_SENTINEL = {};
  72. return (obj, val = GET_SENTINEL) =>
  73. (val === GET_SENTINEL ?
  74. deget(...keyDescriptions) :
  75. decow(...keyDescriptions)(val))
  76. (obj);
  77. };
  78. export const cowWorkshop = (keys, fn = x => x) => (obj, {result = obj, resultKeys = keys, diff} = {}) => {
  79. keys.forEach((key, index) => {
  80. const value = fn(deget(key)(obj));
  81. if (typeof value === "undefined") return;
  82. const modifier = decow(resultKeys[index])(value);
  83. const oldResult = result;
  84. result = modifier(oldResult);
  85. if (result !== oldResult) {
  86. diff = modifier(diff);
  87. }
  88. });
  89. return {result, diff};
  90. };