@verdant-web/common 1.11.0 → 1.12.0

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 (159) hide show
  1. package/dist/cjs/EventSubscriber.d.ts +17 -17
  2. package/dist/cjs/EventSubscriber.js +66 -66
  3. package/dist/cjs/baseline.d.ts +5 -5
  4. package/dist/cjs/baseline.js +2 -2
  5. package/dist/cjs/batching.d.ts +41 -41
  6. package/dist/cjs/batching.js +88 -88
  7. package/dist/cjs/files.d.ts +19 -19
  8. package/dist/cjs/files.js +18 -18
  9. package/dist/cjs/index.d.ts +18 -18
  10. package/dist/cjs/index.js +35 -35
  11. package/dist/cjs/indexes.d.ts +14 -14
  12. package/dist/cjs/indexes.js +115 -115
  13. package/dist/cjs/indexes.test.d.ts +1 -1
  14. package/dist/cjs/indexes.test.js +127 -127
  15. package/dist/cjs/migration.d.ts +98 -98
  16. package/dist/cjs/migration.js +127 -127
  17. package/dist/cjs/oids.d.ts +85 -87
  18. package/dist/cjs/oids.js +363 -374
  19. package/dist/cjs/oids.js.map +1 -1
  20. package/dist/cjs/oids.test.d.ts +1 -1
  21. package/dist/cjs/oids.test.js +269 -251
  22. package/dist/cjs/oids.test.js.map +1 -1
  23. package/dist/cjs/operation.d.ts +139 -139
  24. package/dist/cjs/operation.js +532 -532
  25. package/dist/cjs/operation.js.map +1 -1
  26. package/dist/cjs/operation.test.d.ts +1 -1
  27. package/dist/cjs/operation.test.js +505 -505
  28. package/dist/cjs/operation.test.js.map +1 -1
  29. package/dist/cjs/patch.d.ts +26 -26
  30. package/dist/cjs/patch.js +226 -226
  31. package/dist/cjs/patch.js.map +1 -1
  32. package/dist/cjs/presence.d.ts +27 -27
  33. package/dist/cjs/presence.js +2 -2
  34. package/dist/cjs/protocol.d.ts +145 -145
  35. package/dist/cjs/protocol.js +3 -3
  36. package/dist/cjs/refs.d.ts +5 -5
  37. package/dist/cjs/refs.js +21 -21
  38. package/dist/cjs/replica.d.ts +28 -28
  39. package/dist/cjs/replica.js +28 -28
  40. package/dist/cjs/schema/defaults.d.ts +4 -4
  41. package/dist/cjs/schema/defaults.js +71 -71
  42. package/dist/cjs/schema/defaults.test.d.ts +1 -1
  43. package/dist/cjs/schema/defaults.test.js +20 -20
  44. package/dist/cjs/schema/defaults.test.js.map +1 -1
  45. package/dist/cjs/schema/index.d.ts +8 -8
  46. package/dist/cjs/schema/index.js +28 -28
  47. package/dist/cjs/schema/indexFilters.d.ts +6 -6
  48. package/dist/cjs/schema/indexFilters.js +30 -30
  49. package/dist/cjs/schema/types/collection.d.ts +51 -51
  50. package/dist/cjs/schema/types/collection.js +2 -2
  51. package/dist/cjs/schema/types/compounds.d.ts +12 -12
  52. package/dist/cjs/schema/types/compounds.js +2 -2
  53. package/dist/cjs/schema/types/fields.d.ts +59 -59
  54. package/dist/cjs/schema/types/fields.js +2 -2
  55. package/dist/cjs/schema/types/filters.d.ts +29 -29
  56. package/dist/cjs/schema/types/filters.js +2 -2
  57. package/dist/cjs/schema/types/shapes.d.ts +21 -21
  58. package/dist/cjs/schema/types/shapes.js +2 -2
  59. package/dist/cjs/schema/types/synthetics.d.ts +28 -28
  60. package/dist/cjs/schema/types/synthetics.js +2 -2
  61. package/dist/cjs/schema/types.d.ts +18 -18
  62. package/dist/cjs/schema/types.js +22 -22
  63. package/dist/cjs/timestamp.d.ts +48 -48
  64. package/dist/cjs/timestamp.js +243 -243
  65. package/dist/cjs/timestamp.test.d.ts +1 -1
  66. package/dist/cjs/timestamp.test.js +93 -93
  67. package/dist/cjs/undo.d.ts +3 -3
  68. package/dist/cjs/undo.js +198 -198
  69. package/dist/cjs/utils.d.ts +15 -15
  70. package/dist/cjs/utils.js +127 -127
  71. package/dist/cjs/utils.test.d.ts +1 -1
  72. package/dist/cjs/utils.test.js +36 -36
  73. package/dist/esm/EventSubscriber.d.ts +17 -17
  74. package/dist/esm/EventSubscriber.js +62 -62
  75. package/dist/esm/baseline.d.ts +5 -5
  76. package/dist/esm/baseline.js +1 -1
  77. package/dist/esm/batching.d.ts +41 -41
  78. package/dist/esm/batching.js +83 -83
  79. package/dist/esm/files.d.ts +19 -19
  80. package/dist/esm/files.js +12 -12
  81. package/dist/esm/index.d.ts +18 -18
  82. package/dist/esm/index.js +15 -15
  83. package/dist/esm/indexes.d.ts +14 -14
  84. package/dist/esm/indexes.js +105 -105
  85. package/dist/esm/indexes.test.d.ts +1 -1
  86. package/dist/esm/indexes.test.js +125 -125
  87. package/dist/esm/migration.d.ts +98 -98
  88. package/dist/esm/migration.js +121 -121
  89. package/dist/esm/oids.d.ts +85 -87
  90. package/dist/esm/oids.js +336 -347
  91. package/dist/esm/oids.js.map +1 -1
  92. package/dist/esm/oids.test.d.ts +1 -1
  93. package/dist/esm/oids.test.js +267 -249
  94. package/dist/esm/oids.test.js.map +1 -1
  95. package/dist/esm/operation.d.ts +139 -139
  96. package/dist/esm/operation.js +517 -517
  97. package/dist/esm/operation.js.map +1 -1
  98. package/dist/esm/operation.test.d.ts +1 -1
  99. package/dist/esm/operation.test.js +503 -503
  100. package/dist/esm/operation.test.js.map +1 -1
  101. package/dist/esm/patch.d.ts +26 -26
  102. package/dist/esm/patch.js +222 -222
  103. package/dist/esm/patch.js.map +1 -1
  104. package/dist/esm/presence.d.ts +27 -27
  105. package/dist/esm/presence.js +1 -1
  106. package/dist/esm/protocol.d.ts +145 -145
  107. package/dist/esm/protocol.js +2 -2
  108. package/dist/esm/refs.d.ts +5 -5
  109. package/dist/esm/refs.js +16 -16
  110. package/dist/esm/replica.d.ts +28 -28
  111. package/dist/esm/replica.js +25 -25
  112. package/dist/esm/schema/defaults.d.ts +4 -4
  113. package/dist/esm/schema/defaults.js +65 -65
  114. package/dist/esm/schema/defaults.test.d.ts +1 -1
  115. package/dist/esm/schema/defaults.test.js +18 -18
  116. package/dist/esm/schema/defaults.test.js.map +1 -1
  117. package/dist/esm/schema/index.d.ts +8 -8
  118. package/dist/esm/schema/index.js +9 -9
  119. package/dist/esm/schema/indexFilters.d.ts +6 -6
  120. package/dist/esm/schema/indexFilters.js +22 -22
  121. package/dist/esm/schema/types/collection.d.ts +51 -51
  122. package/dist/esm/schema/types/collection.js +1 -1
  123. package/dist/esm/schema/types/compounds.d.ts +12 -12
  124. package/dist/esm/schema/types/compounds.js +1 -1
  125. package/dist/esm/schema/types/fields.d.ts +59 -59
  126. package/dist/esm/schema/types/fields.js +1 -1
  127. package/dist/esm/schema/types/filters.d.ts +29 -29
  128. package/dist/esm/schema/types/filters.js +1 -1
  129. package/dist/esm/schema/types/shapes.d.ts +21 -21
  130. package/dist/esm/schema/types/shapes.js +1 -1
  131. package/dist/esm/schema/types/synthetics.d.ts +28 -28
  132. package/dist/esm/schema/types/synthetics.js +1 -1
  133. package/dist/esm/schema/types.d.ts +18 -18
  134. package/dist/esm/schema/types.js +6 -6
  135. package/dist/esm/timestamp.d.ts +48 -48
  136. package/dist/esm/timestamp.js +228 -228
  137. package/dist/esm/timestamp.test.d.ts +1 -1
  138. package/dist/esm/timestamp.test.js +91 -91
  139. package/dist/esm/undo.d.ts +3 -3
  140. package/dist/esm/undo.js +194 -194
  141. package/dist/esm/utils.d.ts +15 -15
  142. package/dist/esm/utils.js +113 -113
  143. package/dist/esm/utils.test.d.ts +1 -1
  144. package/dist/esm/utils.test.js +34 -34
  145. package/dist/tsconfig-cjs.tsbuildinfo +1 -1
  146. package/dist/tsconfig.tsbuildinfo +1 -1
  147. package/package.json +1 -1
  148. package/src/oids.test.ts +62 -51
  149. package/src/oids.ts +7 -26
  150. package/src/operation.test.ts +32 -40
  151. package/src/operation.ts +4 -16
  152. package/src/patch.ts +5 -15
  153. package/src/schema/defaults.test.ts +1 -1
  154. package/dist/cjs/files.test.d.ts +0 -1
  155. package/dist/cjs/files.test.js +0 -81
  156. package/dist/cjs/files.test.js.map +0 -1
  157. package/dist/esm/files.test.d.ts +0 -1
  158. package/dist/esm/files.test.js +0 -79
  159. package/dist/esm/files.test.js.map +0 -1
@@ -1,533 +1,533 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.substituteFirstLevelObjectsWithRefs = exports.substituteRefsWithObjects = exports.applyOperations = exports.applyPatch = exports.groupBaselinesByRootOid = exports.groupPatchesByRootOid = exports.groupPatchesByIdentifier = exports.shallowInitialToPatches = exports.initialToPatches = exports.shallowDiffToPatches = exports.diffToPatches = exports.isObjectRef = void 0;
4
- const oids_js_1 = require("./oids.js");
5
- const refs_js_1 = require("./refs.js");
6
- const utils_js_1 = require("./utils.js");
7
- function isObjectRef(obj) {
8
- return obj && typeof obj === 'object' && obj['@@type'] === 'ref';
9
- }
10
- exports.isObjectRef = isObjectRef;
11
- function isDiffableObject(val) {
12
- return (0, utils_js_1.isObject)(val) && !(0, refs_js_1.isRef)(val);
13
- }
14
- function compareNonDiffable(a, b) {
15
- if (a === b)
16
- return true;
17
- if ((0, refs_js_1.isRef)(a) && (0, refs_js_1.isRef)(b))
18
- return (0, refs_js_1.compareRefs)(a, b);
19
- return false;
20
- }
21
- function diffToPatches(from, to, getNow, keyPath, createSubId, patches = [], options = {}) {
22
- const oid = (0, oids_js_1.getOid)(from);
23
- function diffItems(key, value, oldValue) {
24
- if (!isDiffableObject(value)) {
25
- // for primitive fields, we can use plain sets and
26
- // do not need to recurse, of course
27
- if (!compareNonDiffable(value, oldValue)) {
28
- patches.push({
29
- oid,
30
- timestamp: getNow(),
31
- data: {
32
- op: 'set',
33
- name: key,
34
- value,
35
- },
36
- });
37
- }
38
- }
39
- else {
40
- const oldValueOid = (0, oids_js_1.maybeGetOid)(oldValue);
41
- let valueOid;
42
- if (!options.mergeUnknownObjects) {
43
- valueOid = (0, oids_js_1.ensureOid)(value, oid, key, createSubId);
44
- }
45
- else {
46
- // if merge unknown objects is requested, we copy the previous value's oid
47
- // to any mirrored new value if it doesn't have one assigned already.
48
- valueOid = (0, oids_js_1.maybeGetOid)(value);
49
- if (!valueOid && oldValueOid) {
50
- (0, oids_js_1.assignOid)(value, oldValueOid);
51
- valueOid = oldValueOid;
52
- }
53
- else {
54
- valueOid = (0, oids_js_1.ensureOid)(value, oid, key, createSubId);
55
- }
56
- }
57
- if (oldValue === undefined || valueOid !== oldValueOid) {
58
- // first case: previous value exists but the OIDs are different,
59
- // meaning the object identity has changed
60
- // we add the whole new object and also update the reference on this
61
- // key of the parent to point to the new object
62
- initialToPatches(value, valueOid, getNow, createSubId, patches);
63
- patches.push({
64
- oid,
65
- timestamp: getNow(),
66
- data: {
67
- op: 'set',
68
- name: key,
69
- value: (0, oids_js_1.createRef)(valueOid),
70
- },
71
- });
72
- // if there was an old value, we need to delete it altogether.
73
- if (oldValueOid !== undefined) {
74
- patches.push({
75
- oid: oldValueOid,
76
- timestamp: getNow(),
77
- data: {
78
- op: 'delete',
79
- },
80
- });
81
- }
82
- }
83
- else {
84
- // third case: OIDs are the same, meaning the identity is the same,
85
- // and we must diff the objects
86
- diffToPatches(oldValue, value, getNow, [...keyPath, key], createSubId, patches, options);
87
- }
88
- }
89
- }
90
- if (Array.isArray(from) && Array.isArray(to)) {
91
- // diffing is more naive than native array operations.
92
- // we can only look at each element and decide if it should
93
- // be replaced or removed - no moves or pushes, etc.
94
- for (let i = 0; i < to.length; i++) {
95
- const value = to[i];
96
- const oldValue = from[i];
97
- diffItems(i, value, oldValue);
98
- }
99
- // remove any remaining items at the end of the array
100
- const deletedItemsAtEnd = from.length - to.length;
101
- if (deletedItemsAtEnd > 0) {
102
- // if sub-items were objects, we need to delete them all
103
- for (let i = to.length; i < from.length; i++) {
104
- const value = from[i];
105
- const valueOid = (0, oids_js_1.maybeGetOid)(value);
106
- if (valueOid) {
107
- patches.push({
108
- oid: valueOid,
109
- timestamp: getNow(),
110
- data: {
111
- op: 'delete',
112
- },
113
- });
114
- }
115
- }
116
- // push the list-delete for the deleted items
117
- patches.push({
118
- oid,
119
- timestamp: getNow(),
120
- data: {
121
- op: 'list-delete',
122
- index: to.length,
123
- count: deletedItemsAtEnd,
124
- },
125
- });
126
- }
127
- }
128
- else if (Array.isArray(from) || Array.isArray(to)) {
129
- throw new Error('Cannot diff an array with an object');
130
- }
131
- else if (isDiffableObject(from) && isDiffableObject(to)) {
132
- const oldKeys = new Set(Object.keys(from));
133
- for (const [key, value] of Object.entries(to)) {
134
- if (value === undefined && options.defaultUndefined)
135
- continue;
136
- oldKeys.delete(key);
137
- if ((0, oids_js_1.isOidKey)(key))
138
- continue;
139
- const oldValue = from[key];
140
- diffItems(key, value, oldValue);
141
- }
142
- // this set now only contains keys which were not in the new object
143
- if (!options.defaultUndefined) {
144
- for (const key of oldKeys) {
145
- if ((0, oids_js_1.isOidKey)(key))
146
- continue;
147
- // push the delete for the property
148
- patches.push({
149
- oid,
150
- timestamp: getNow(),
151
- data: {
152
- op: 'remove',
153
- name: key,
154
- },
155
- });
156
- }
157
- }
158
- }
159
- return patches;
160
- }
161
- exports.diffToPatches = diffToPatches;
162
- function shallowDiffToPatches(from, to, getNow, patches = []) {
163
- const oid = (0, oids_js_1.getOid)(from);
164
- function diffItems(key, value, oldValue) {
165
- if (!isDiffableObject(value)) {
166
- // for primitive fields, we can use plain sets and
167
- // do not need to recurse, of course
168
- if (!compareNonDiffable(value, oldValue)) {
169
- patches.push({
170
- oid,
171
- timestamp: getNow(),
172
- data: {
173
- op: 'set',
174
- name: key,
175
- value,
176
- },
177
- });
178
- }
179
- }
180
- else {
181
- throw new Error('Shallow diff was given a nested object. This is an error in verdant!');
182
- }
183
- }
184
- if (Array.isArray(from) && Array.isArray(to)) {
185
- // diffing is more naive than native array operations.
186
- // we can only look at each element and decide if it should
187
- // be replaced or removed - no moves or pushes, etc.
188
- for (let i = 0; i < to.length; i++) {
189
- const value = to[i];
190
- const oldValue = from[i];
191
- diffItems(i, value, oldValue);
192
- }
193
- // remove any remaining items at the end of the array
194
- const deletedItemsAtEnd = from.length - to.length;
195
- if (deletedItemsAtEnd > 0) {
196
- // push the list-delete for the deleted items
197
- patches.push({
198
- oid,
199
- timestamp: getNow(),
200
- data: {
201
- op: 'list-delete',
202
- index: to.length,
203
- count: deletedItemsAtEnd,
204
- },
205
- });
206
- }
207
- }
208
- else if (Array.isArray(from) || Array.isArray(to)) {
209
- throw new Error('Cannot diff an array with an object');
210
- }
211
- else if (isDiffableObject(from) && isDiffableObject(to)) {
212
- const oldKeys = new Set(Object.keys(from));
213
- for (const [key, value] of Object.entries(to)) {
214
- oldKeys.delete(key);
215
- if ((0, oids_js_1.isOidKey)(key))
216
- continue;
217
- const oldValue = from[key];
218
- diffItems(key, value, oldValue);
219
- }
220
- // this set now only contains keys which were not in the new object
221
- for (const key of oldKeys) {
222
- if ((0, oids_js_1.isOidKey)(key))
223
- continue;
224
- // push the delete for the property
225
- patches.push({
226
- oid,
227
- timestamp: getNow(),
228
- data: {
229
- op: 'remove',
230
- name: key,
231
- },
232
- });
233
- }
234
- }
235
- return patches;
236
- }
237
- exports.shallowDiffToPatches = shallowDiffToPatches;
238
- /**
239
- * Takes a basic object and constructs a patch list to create it and
240
- * all of its nested objects.
241
- */
242
- function initialToPatches(initial, rootOid, getNow, createSubId, patches = []) {
243
- (0, oids_js_1.assignOid)(initial, rootOid);
244
- (0, oids_js_1.assignOidsToAllSubObjects)(initial, createSubId);
245
- const normalized = (0, oids_js_1.normalize)(initial);
246
- for (const key of normalized.keys()) {
247
- const value = normalized.get(key);
248
- patches.push({
249
- oid: key,
250
- timestamp: getNow(),
251
- data: {
252
- op: 'initialize',
253
- value: (0, oids_js_1.removeOid)(value),
254
- },
255
- });
256
- }
257
- return patches;
258
- }
259
- exports.initialToPatches = initialToPatches;
260
- function shallowInitialToPatches(initial, rootOid, getNow, patches = []) {
261
- patches.push({
262
- oid: rootOid,
263
- timestamp: getNow(),
264
- data: {
265
- op: 'initialize',
266
- value: initial,
267
- },
268
- });
269
- return patches;
270
- }
271
- exports.shallowInitialToPatches = shallowInitialToPatches;
272
- function groupPatchesByIdentifier(patches) {
273
- const grouped = {};
274
- for (const patch of patches) {
275
- if (patch.oid in grouped) {
276
- grouped[patch.oid].push(patch);
277
- }
278
- else {
279
- grouped[patch.oid] = [patch];
280
- }
281
- }
282
- return grouped;
283
- }
284
- exports.groupPatchesByIdentifier = groupPatchesByIdentifier;
285
- function groupPatchesByRootOid(patches) {
286
- const grouped = {};
287
- for (const patch of patches) {
288
- const root = (0, oids_js_1.getOidRoot)(patch.oid);
289
- if (root in grouped) {
290
- grouped[root].push(patch);
291
- }
292
- else {
293
- grouped[root] = [patch];
294
- }
295
- }
296
- return grouped;
297
- }
298
- exports.groupPatchesByRootOid = groupPatchesByRootOid;
299
- function groupBaselinesByRootOid(baselines) {
300
- const grouped = {};
301
- for (const patch of baselines) {
302
- const root = (0, oids_js_1.getOidRoot)(patch.oid);
303
- if (root in grouped) {
304
- grouped[root].push(patch);
305
- }
306
- else {
307
- grouped[root] = [patch];
308
- }
309
- }
310
- return grouped;
311
- }
312
- exports.groupBaselinesByRootOid = groupBaselinesByRootOid;
313
- function listCheck(obj) {
314
- if (!Array.isArray(obj)) {
315
- console.error(`Cannot apply list patch; expected array, received ${JSON.stringify(obj)}. This suggests your data is changing from a list to an object over time. (OID: ${(0, oids_js_1.maybeGetOid)(obj)})`);
316
- return false;
317
- }
318
- else {
319
- return true;
320
- }
321
- }
322
- /**
323
- * The incoming object should already be normalized!
324
- * This function will mutate the base object.
325
- */
326
- function applyPatch(base, patch,
327
- /**
328
- * Optionally supply a list to which any refs which
329
- * are removed during the patch will be appended.
330
- */
331
- deletedRefs) {
332
- // deleted objects are represented by undefined
333
- // and remain deleted unless re-initialized
334
- if ((base === undefined || base === null) && patch.op !== 'initialize') {
335
- return base;
336
- }
337
- // ditch typing internally.
338
- const baseAsAny = base;
339
- let index;
340
- let spliceResult;
341
- // a helper, pass it a value which is about
342
- // to be removed and it will append it to the list
343
- // if it was a ref
344
- function checkRef(field) {
345
- // don't bother if not supplied
346
- if (!deletedRefs)
347
- return;
348
- if ((0, refs_js_1.isRef)(field)) {
349
- deletedRefs.push(field);
350
- }
351
- }
352
- switch (patch.op) {
353
- case 'set':
354
- checkRef(baseAsAny[patch.name]);
355
- baseAsAny[patch.name] = patch.value;
356
- break;
357
- case 'remove':
358
- checkRef(baseAsAny[patch.name]);
359
- delete baseAsAny[patch.name];
360
- break;
361
- case 'list-push':
362
- if (listCheck(base)) {
363
- base.push(patch.value);
364
- }
365
- break;
366
- case 'list-delete':
367
- if (listCheck(base)) {
368
- checkRef(base[patch.index]);
369
- base.splice(patch.index, patch.count);
370
- }
371
- break;
372
- case 'list-move-by-index':
373
- if (listCheck(base)) {
374
- spliceResult = base.splice(patch.from, 1);
375
- base.splice(patch.to, 0, spliceResult[0]);
376
- }
377
- break;
378
- case 'list-remove':
379
- if (listCheck(base)) {
380
- do {
381
- const valueToRemove = patch.value;
382
- if (patch.only === 'last') {
383
- if (isObjectRef(valueToRemove)) {
384
- index = (0, utils_js_1.findLastIndex)(base, (item) => item.id === valueToRemove.id);
385
- }
386
- else {
387
- index = base.lastIndexOf(valueToRemove);
388
- }
389
- }
390
- else {
391
- if (isObjectRef(valueToRemove)) {
392
- index = base.findIndex((item) => item.id === valueToRemove.id);
393
- }
394
- else {
395
- index = base.indexOf(valueToRemove);
396
- }
397
- }
398
- if (index !== -1) {
399
- checkRef(base[index]);
400
- base.splice(index, 1);
401
- }
402
- } while (!patch.only && index !== -1);
403
- }
404
- break;
405
- case 'list-add':
406
- if (listCheck(base)) {
407
- const alreadyHas = base.some((item) => {
408
- if (isObjectRef(item) && isObjectRef(patch.value)) {
409
- return item.id === patch.value.id;
410
- }
411
- else {
412
- return item === patch.value;
413
- }
414
- });
415
- if (!alreadyHas) {
416
- base.push(patch.value);
417
- }
418
- }
419
- break;
420
- case 'list-move-by-ref':
421
- if (listCheck(base)) {
422
- index = base.indexOf(patch.value);
423
- spliceResult = base.splice(index, 1);
424
- base.splice(patch.index, 0, spliceResult[0]);
425
- }
426
- break;
427
- case 'list-insert':
428
- if (listCheck(base)) {
429
- if (!patch.value && !patch.values) {
430
- throw new Error(`Cannot apply list insert patch; expected value or values, received ${JSON.stringify(patch)}`);
431
- }
432
- if (patch.value) {
433
- base.splice(patch.index, 0, patch.value);
434
- }
435
- else {
436
- base.splice(patch.index, 0, ...patch.values);
437
- }
438
- }
439
- break;
440
- case 'delete':
441
- // collect all refs that are deleted
442
- if (Array.isArray(base)) {
443
- base.forEach(checkRef);
444
- }
445
- else if ((0, utils_js_1.isObject)(base)) {
446
- Object.values(base || {}).forEach(checkRef);
447
- }
448
- return undefined;
449
- case 'initialize':
450
- return (0, utils_js_1.cloneDeep)(patch.value);
451
- default:
452
- throw new Error(`Unsupported patch operation: ${patch.op}`);
453
- }
454
- return base;
455
- }
456
- exports.applyPatch = applyPatch;
457
- function applyOperations(base, operations, deletedRefs) {
458
- let cur = base;
459
- for (const op of operations) {
460
- cur = applyPatch(cur, op.data, deletedRefs);
461
- }
462
- return cur;
463
- }
464
- exports.applyOperations = applyOperations;
465
- /**
466
- * Mutates the original object in place. Returns all referenced
467
- * objects oids.
468
- */
469
- function substituteRefsWithObjects(base, refs, used = []) {
470
- if (Array.isArray(base)) {
471
- for (let i = 0; i < base.length; i++) {
472
- const item = base[i];
473
- base[i] = dereference(item, refs, used);
474
- if ((0, utils_js_1.isObject)(base[i])) {
475
- substituteRefsWithObjects(base[i], refs, used);
476
- }
477
- }
478
- }
479
- else if ((0, utils_js_1.isObject)(base)) {
480
- // not sure where to put this assertion but it's important to make
481
- // sure all nested objects include an OID
482
- (0, utils_js_1.assert)((0, oids_js_1.maybeGetOid)(base), `Object ${JSON.stringify(base)} must have an oid`);
483
- for (const key of Object.keys(base)) {
484
- base[key] = dereference(base[key], refs, used);
485
- // now that objects are in place, recursively substitute
486
- if ((0, utils_js_1.isObject)(base[key])) {
487
- substituteRefsWithObjects(base[key], refs, used);
488
- }
489
- }
490
- }
491
- return used;
492
- }
493
- exports.substituteRefsWithObjects = substituteRefsWithObjects;
494
- function dereference(input, refs, used) {
495
- if (isObjectRef(input)) {
496
- used.push(input.id);
497
- const resolved = refs.get(input.id);
498
- (0, utils_js_1.assert)(!!resolved, `No value was found in object map for ${input.id}`);
499
- return (0, oids_js_1.assignOid)(resolved, input.id);
500
- }
501
- else {
502
- return input;
503
- }
504
- }
505
- function substituteFirstLevelObjectsWithRefs(base, refObjects = new Map()) {
506
- if (Array.isArray(base)) {
507
- for (let i = 0; i < base.length; i++) {
508
- const item = base[i];
509
- if ((0, utils_js_1.isObject)(item)) {
510
- const oid = (0, oids_js_1.getOid)(item);
511
- base[i] = {
512
- '@@type': 'ref',
513
- id: oid,
514
- };
515
- refObjects.set(oid, item);
516
- }
517
- }
518
- }
519
- else {
520
- for (const [key, value] of Object.entries(base)) {
521
- if ((0, utils_js_1.isObject)(value)) {
522
- base[key] = {
523
- '@@type': 'ref',
524
- id: (0, oids_js_1.getOid)(value),
525
- };
526
- refObjects.set((0, oids_js_1.getOid)(value), value);
527
- }
528
- }
529
- }
530
- return refObjects;
531
- }
532
- exports.substituteFirstLevelObjectsWithRefs = substituteFirstLevelObjectsWithRefs;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.substituteFirstLevelObjectsWithRefs = exports.substituteRefsWithObjects = exports.applyOperations = exports.applyPatch = exports.groupBaselinesByRootOid = exports.groupPatchesByRootOid = exports.groupPatchesByIdentifier = exports.shallowInitialToPatches = exports.initialToPatches = exports.shallowDiffToPatches = exports.diffToPatches = exports.isObjectRef = void 0;
4
+ const oids_js_1 = require("./oids.js");
5
+ const refs_js_1 = require("./refs.js");
6
+ const utils_js_1 = require("./utils.js");
7
+ function isObjectRef(obj) {
8
+ return obj && typeof obj === 'object' && obj['@@type'] === 'ref';
9
+ }
10
+ exports.isObjectRef = isObjectRef;
11
+ function isDiffableObject(val) {
12
+ return (0, utils_js_1.isObject)(val) && !(0, refs_js_1.isRef)(val);
13
+ }
14
+ function compareNonDiffable(a, b) {
15
+ if (a === b)
16
+ return true;
17
+ if ((0, refs_js_1.isRef)(a) && (0, refs_js_1.isRef)(b))
18
+ return (0, refs_js_1.compareRefs)(a, b);
19
+ return false;
20
+ }
21
+ function diffToPatches(from, to, getNow, createSubId, patches = [], options = {}) {
22
+ const oid = (0, oids_js_1.getOid)(from);
23
+ function diffItems(key, value, oldValue) {
24
+ if (!isDiffableObject(value)) {
25
+ // for primitive fields, we can use plain sets and
26
+ // do not need to recurse, of course
27
+ if (!compareNonDiffable(value, oldValue)) {
28
+ patches.push({
29
+ oid,
30
+ timestamp: getNow(),
31
+ data: {
32
+ op: 'set',
33
+ name: key,
34
+ value,
35
+ },
36
+ });
37
+ }
38
+ }
39
+ else {
40
+ const oldValueOid = (0, oids_js_1.maybeGetOid)(oldValue);
41
+ let valueOid;
42
+ if (!options.mergeUnknownObjects) {
43
+ valueOid = (0, oids_js_1.ensureOid)(value, oid, createSubId);
44
+ }
45
+ else {
46
+ // if merge unknown objects is requested, we copy the previous value's oid
47
+ // to any mirrored new value if it doesn't have one assigned already.
48
+ valueOid = (0, oids_js_1.maybeGetOid)(value);
49
+ if (!valueOid && oldValueOid) {
50
+ (0, oids_js_1.assignOid)(value, oldValueOid);
51
+ valueOid = oldValueOid;
52
+ }
53
+ else {
54
+ valueOid = (0, oids_js_1.ensureOid)(value, oid, createSubId);
55
+ }
56
+ }
57
+ if (oldValue === undefined || valueOid !== oldValueOid) {
58
+ // first case: previous value exists but the OIDs are different,
59
+ // meaning the object identity has changed
60
+ // we add the whole new object and also update the reference on this
61
+ // key of the parent to point to the new object
62
+ initialToPatches(value, valueOid, getNow, createSubId, patches);
63
+ patches.push({
64
+ oid,
65
+ timestamp: getNow(),
66
+ data: {
67
+ op: 'set',
68
+ name: key,
69
+ value: (0, oids_js_1.createRef)(valueOid),
70
+ },
71
+ });
72
+ // if there was an old value, we need to delete it altogether.
73
+ if (oldValueOid !== undefined) {
74
+ patches.push({
75
+ oid: oldValueOid,
76
+ timestamp: getNow(),
77
+ data: {
78
+ op: 'delete',
79
+ },
80
+ });
81
+ }
82
+ }
83
+ else {
84
+ // third case: OIDs are the same, meaning the identity is the same,
85
+ // and we must diff the objects
86
+ diffToPatches(oldValue, value, getNow, createSubId, patches, options);
87
+ }
88
+ }
89
+ }
90
+ if (Array.isArray(from) && Array.isArray(to)) {
91
+ // diffing is more naive than native array operations.
92
+ // we can only look at each element and decide if it should
93
+ // be replaced or removed - no moves or pushes, etc.
94
+ for (let i = 0; i < to.length; i++) {
95
+ const value = to[i];
96
+ const oldValue = from[i];
97
+ diffItems(i, value, oldValue);
98
+ }
99
+ // remove any remaining items at the end of the array
100
+ const deletedItemsAtEnd = from.length - to.length;
101
+ if (deletedItemsAtEnd > 0) {
102
+ // if sub-items were objects, we need to delete them all
103
+ for (let i = to.length; i < from.length; i++) {
104
+ const value = from[i];
105
+ const valueOid = (0, oids_js_1.maybeGetOid)(value);
106
+ if (valueOid) {
107
+ patches.push({
108
+ oid: valueOid,
109
+ timestamp: getNow(),
110
+ data: {
111
+ op: 'delete',
112
+ },
113
+ });
114
+ }
115
+ }
116
+ // push the list-delete for the deleted items
117
+ patches.push({
118
+ oid,
119
+ timestamp: getNow(),
120
+ data: {
121
+ op: 'list-delete',
122
+ index: to.length,
123
+ count: deletedItemsAtEnd,
124
+ },
125
+ });
126
+ }
127
+ }
128
+ else if (Array.isArray(from) || Array.isArray(to)) {
129
+ throw new Error('Cannot diff an array with an object');
130
+ }
131
+ else if (isDiffableObject(from) && isDiffableObject(to)) {
132
+ const oldKeys = new Set(Object.keys(from));
133
+ for (const [key, value] of Object.entries(to)) {
134
+ if (value === undefined && options.defaultUndefined)
135
+ continue;
136
+ oldKeys.delete(key);
137
+ if ((0, oids_js_1.isOidKey)(key))
138
+ continue;
139
+ const oldValue = from[key];
140
+ diffItems(key, value, oldValue);
141
+ }
142
+ // this set now only contains keys which were not in the new object
143
+ if (!options.defaultUndefined) {
144
+ for (const key of oldKeys) {
145
+ if ((0, oids_js_1.isOidKey)(key))
146
+ continue;
147
+ // push the delete for the property
148
+ patches.push({
149
+ oid,
150
+ timestamp: getNow(),
151
+ data: {
152
+ op: 'remove',
153
+ name: key,
154
+ },
155
+ });
156
+ }
157
+ }
158
+ }
159
+ return patches;
160
+ }
161
+ exports.diffToPatches = diffToPatches;
162
+ function shallowDiffToPatches(from, to, getNow, patches = []) {
163
+ const oid = (0, oids_js_1.getOid)(from);
164
+ function diffItems(key, value, oldValue) {
165
+ if (!isDiffableObject(value)) {
166
+ // for primitive fields, we can use plain sets and
167
+ // do not need to recurse, of course
168
+ if (!compareNonDiffable(value, oldValue)) {
169
+ patches.push({
170
+ oid,
171
+ timestamp: getNow(),
172
+ data: {
173
+ op: 'set',
174
+ name: key,
175
+ value,
176
+ },
177
+ });
178
+ }
179
+ }
180
+ else {
181
+ throw new Error('Shallow diff was given a nested object. This is an error in verdant!');
182
+ }
183
+ }
184
+ if (Array.isArray(from) && Array.isArray(to)) {
185
+ // diffing is more naive than native array operations.
186
+ // we can only look at each element and decide if it should
187
+ // be replaced or removed - no moves or pushes, etc.
188
+ for (let i = 0; i < to.length; i++) {
189
+ const value = to[i];
190
+ const oldValue = from[i];
191
+ diffItems(i, value, oldValue);
192
+ }
193
+ // remove any remaining items at the end of the array
194
+ const deletedItemsAtEnd = from.length - to.length;
195
+ if (deletedItemsAtEnd > 0) {
196
+ // push the list-delete for the deleted items
197
+ patches.push({
198
+ oid,
199
+ timestamp: getNow(),
200
+ data: {
201
+ op: 'list-delete',
202
+ index: to.length,
203
+ count: deletedItemsAtEnd,
204
+ },
205
+ });
206
+ }
207
+ }
208
+ else if (Array.isArray(from) || Array.isArray(to)) {
209
+ throw new Error('Cannot diff an array with an object');
210
+ }
211
+ else if (isDiffableObject(from) && isDiffableObject(to)) {
212
+ const oldKeys = new Set(Object.keys(from));
213
+ for (const [key, value] of Object.entries(to)) {
214
+ oldKeys.delete(key);
215
+ if ((0, oids_js_1.isOidKey)(key))
216
+ continue;
217
+ const oldValue = from[key];
218
+ diffItems(key, value, oldValue);
219
+ }
220
+ // this set now only contains keys which were not in the new object
221
+ for (const key of oldKeys) {
222
+ if ((0, oids_js_1.isOidKey)(key))
223
+ continue;
224
+ // push the delete for the property
225
+ patches.push({
226
+ oid,
227
+ timestamp: getNow(),
228
+ data: {
229
+ op: 'remove',
230
+ name: key,
231
+ },
232
+ });
233
+ }
234
+ }
235
+ return patches;
236
+ }
237
+ exports.shallowDiffToPatches = shallowDiffToPatches;
238
+ /**
239
+ * Takes a basic object and constructs a patch list to create it and
240
+ * all of its nested objects.
241
+ */
242
+ function initialToPatches(initial, rootOid, getNow, createSubId, patches = []) {
243
+ (0, oids_js_1.assignOid)(initial, rootOid);
244
+ (0, oids_js_1.assignOidsToAllSubObjects)(initial, createSubId);
245
+ const normalized = (0, oids_js_1.normalize)(initial);
246
+ for (const key of normalized.keys()) {
247
+ const value = normalized.get(key);
248
+ patches.push({
249
+ oid: key,
250
+ timestamp: getNow(),
251
+ data: {
252
+ op: 'initialize',
253
+ value: (0, oids_js_1.removeOid)(value),
254
+ },
255
+ });
256
+ }
257
+ return patches;
258
+ }
259
+ exports.initialToPatches = initialToPatches;
260
+ function shallowInitialToPatches(initial, rootOid, getNow, patches = []) {
261
+ patches.push({
262
+ oid: rootOid,
263
+ timestamp: getNow(),
264
+ data: {
265
+ op: 'initialize',
266
+ value: initial,
267
+ },
268
+ });
269
+ return patches;
270
+ }
271
+ exports.shallowInitialToPatches = shallowInitialToPatches;
272
+ function groupPatchesByIdentifier(patches) {
273
+ const grouped = {};
274
+ for (const patch of patches) {
275
+ if (patch.oid in grouped) {
276
+ grouped[patch.oid].push(patch);
277
+ }
278
+ else {
279
+ grouped[patch.oid] = [patch];
280
+ }
281
+ }
282
+ return grouped;
283
+ }
284
+ exports.groupPatchesByIdentifier = groupPatchesByIdentifier;
285
+ function groupPatchesByRootOid(patches) {
286
+ const grouped = {};
287
+ for (const patch of patches) {
288
+ const root = (0, oids_js_1.getOidRoot)(patch.oid);
289
+ if (root in grouped) {
290
+ grouped[root].push(patch);
291
+ }
292
+ else {
293
+ grouped[root] = [patch];
294
+ }
295
+ }
296
+ return grouped;
297
+ }
298
+ exports.groupPatchesByRootOid = groupPatchesByRootOid;
299
+ function groupBaselinesByRootOid(baselines) {
300
+ const grouped = {};
301
+ for (const patch of baselines) {
302
+ const root = (0, oids_js_1.getOidRoot)(patch.oid);
303
+ if (root in grouped) {
304
+ grouped[root].push(patch);
305
+ }
306
+ else {
307
+ grouped[root] = [patch];
308
+ }
309
+ }
310
+ return grouped;
311
+ }
312
+ exports.groupBaselinesByRootOid = groupBaselinesByRootOid;
313
+ function listCheck(obj) {
314
+ if (!Array.isArray(obj)) {
315
+ console.error(`Cannot apply list patch; expected array, received ${JSON.stringify(obj)}. This suggests your data is changing from a list to an object over time. (OID: ${(0, oids_js_1.maybeGetOid)(obj)})`);
316
+ return false;
317
+ }
318
+ else {
319
+ return true;
320
+ }
321
+ }
322
+ /**
323
+ * The incoming object should already be normalized!
324
+ * This function will mutate the base object.
325
+ */
326
+ function applyPatch(base, patch,
327
+ /**
328
+ * Optionally supply a list to which any refs which
329
+ * are removed during the patch will be appended.
330
+ */
331
+ deletedRefs) {
332
+ // deleted objects are represented by undefined
333
+ // and remain deleted unless re-initialized
334
+ if ((base === undefined || base === null) && patch.op !== 'initialize') {
335
+ return base;
336
+ }
337
+ // ditch typing internally.
338
+ const baseAsAny = base;
339
+ let index;
340
+ let spliceResult;
341
+ // a helper, pass it a value which is about
342
+ // to be removed and it will append it to the list
343
+ // if it was a ref
344
+ function checkRef(field) {
345
+ // don't bother if not supplied
346
+ if (!deletedRefs)
347
+ return;
348
+ if ((0, refs_js_1.isRef)(field)) {
349
+ deletedRefs.push(field);
350
+ }
351
+ }
352
+ switch (patch.op) {
353
+ case 'set':
354
+ checkRef(baseAsAny[patch.name]);
355
+ baseAsAny[patch.name] = patch.value;
356
+ break;
357
+ case 'remove':
358
+ checkRef(baseAsAny[patch.name]);
359
+ delete baseAsAny[patch.name];
360
+ break;
361
+ case 'list-push':
362
+ if (listCheck(base)) {
363
+ base.push(patch.value);
364
+ }
365
+ break;
366
+ case 'list-delete':
367
+ if (listCheck(base)) {
368
+ checkRef(base[patch.index]);
369
+ base.splice(patch.index, patch.count);
370
+ }
371
+ break;
372
+ case 'list-move-by-index':
373
+ if (listCheck(base)) {
374
+ spliceResult = base.splice(patch.from, 1);
375
+ base.splice(patch.to, 0, spliceResult[0]);
376
+ }
377
+ break;
378
+ case 'list-remove':
379
+ if (listCheck(base)) {
380
+ do {
381
+ const valueToRemove = patch.value;
382
+ if (patch.only === 'last') {
383
+ if (isObjectRef(valueToRemove)) {
384
+ index = (0, utils_js_1.findLastIndex)(base, (item) => item.id === valueToRemove.id);
385
+ }
386
+ else {
387
+ index = base.lastIndexOf(valueToRemove);
388
+ }
389
+ }
390
+ else {
391
+ if (isObjectRef(valueToRemove)) {
392
+ index = base.findIndex((item) => item.id === valueToRemove.id);
393
+ }
394
+ else {
395
+ index = base.indexOf(valueToRemove);
396
+ }
397
+ }
398
+ if (index !== -1) {
399
+ checkRef(base[index]);
400
+ base.splice(index, 1);
401
+ }
402
+ } while (!patch.only && index !== -1);
403
+ }
404
+ break;
405
+ case 'list-add':
406
+ if (listCheck(base)) {
407
+ const alreadyHas = base.some((item) => {
408
+ if (isObjectRef(item) && isObjectRef(patch.value)) {
409
+ return item.id === patch.value.id;
410
+ }
411
+ else {
412
+ return item === patch.value;
413
+ }
414
+ });
415
+ if (!alreadyHas) {
416
+ base.push(patch.value);
417
+ }
418
+ }
419
+ break;
420
+ case 'list-move-by-ref':
421
+ if (listCheck(base)) {
422
+ index = base.indexOf(patch.value);
423
+ spliceResult = base.splice(index, 1);
424
+ base.splice(patch.index, 0, spliceResult[0]);
425
+ }
426
+ break;
427
+ case 'list-insert':
428
+ if (listCheck(base)) {
429
+ if (!patch.value && !patch.values) {
430
+ throw new Error(`Cannot apply list insert patch; expected value or values, received ${JSON.stringify(patch)}`);
431
+ }
432
+ if (patch.value) {
433
+ base.splice(patch.index, 0, patch.value);
434
+ }
435
+ else {
436
+ base.splice(patch.index, 0, ...patch.values);
437
+ }
438
+ }
439
+ break;
440
+ case 'delete':
441
+ // collect all refs that are deleted
442
+ if (Array.isArray(base)) {
443
+ base.forEach(checkRef);
444
+ }
445
+ else if ((0, utils_js_1.isObject)(base)) {
446
+ Object.values(base || {}).forEach(checkRef);
447
+ }
448
+ return undefined;
449
+ case 'initialize':
450
+ return (0, utils_js_1.cloneDeep)(patch.value);
451
+ default:
452
+ throw new Error(`Unsupported patch operation: ${patch.op}`);
453
+ }
454
+ return base;
455
+ }
456
+ exports.applyPatch = applyPatch;
457
+ function applyOperations(base, operations, deletedRefs) {
458
+ let cur = base;
459
+ for (const op of operations) {
460
+ cur = applyPatch(cur, op.data, deletedRefs);
461
+ }
462
+ return cur;
463
+ }
464
+ exports.applyOperations = applyOperations;
465
+ /**
466
+ * Mutates the original object in place. Returns all referenced
467
+ * objects oids.
468
+ */
469
+ function substituteRefsWithObjects(base, refs, used = []) {
470
+ if (Array.isArray(base)) {
471
+ for (let i = 0; i < base.length; i++) {
472
+ const item = base[i];
473
+ base[i] = dereference(item, refs, used);
474
+ if ((0, utils_js_1.isObject)(base[i])) {
475
+ substituteRefsWithObjects(base[i], refs, used);
476
+ }
477
+ }
478
+ }
479
+ else if ((0, utils_js_1.isObject)(base)) {
480
+ // not sure where to put this assertion but it's important to make
481
+ // sure all nested objects include an OID
482
+ (0, utils_js_1.assert)((0, oids_js_1.maybeGetOid)(base), `Object ${JSON.stringify(base)} must have an oid`);
483
+ for (const key of Object.keys(base)) {
484
+ base[key] = dereference(base[key], refs, used);
485
+ // now that objects are in place, recursively substitute
486
+ if ((0, utils_js_1.isObject)(base[key])) {
487
+ substituteRefsWithObjects(base[key], refs, used);
488
+ }
489
+ }
490
+ }
491
+ return used;
492
+ }
493
+ exports.substituteRefsWithObjects = substituteRefsWithObjects;
494
+ function dereference(input, refs, used) {
495
+ if (isObjectRef(input)) {
496
+ used.push(input.id);
497
+ const resolved = refs.get(input.id);
498
+ (0, utils_js_1.assert)(!!resolved, `No value was found in object map for ${input.id}`);
499
+ return (0, oids_js_1.assignOid)(resolved, input.id);
500
+ }
501
+ else {
502
+ return input;
503
+ }
504
+ }
505
+ function substituteFirstLevelObjectsWithRefs(base, refObjects = new Map()) {
506
+ if (Array.isArray(base)) {
507
+ for (let i = 0; i < base.length; i++) {
508
+ const item = base[i];
509
+ if ((0, utils_js_1.isObject)(item)) {
510
+ const oid = (0, oids_js_1.getOid)(item);
511
+ base[i] = {
512
+ '@@type': 'ref',
513
+ id: oid,
514
+ };
515
+ refObjects.set(oid, item);
516
+ }
517
+ }
518
+ }
519
+ else {
520
+ for (const [key, value] of Object.entries(base)) {
521
+ if ((0, utils_js_1.isObject)(value)) {
522
+ base[key] = {
523
+ '@@type': 'ref',
524
+ id: (0, oids_js_1.getOid)(value),
525
+ };
526
+ refObjects.set((0, oids_js_1.getOid)(value), value);
527
+ }
528
+ }
529
+ }
530
+ return refObjects;
531
+ }
532
+ exports.substituteFirstLevelObjectsWithRefs = substituteFirstLevelObjectsWithRefs;
533
533
  //# sourceMappingURL=operation.js.map