mongodb-livedata-server 0.0.1

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.
Files changed (94) hide show
  1. package/README.md +63 -0
  2. package/dist/livedata_server.js +9 -0
  3. package/dist/meteor/binary-heap/max_heap.js +186 -0
  4. package/dist/meteor/binary-heap/min_heap.js +17 -0
  5. package/dist/meteor/binary-heap/min_max_heap.js +48 -0
  6. package/dist/meteor/callback-hook/hook.js +78 -0
  7. package/dist/meteor/ddp/crossbar.js +136 -0
  8. package/dist/meteor/ddp/heartbeat.js +77 -0
  9. package/dist/meteor/ddp/livedata_server.js +403 -0
  10. package/dist/meteor/ddp/method-invocation.js +72 -0
  11. package/dist/meteor/ddp/random-stream.js +100 -0
  12. package/dist/meteor/ddp/session-collection-view.js +106 -0
  13. package/dist/meteor/ddp/session-document-view.js +82 -0
  14. package/dist/meteor/ddp/session.js +570 -0
  15. package/dist/meteor/ddp/stream_server.js +181 -0
  16. package/dist/meteor/ddp/subscription.js +347 -0
  17. package/dist/meteor/ddp/utils.js +104 -0
  18. package/dist/meteor/ddp/writefence.js +111 -0
  19. package/dist/meteor/diff-sequence/diff.js +257 -0
  20. package/dist/meteor/ejson/ejson.js +569 -0
  21. package/dist/meteor/ejson/stringify.js +119 -0
  22. package/dist/meteor/ejson/utils.js +42 -0
  23. package/dist/meteor/id-map/id_map.js +92 -0
  24. package/dist/meteor/mongo/caching_change_observer.js +94 -0
  25. package/dist/meteor/mongo/doc_fetcher.js +53 -0
  26. package/dist/meteor/mongo/geojson_utils.js +41 -0
  27. package/dist/meteor/mongo/live_connection.js +264 -0
  28. package/dist/meteor/mongo/live_cursor.js +57 -0
  29. package/dist/meteor/mongo/minimongo_common.js +2002 -0
  30. package/dist/meteor/mongo/minimongo_matcher.js +217 -0
  31. package/dist/meteor/mongo/minimongo_sorter.js +268 -0
  32. package/dist/meteor/mongo/observe_driver_utils.js +73 -0
  33. package/dist/meteor/mongo/observe_multiplexer.js +228 -0
  34. package/dist/meteor/mongo/oplog-observe-driver.js +919 -0
  35. package/dist/meteor/mongo/oplog_tailing.js +352 -0
  36. package/dist/meteor/mongo/oplog_v2_converter.js +126 -0
  37. package/dist/meteor/mongo/polling_observe_driver.js +195 -0
  38. package/dist/meteor/mongo/synchronous-cursor.js +261 -0
  39. package/dist/meteor/mongo/synchronous-queue.js +110 -0
  40. package/dist/meteor/ordered-dict/ordered_dict.js +198 -0
  41. package/dist/meteor/random/AbstractRandomGenerator.js +92 -0
  42. package/dist/meteor/random/AleaRandomGenerator.js +90 -0
  43. package/dist/meteor/random/NodeRandomGenerator.js +42 -0
  44. package/dist/meteor/random/createAleaGenerator.js +32 -0
  45. package/dist/meteor/random/createRandom.js +22 -0
  46. package/dist/meteor/random/main.js +12 -0
  47. package/livedata_server.ts +3 -0
  48. package/meteor/LICENSE +28 -0
  49. package/meteor/binary-heap/max_heap.ts +225 -0
  50. package/meteor/binary-heap/min_heap.ts +15 -0
  51. package/meteor/binary-heap/min_max_heap.ts +53 -0
  52. package/meteor/callback-hook/hook.ts +85 -0
  53. package/meteor/ddp/crossbar.ts +148 -0
  54. package/meteor/ddp/heartbeat.ts +97 -0
  55. package/meteor/ddp/livedata_server.ts +473 -0
  56. package/meteor/ddp/method-invocation.ts +86 -0
  57. package/meteor/ddp/random-stream.ts +102 -0
  58. package/meteor/ddp/session-collection-view.ts +119 -0
  59. package/meteor/ddp/session-document-view.ts +92 -0
  60. package/meteor/ddp/session.ts +708 -0
  61. package/meteor/ddp/stream_server.ts +204 -0
  62. package/meteor/ddp/subscription.ts +392 -0
  63. package/meteor/ddp/utils.ts +119 -0
  64. package/meteor/ddp/writefence.ts +130 -0
  65. package/meteor/diff-sequence/diff.ts +295 -0
  66. package/meteor/ejson/ejson.ts +601 -0
  67. package/meteor/ejson/stringify.ts +122 -0
  68. package/meteor/ejson/utils.ts +38 -0
  69. package/meteor/id-map/id_map.ts +84 -0
  70. package/meteor/mongo/caching_change_observer.ts +120 -0
  71. package/meteor/mongo/doc_fetcher.ts +52 -0
  72. package/meteor/mongo/geojson_utils.ts +42 -0
  73. package/meteor/mongo/live_connection.ts +302 -0
  74. package/meteor/mongo/live_cursor.ts +79 -0
  75. package/meteor/mongo/minimongo_common.ts +2440 -0
  76. package/meteor/mongo/minimongo_matcher.ts +275 -0
  77. package/meteor/mongo/minimongo_sorter.ts +331 -0
  78. package/meteor/mongo/observe_driver_utils.ts +79 -0
  79. package/meteor/mongo/observe_multiplexer.ts +256 -0
  80. package/meteor/mongo/oplog-observe-driver.ts +1049 -0
  81. package/meteor/mongo/oplog_tailing.ts +414 -0
  82. package/meteor/mongo/oplog_v2_converter.ts +124 -0
  83. package/meteor/mongo/polling_observe_driver.ts +247 -0
  84. package/meteor/mongo/synchronous-cursor.ts +293 -0
  85. package/meteor/mongo/synchronous-queue.ts +119 -0
  86. package/meteor/ordered-dict/ordered_dict.ts +229 -0
  87. package/meteor/random/AbstractRandomGenerator.ts +99 -0
  88. package/meteor/random/AleaRandomGenerator.ts +96 -0
  89. package/meteor/random/NodeRandomGenerator.ts +37 -0
  90. package/meteor/random/createAleaGenerator.ts +31 -0
  91. package/meteor/random/createRandom.ts +19 -0
  92. package/meteor/random/main.ts +8 -0
  93. package/package.json +30 -0
  94. package/tsconfig.json +10 -0
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+
3
+ import { clone, _adjustTypesFromJSONValue, _adjustTypesToJSONValue } from "../ejson/ejson";
4
+
5
+ export const hasOwn = Object.prototype.hasOwnProperty;
6
+ export const slice = Array.prototype.slice;
7
+
8
+ export function keys(obj) {
9
+ return Object.keys(Object(obj));
10
+ }
11
+
12
+ export function isEmpty(obj) {
13
+ if (obj == null) {
14
+ return true;
15
+ }
16
+
17
+ if (Array.isArray(obj) ||
18
+ typeof obj === "string") {
19
+ return obj.length === 0;
20
+ }
21
+
22
+ for (const key in obj) {
23
+ if (hasOwn.call(obj, key)) {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ return true;
29
+ }
30
+
31
+ export function last(array, n, guard) {
32
+ if (array == null) {
33
+ return;
34
+ }
35
+
36
+ if ((n == null) || guard) {
37
+ return array[array.length - 1];
38
+ }
39
+
40
+ return slice.call(array, Math.max(array.length - n, 0));
41
+ }
42
+
43
+ export const SUPPORTED_DDP_VERSIONS = [ '1', 'pre2', 'pre1' ];
44
+
45
+ export function parseDDP(stringMessage: string) {
46
+ try {
47
+ var msg = JSON.parse(stringMessage);
48
+ } catch (e) {
49
+ console.log("Discarding message with invalid JSON", stringMessage);
50
+ return null;
51
+ }
52
+ // DDP messages must be objects.
53
+ if (msg === null || typeof msg !== 'object') {
54
+ console.log("Discarding non-object DDP message", stringMessage);
55
+ return null;
56
+ }
57
+
58
+ // massage msg to get it into "abstract ddp" rather than "wire ddp" format.
59
+
60
+ // switch between "cleared" rep of unsetting fields and "undefined"
61
+ // rep of same
62
+ if (hasOwn.call(msg, 'cleared')) {
63
+ if (! hasOwn.call(msg, 'fields')) {
64
+ msg.fields = {};
65
+ }
66
+ msg.cleared.forEach(clearKey => {
67
+ msg.fields[clearKey] = undefined;
68
+ });
69
+ delete msg.cleared;
70
+ }
71
+
72
+ ['fields', 'params', 'result'].forEach(field => {
73
+ if (hasOwn.call(msg, field)) {
74
+ msg[field] = _adjustTypesFromJSONValue(msg[field]);
75
+ }
76
+ });
77
+
78
+ return msg;
79
+ };
80
+
81
+ export function stringifyDDP(msg) {
82
+ const copy = clone(msg);
83
+
84
+ // swizzle 'changed' messages from 'fields undefined' rep to 'fields
85
+ // and cleared' rep
86
+ if (hasOwn.call(msg, 'fields')) {
87
+ const cleared = [];
88
+
89
+ Object.keys(msg.fields).forEach(key => {
90
+ const value = msg.fields[key];
91
+
92
+ if (typeof value === "undefined") {
93
+ cleared.push(key);
94
+ delete copy.fields[key];
95
+ }
96
+ });
97
+
98
+ if (! isEmpty(cleared)) {
99
+ copy.cleared = cleared;
100
+ }
101
+
102
+ if (isEmpty(copy.fields)) {
103
+ delete copy.fields;
104
+ }
105
+ }
106
+
107
+ // adjust types to basic
108
+ ['fields', 'params', 'result'].forEach(field => {
109
+ if (hasOwn.call(copy, field)) {
110
+ copy[field] = _adjustTypesToJSONValue(copy[field]);
111
+ }
112
+ });
113
+
114
+ if (msg.id && typeof msg.id !== 'string') {
115
+ throw new Error("Message id is not a string");
116
+ }
117
+
118
+ return JSON.stringify(copy);
119
+ };
@@ -0,0 +1,130 @@
1
+ // A write fence collects a group of writes, and provides a callback
2
+ // when all of the writes are fully committed and propagated (all
3
+
4
+ import { OplogObserveDriver } from "../mongo/oplog-observe-driver";
5
+
6
+ // observers have been notified of the write and acknowledged it.)
7
+ export class _WriteFence {
8
+
9
+ public fired: boolean;
10
+ public _oplogObserveDrivers: Record<string, OplogObserveDriver>;
11
+
12
+ private armed: boolean;
13
+ private retired: boolean;
14
+ private outstanding_writes: number;
15
+ private before_fire_callbacks: Function[];
16
+ private completion_callbacks: Function[];
17
+
18
+ constructor () {
19
+ this.armed = false;
20
+ this.fired = false;
21
+ this.retired = false;
22
+ this.outstanding_writes = 0;
23
+ this.before_fire_callbacks = [];
24
+ this.completion_callbacks = [];
25
+ };
26
+
27
+ // The current write fence. When there is a current write fence, code
28
+ // that writes to databases should register their writes with it using
29
+ // beginWrite().
30
+ //
31
+ public static _CurrentWriteFence = new _WriteFence();
32
+
33
+ // Start tracking a write, and return an object to represent it. The
34
+ // object has a single method, committed(). This method should be
35
+ // called when the write is fully committed and propagated. You can
36
+ // continue to add writes to the WriteFence up until it is triggered
37
+ // (calls its callbacks because all writes have committed.)
38
+ beginWrite() {
39
+ var self = this;
40
+
41
+ if (self.retired)
42
+ return { committed: function () { } };
43
+
44
+ if (self.fired)
45
+ throw new Error("fence has already activated -- too late to add writes");
46
+
47
+ self.outstanding_writes++;
48
+ var committed = false;
49
+ return {
50
+ committed: function () {
51
+ if (committed)
52
+ throw new Error("committed called twice on the same write");
53
+ committed = true;
54
+ self.outstanding_writes--;
55
+ self._maybeFire();
56
+ }
57
+ };
58
+ }
59
+
60
+ // Arm the fence. Once the fence is armed, and there are no more
61
+ // uncommitted writes, it will activate.
62
+ arm() {
63
+ var self = this;
64
+ if (self === _WriteFence._CurrentWriteFence)
65
+ throw Error("Can't arm the current fence");
66
+ self.armed = true;
67
+ self._maybeFire();
68
+ }
69
+
70
+ // Register a function to be called once before firing the fence.
71
+ // Callback function can add new writes to the fence, in which case
72
+ // it won't fire until those writes are done as well.
73
+ onBeforeFire(func) {
74
+ var self = this;
75
+ if (self.fired)
76
+ throw new Error("fence has already activated -- too late to " +
77
+ "add a callback");
78
+ self.before_fire_callbacks.push(func);
79
+ }
80
+
81
+ // Register a function to be called when the fence fires.
82
+ onAllCommitted(func) {
83
+ var self = this;
84
+ if (self.fired)
85
+ throw new Error("fence has already activated -- too late to " +
86
+ "add a callback");
87
+ self.completion_callbacks.push(func);
88
+ }
89
+
90
+ _maybeFire() {
91
+ var self = this;
92
+ if (self.fired)
93
+ throw new Error("write fence already activated?");
94
+ if (self.armed && !self.outstanding_writes) {
95
+ self.outstanding_writes++;
96
+ while (self.before_fire_callbacks.length > 0) {
97
+ var callbacks = self.before_fire_callbacks;
98
+ self.before_fire_callbacks = [];
99
+ for (const callback of callbacks)
100
+ invokeCallback(callback, self);
101
+ }
102
+ self.outstanding_writes--;
103
+
104
+ if (!self.outstanding_writes) {
105
+ self.fired = true;
106
+ var callbacks = self.completion_callbacks;
107
+ self.completion_callbacks = [];
108
+ for (const callback of callbacks)
109
+ invokeCallback(callback, self);
110
+ }
111
+ }
112
+ }
113
+
114
+ // Deactivate this fence so that adding more writes has no effect.
115
+ // The fence must have already fired.
116
+ retire() {
117
+ var self = this;
118
+ if (!self.fired)
119
+ throw new Error("Can't retire a fence that hasn't fired.");
120
+ self.retired = true;
121
+ }
122
+ }
123
+
124
+ function invokeCallback(func: Function, self: _WriteFence) {
125
+ try {
126
+ func(self);
127
+ } catch (err) {
128
+ console.error("exception in write fence callback", err);
129
+ }
130
+ }
@@ -0,0 +1,295 @@
1
+ import { clone, equals } from "../ejson/ejson";
2
+ import { IdMap } from "../id-map/id_map";
3
+
4
+ interface DiffSequence {
5
+ diffQueryChanges(ordered: boolean, oldResults: any[], newResults: any[], observer, options?): any;
6
+ diffQueryChanges(ordered: boolean, oldResults: IdMap, newResults: IdMap, observer, options?): any;
7
+ diffQueryUnorderedChanges(oldResults, newResults, observer, options?): any;
8
+ diffQueryOrderedChanges(old_results, new_results, observer, options?): any;
9
+ diffObjects(left, right, callbacks): any;
10
+ diffMaps(left, right, callbacks): any;
11
+ makeChangedFields(newDoc, oldDoc): any;
12
+ applyChanges(doc, changeFields): void;
13
+ }
14
+
15
+ export const DiffSequence: DiffSequence = {} as any;
16
+
17
+ const hasOwn = Object.prototype.hasOwnProperty;
18
+
19
+ function isObjEmpty(obj) {
20
+ for (let key in Object(obj)) {
21
+ if (hasOwn.call(obj, key)) {
22
+ return false;
23
+ }
24
+ }
25
+ return true;
26
+ }
27
+
28
+ // ordered: bool.
29
+ // old_results and new_results: collections of documents.
30
+ // if ordered, they are arrays.
31
+ // if unordered, they are IdMaps
32
+ DiffSequence.diffQueryChanges = function (ordered: boolean, oldResults: any, newResults: any, observer, options) {
33
+ if (ordered)
34
+ DiffSequence.diffQueryOrderedChanges(oldResults, newResults, observer, options);
35
+ else
36
+ DiffSequence.diffQueryUnorderedChanges(oldResults, newResults, observer, options);
37
+ };
38
+
39
+ DiffSequence.diffQueryUnorderedChanges = function (oldResults: IdMap, newResults: IdMap, observer, options) {
40
+ options = options || {};
41
+ var projectionFn = options.projectionFn || clone;
42
+
43
+ if (observer.movedBefore) {
44
+ throw new Error("_diffQueryUnordered called with a movedBefore observer!");
45
+ }
46
+
47
+ newResults.forEach(function (newDoc, id) {
48
+ var oldDoc = oldResults.get(id);
49
+ if (oldDoc) {
50
+ if (observer.changed && !equals(oldDoc, newDoc)) {
51
+ var projectedNew = projectionFn(newDoc);
52
+ var projectedOld = projectionFn(oldDoc);
53
+ var changedFields =
54
+ DiffSequence.makeChangedFields(projectedNew, projectedOld);
55
+ if (!isObjEmpty(changedFields)) {
56
+ observer.changed(id, changedFields);
57
+ }
58
+ }
59
+ } else if (observer.added) {
60
+ var fields = projectionFn(newDoc);
61
+ delete fields._id;
62
+ observer.added(newDoc._id, fields);
63
+ }
64
+ });
65
+
66
+ if (observer.removed) {
67
+ oldResults.forEach(function (oldDoc, id) {
68
+ if (!newResults.has(id))
69
+ observer.removed(id);
70
+ });
71
+ }
72
+ };
73
+
74
+ DiffSequence.diffQueryOrderedChanges = function (old_results: any[], new_results: any[], observer, options) {
75
+ options = options || {};
76
+ var projectionFn = options.projectionFn || clone;
77
+
78
+ var new_presence_of_id = {};
79
+ new_results.forEach(function (doc) {
80
+ if (new_presence_of_id[doc._id])
81
+ console.log("Duplicate _id in new_results");
82
+ new_presence_of_id[doc._id] = true;
83
+ });
84
+
85
+ var old_index_of_id = {};
86
+ old_results.forEach(function (doc, i) {
87
+ if (doc._id in old_index_of_id)
88
+ console.log("Duplicate _id in old_results");
89
+ old_index_of_id[doc._id] = i;
90
+ });
91
+
92
+ // ALGORITHM:
93
+ //
94
+ // To determine which docs should be considered "moved" (and which
95
+ // merely change position because of other docs moving) we run
96
+ // a "longest common subsequence" (LCS) algorithm. The LCS of the
97
+ // old doc IDs and the new doc IDs gives the docs that should NOT be
98
+ // considered moved.
99
+
100
+ // To actually call the appropriate callbacks to get from the old state to the
101
+ // new state:
102
+
103
+ // First, we call removed() on all the items that only appear in the old
104
+ // state.
105
+
106
+ // Then, once we have the items that should not move, we walk through the new
107
+ // results array group-by-group, where a "group" is a set of items that have
108
+ // moved, anchored on the end by an item that should not move. One by one, we
109
+ // move each of those elements into place "before" the anchoring end-of-group
110
+ // item, and fire changed events on them if necessary. Then we fire a changed
111
+ // event on the anchor, and move on to the next group. There is always at
112
+ // least one group; the last group is anchored by a virtual "null" id at the
113
+ // end.
114
+
115
+ // Asymptotically: O(N k) where k is number of ops, or potentially
116
+ // O(N log N) if inner loop of LCS were made to be binary search.
117
+
118
+
119
+ //////// LCS (longest common sequence, with respect to _id)
120
+ // (see Wikipedia article on Longest Increasing Subsequence,
121
+ // where the LIS is taken of the sequence of old indices of the
122
+ // docs in new_results)
123
+ //
124
+ // unmoved: the output of the algorithm; members of the LCS,
125
+ // in the form of indices into new_results
126
+ var unmoved = [];
127
+ // max_seq_len: length of LCS found so far
128
+ var max_seq_len = 0;
129
+ // seq_ends[i]: the index into new_results of the last doc in a
130
+ // common subsequence of length of i+1 <= max_seq_len
131
+ var N = new_results.length;
132
+ var seq_ends = new Array(N);
133
+ // ptrs: the common subsequence ending with new_results[n] extends
134
+ // a common subsequence ending with new_results[ptr[n]], unless
135
+ // ptr[n] is -1.
136
+ var ptrs = new Array(N);
137
+ // virtual sequence of old indices of new results
138
+ var old_idx_seq = function (i_new) {
139
+ return old_index_of_id[new_results[i_new]._id];
140
+ };
141
+ // for each item in new_results, use it to extend a common subsequence
142
+ // of length j <= max_seq_len
143
+ for (var i = 0; i < N; i++) {
144
+ if (old_index_of_id[new_results[i]._id] !== undefined) {
145
+ var j = max_seq_len;
146
+ // this inner loop would traditionally be a binary search,
147
+ // but scanning backwards we will likely find a subseq to extend
148
+ // pretty soon, bounded for example by the total number of ops.
149
+ // If this were to be changed to a binary search, we'd still want
150
+ // to scan backwards a bit as an optimization.
151
+ while (j > 0) {
152
+ if (old_idx_seq(seq_ends[j - 1]) < old_idx_seq(i))
153
+ break;
154
+ j--;
155
+ }
156
+
157
+ ptrs[i] = (j === 0 ? -1 : seq_ends[j - 1]);
158
+ seq_ends[j] = i;
159
+ if (j + 1 > max_seq_len)
160
+ max_seq_len = j + 1;
161
+ }
162
+ }
163
+
164
+ // pull out the LCS/LIS into unmoved
165
+ var idx = (max_seq_len === 0 ? -1 : seq_ends[max_seq_len - 1]);
166
+ while (idx >= 0) {
167
+ unmoved.push(idx);
168
+ idx = ptrs[idx];
169
+ }
170
+ // the unmoved item list is built backwards, so fix that
171
+ unmoved.reverse();
172
+
173
+ // the last group is always anchored by the end of the result list, which is
174
+ // an id of "null"
175
+ unmoved.push(new_results.length);
176
+
177
+ old_results.forEach(function (doc) {
178
+ if (!new_presence_of_id[doc._id])
179
+ observer.removed && observer.removed(doc._id);
180
+ });
181
+
182
+ // for each group of things in the new_results that is anchored by an unmoved
183
+ // element, iterate through the things before it.
184
+ var startOfGroup = 0;
185
+ unmoved.forEach(function (endOfGroup) {
186
+ var groupId = new_results[endOfGroup] ? new_results[endOfGroup]._id : null;
187
+ var oldDoc, newDoc, fields, projectedNew, projectedOld;
188
+ for (var i = startOfGroup; i < endOfGroup; i++) {
189
+ newDoc = new_results[i];
190
+ if (!hasOwn.call(old_index_of_id, newDoc._id)) {
191
+ fields = projectionFn(newDoc);
192
+ delete fields._id;
193
+ observer.addedBefore && observer.addedBefore(newDoc._id, fields, groupId);
194
+ observer.added && observer.added(newDoc._id, fields);
195
+ } else {
196
+ // moved
197
+ oldDoc = old_results[old_index_of_id[newDoc._id]];
198
+ projectedNew = projectionFn(newDoc);
199
+ projectedOld = projectionFn(oldDoc);
200
+ fields = DiffSequence.makeChangedFields(projectedNew, projectedOld);
201
+ if (!isObjEmpty(fields)) {
202
+ observer.changed && observer.changed(newDoc._id, fields);
203
+ }
204
+ observer.movedBefore && observer.movedBefore(newDoc._id, groupId);
205
+ }
206
+ }
207
+ if (groupId) {
208
+ newDoc = new_results[endOfGroup];
209
+ oldDoc = old_results[old_index_of_id[newDoc._id]];
210
+ projectedNew = projectionFn(newDoc);
211
+ projectedOld = projectionFn(oldDoc);
212
+ fields = DiffSequence.makeChangedFields(projectedNew, projectedOld);
213
+ if (!isObjEmpty(fields)) {
214
+ observer.changed && observer.changed(newDoc._id, fields);
215
+ }
216
+ }
217
+ startOfGroup = endOfGroup + 1;
218
+ });
219
+
220
+
221
+ };
222
+
223
+
224
+ // General helper for diff-ing two objects.
225
+ // callbacks is an object like so:
226
+ // { leftOnly: function (key, leftValue) {...},
227
+ // rightOnly: function (key, rightValue) {...},
228
+ // both: function (key, leftValue, rightValue) {...},
229
+ // }
230
+ DiffSequence.diffObjects = function (left, right, callbacks) {
231
+ Object.keys(left).forEach(key => {
232
+ const leftValue = left[key];
233
+ if (hasOwn.call(right, key)) {
234
+ callbacks.both && callbacks.both(key, leftValue, right[key]);
235
+ } else {
236
+ callbacks.leftOnly && callbacks.leftOnly(key, leftValue);
237
+ }
238
+ });
239
+
240
+ if (callbacks.rightOnly) {
241
+ Object.keys(right).forEach(key => {
242
+ const rightValue = right[key];
243
+ if (!hasOwn.call(left, key)) {
244
+ callbacks.rightOnly(key, rightValue);
245
+ }
246
+ });
247
+ }
248
+ };
249
+
250
+ DiffSequence.diffMaps = function (left, right, callbacks) {
251
+ left.forEach(function (leftValue, key) {
252
+ if (right.has(key)) {
253
+ callbacks.both && callbacks.both(key, leftValue, right.get(key));
254
+ } else {
255
+ callbacks.leftOnly && callbacks.leftOnly(key, leftValue);
256
+ }
257
+ });
258
+
259
+ if (callbacks.rightOnly) {
260
+ right.forEach(function (rightValue, key) {
261
+ if (!left.has(key)) {
262
+ callbacks.rightOnly(key, rightValue);
263
+ }
264
+ });
265
+ }
266
+ };
267
+
268
+
269
+ DiffSequence.makeChangedFields = function (newDoc, oldDoc) {
270
+ var fields = {};
271
+ DiffSequence.diffObjects(oldDoc, newDoc, {
272
+ leftOnly: function (key, value) {
273
+ fields[key] = undefined;
274
+ },
275
+ rightOnly: function (key, value) {
276
+ fields[key] = value;
277
+ },
278
+ both: function (key, leftValue, rightValue) {
279
+ if (!equals(leftValue, rightValue))
280
+ fields[key] = rightValue;
281
+ }
282
+ });
283
+ return fields;
284
+ };
285
+
286
+ DiffSequence.applyChanges = function (doc, changeFields) {
287
+ Object.keys(changeFields).forEach(key => {
288
+ const value = changeFields[key];
289
+ if (typeof value === "undefined") {
290
+ delete doc[key];
291
+ } else {
292
+ doc[key] = value;
293
+ }
294
+ });
295
+ };