react-deepwatch 1.6.2 → 1.7.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.
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,10 +275,10 @@ 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();
281
+ const result = inner() as T; // "as T" because i don't get why inner is "T | object"
261
282
  if(result !== newValue) { // old was preserved
262
283
  if(newValue !== null && typeof newValue === "object") {
263
284
  call.possiblyObsoleteObjects.add(newValue as object);
@@ -318,16 +339,17 @@ function preserve_map<T>(oldMap: Map<unknown, unknown>, newMap: Map<unknown, unk
318
339
 
319
340
  // Register old ids/keys:
320
341
  const oldKeysRegistry = new ObjRegistry(call.options);
321
- const oldValuesRegistry = new ObjRegistry(call.options);
322
- for(const [key,value] of oldMap.entries()) {
342
+ for(const key of oldMap.keys()) {
323
343
  oldKeysRegistry.register(key, `${diagnosis_path}[${diagnisis_shortenValue(key)}]`);
324
- oldValuesRegistry.register(value, `${diagnosis_path}[${diagnisis_shortenValue(key)}]`);
325
344
  }
326
345
 
346
+ const oldMapCopy = new Map<unknown, unknown>(oldMap.entries());
327
347
  oldMap.clear();
348
+
328
349
  for(let newKey of newMap.keys()) {
329
350
  let newValue = newMap.get(newKey);
330
- oldMap.set(oldKeysRegistry.getPreserved(newKey, call, diagnosis_path), oldValuesRegistry.getPreserved(newValue, call, `${diagnosis_path}[${diagnisis_shortenValue(newKey)}]`));
351
+ const preservedKey = oldKeysRegistry.getPreserved(newKey, call, diagnosis_path);
352
+ oldMap.set(preservedKey, preserve_inner(oldMapCopy.get(preservedKey), newValue, call, `${diagnosis_path}[${diagnisis_shortenValue(newKey)}]`));
331
353
  }
332
354
 
333
355
  return oldMap;
@@ -449,7 +471,7 @@ function mergeable(oldValue: unknown, newValue: unknown) {
449
471
 
450
472
  /**
451
473
  *
452
- * 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.
474
+ * 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.
453
475
  * @see normalizeList
454
476
  * @param root
455
477
  * @param options
@@ -460,11 +482,11 @@ export function normalizeLists<T extends object>(root: T, options: PreserveOptio
460
482
  normalizeList(value, options, context.diagnosis_path);
461
483
  }
462
484
  return visitChilds(value, context);
463
- }, "onError");
485
+ }, {trackPath: "onError"});
464
486
  }
465
487
 
466
488
  /**
467
- * Squeezes the items with the same id/key into one object instance.
489
+ * Makes identical items with the same id/key one (shared) object instance
468
490
  * @param list
469
491
  * @param options
470
492
  * @param diagnosis_path internal