index.mjs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // src/index.ts
  2. import equal2 from "@gilbarbara/deep-equal";
  3. import is2 from "is-lite";
  4. // src/helpers.ts
  5. import equal from "@gilbarbara/deep-equal";
  6. import is from "is-lite";
  7. function canHaveLength(...arguments_) {
  8. return arguments_.every((d) => is.string(d) || is.array(d) || is.plainObject(d));
  9. }
  10. function checkEquality(left, right, value) {
  11. if (!isSameType(left, right)) {
  12. return false;
  13. }
  14. if ([left, right].every(is.array)) {
  15. return !left.some(hasValue(value)) && right.some(hasValue(value));
  16. }
  17. if ([left, right].every(is.plainObject)) {
  18. return !Object.entries(left).some(hasEntry(value)) && Object.entries(right).some(hasEntry(value));
  19. }
  20. return right === value;
  21. }
  22. function compareNumbers(previousData, data, options) {
  23. const { actual, key, previous, type } = options;
  24. const left = nested(previousData, key);
  25. const right = nested(data, key);
  26. let changed = [left, right].every(is.number) && (type === "increased" ? left < right : left > right);
  27. if (!is.undefined(actual)) {
  28. changed = changed && right === actual;
  29. }
  30. if (!is.undefined(previous)) {
  31. changed = changed && left === previous;
  32. }
  33. return changed;
  34. }
  35. function compareValues(previousData, data, options) {
  36. const { key, type, value } = options;
  37. const left = nested(previousData, key);
  38. const right = nested(data, key);
  39. const primary = type === "added" ? left : right;
  40. const secondary = type === "added" ? right : left;
  41. if (!is.nullOrUndefined(value)) {
  42. if (is.defined(primary)) {
  43. if (is.array(primary) || is.plainObject(primary)) {
  44. return checkEquality(primary, secondary, value);
  45. }
  46. } else {
  47. return equal(secondary, value);
  48. }
  49. return false;
  50. }
  51. if ([left, right].every(is.array)) {
  52. return !secondary.every(isEqualPredicate(primary));
  53. }
  54. if ([left, right].every(is.plainObject)) {
  55. return hasExtraKeys(Object.keys(primary), Object.keys(secondary));
  56. }
  57. return ![left, right].every((d) => is.primitive(d) && is.defined(d)) && (type === "added" ? !is.defined(left) && is.defined(right) : is.defined(left) && !is.defined(right));
  58. }
  59. function getIterables(previousData, data, { key } = {}) {
  60. let left = nested(previousData, key);
  61. let right = nested(data, key);
  62. if (!isSameType(left, right)) {
  63. throw new TypeError("Inputs have different types");
  64. }
  65. if (!canHaveLength(left, right)) {
  66. throw new TypeError("Inputs don't have length");
  67. }
  68. if ([left, right].every(is.plainObject)) {
  69. left = Object.keys(left);
  70. right = Object.keys(right);
  71. }
  72. return [left, right];
  73. }
  74. function hasEntry(input) {
  75. return ([key, value]) => {
  76. if (is.array(input)) {
  77. return equal(input, value) || input.some((d) => equal(d, value) || is.array(value) && isEqualPredicate(value)(d));
  78. }
  79. if (is.plainObject(input) && input[key]) {
  80. return !!input[key] && equal(input[key], value);
  81. }
  82. return equal(input, value);
  83. };
  84. }
  85. function hasExtraKeys(left, right) {
  86. return right.some((d) => !left.includes(d));
  87. }
  88. function hasValue(input) {
  89. return (value) => {
  90. if (is.array(input)) {
  91. return input.some((d) => equal(d, value) || is.array(value) && isEqualPredicate(value)(d));
  92. }
  93. return equal(input, value);
  94. };
  95. }
  96. function includesOrEqualsTo(previousValue, value) {
  97. return is.array(previousValue) ? previousValue.some((d) => equal(d, value)) : equal(previousValue, value);
  98. }
  99. function isEqualPredicate(data) {
  100. return (value) => data.some((d) => equal(d, value));
  101. }
  102. function isSameType(...arguments_) {
  103. return arguments_.every(is.array) || arguments_.every(is.number) || arguments_.every(is.plainObject) || arguments_.every(is.string);
  104. }
  105. function nested(data, property) {
  106. if (is.plainObject(data) || is.array(data)) {
  107. if (is.string(property)) {
  108. const props = property.split(".");
  109. return props.reduce((acc, d) => acc && acc[d], data);
  110. }
  111. if (is.number(property)) {
  112. return data[property];
  113. }
  114. return data;
  115. }
  116. return data;
  117. }
  118. // src/index.ts
  119. function treeChanges(previousData, data) {
  120. if ([previousData, data].some(is2.nullOrUndefined)) {
  121. throw new Error("Missing required parameters");
  122. }
  123. if (![previousData, data].every((d) => is2.plainObject(d) || is2.array(d))) {
  124. throw new Error("Expected plain objects or array");
  125. }
  126. const added = (key, value) => {
  127. try {
  128. return compareValues(previousData, data, { key, type: "added", value });
  129. } catch {
  130. return false;
  131. }
  132. };
  133. const changed = (key, actual, previous) => {
  134. try {
  135. const left = nested(previousData, key);
  136. const right = nested(data, key);
  137. const hasActual = is2.defined(actual);
  138. const hasPrevious = is2.defined(previous);
  139. if (hasActual || hasPrevious) {
  140. const leftComparator = hasPrevious ? includesOrEqualsTo(previous, left) : !includesOrEqualsTo(actual, left);
  141. const rightComparator = includesOrEqualsTo(actual, right);
  142. return leftComparator && rightComparator;
  143. }
  144. if ([left, right].every(is2.array) || [left, right].every(is2.plainObject)) {
  145. return !equal2(left, right);
  146. }
  147. return left !== right;
  148. } catch {
  149. return false;
  150. }
  151. };
  152. const changedFrom = (key, previous, actual) => {
  153. if (!is2.defined(key)) {
  154. return false;
  155. }
  156. try {
  157. const left = nested(previousData, key);
  158. const right = nested(data, key);
  159. const hasActual = is2.defined(actual);
  160. return includesOrEqualsTo(previous, left) && (hasActual ? includesOrEqualsTo(actual, right) : !hasActual);
  161. } catch {
  162. return false;
  163. }
  164. };
  165. const decreased = (key, actual, previous) => {
  166. if (!is2.defined(key)) {
  167. return false;
  168. }
  169. try {
  170. return compareNumbers(previousData, data, { key, actual, previous, type: "decreased" });
  171. } catch {
  172. return false;
  173. }
  174. };
  175. const emptied = (key) => {
  176. try {
  177. const [left, right] = getIterables(previousData, data, { key });
  178. return !!left.length && !right.length;
  179. } catch {
  180. return false;
  181. }
  182. };
  183. const filled = (key) => {
  184. try {
  185. const [left, right] = getIterables(previousData, data, { key });
  186. return !left.length && !!right.length;
  187. } catch {
  188. return false;
  189. }
  190. };
  191. const increased = (key, actual, previous) => {
  192. if (!is2.defined(key)) {
  193. return false;
  194. }
  195. try {
  196. return compareNumbers(previousData, data, { key, actual, previous, type: "increased" });
  197. } catch {
  198. return false;
  199. }
  200. };
  201. const removed = (key, value) => {
  202. try {
  203. return compareValues(previousData, data, { key, type: "removed", value });
  204. } catch {
  205. return false;
  206. }
  207. };
  208. return { added, changed, changedFrom, decreased, emptied, filled, increased, removed };
  209. }
  210. export {
  211. treeChanges as default
  212. };
  213. //# sourceMappingURL=index.mjs.map