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/Util.ts +89 -13
- package/dist/browser/Util.d.ts +11 -2
- package/dist/browser/Util.d.ts.map +1 -1
- package/dist/browser/Util.js +50 -12
- package/dist/browser/Util.js.map +1 -1
- package/dist/browser/preserve.d.ts +9 -5
- package/dist/browser/preserve.d.ts.map +1 -1
- package/dist/browser/preserve.js +45 -26
- package/dist/browser/preserve.js.map +1 -1
- package/dist/default/Util.d.ts +11 -2
- package/dist/default/Util.d.ts.map +1 -1
- package/dist/default/Util.js +50 -12
- package/dist/default/Util.js.map +1 -1
- package/dist/default/preserve.d.ts +9 -5
- package/dist/default/preserve.d.ts.map +1 -1
- package/dist/default/preserve.js +44 -25
- package/dist/default/preserve.js.map +1 -1
- package/package.json +1 -1
- package/preserve.ts +53 -31
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
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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.
|
|
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.
|
|
146
|
+
this.diag_objectsById.set(idAndKey.id, value)
|
|
146
147
|
}
|
|
147
148
|
if(idAndKey.key !== undefined) {
|
|
148
|
-
this.
|
|
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.
|
|
231
|
+
if (call.preservedToNew.has(oldValue)) { // Already merged or currently merging?
|
|
222
232
|
// Safety check:
|
|
223
|
-
if (call.
|
|
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.
|
|
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
|
-
|
|
232
|
-
call.newToPreserved.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|