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/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 +58 -28
- 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 +57 -27
- package/dist/default/preserve.js.map +1 -1
- package/package.json +1 -1
- package/preserve.ts +66 -34
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,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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
267
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|