object-diagram-js-differ 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1465 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.odDiff = factory());
5
+ })(this, (function () { 'use strict';
6
+
7
+ /**
8
+ * Flatten array, one level deep.
9
+ *
10
+ * @template T
11
+ *
12
+ * @param {T[][] | T[] | null} [arr]
13
+ *
14
+ * @return {T[]}
15
+ */
16
+
17
+ const nativeToString = Object.prototype.toString;
18
+ const nativeHasOwnProperty = Object.prototype.hasOwnProperty;
19
+
20
+ function isUndefined(obj) {
21
+ return obj === undefined;
22
+ }
23
+
24
+ function isArray(obj) {
25
+ return nativeToString.call(obj) === '[object Array]';
26
+ }
27
+
28
+ /**
29
+ * Return true, if target owns a property with the given key.
30
+ *
31
+ * @param {Object} target
32
+ * @param {String} key
33
+ *
34
+ * @return {Boolean}
35
+ */
36
+ function has(target, key) {
37
+ return nativeHasOwnProperty.call(target, key);
38
+ }
39
+
40
+
41
+ /**
42
+ * Iterate over collection; returning something
43
+ * (non-undefined) will stop iteration.
44
+ *
45
+ * @template T
46
+ * @param {Collection<T>} collection
47
+ * @param { ((item: T, idx: number) => (boolean|void)) | ((item: T, key: string) => (boolean|void)) } iterator
48
+ *
49
+ * @return {T} return result that stopped the iteration
50
+ */
51
+ function forEach(collection, iterator) {
52
+
53
+ let val,
54
+ result;
55
+
56
+ if (isUndefined(collection)) {
57
+ return;
58
+ }
59
+
60
+ const convertKey = isArray(collection) ? toNum : identity;
61
+
62
+ for (let key in collection) {
63
+
64
+ if (has(collection, key)) {
65
+ val = collection[key];
66
+
67
+ result = iterator(val, convertKey(key));
68
+
69
+ if (result === false) {
70
+ return val;
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+
77
+ /**
78
+ * Reduce collection, returning a single result.
79
+ *
80
+ * @template T
81
+ * @template V
82
+ *
83
+ * @param {Collection<T>} collection
84
+ * @param {(result: V, entry: T, index: any) => V} iterator
85
+ * @param {V} result
86
+ *
87
+ * @return {V} result returned from last iterator
88
+ */
89
+ function reduce(collection, iterator, result) {
90
+
91
+ forEach(collection, function(value, idx) {
92
+ result = iterator(result, value, idx);
93
+ });
94
+
95
+ return result;
96
+ }
97
+
98
+
99
+ function identity(arg) {
100
+ return arg;
101
+ }
102
+
103
+ function toNum(arg) {
104
+ return Number(arg);
105
+ }
106
+
107
+ class Processor {
108
+ constructor(options) {
109
+ this.selfOptions = options || {};
110
+ this.pipes = {};
111
+ }
112
+ options(options) {
113
+ if (options) {
114
+ this.selfOptions = options;
115
+ }
116
+ return this.selfOptions;
117
+ }
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
+ pipe(name, pipeArg) {
120
+ let pipe = pipeArg;
121
+ if (typeof name === 'string') {
122
+ if (typeof pipe === 'undefined') {
123
+ return this.pipes[name];
124
+ }
125
+ else {
126
+ this.pipes[name] = pipe;
127
+ }
128
+ }
129
+ if (name && name.name) {
130
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
+ pipe = name;
132
+ if (pipe.processor === this) {
133
+ return pipe;
134
+ }
135
+ this.pipes[pipe.name] = pipe;
136
+ }
137
+ pipe.processor = this;
138
+ return pipe;
139
+ }
140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ process(input, pipe) {
142
+ let context = input;
143
+ context.options = this.options();
144
+ let nextPipe = pipe || input.pipe || 'default';
145
+ let lastPipe;
146
+ while (nextPipe) {
147
+ if (typeof context.nextAfterChildren !== 'undefined') {
148
+ // children processed and coming back to parent
149
+ context.next = context.nextAfterChildren;
150
+ context.nextAfterChildren = null;
151
+ }
152
+ if (typeof nextPipe === 'string') {
153
+ nextPipe = this.pipe(nextPipe);
154
+ }
155
+ nextPipe.process(context);
156
+ lastPipe = nextPipe;
157
+ nextPipe = null;
158
+ if (context) {
159
+ if (context.next) {
160
+ context = context.next;
161
+ nextPipe = context.pipe || lastPipe;
162
+ }
163
+ }
164
+ }
165
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
166
+ return context.hasResult ? context.result : undefined;
167
+ }
168
+ }
169
+
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
+ class Pipe {
172
+ constructor(name) {
173
+ this.name = name;
174
+ this.filters = [];
175
+ }
176
+ process(input) {
177
+ if (!this.processor) {
178
+ throw new Error('add this pipe to a processor before using it');
179
+ }
180
+ const debug = this.debug;
181
+ const length = this.filters.length;
182
+ const context = input;
183
+ for (let index = 0; index < length; index++) {
184
+ const filter = this.filters[index];
185
+ if (debug) {
186
+ this.log(`filter: ${filter.filterName}`);
187
+ }
188
+ filter(context);
189
+ if (typeof context === 'object' && context.exiting) {
190
+ context.exiting = false;
191
+ break;
192
+ }
193
+ }
194
+ if (!context.next && this.resultCheck) {
195
+ this.resultCheck(context);
196
+ }
197
+ }
198
+ log(msg) {
199
+ console.log(`[jsondiffpatch] ${this.name} pipe, ${msg}`);
200
+ }
201
+ append(...args) {
202
+ this.filters.push(...args);
203
+ return this;
204
+ }
205
+ prepend(...args) {
206
+ this.filters.unshift(...args);
207
+ return this;
208
+ }
209
+ indexOf(filterName) {
210
+ if (!filterName) {
211
+ throw new Error('a filter name is required');
212
+ }
213
+ for (let index = 0; index < this.filters.length; index++) {
214
+ const filter = this.filters[index];
215
+ if (filter.filterName === filterName) {
216
+ return index;
217
+ }
218
+ }
219
+ throw new Error(`filter not found: ${filterName}`);
220
+ }
221
+ list() {
222
+ return this.filters.map((f) => f.filterName);
223
+ }
224
+ after(filterName, ...params) {
225
+ const index = this.indexOf(filterName);
226
+ this.filters.splice(index + 1, 0, ...params);
227
+ return this;
228
+ }
229
+ before(filterName, ...params) {
230
+ const index = this.indexOf(filterName);
231
+ this.filters.splice(index, 0, ...params);
232
+ return this;
233
+ }
234
+ replace(filterName, ...params) {
235
+ const index = this.indexOf(filterName);
236
+ this.filters.splice(index, 1, ...params);
237
+ return this;
238
+ }
239
+ remove(filterName) {
240
+ const index = this.indexOf(filterName);
241
+ this.filters.splice(index, 1);
242
+ return this;
243
+ }
244
+ clear() {
245
+ this.filters.length = 0;
246
+ return this;
247
+ }
248
+ shouldHaveResult(should) {
249
+ if (should === false) {
250
+ this.resultCheck = null;
251
+ return;
252
+ }
253
+ if (this.resultCheck) {
254
+ return;
255
+ }
256
+ this.resultCheck = (context) => {
257
+ if (!context.hasResult) {
258
+ console.log(context);
259
+ const error = new Error(`${this.name} failed`);
260
+ error.noResult = true;
261
+ throw error;
262
+ }
263
+ };
264
+ return this;
265
+ }
266
+ }
267
+
268
+ class Context {
269
+ setResult(result) {
270
+ this.result = result;
271
+ this.hasResult = true;
272
+ return this;
273
+ }
274
+ exit() {
275
+ this.exiting = true;
276
+ return this;
277
+ }
278
+ push(child, name) {
279
+ child.parent = this;
280
+ if (typeof name !== 'undefined') {
281
+ child.childName = name;
282
+ }
283
+ child.root = this.root || this;
284
+ child.options = child.options || this.options;
285
+ if (!this.children) {
286
+ this.children = [child];
287
+ this.nextAfterChildren = this.next || null;
288
+ this.next = child;
289
+ }
290
+ else {
291
+ this.children[this.children.length - 1].next = child;
292
+ this.children.push(child);
293
+ }
294
+ child.next = this;
295
+ return this;
296
+ }
297
+ }
298
+
299
+ function cloneRegExp(re) {
300
+ const regexMatch = /^\/(.*)\/([gimyu]*)$/.exec(re.toString());
301
+ return new RegExp(regexMatch[1], regexMatch[2]);
302
+ }
303
+ function clone(arg) {
304
+ if (typeof arg !== 'object') {
305
+ return arg;
306
+ }
307
+ if (arg === null) {
308
+ return null;
309
+ }
310
+ if (Array.isArray(arg)) {
311
+ return arg.map(clone);
312
+ }
313
+ if (arg instanceof Date) {
314
+ return new Date(arg.getTime());
315
+ }
316
+ if (arg instanceof RegExp) {
317
+ return cloneRegExp(arg);
318
+ }
319
+ const cloned = {};
320
+ for (const name in arg) {
321
+ if (Object.prototype.hasOwnProperty.call(arg, name)) {
322
+ cloned[name] = clone(arg[name]);
323
+ }
324
+ }
325
+ return cloned;
326
+ }
327
+
328
+ class DiffContext extends Context {
329
+ constructor(left, right) {
330
+ super();
331
+ this.left = left;
332
+ this.right = right;
333
+ this.pipe = 'diff';
334
+ }
335
+ setResult(result) {
336
+ if (this.options.cloneDiffValues && typeof result === 'object') {
337
+ const clone$1 = typeof this.options.cloneDiffValues === 'function'
338
+ ? this.options.cloneDiffValues
339
+ : clone;
340
+ if (typeof result[0] === 'object') {
341
+ result[0] = clone$1(result[0]);
342
+ }
343
+ if (typeof result[1] === 'object') {
344
+ result[1] = clone$1(result[1]);
345
+ }
346
+ }
347
+ return super.setResult(result);
348
+ }
349
+ }
350
+
351
+ class PatchContext extends Context {
352
+ constructor(left, delta) {
353
+ super();
354
+ this.left = left;
355
+ this.delta = delta;
356
+ this.pipe = 'patch';
357
+ }
358
+ }
359
+
360
+ class ReverseContext extends Context {
361
+ constructor(delta) {
362
+ super();
363
+ this.delta = delta;
364
+ this.pipe = 'reverse';
365
+ }
366
+ }
367
+
368
+ const diffFilter$3 = function trivialMatchesDiffFilter(context) {
369
+ if (context.left === context.right) {
370
+ context.setResult(undefined).exit();
371
+ return;
372
+ }
373
+ if (typeof context.left === 'undefined') {
374
+ if (typeof context.right === 'function') {
375
+ throw new Error('functions are not supported');
376
+ }
377
+ context.setResult([context.right]).exit();
378
+ return;
379
+ }
380
+ if (typeof context.right === 'undefined') {
381
+ context.setResult([context.left, 0, 0]).exit();
382
+ return;
383
+ }
384
+ if (typeof context.left === 'function' ||
385
+ typeof context.right === 'function') {
386
+ throw new Error('functions are not supported');
387
+ }
388
+ context.leftType = context.left === null ? 'null' : typeof context.left;
389
+ context.rightType = context.right === null ? 'null' : typeof context.right;
390
+ if (context.leftType !== context.rightType) {
391
+ context.setResult([context.left, context.right]).exit();
392
+ return;
393
+ }
394
+ if (context.leftType === 'boolean' || context.leftType === 'number') {
395
+ context.setResult([context.left, context.right]).exit();
396
+ return;
397
+ }
398
+ if (context.leftType === 'object') {
399
+ context.leftIsArray = Array.isArray(context.left);
400
+ }
401
+ if (context.rightType === 'object') {
402
+ context.rightIsArray = Array.isArray(context.right);
403
+ }
404
+ if (context.leftIsArray !== context.rightIsArray) {
405
+ context.setResult([context.left, context.right]).exit();
406
+ return;
407
+ }
408
+ if (context.left instanceof RegExp) {
409
+ if (context.right instanceof RegExp) {
410
+ context
411
+ .setResult([context.left.toString(), context.right.toString()])
412
+ .exit();
413
+ }
414
+ else {
415
+ context.setResult([context.left, context.right]).exit();
416
+ }
417
+ }
418
+ };
419
+ diffFilter$3.filterName = 'trivial';
420
+ const patchFilter$3 = function trivialMatchesPatchFilter(context) {
421
+ if (typeof context.delta === 'undefined') {
422
+ context.setResult(context.left).exit();
423
+ return;
424
+ }
425
+ context.nested = !Array.isArray(context.delta);
426
+ if (context.nested) {
427
+ return;
428
+ }
429
+ const nonNestedDelta = context.delta;
430
+ if (nonNestedDelta.length === 1) {
431
+ context.setResult(nonNestedDelta[0]).exit();
432
+ return;
433
+ }
434
+ if (nonNestedDelta.length === 2) {
435
+ if (context.left instanceof RegExp) {
436
+ const regexArgs = /^\/(.*)\/([gimyu]+)$/.exec(nonNestedDelta[1]);
437
+ if (regexArgs) {
438
+ context.setResult(new RegExp(regexArgs[1], regexArgs[2])).exit();
439
+ return;
440
+ }
441
+ }
442
+ context.setResult(nonNestedDelta[1]).exit();
443
+ return;
444
+ }
445
+ if (nonNestedDelta.length === 3 && nonNestedDelta[2] === 0) {
446
+ context.setResult(undefined).exit();
447
+ }
448
+ };
449
+ patchFilter$3.filterName = 'trivial';
450
+ const reverseFilter$3 = function trivialReferseFilter(context) {
451
+ if (typeof context.delta === 'undefined') {
452
+ context.setResult(context.delta).exit();
453
+ return;
454
+ }
455
+ context.nested = !Array.isArray(context.delta);
456
+ if (context.nested) {
457
+ return;
458
+ }
459
+ const nonNestedDelta = context.delta;
460
+ if (nonNestedDelta.length === 1) {
461
+ context.setResult([nonNestedDelta[0], 0, 0]).exit();
462
+ return;
463
+ }
464
+ if (nonNestedDelta.length === 2) {
465
+ context.setResult([nonNestedDelta[1], nonNestedDelta[0]]).exit();
466
+ return;
467
+ }
468
+ if (nonNestedDelta.length === 3 && nonNestedDelta[2] === 0) {
469
+ context.setResult([nonNestedDelta[0]]).exit();
470
+ }
471
+ };
472
+ reverseFilter$3.filterName = 'trivial';
473
+
474
+ const collectChildrenDiffFilter = (context) => {
475
+ if (!context || !context.children) {
476
+ return;
477
+ }
478
+ const length = context.children.length;
479
+ let child;
480
+ let result = context.result;
481
+ for (let index = 0; index < length; index++) {
482
+ child = context.children[index];
483
+ if (typeof child.result === 'undefined') {
484
+ continue;
485
+ }
486
+ result = result || {};
487
+ result[child.childName] = child.result;
488
+ }
489
+ if (result && context.leftIsArray) {
490
+ result._t = 'a';
491
+ }
492
+ context.setResult(result).exit();
493
+ };
494
+ collectChildrenDiffFilter.filterName = 'collectChildren';
495
+ const objectsDiffFilter = (context) => {
496
+ if (context.leftIsArray || context.leftType !== 'object') {
497
+ return;
498
+ }
499
+ const left = context.left;
500
+ const right = context.right;
501
+ let name;
502
+ let child;
503
+ const propertyFilter = context.options.propertyFilter;
504
+ for (name in left) {
505
+ if (!Object.prototype.hasOwnProperty.call(left, name)) {
506
+ continue;
507
+ }
508
+ if (propertyFilter && !propertyFilter(name, context)) {
509
+ continue;
510
+ }
511
+ child = new DiffContext(left[name], right[name]);
512
+ context.push(child, name);
513
+ }
514
+ for (name in right) {
515
+ if (!Object.prototype.hasOwnProperty.call(right, name)) {
516
+ continue;
517
+ }
518
+ if (propertyFilter && !propertyFilter(name, context)) {
519
+ continue;
520
+ }
521
+ if (typeof left[name] === 'undefined') {
522
+ child = new DiffContext(undefined, right[name]);
523
+ context.push(child, name);
524
+ }
525
+ }
526
+ if (!context.children || context.children.length === 0) {
527
+ context.setResult(undefined).exit();
528
+ return;
529
+ }
530
+ context.exit();
531
+ };
532
+ objectsDiffFilter.filterName = 'objects';
533
+ const patchFilter$2 = function nestedPatchFilter(context) {
534
+ if (!context.nested) {
535
+ return;
536
+ }
537
+ const nestedDelta = context.delta;
538
+ if (nestedDelta._t) {
539
+ return;
540
+ }
541
+ const objectDelta = nestedDelta;
542
+ let name;
543
+ let child;
544
+ for (name in objectDelta) {
545
+ child = new PatchContext(context.left[name], objectDelta[name]);
546
+ context.push(child, name);
547
+ }
548
+ context.exit();
549
+ };
550
+ patchFilter$2.filterName = 'objects';
551
+ const collectChildrenPatchFilter$1 = function collectChildrenPatchFilter(context) {
552
+ if (!context || !context.children) {
553
+ return;
554
+ }
555
+ const deltaWithChildren = context.delta;
556
+ if (deltaWithChildren._t) {
557
+ return;
558
+ }
559
+ const object = context.left;
560
+ const length = context.children.length;
561
+ let child;
562
+ for (let index = 0; index < length; index++) {
563
+ child = context.children[index];
564
+ const property = child.childName;
565
+ if (Object.prototype.hasOwnProperty.call(context.left, property) &&
566
+ child.result === undefined) {
567
+ delete object[property];
568
+ }
569
+ else if (object[property] !== child.result) {
570
+ object[property] = child.result;
571
+ }
572
+ }
573
+ context.setResult(object).exit();
574
+ };
575
+ collectChildrenPatchFilter$1.filterName = 'collectChildren';
576
+ const reverseFilter$2 = function nestedReverseFilter(context) {
577
+ if (!context.nested) {
578
+ return;
579
+ }
580
+ const nestedDelta = context.delta;
581
+ if (nestedDelta._t) {
582
+ return;
583
+ }
584
+ const objectDelta = context.delta;
585
+ let name;
586
+ let child;
587
+ for (name in objectDelta) {
588
+ child = new ReverseContext(objectDelta[name]);
589
+ context.push(child, name);
590
+ }
591
+ context.exit();
592
+ };
593
+ reverseFilter$2.filterName = 'objects';
594
+ const collectChildrenReverseFilter$1 = (context) => {
595
+ if (!context || !context.children) {
596
+ return;
597
+ }
598
+ const deltaWithChildren = context.delta;
599
+ if (deltaWithChildren._t) {
600
+ return;
601
+ }
602
+ const length = context.children.length;
603
+ let child;
604
+ const delta = {};
605
+ for (let index = 0; index < length; index++) {
606
+ child = context.children[index];
607
+ const property = child.childName;
608
+ if (delta[property] !== child.result) {
609
+ delta[property] = child.result;
610
+ }
611
+ }
612
+ context.setResult(delta).exit();
613
+ };
614
+ collectChildrenReverseFilter$1.filterName = 'collectChildren';
615
+
616
+ /*
617
+
618
+ LCS implementation that supports arrays or strings
619
+
620
+ reference: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
621
+
622
+ */
623
+ const defaultMatch = function (array1, array2, index1, index2) {
624
+ return array1[index1] === array2[index2];
625
+ };
626
+ const lengthMatrix = function (array1, array2, match, context) {
627
+ const len1 = array1.length;
628
+ const len2 = array2.length;
629
+ let x, y;
630
+ // initialize empty matrix of len1+1 x len2+1
631
+ const matrix = new Array(len1 + 1);
632
+ for (x = 0; x < len1 + 1; x++) {
633
+ matrix[x] = new Array(len2 + 1);
634
+ for (y = 0; y < len2 + 1; y++) {
635
+ matrix[x][y] = 0;
636
+ }
637
+ }
638
+ matrix.match = match;
639
+ // save sequence lengths for each coordinate
640
+ for (x = 1; x < len1 + 1; x++) {
641
+ for (y = 1; y < len2 + 1; y++) {
642
+ if (match(array1, array2, x - 1, y - 1, context)) {
643
+ matrix[x][y] = matrix[x - 1][y - 1] + 1;
644
+ }
645
+ else {
646
+ matrix[x][y] = Math.max(matrix[x - 1][y], matrix[x][y - 1]);
647
+ }
648
+ }
649
+ }
650
+ return matrix;
651
+ };
652
+ const backtrack = function (matrix, array1, array2, context) {
653
+ let index1 = array1.length;
654
+ let index2 = array2.length;
655
+ const subsequence = {
656
+ sequence: [],
657
+ indices1: [],
658
+ indices2: [],
659
+ };
660
+ while (index1 !== 0 && index2 !== 0) {
661
+ const sameLetter = matrix.match(array1, array2, index1 - 1, index2 - 1, context);
662
+ if (sameLetter) {
663
+ subsequence.sequence.unshift(array1[index1 - 1]);
664
+ subsequence.indices1.unshift(index1 - 1);
665
+ subsequence.indices2.unshift(index2 - 1);
666
+ --index1;
667
+ --index2;
668
+ }
669
+ else {
670
+ const valueAtMatrixAbove = matrix[index1][index2 - 1];
671
+ const valueAtMatrixLeft = matrix[index1 - 1][index2];
672
+ if (valueAtMatrixAbove > valueAtMatrixLeft) {
673
+ --index2;
674
+ }
675
+ else {
676
+ --index1;
677
+ }
678
+ }
679
+ }
680
+ return subsequence;
681
+ };
682
+ const get = function (array1, array2, match, context) {
683
+ const innerContext = context || {};
684
+ const matrix = lengthMatrix(array1, array2, match || defaultMatch, innerContext);
685
+ return backtrack(matrix, array1, array2, innerContext);
686
+ };
687
+ var lcs = {
688
+ get,
689
+ };
690
+
691
+ const ARRAY_MOVE = 3;
692
+ function arraysHaveMatchByRef(array1, array2, len1, len2) {
693
+ for (let index1 = 0; index1 < len1; index1++) {
694
+ const val1 = array1[index1];
695
+ for (let index2 = 0; index2 < len2; index2++) {
696
+ const val2 = array2[index2];
697
+ if (index1 !== index2 && val1 === val2) {
698
+ return true;
699
+ }
700
+ }
701
+ }
702
+ }
703
+ function matchItems(array1, array2, index1, index2, context) {
704
+ const value1 = array1[index1];
705
+ const value2 = array2[index2];
706
+ if (value1 === value2) {
707
+ return true;
708
+ }
709
+ if (typeof value1 !== 'object' || typeof value2 !== 'object') {
710
+ return false;
711
+ }
712
+ const objectHash = context.objectHash;
713
+ if (!objectHash) {
714
+ // no way to match objects was provided, try match by position
715
+ return context.matchByPosition && index1 === index2;
716
+ }
717
+ context.hashCache1 = context.hashCache1 || [];
718
+ let hash1 = context.hashCache1[index1];
719
+ if (typeof hash1 === 'undefined') {
720
+ context.hashCache1[index1] = hash1 = objectHash(value1, index1);
721
+ }
722
+ if (typeof hash1 === 'undefined') {
723
+ return false;
724
+ }
725
+ context.hashCache2 = context.hashCache2 || [];
726
+ let hash2 = context.hashCache2[index2];
727
+ if (typeof hash2 === 'undefined') {
728
+ context.hashCache2[index2] = hash2 = objectHash(value2, index2);
729
+ }
730
+ if (typeof hash2 === 'undefined') {
731
+ return false;
732
+ }
733
+ return hash1 === hash2;
734
+ }
735
+ const diffFilter$2 = function arraysDiffFilter(context) {
736
+ if (!context.leftIsArray) {
737
+ return;
738
+ }
739
+ const matchContext = {
740
+ objectHash: context.options && context.options.objectHash,
741
+ matchByPosition: context.options && context.options.matchByPosition,
742
+ };
743
+ let commonHead = 0;
744
+ let commonTail = 0;
745
+ let index;
746
+ let index1;
747
+ let index2;
748
+ const array1 = context.left;
749
+ const array2 = context.right;
750
+ const len1 = array1.length;
751
+ const len2 = array2.length;
752
+ let child;
753
+ if (len1 > 0 &&
754
+ len2 > 0 &&
755
+ !matchContext.objectHash &&
756
+ typeof matchContext.matchByPosition !== 'boolean') {
757
+ matchContext.matchByPosition = !arraysHaveMatchByRef(array1, array2, len1, len2);
758
+ }
759
+ // separate common head
760
+ while (commonHead < len1 &&
761
+ commonHead < len2 &&
762
+ matchItems(array1, array2, commonHead, commonHead, matchContext)) {
763
+ index = commonHead;
764
+ child = new DiffContext(array1[index], array2[index]);
765
+ context.push(child, index);
766
+ commonHead++;
767
+ }
768
+ // separate common tail
769
+ while (commonTail + commonHead < len1 &&
770
+ commonTail + commonHead < len2 &&
771
+ matchItems(array1, array2, len1 - 1 - commonTail, len2 - 1 - commonTail, matchContext)) {
772
+ index1 = len1 - 1 - commonTail;
773
+ index2 = len2 - 1 - commonTail;
774
+ child = new DiffContext(array1[index1], array2[index2]);
775
+ context.push(child, index2);
776
+ commonTail++;
777
+ }
778
+ let result;
779
+ if (commonHead + commonTail === len1) {
780
+ if (len1 === len2) {
781
+ // arrays are identical
782
+ context.setResult(undefined).exit();
783
+ return;
784
+ }
785
+ // trivial case, a block (1 or more consecutive items) was added
786
+ result = result || {
787
+ _t: 'a',
788
+ };
789
+ for (index = commonHead; index < len2 - commonTail; index++) {
790
+ result[index] = [array2[index]];
791
+ }
792
+ context.setResult(result).exit();
793
+ return;
794
+ }
795
+ if (commonHead + commonTail === len2) {
796
+ // trivial case, a block (1 or more consecutive items) was removed
797
+ result = result || {
798
+ _t: 'a',
799
+ };
800
+ for (index = commonHead; index < len1 - commonTail; index++) {
801
+ result[`_${index}`] = [array1[index], 0, 0];
802
+ }
803
+ context.setResult(result).exit();
804
+ return;
805
+ }
806
+ // reset hash cache
807
+ delete matchContext.hashCache1;
808
+ delete matchContext.hashCache2;
809
+ // diff is not trivial, find the LCS (Longest Common Subsequence)
810
+ const trimmed1 = array1.slice(commonHead, len1 - commonTail);
811
+ const trimmed2 = array2.slice(commonHead, len2 - commonTail);
812
+ const seq = lcs.get(trimmed1, trimmed2, matchItems, matchContext);
813
+ const removedItems = [];
814
+ result = result || {
815
+ _t: 'a',
816
+ };
817
+ for (index = commonHead; index < len1 - commonTail; index++) {
818
+ if (seq.indices1.indexOf(index - commonHead) < 0) {
819
+ // removed
820
+ result[`_${index}`] = [array1[index], 0, 0];
821
+ removedItems.push(index);
822
+ }
823
+ }
824
+ let detectMove = true;
825
+ if (context.options &&
826
+ context.options.arrays &&
827
+ context.options.arrays.detectMove === false) {
828
+ detectMove = false;
829
+ }
830
+ let includeValueOnMove = false;
831
+ if (context.options &&
832
+ context.options.arrays &&
833
+ context.options.arrays.includeValueOnMove) {
834
+ includeValueOnMove = true;
835
+ }
836
+ const removedItemsLength = removedItems.length;
837
+ for (index = commonHead; index < len2 - commonTail; index++) {
838
+ const indexOnArray2 = seq.indices2.indexOf(index - commonHead);
839
+ if (indexOnArray2 < 0) {
840
+ // added, try to match with a removed item and register as position move
841
+ let isMove = false;
842
+ if (detectMove && removedItemsLength > 0) {
843
+ for (let removeItemIndex1 = 0; removeItemIndex1 < removedItemsLength; removeItemIndex1++) {
844
+ index1 = removedItems[removeItemIndex1];
845
+ if (matchItems(trimmed1, trimmed2, index1 - commonHead, index - commonHead, matchContext)) {
846
+ // store position move as: [originalValue, newPosition, ARRAY_MOVE]
847
+ result[`_${index1}`].splice(1, 2, index, ARRAY_MOVE);
848
+ if (!includeValueOnMove) {
849
+ // don't include moved value on diff, to save bytes
850
+ result[`_${index1}`][0] = '';
851
+ }
852
+ index2 = index;
853
+ child = new DiffContext(array1[index1], array2[index2]);
854
+ context.push(child, index2);
855
+ removedItems.splice(removeItemIndex1, 1);
856
+ isMove = true;
857
+ break;
858
+ }
859
+ }
860
+ }
861
+ if (!isMove) {
862
+ // added
863
+ result[index] = [array2[index]];
864
+ }
865
+ }
866
+ else {
867
+ // match, do inner diff
868
+ index1 = seq.indices1[indexOnArray2] + commonHead;
869
+ index2 = seq.indices2[indexOnArray2] + commonHead;
870
+ child = new DiffContext(array1[index1], array2[index2]);
871
+ context.push(child, index2);
872
+ }
873
+ }
874
+ context.setResult(result).exit();
875
+ };
876
+ diffFilter$2.filterName = 'arrays';
877
+ const compare = {
878
+ numerically(a, b) {
879
+ return a - b;
880
+ },
881
+ numericallyBy(name) {
882
+ return (a, b) => a[name] - b[name];
883
+ },
884
+ };
885
+ const patchFilter$1 = function nestedPatchFilter(context) {
886
+ if (!context.nested) {
887
+ return;
888
+ }
889
+ const nestedDelta = context.delta;
890
+ if (nestedDelta._t !== 'a') {
891
+ return;
892
+ }
893
+ let index;
894
+ let index1;
895
+ const delta = nestedDelta;
896
+ const array = context.left;
897
+ // first, separate removals, insertions and modifications
898
+ let toRemove = [];
899
+ let toInsert = [];
900
+ const toModify = [];
901
+ for (index in delta) {
902
+ if (index !== '_t') {
903
+ if (index[0] === '_') {
904
+ const removedOrMovedIndex = index;
905
+ // removed item from original array
906
+ if (delta[removedOrMovedIndex][2] === 0 ||
907
+ delta[removedOrMovedIndex][2] === ARRAY_MOVE) {
908
+ toRemove.push(parseInt(index.slice(1), 10));
909
+ }
910
+ else {
911
+ throw new Error('only removal or move can be applied at original array indices,' +
912
+ ` invalid diff type: ${delta[removedOrMovedIndex][2]}`);
913
+ }
914
+ }
915
+ else {
916
+ const numberIndex = index;
917
+ if (delta[numberIndex].length === 1) {
918
+ // added item at new array
919
+ toInsert.push({
920
+ index: parseInt(numberIndex, 10),
921
+ value: delta[numberIndex][0],
922
+ });
923
+ }
924
+ else {
925
+ // modified item at new array
926
+ toModify.push({
927
+ index: parseInt(numberIndex, 10),
928
+ delta: delta[numberIndex],
929
+ });
930
+ }
931
+ }
932
+ }
933
+ }
934
+ // remove items, in reverse order to avoid sawing our own floor
935
+ toRemove = toRemove.sort(compare.numerically);
936
+ for (index = toRemove.length - 1; index >= 0; index--) {
937
+ index1 = toRemove[index];
938
+ const indexDiff = delta[`_${index1}`];
939
+ const removedValue = array.splice(index1, 1)[0];
940
+ if (indexDiff[2] === ARRAY_MOVE) {
941
+ // reinsert later
942
+ toInsert.push({
943
+ index: indexDiff[1],
944
+ value: removedValue,
945
+ });
946
+ }
947
+ }
948
+ // insert items, in reverse order to avoid moving our own floor
949
+ toInsert = toInsert.sort(compare.numericallyBy('index'));
950
+ const toInsertLength = toInsert.length;
951
+ for (index = 0; index < toInsertLength; index++) {
952
+ const insertion = toInsert[index];
953
+ array.splice(insertion.index, 0, insertion.value);
954
+ }
955
+ // apply modifications
956
+ const toModifyLength = toModify.length;
957
+ let child;
958
+ if (toModifyLength > 0) {
959
+ for (index = 0; index < toModifyLength; index++) {
960
+ const modification = toModify[index];
961
+ child = new PatchContext(array[modification.index], modification.delta);
962
+ context.push(child, modification.index);
963
+ }
964
+ }
965
+ if (!context.children) {
966
+ context.setResult(array).exit();
967
+ return;
968
+ }
969
+ context.exit();
970
+ };
971
+ patchFilter$1.filterName = 'arrays';
972
+ const collectChildrenPatchFilter = function collectChildrenPatchFilter(context) {
973
+ if (!context || !context.children) {
974
+ return;
975
+ }
976
+ const deltaWithChildren = context.delta;
977
+ if (deltaWithChildren._t !== 'a') {
978
+ return;
979
+ }
980
+ const array = context.left;
981
+ const length = context.children.length;
982
+ let child;
983
+ for (let index = 0; index < length; index++) {
984
+ child = context.children[index];
985
+ const arrayIndex = child.childName;
986
+ array[arrayIndex] = child.result;
987
+ }
988
+ context.setResult(array).exit();
989
+ };
990
+ collectChildrenPatchFilter.filterName = 'arraysCollectChildren';
991
+ const reverseFilter$1 = function arraysReverseFilter(context) {
992
+ if (!context.nested) {
993
+ const nonNestedDelta = context.delta;
994
+ if (nonNestedDelta[2] === ARRAY_MOVE) {
995
+ const arrayMoveDelta = nonNestedDelta;
996
+ context.newName = `_${arrayMoveDelta[1]}`;
997
+ context
998
+ .setResult([
999
+ arrayMoveDelta[0],
1000
+ parseInt(context.childName.substring(1), 10),
1001
+ ARRAY_MOVE,
1002
+ ])
1003
+ .exit();
1004
+ }
1005
+ return;
1006
+ }
1007
+ const nestedDelta = context.delta;
1008
+ if (nestedDelta._t !== 'a') {
1009
+ return;
1010
+ }
1011
+ const arrayDelta = nestedDelta;
1012
+ let name;
1013
+ let child;
1014
+ for (name in arrayDelta) {
1015
+ if (name === '_t') {
1016
+ continue;
1017
+ }
1018
+ child = new ReverseContext(arrayDelta[name]);
1019
+ context.push(child, name);
1020
+ }
1021
+ context.exit();
1022
+ };
1023
+ reverseFilter$1.filterName = 'arrays';
1024
+ const reverseArrayDeltaIndex = (delta, index, itemDelta) => {
1025
+ if (typeof index === 'string' && index[0] === '_') {
1026
+ return parseInt(index.substring(1), 10);
1027
+ }
1028
+ else if (Array.isArray(itemDelta) && itemDelta[2] === 0) {
1029
+ return `_${index}`;
1030
+ }
1031
+ let reverseIndex = +index;
1032
+ for (const deltaIndex in delta) {
1033
+ const deltaItem = delta[deltaIndex];
1034
+ if (Array.isArray(deltaItem)) {
1035
+ if (deltaItem[2] === ARRAY_MOVE) {
1036
+ const moveFromIndex = parseInt(deltaIndex.substring(1), 10);
1037
+ const moveToIndex = deltaItem[1];
1038
+ if (moveToIndex === +index) {
1039
+ return moveFromIndex;
1040
+ }
1041
+ if (moveFromIndex <= reverseIndex && moveToIndex > reverseIndex) {
1042
+ reverseIndex++;
1043
+ }
1044
+ else if (moveFromIndex >= reverseIndex &&
1045
+ moveToIndex < reverseIndex) {
1046
+ reverseIndex--;
1047
+ }
1048
+ }
1049
+ else if (deltaItem[2] === 0) {
1050
+ const deleteIndex = parseInt(deltaIndex.substring(1), 10);
1051
+ if (deleteIndex <= reverseIndex) {
1052
+ reverseIndex++;
1053
+ }
1054
+ }
1055
+ else if (deltaItem.length === 1 &&
1056
+ parseInt(deltaIndex, 10) <= reverseIndex) {
1057
+ reverseIndex--;
1058
+ }
1059
+ }
1060
+ }
1061
+ return reverseIndex;
1062
+ };
1063
+ const collectChildrenReverseFilter = (context) => {
1064
+ if (!context || !context.children) {
1065
+ return;
1066
+ }
1067
+ const deltaWithChildren = context.delta;
1068
+ if (deltaWithChildren._t !== 'a') {
1069
+ return;
1070
+ }
1071
+ const arrayDelta = deltaWithChildren;
1072
+ const length = context.children.length;
1073
+ let child;
1074
+ const delta = {
1075
+ _t: 'a',
1076
+ };
1077
+ for (let index = 0; index < length; index++) {
1078
+ child = context.children[index];
1079
+ let name = child.newName;
1080
+ if (typeof name === 'undefined') {
1081
+ name = reverseArrayDeltaIndex(arrayDelta, child.childName, child.result);
1082
+ }
1083
+ if (delta[name] !== child.result) {
1084
+ // There's no way to type this well.
1085
+ delta[name] = child.result;
1086
+ }
1087
+ }
1088
+ context.setResult(delta).exit();
1089
+ };
1090
+ collectChildrenReverseFilter.filterName = 'arraysCollectChildren';
1091
+
1092
+ const diffFilter$1 = function datesDiffFilter(context) {
1093
+ if (context.left instanceof Date) {
1094
+ if (context.right instanceof Date) {
1095
+ if (context.left.getTime() !== context.right.getTime()) {
1096
+ context.setResult([context.left, context.right]);
1097
+ }
1098
+ else {
1099
+ context.setResult(undefined);
1100
+ }
1101
+ }
1102
+ else {
1103
+ context.setResult([context.left, context.right]);
1104
+ }
1105
+ context.exit();
1106
+ }
1107
+ else if (context.right instanceof Date) {
1108
+ context.setResult([context.left, context.right]).exit();
1109
+ }
1110
+ };
1111
+ diffFilter$1.filterName = 'dates';
1112
+
1113
+ const TEXT_DIFF = 2;
1114
+ const DEFAULT_MIN_LENGTH = 60;
1115
+ let cachedDiffPatch = null;
1116
+ function getDiffMatchPatch(options, required) {
1117
+ var _a;
1118
+ if (!cachedDiffPatch) {
1119
+ let instance;
1120
+ if ((_a = options === null || options === void 0 ? void 0 : options.textDiff) === null || _a === void 0 ? void 0 : _a.diffMatchPatch) {
1121
+ instance = new options.textDiff.diffMatchPatch();
1122
+ }
1123
+ else {
1124
+ if (!required) {
1125
+ return null;
1126
+ }
1127
+ const error = new Error('The diff-match-patch library was not provided. Pass the library in through the options or use the `jsondiffpatch/with-text-diffs` entry-point.');
1128
+ // eslint-disable-next-line camelcase
1129
+ error.diff_match_patch_not_found = true;
1130
+ throw error;
1131
+ }
1132
+ cachedDiffPatch = {
1133
+ diff: function (txt1, txt2) {
1134
+ return instance.patch_toText(instance.patch_make(txt1, txt2));
1135
+ },
1136
+ patch: function (txt1, patch) {
1137
+ const results = instance.patch_apply(instance.patch_fromText(patch), txt1);
1138
+ for (let i = 0; i < results[1].length; i++) {
1139
+ if (!results[1][i]) {
1140
+ const error = new Error('text patch failed');
1141
+ error.textPatchFailed = true;
1142
+ }
1143
+ }
1144
+ return results[0];
1145
+ },
1146
+ };
1147
+ }
1148
+ return cachedDiffPatch;
1149
+ }
1150
+ const diffFilter = function textsDiffFilter(context) {
1151
+ if (context.leftType !== 'string') {
1152
+ return;
1153
+ }
1154
+ const left = context.left;
1155
+ const right = context.right;
1156
+ const minLength = (context.options &&
1157
+ context.options.textDiff &&
1158
+ context.options.textDiff.minLength) ||
1159
+ DEFAULT_MIN_LENGTH;
1160
+ if (left.length < minLength || right.length < minLength) {
1161
+ context.setResult([left, right]).exit();
1162
+ return;
1163
+ }
1164
+ // large text, try to use a text-diff algorithm
1165
+ const diffMatchPatch = getDiffMatchPatch(context.options);
1166
+ if (!diffMatchPatch) {
1167
+ // diff-match-patch library not available,
1168
+ // fallback to regular string replace
1169
+ context.setResult([left, right]).exit();
1170
+ return;
1171
+ }
1172
+ const diff = diffMatchPatch.diff;
1173
+ context.setResult([diff(left, right), 0, TEXT_DIFF]).exit();
1174
+ };
1175
+ diffFilter.filterName = 'texts';
1176
+ const patchFilter = function textsPatchFilter(context) {
1177
+ if (context.nested) {
1178
+ return;
1179
+ }
1180
+ const nonNestedDelta = context.delta;
1181
+ if (nonNestedDelta[2] !== TEXT_DIFF) {
1182
+ return;
1183
+ }
1184
+ const textDiffDelta = nonNestedDelta;
1185
+ // text-diff, use a text-patch algorithm
1186
+ const patch = getDiffMatchPatch(context.options, true).patch;
1187
+ context.setResult(patch(context.left, textDiffDelta[0])).exit();
1188
+ };
1189
+ patchFilter.filterName = 'texts';
1190
+ const textDeltaReverse = function (delta) {
1191
+ let i;
1192
+ let l;
1193
+ let line;
1194
+ let lineTmp;
1195
+ let header = null;
1196
+ const headerRegex = /^@@ +-(\d+),(\d+) +\+(\d+),(\d+) +@@$/;
1197
+ let lineHeader;
1198
+ const lines = delta.split('\n');
1199
+ for (i = 0, l = lines.length; i < l; i++) {
1200
+ line = lines[i];
1201
+ const lineStart = line.slice(0, 1);
1202
+ if (lineStart === '@') {
1203
+ header = headerRegex.exec(line);
1204
+ lineHeader = i;
1205
+ // fix header
1206
+ lines[lineHeader] =
1207
+ '@@ -' +
1208
+ header[3] +
1209
+ ',' +
1210
+ header[4] +
1211
+ ' +' +
1212
+ header[1] +
1213
+ ',' +
1214
+ header[2] +
1215
+ ' @@';
1216
+ }
1217
+ else if (lineStart === '+') {
1218
+ lines[i] = '-' + lines[i].slice(1);
1219
+ if (lines[i - 1].slice(0, 1) === '+') {
1220
+ // swap lines to keep default order (-+)
1221
+ lineTmp = lines[i];
1222
+ lines[i] = lines[i - 1];
1223
+ lines[i - 1] = lineTmp;
1224
+ }
1225
+ }
1226
+ else if (lineStart === '-') {
1227
+ lines[i] = '+' + lines[i].slice(1);
1228
+ }
1229
+ }
1230
+ return lines.join('\n');
1231
+ };
1232
+ const reverseFilter = function textsReverseFilter(context) {
1233
+ if (context.nested) {
1234
+ return;
1235
+ }
1236
+ const nonNestedDelta = context.delta;
1237
+ if (nonNestedDelta[2] !== TEXT_DIFF) {
1238
+ return;
1239
+ }
1240
+ const textDiffDelta = nonNestedDelta;
1241
+ // text-diff, use a text-diff algorithm
1242
+ context
1243
+ .setResult([textDeltaReverse(textDiffDelta[0]), 0, TEXT_DIFF])
1244
+ .exit();
1245
+ };
1246
+ reverseFilter.filterName = 'texts';
1247
+
1248
+ class DiffPatcher {
1249
+ constructor(options) {
1250
+ this.processor = new Processor(options);
1251
+ this.processor.pipe(new Pipe('diff')
1252
+ .append(collectChildrenDiffFilter, diffFilter$3, diffFilter$1, diffFilter, objectsDiffFilter, diffFilter$2)
1253
+ .shouldHaveResult());
1254
+ this.processor.pipe(new Pipe('patch')
1255
+ .append(collectChildrenPatchFilter$1, collectChildrenPatchFilter, patchFilter$3, patchFilter, patchFilter$2, patchFilter$1)
1256
+ .shouldHaveResult());
1257
+ this.processor.pipe(new Pipe('reverse')
1258
+ .append(collectChildrenReverseFilter$1, collectChildrenReverseFilter, reverseFilter$3, reverseFilter, reverseFilter$2, reverseFilter$1)
1259
+ .shouldHaveResult());
1260
+ }
1261
+ options(options) {
1262
+ return this.processor.options(options);
1263
+ }
1264
+ diff(left, right) {
1265
+ return this.processor.process(new DiffContext(left, right));
1266
+ }
1267
+ patch(left, delta) {
1268
+ return this.processor.process(new PatchContext(left, delta));
1269
+ }
1270
+ reverse(delta) {
1271
+ return this.processor.process(new ReverseContext(delta));
1272
+ }
1273
+ unpatch(right, delta) {
1274
+ return this.patch(right, this.reverse(delta));
1275
+ }
1276
+ clone(value) {
1277
+ return clone(value);
1278
+ }
1279
+ }
1280
+
1281
+ /* eslint no-cond-assign: 0 */
1282
+
1283
+ function is(element, type) {
1284
+ return element.$instanceOf(type);
1285
+ }
1286
+
1287
+ function isAny(element, types) {
1288
+ return types.some(function(t) {
1289
+ return is(element, t);
1290
+ });
1291
+ }
1292
+
1293
+ function isTracked(element) {
1294
+
1295
+ const track = isAny(element, [
1296
+ 'od:OdBoard',
1297
+ 'od:Object',
1298
+ 'od:Link',
1299
+ ]);
1300
+
1301
+ if (track) {
1302
+ return {
1303
+ element: element,
1304
+ property: ''
1305
+ };
1306
+ }
1307
+ }
1308
+
1309
+ function ChangeHandler() {
1310
+ this._layoutChanged = {};
1311
+ this._changed = {};
1312
+ this._removed = {};
1313
+ this._added = {};
1314
+ }
1315
+
1316
+
1317
+ ChangeHandler.prototype.removed = function(model, property, element, idx) {
1318
+
1319
+ let tracked;
1320
+
1321
+ if (tracked = isTracked(element)) {
1322
+ if (!this._removed[tracked.element.id]) {
1323
+ this._removed[tracked.element.id] = element;
1324
+ }
1325
+ } else
1326
+
1327
+ if (tracked = isTracked(model)) {
1328
+ this.changed(tracked.element, tracked.property + property + '[' + idx + ']', null, element);
1329
+ }
1330
+ };
1331
+
1332
+ ChangeHandler.prototype.changed = function(model, property, newValue, oldValue) {
1333
+
1334
+ let tracked;
1335
+
1336
+ if (tracked = isTracked(model)) {
1337
+ let changed = this._changed[tracked.element.id];
1338
+
1339
+ if (!changed) {
1340
+ changed = this._changed[tracked.element.id] = { model: model, attrs: { } };
1341
+ }
1342
+
1343
+ if (oldValue !== undefined || newValue !== undefined) {
1344
+ changed.attrs[property] = { oldValue: oldValue, newValue: newValue };
1345
+ }
1346
+ }
1347
+ };
1348
+
1349
+ ChangeHandler.prototype.added = function(model, property, element, idx) {
1350
+
1351
+ let tracked;
1352
+
1353
+ if (tracked = isTracked(element)) {
1354
+ if (!this._added[tracked.element.id]) {
1355
+ this._added[tracked.element.id] = element;
1356
+ }
1357
+ } else
1358
+
1359
+ if (tracked = isTracked(model)) {
1360
+ this.changed(tracked.element, tracked.property + property + '[' + idx + ']', element, null);
1361
+ }
1362
+ };
1363
+
1364
+ ChangeHandler.prototype.moved = function(model, property, oldIndex, newIndex) {
1365
+
1366
+ // noop
1367
+ };
1368
+
1369
+ function Differ() { }
1370
+
1371
+
1372
+ Differ.prototype.createDiff = function(a, b) {
1373
+
1374
+ // create a configured instance, match objects by name
1375
+ const diffpatcher = new DiffPatcher({
1376
+ objectHash: function(obj) {
1377
+ return obj.id || JSON.stringify(obj);
1378
+ },
1379
+ propertyFilter: function(name, context) {
1380
+ return name !== '$instanceOf';
1381
+ }
1382
+ });
1383
+
1384
+ return diffpatcher.diff(a, b);
1385
+ };
1386
+
1387
+
1388
+ Differ.prototype.diff = function(a, b, handler) {
1389
+
1390
+ handler = handler || new ChangeHandler();
1391
+
1392
+ function walk(diff, model) {
1393
+
1394
+ forEach(diff, function(d, key) {
1395
+
1396
+ if (d._t !== 'a' && isArray(d)) {
1397
+
1398
+ // take into account that collection properties are lazily
1399
+ // initialized; this means that adding to an empty collection
1400
+ // looks like setting an undefined variable to []
1401
+ //
1402
+ // ensure we detect this case and change it to an array diff
1403
+ if (isArray(d[0])) {
1404
+
1405
+ d = reduce(d[0], function(newDelta, element, idx) {
1406
+ const prefix = d.length === 3 ? '_' : '';
1407
+
1408
+ newDelta[prefix + idx] = [ element ];
1409
+
1410
+ return newDelta;
1411
+ }, { _t: 'a' });
1412
+ }
1413
+
1414
+ }
1415
+
1416
+
1417
+ // is array
1418
+ if (d._t === 'a') {
1419
+
1420
+ forEach(d, function(val, idx) {
1421
+
1422
+ if (idx === '_t') {
1423
+ return;
1424
+ }
1425
+
1426
+ const removed = /^_/.test(idx),
1427
+ added = !removed && isArray(val),
1428
+ moved = removed && val[0] === '';
1429
+
1430
+ idx = parseInt(removed ? idx.slice(1) : idx, 10);
1431
+
1432
+ if (added || (removed && !moved)) {
1433
+ handler[removed ? 'removed' : 'added'](model, key, val[0], idx);
1434
+ } else
1435
+ if (moved) {
1436
+ handler.moved(model, key, val[1], val[2]);
1437
+ } else {
1438
+ walk(val, model[key][idx]);
1439
+ }
1440
+ });
1441
+ } else {
1442
+ if (isArray(d)) {
1443
+ handler.changed(model, key, d[0], d[1]);
1444
+ } else {
1445
+ handler.changed(model, key);
1446
+ walk(d, model[key]);
1447
+ }
1448
+ }
1449
+ });
1450
+ }
1451
+
1452
+ const diff = this.createDiff(a, b);
1453
+
1454
+ walk(diff, b);
1455
+
1456
+ return handler;
1457
+ };
1458
+
1459
+ function diff(a, b, handler) {
1460
+ return new Differ().diff(a, b, handler);
1461
+ }
1462
+
1463
+ return diff;
1464
+
1465
+ }));