index.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. ;(function(root, factory) { // eslint-disable-line no-extra-semi
  2. var deepDiff = factory(root);
  3. // eslint-disable-next-line no-undef
  4. if (typeof define === 'function' && define.amd) {
  5. // AMD
  6. define('DeepDiff', function() { // eslint-disable-line no-undef
  7. return deepDiff;
  8. });
  9. } else if (typeof exports === 'object' || typeof navigator === 'object' && navigator.product.match(/ReactNative/i)) {
  10. // Node.js or ReactNative
  11. module.exports = deepDiff;
  12. } else {
  13. // Browser globals
  14. var _deepdiff = root.DeepDiff;
  15. deepDiff.noConflict = function() {
  16. if (root.DeepDiff === deepDiff) {
  17. root.DeepDiff = _deepdiff;
  18. }
  19. return deepDiff;
  20. };
  21. root.DeepDiff = deepDiff;
  22. }
  23. }(this, function(root) {
  24. var validKinds = ['N', 'E', 'A', 'D'];
  25. // nodejs compatible on server side and in the browser.
  26. function inherits(ctor, superCtor) {
  27. ctor.super_ = superCtor;
  28. ctor.prototype = Object.create(superCtor.prototype, {
  29. constructor: {
  30. value: ctor,
  31. enumerable: false,
  32. writable: true,
  33. configurable: true
  34. }
  35. });
  36. }
  37. function Diff(kind, path) {
  38. Object.defineProperty(this, 'kind', {
  39. value: kind,
  40. enumerable: true
  41. });
  42. if (path && path.length) {
  43. Object.defineProperty(this, 'path', {
  44. value: path,
  45. enumerable: true
  46. });
  47. }
  48. }
  49. function DiffEdit(path, origin, value) {
  50. DiffEdit.super_.call(this, 'E', path);
  51. Object.defineProperty(this, 'lhs', {
  52. value: origin,
  53. enumerable: true
  54. });
  55. Object.defineProperty(this, 'rhs', {
  56. value: value,
  57. enumerable: true
  58. });
  59. }
  60. inherits(DiffEdit, Diff);
  61. function DiffNew(path, value) {
  62. DiffNew.super_.call(this, 'N', path);
  63. Object.defineProperty(this, 'rhs', {
  64. value: value,
  65. enumerable: true
  66. });
  67. }
  68. inherits(DiffNew, Diff);
  69. function DiffDeleted(path, value) {
  70. DiffDeleted.super_.call(this, 'D', path);
  71. Object.defineProperty(this, 'lhs', {
  72. value: value,
  73. enumerable: true
  74. });
  75. }
  76. inherits(DiffDeleted, Diff);
  77. function DiffArray(path, index, item) {
  78. DiffArray.super_.call(this, 'A', path);
  79. Object.defineProperty(this, 'index', {
  80. value: index,
  81. enumerable: true
  82. });
  83. Object.defineProperty(this, 'item', {
  84. value: item,
  85. enumerable: true
  86. });
  87. }
  88. inherits(DiffArray, Diff);
  89. function arrayRemove(arr, from, to) {
  90. var rest = arr.slice((to || from) + 1 || arr.length);
  91. arr.length = from < 0 ? arr.length + from : from;
  92. arr.push.apply(arr, rest);
  93. return arr;
  94. }
  95. function realTypeOf(subject) {
  96. var type = typeof subject;
  97. if (type !== 'object') {
  98. return type;
  99. }
  100. if (subject === Math) {
  101. return 'math';
  102. } else if (subject === null) {
  103. return 'null';
  104. } else if (Array.isArray(subject)) {
  105. return 'array';
  106. } else if (Object.prototype.toString.call(subject) === '[object Date]') {
  107. return 'date';
  108. } else if (typeof subject.toString === 'function' && /^\/.*\//.test(subject.toString())) {
  109. return 'regexp';
  110. }
  111. return 'object';
  112. }
  113. // http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
  114. function hashThisString(string) {
  115. var hash = 0;
  116. if (string.length === 0) { return hash; }
  117. for (var i = 0; i < string.length; i++) {
  118. var char = string.charCodeAt(i);
  119. hash = ((hash << 5) - hash) + char;
  120. hash = hash & hash; // Convert to 32bit integer
  121. }
  122. return hash;
  123. }
  124. // Gets a hash of the given object in an array order-independent fashion
  125. // also object key order independent (easier since they can be alphabetized)
  126. function getOrderIndependentHash(object) {
  127. var accum = 0;
  128. var type = realTypeOf(object);
  129. if (type === 'array') {
  130. object.forEach(function (item) {
  131. // Addition is commutative so this is order indep
  132. accum += getOrderIndependentHash(item);
  133. });
  134. var arrayString = '[type: array, hash: ' + accum + ']';
  135. return accum + hashThisString(arrayString);
  136. }
  137. if (type === 'object') {
  138. for (var key in object) {
  139. if (object.hasOwnProperty(key)) {
  140. var keyValueString = '[ type: object, key: ' + key + ', value hash: ' + getOrderIndependentHash(object[key]) + ']';
  141. accum += hashThisString(keyValueString);
  142. }
  143. }
  144. return accum;
  145. }
  146. // Non object, non array...should be good?
  147. var stringToHash = '[ type: ' + type + ' ; value: ' + object + ']';
  148. return accum + hashThisString(stringToHash);
  149. }
  150. function deepDiff(lhs, rhs, changes, prefilter, path, key, stack, orderIndependent) {
  151. changes = changes || [];
  152. path = path || [];
  153. stack = stack || [];
  154. var currentPath = path.slice(0);
  155. if (typeof key !== 'undefined' && key !== null) {
  156. if (prefilter) {
  157. if (typeof (prefilter) === 'function' && prefilter(currentPath, key)) {
  158. return;
  159. } else if (typeof (prefilter) === 'object') {
  160. if (prefilter.prefilter && prefilter.prefilter(currentPath, key)) {
  161. return;
  162. }
  163. if (prefilter.normalize) {
  164. var alt = prefilter.normalize(currentPath, key, lhs, rhs);
  165. if (alt) {
  166. lhs = alt[0];
  167. rhs = alt[1];
  168. }
  169. }
  170. }
  171. }
  172. currentPath.push(key);
  173. }
  174. // Use string comparison for regexes
  175. if (realTypeOf(lhs) === 'regexp' && realTypeOf(rhs) === 'regexp') {
  176. lhs = lhs.toString();
  177. rhs = rhs.toString();
  178. }
  179. var ltype = typeof lhs;
  180. var rtype = typeof rhs;
  181. var i, j, k, other;
  182. var ldefined = ltype !== 'undefined' ||
  183. (stack && (stack.length > 0) && stack[stack.length - 1].lhs &&
  184. Object.getOwnPropertyDescriptor(stack[stack.length - 1].lhs, key));
  185. var rdefined = rtype !== 'undefined' ||
  186. (stack && (stack.length > 0) && stack[stack.length - 1].rhs &&
  187. Object.getOwnPropertyDescriptor(stack[stack.length - 1].rhs, key));
  188. if (!ldefined && rdefined) {
  189. changes.push(new DiffNew(currentPath, rhs));
  190. } else if (!rdefined && ldefined) {
  191. changes.push(new DiffDeleted(currentPath, lhs));
  192. } else if (realTypeOf(lhs) !== realTypeOf(rhs)) {
  193. changes.push(new DiffEdit(currentPath, lhs, rhs));
  194. } else if (realTypeOf(lhs) === 'date' && (lhs - rhs) !== 0) {
  195. changes.push(new DiffEdit(currentPath, lhs, rhs));
  196. } else if (ltype === 'object' && lhs !== null && rhs !== null) {
  197. for (i = stack.length - 1; i > -1; --i) {
  198. if (stack[i].lhs === lhs) {
  199. other = true;
  200. break;
  201. }
  202. }
  203. if (!other) {
  204. stack.push({ lhs: lhs, rhs: rhs });
  205. if (Array.isArray(lhs)) {
  206. // If order doesn't matter, we need to sort our arrays
  207. if (orderIndependent) {
  208. lhs.sort(function (a, b) {
  209. return getOrderIndependentHash(a) - getOrderIndependentHash(b);
  210. });
  211. rhs.sort(function (a, b) {
  212. return getOrderIndependentHash(a) - getOrderIndependentHash(b);
  213. });
  214. }
  215. i = rhs.length - 1;
  216. j = lhs.length - 1;
  217. while (i > j) {
  218. changes.push(new DiffArray(currentPath, i, new DiffNew(undefined, rhs[i--])));
  219. }
  220. while (j > i) {
  221. changes.push(new DiffArray(currentPath, j, new DiffDeleted(undefined, lhs[j--])));
  222. }
  223. for (; i >= 0; --i) {
  224. deepDiff(lhs[i], rhs[i], changes, prefilter, currentPath, i, stack, orderIndependent);
  225. }
  226. } else {
  227. var akeys = Object.keys(lhs);
  228. var pkeys = Object.keys(rhs);
  229. for (i = 0; i < akeys.length; ++i) {
  230. k = akeys[i];
  231. other = pkeys.indexOf(k);
  232. if (other >= 0) {
  233. deepDiff(lhs[k], rhs[k], changes, prefilter, currentPath, k, stack, orderIndependent);
  234. pkeys[other] = null;
  235. } else {
  236. deepDiff(lhs[k], undefined, changes, prefilter, currentPath, k, stack, orderIndependent);
  237. }
  238. }
  239. for (i = 0; i < pkeys.length; ++i) {
  240. k = pkeys[i];
  241. if (k) {
  242. deepDiff(undefined, rhs[k], changes, prefilter, currentPath, k, stack, orderIndependent);
  243. }
  244. }
  245. }
  246. stack.length = stack.length - 1;
  247. } else if (lhs !== rhs) {
  248. // lhs is contains a cycle at this element and it differs from rhs
  249. changes.push(new DiffEdit(currentPath, lhs, rhs));
  250. }
  251. } else if (lhs !== rhs) {
  252. if (!(ltype === 'number' && isNaN(lhs) && isNaN(rhs))) {
  253. changes.push(new DiffEdit(currentPath, lhs, rhs));
  254. }
  255. }
  256. }
  257. function observableDiff(lhs, rhs, observer, prefilter, orderIndependent) {
  258. var changes = [];
  259. deepDiff(lhs, rhs, changes, prefilter, null, null, null, orderIndependent);
  260. if (observer) {
  261. for (var i = 0; i < changes.length; ++i) {
  262. observer(changes[i]);
  263. }
  264. }
  265. return changes;
  266. }
  267. function orderIndependentDeepDiff(lhs, rhs, changes, prefilter, path, key, stack) {
  268. return deepDiff(lhs, rhs, changes, prefilter, path, key, stack, true);
  269. }
  270. function accumulateDiff(lhs, rhs, prefilter, accum) {
  271. var observer = (accum) ?
  272. function (difference) {
  273. if (difference) {
  274. accum.push(difference);
  275. }
  276. } : undefined;
  277. var changes = observableDiff(lhs, rhs, observer, prefilter);
  278. return (accum) ? accum : (changes.length) ? changes : undefined;
  279. }
  280. function accumulateOrderIndependentDiff(lhs, rhs, prefilter, accum) {
  281. var observer = (accum) ?
  282. function (difference) {
  283. if (difference) {
  284. accum.push(difference);
  285. }
  286. } : undefined;
  287. var changes = observableDiff(lhs, rhs, observer, prefilter, true);
  288. return (accum) ? accum : (changes.length) ? changes : undefined;
  289. }
  290. function applyArrayChange(arr, index, change) {
  291. if (change.path && change.path.length) {
  292. var it = arr[index],
  293. i, u = change.path.length - 1;
  294. for (i = 0; i < u; i++) {
  295. it = it[change.path[i]];
  296. }
  297. switch (change.kind) {
  298. case 'A':
  299. applyArrayChange(it[change.path[i]], change.index, change.item);
  300. break;
  301. case 'D':
  302. delete it[change.path[i]];
  303. break;
  304. case 'E':
  305. case 'N':
  306. it[change.path[i]] = change.rhs;
  307. break;
  308. }
  309. } else {
  310. switch (change.kind) {
  311. case 'A':
  312. applyArrayChange(arr[index], change.index, change.item);
  313. break;
  314. case 'D':
  315. arr = arrayRemove(arr, index);
  316. break;
  317. case 'E':
  318. case 'N':
  319. arr[index] = change.rhs;
  320. break;
  321. }
  322. }
  323. return arr;
  324. }
  325. function applyChange(target, source, change) {
  326. if (typeof change === 'undefined' && source && ~validKinds.indexOf(source.kind)) {
  327. change = source;
  328. }
  329. if (target && change && change.kind) {
  330. var it = target,
  331. i = -1,
  332. last = change.path ? change.path.length - 1 : 0;
  333. while (++i < last) {
  334. if (typeof it[change.path[i]] === 'undefined') {
  335. it[change.path[i]] = (typeof change.path[i + 1] !== 'undefined' && typeof change.path[i + 1] === 'number') ? [] : {};
  336. }
  337. it = it[change.path[i]];
  338. }
  339. switch (change.kind) {
  340. case 'A':
  341. if (change.path && typeof it[change.path[i]] === 'undefined') {
  342. it[change.path[i]] = [];
  343. }
  344. applyArrayChange(change.path ? it[change.path[i]] : it, change.index, change.item);
  345. break;
  346. case 'D':
  347. delete it[change.path[i]];
  348. break;
  349. case 'E':
  350. case 'N':
  351. it[change.path[i]] = change.rhs;
  352. break;
  353. }
  354. }
  355. }
  356. function revertArrayChange(arr, index, change) {
  357. if (change.path && change.path.length) {
  358. // the structure of the object at the index has changed...
  359. var it = arr[index],
  360. i, u = change.path.length - 1;
  361. for (i = 0; i < u; i++) {
  362. it = it[change.path[i]];
  363. }
  364. switch (change.kind) {
  365. case 'A':
  366. revertArrayChange(it[change.path[i]], change.index, change.item);
  367. break;
  368. case 'D':
  369. it[change.path[i]] = change.lhs;
  370. break;
  371. case 'E':
  372. it[change.path[i]] = change.lhs;
  373. break;
  374. case 'N':
  375. delete it[change.path[i]];
  376. break;
  377. }
  378. } else {
  379. // the array item is different...
  380. switch (change.kind) {
  381. case 'A':
  382. revertArrayChange(arr[index], change.index, change.item);
  383. break;
  384. case 'D':
  385. arr[index] = change.lhs;
  386. break;
  387. case 'E':
  388. arr[index] = change.lhs;
  389. break;
  390. case 'N':
  391. arr = arrayRemove(arr, index);
  392. break;
  393. }
  394. }
  395. return arr;
  396. }
  397. function revertChange(target, source, change) {
  398. if (target && source && change && change.kind) {
  399. var it = target,
  400. i, u;
  401. u = change.path.length - 1;
  402. for (i = 0; i < u; i++) {
  403. if (typeof it[change.path[i]] === 'undefined') {
  404. it[change.path[i]] = {};
  405. }
  406. it = it[change.path[i]];
  407. }
  408. switch (change.kind) {
  409. case 'A':
  410. // Array was modified...
  411. // it will be an array...
  412. revertArrayChange(it[change.path[i]], change.index, change.item);
  413. break;
  414. case 'D':
  415. // Item was deleted...
  416. it[change.path[i]] = change.lhs;
  417. break;
  418. case 'E':
  419. // Item was edited...
  420. it[change.path[i]] = change.lhs;
  421. break;
  422. case 'N':
  423. // Item is new...
  424. delete it[change.path[i]];
  425. break;
  426. }
  427. }
  428. }
  429. function applyDiff(target, source, filter) {
  430. if (target && source) {
  431. var onChange = function (change) {
  432. if (!filter || filter(target, source, change)) {
  433. applyChange(target, source, change);
  434. }
  435. };
  436. observableDiff(target, source, onChange);
  437. }
  438. }
  439. Object.defineProperties(accumulateDiff, {
  440. diff: {
  441. value: accumulateDiff,
  442. enumerable: true
  443. },
  444. orderIndependentDiff: {
  445. value: accumulateOrderIndependentDiff,
  446. enumerable: true
  447. },
  448. observableDiff: {
  449. value: observableDiff,
  450. enumerable: true
  451. },
  452. orderIndependentObservableDiff: {
  453. value: orderIndependentDeepDiff,
  454. enumerable: true
  455. },
  456. orderIndepHash: {
  457. value: getOrderIndependentHash,
  458. enumerable: true
  459. },
  460. applyDiff: {
  461. value: applyDiff,
  462. enumerable: true
  463. },
  464. applyChange: {
  465. value: applyChange,
  466. enumerable: true
  467. },
  468. revertChange: {
  469. value: revertChange,
  470. enumerable: true
  471. },
  472. isConflict: {
  473. value: function () {
  474. return typeof $conflict !== 'undefined';
  475. },
  476. enumerable: true
  477. }
  478. });
  479. // hackish...
  480. accumulateDiff.DeepDiff = accumulateDiff;
  481. // ...but works with:
  482. // import DeepDiff from 'deep-diff'
  483. // import { DeepDiff } from 'deep-diff'
  484. // const DeepDiff = require('deep-diff');
  485. // const { DeepDiff } = require('deep-diff');
  486. if (root) {
  487. root.DeepDiff = accumulateDiff;
  488. }
  489. return accumulateDiff;
  490. }));