react-deepwatch 1.6.3 → 1.7.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.
package/preserve.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {visitReplace} from "./Util";
1
+ import {isObject, visitReplace} from "./Util";
2
2
  import _ from "underscore";
3
3
  import {invalidateObject, deleteProperty} from "proxy-facades";
4
4
 
@@ -29,9 +29,10 @@ export type PreserveOptions = {
29
29
  ignoresKeys?: boolean,
30
30
 
31
31
  /**
32
- * Threats an object that was preserved and reoccurs on a completely differnt place (not the same parent) as the same = re-uses that instance.
32
+ * Threats an object that was preserved and reoccurs on a completely different place (not the same parent) as the same = re-uses that instance.
33
33
  * <p>Default: false</p>
34
34
  * <p>Disabled by default because too much magic / behaviour flipping depending indirectly related data.</p>
35
+ * @deprecated Those are preserved (as logically expected) there will be an error on conflicts
35
36
  */
36
37
  preserveCircular?: boolean
37
38
 
@@ -46,12 +47,12 @@ class PreserveCall {
46
47
  /**
47
48
  * preserved/old -> new (obsolete) object
48
49
  */
49
- mergedToNew = new Map<object,object>();
50
+ preservedToNew = new Map<object,object>();
50
51
 
51
52
  /**
52
53
  * new (obsolete) object -> preserved/old object
53
54
  */
54
- newToPreserved = new Map<object,object>();
55
+ newToPreserved = new Map<object, {preserved: object, diag_newPath: string}>();
55
56
 
56
57
  possiblyObsoleteObjects = new Set<object>();
57
58
 
@@ -82,8 +83,8 @@ type ID = string | number;
82
83
  class ObjRegistry {
83
84
  preserveOptions: PreserveOptions;
84
85
 
85
- objectsById = new Map<ID, object>();
86
- objectsByKey = new Map<ID, object>();
86
+ diag_objectsById = new Map<ID, object>();
87
+ diag_objectsByKey = new Map<ID, object>();
87
88
  objectsByIdAndKey = new Map<string, object>();
88
89
 
89
90
 
@@ -96,7 +97,7 @@ class ObjRegistry {
96
97
 
97
98
  const result: {id?: ID, key?: ID} = {};
98
99
 
99
- for(const mode of [{propName: "id", map: this.objectsById, diag_other: this.objectsByKey}, {propName: "key", map: this.objectsByKey, diag_other: this.objectsById}]) {
100
+ for(const mode of [{propName: "id", map: this.diag_objectsById, diag_other: this.diag_objectsByKey}, {propName: "key", map: this.diag_objectsByKey, diag_other: this.diag_objectsById}]) {
100
101
  const throwInconsistent = (map: Map<ID, object>) => {throw new PreserveError(`Objects must be consistent in either having an id or a key or both set, but found: ${diagnisis_shortenValue(obj)} and ${diagnisis_shortenValue(map.values().next().value)}. Path: ${diagnosis_path}`)};
101
102
 
102
103
  //@ts-ignore
@@ -142,10 +143,10 @@ class ObjRegistry {
142
143
  // Add to maps:
143
144
  this.objectsByIdAndKey.set(idAndKeyString, value);
144
145
  if(idAndKey.id !== undefined) {
145
- this.objectsById.set(idAndKey.id, value)
146
+ this.diag_objectsById.set(idAndKey.id, value)
146
147
  }
147
148
  if(idAndKey.key !== undefined) {
148
- this.objectsByKey.set(idAndKey.key, value)
149
+ this.diag_objectsByKey.set(idAndKey.key, value)
149
150
  }
150
151
  }
151
152
 
@@ -175,6 +176,16 @@ export function _preserve<T>(oldValue: T, newValue: T, options: PreserveOptions,
175
176
  let call = new PreserveCall(options, diagnosis);
176
177
  let result = preserve_inner(oldValue, newValue, call, "<root>");
177
178
 
179
+ if(isObject(result)) {
180
+ // Find all new objects in the result that were preserved in some other place and set them to that preserved instance:
181
+ result = visitReplace(result, (value, visitChilds, context) => {
182
+ if (isObject(value) && call.newToPreserved.has(value)) {
183
+ value = call.newToPreserved.get(value)!.preserved;
184
+ }
185
+ return visitChilds(value, context) // Should be necessary but not really thought about it much
186
+ });
187
+ }
188
+
178
189
  // Invalidate obsolete objects
179
190
  if(options.destroyObsolete !== false && call.possiblyObsoleteObjects.size > 0) {
180
191
  const obsoleteCause = diagnosis?.callStack || new Error("Preserve was called");
@@ -201,35 +212,45 @@ export function preserve_inner<T>(oldValue: T, newValue: T, call: PreserveCall,
201
212
  return newValue;
202
213
  }
203
214
 
204
- if (call.options.preserveCircular) {
205
- const preserved = call.newToPreserved.get(newValue);
206
- if (preserved !== undefined) {
207
- return preserved as T;
208
- }
209
- }
210
-
211
215
  if (!mergeable(oldValue, newValue)) {
216
+ if(call.newToPreserved.has(newValue)) {
217
+ return call.newToPreserved.get(newValue)!.preserved;
218
+ }
212
219
  return newValue;
213
220
  }
214
221
 
215
222
  // Narrow types:
216
223
  if (oldValue === null || typeof oldValue !== "object" || newValue === null || typeof newValue !== "object") {
224
+ if(call.newToPreserved.has(newValue)) {
225
+ return call.newToPreserved.get(newValue)!.preserved;
226
+ }
217
227
  return newValue;
218
228
  }
219
229
 
220
230
 
221
- if (call.mergedToNew.has(oldValue)) { // Already merged or currently merging?
231
+ if (call.preservedToNew.has(oldValue)) { // Already merged or currently merging?
222
232
  // Safety check:
223
- if (call.mergedToNew.get(oldValue) !== newValue) {
224
- throw new PreserveError(`Cannot replace object ${diagnisis_shortenValue(oldValue)} into ${diagnisis_shortenValue(newValue)} in: ${diagnosis_path}. It has already been replaced by another object: ${diagnisis_shortenValue(call.mergedToNew.get(oldValue))}. Please make your objects have a proper id or key and are not used in multiple places where these can be mistaken.\n${normalizeListsHint}`)
233
+ if (call.preservedToNew.get(oldValue) !== newValue) {
234
+ throw new PreserveError(`Cannot replace object ${diagnisis_shortenValue(oldValue)} into ${diagnisis_shortenValue(newValue)} in: ${diagnosis_path}. It has already been replaced by another object: ${diagnisis_shortenValue(call.preservedToNew.get(oldValue))}. Please make sure, your objects have a proper id or key and are not used in multiple places where these can be mistaken.\n${normalizeListsHint}`)
225
235
  }
226
236
 
227
- return oldValue;
237
+ return oldValue as T;
228
238
  }
229
239
 
230
240
  // *** Merge: ****
231
- call.mergedToNew.set(oldValue, newValue);
232
- call.newToPreserved.set(newValue, oldValue);
241
+ // Conflict check:
242
+ if(call.newToPreserved.has(newValue)) {
243
+ const otherPreserved = call.newToPreserved.get(newValue)!;
244
+ if(otherPreserved.preserved === oldValue) { // new -> old was exactly already preserved through other place?
245
+ return oldValue as T;
246
+ }
247
+ else {
248
+ throw new PreserveError(`Conflict: The same object instance (used in 2 places) is about to be preserved to different objects. This is not meaningful, that an instance in the new data structure represents 2 different instances in the old structure.\nPlace a: ${otherPreserved.diag_newPath}; old/preserved object: ${diagnisis_shortenValue(otherPreserved.preserved)}\nPlace b: ${diagnosis_path}; old/preserved object: ${diagnisis_shortenValue(oldValue)}`)
249
+ }
250
+ }
251
+
252
+ call.preservedToNew.set(oldValue, newValue);
253
+ call.newToPreserved.set(newValue, {preserved: oldValue, diag_newPath: diagnosis_path});
233
254
 
234
255
  if (Array.isArray(oldValue)) {
235
256
  return preserve_array(oldValue as unknown[], newValue as unknown[], call, diagnosis_path) as T;
@@ -254,20 +275,31 @@ export function preserve_inner<T>(oldValue: T, newValue: T, call: PreserveCall,
254
275
  }
255
276
  }
256
277
  }
257
- return oldValue;
278
+ return oldValue as T;
258
279
  }
259
280
 
260
- const result = inner();
261
- if(result !== newValue) { // old was preserved
262
- if(newValue !== null && typeof newValue === "object") {
263
- call.possiblyObsoleteObjects.add(newValue as object);
281
+ try {
282
+
283
+ const result = inner() as T; // "as T" because i don't get why inner is "T | object"
284
+ if (result !== newValue) { // old was preserved
285
+ if (newValue !== null && typeof newValue === "object") {
286
+ call.possiblyObsoleteObjects.add(newValue as object);
287
+ }
288
+ } else {
289
+ call.markUsed(newValue);
264
290
  }
291
+
292
+ return result;
265
293
  }
266
- else {
267
- call.markUsed(newValue);
294
+ catch (e) {
295
+ // Refine message. Adds more comfort to debugging:
296
+ if(e instanceof Error) {
297
+ if(e.message.indexOf("\nPath") === -1) {
298
+ e.message+=`\nPath: ${diagnosis_path}`;
299
+ }
300
+ }
301
+ throw e;
268
302
  }
269
-
270
- return result;
271
303
  }
272
304
 
273
305
  function preserve_array<T>(oldArray: Array<unknown>, newArray: Array<unknown>, call: PreserveCall, diagnosis_path: string): Array<unknown> {
@@ -450,7 +482,7 @@ function mergeable(oldValue: unknown, newValue: unknown) {
450
482
 
451
483
  /**
452
484
  *
453
- * Scans root deeply for Arrays and for each found array, it call normalizeList, which squeezes the items with the same id/key into one object instance.
485
+ * Scans root deeply for Arrays and for each found array, it calls normalizeList, which makes identical items with the same id/key one (shared) object instance.
454
486
  * @see normalizeList
455
487
  * @param root
456
488
  * @param options
@@ -461,11 +493,11 @@ export function normalizeLists<T extends object>(root: T, options: PreserveOptio
461
493
  normalizeList(value, options, context.diagnosis_path);
462
494
  }
463
495
  return visitChilds(value, context);
464
- }, "onError");
496
+ }, {trackPath: "onError"});
465
497
  }
466
498
 
467
499
  /**
468
- * Squeezes the items with the same id/key into one object instance.
500
+ * Makes identical items with the same id/key one (shared) object instance
469
501
  * @param list
470
502
  * @param options
471
503
  * @param diagnosis_path internal