j-templates 8.2.0 → 9.0.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/Store/Diff/diffTree.d.ts +24 -49
- package/Store/Diff/diffTree.js +112 -511
- package/Store/Diff/index.d.ts +1 -0
- package/Store/Diff/index.js +1 -0
- package/Store/Diff/utils/diffTree.types.d.ts +6 -1
- package/Store/Diff/utils/homes.d.ts +46 -0
- package/Store/Diff/utils/homes.js +213 -0
- package/Store/Diff/utils/paths.d.ts +42 -0
- package/Store/Diff/utils/paths.js +87 -0
- package/Store/Diff/utils/projections.d.ts +55 -0
- package/Store/Diff/utils/projections.js +290 -0
- package/Store/Diff/utils/routes.d.ts +27 -0
- package/Store/Diff/utils/routes.js +168 -0
- package/Store/Diff/utils/state.d.ts +56 -0
- package/Store/Diff/utils/state.js +155 -0
- package/Store/Store/store.d.ts +8 -0
- package/Store/Store/store.js +13 -0
- package/Store/Store/storeAsync.js +7 -6
- package/Store/Store/storeSync.js +3 -3
- package/Store/Tree/observableNode.js +18 -15
- package/Store/Tree/snapshot.d.ts +19 -21
- package/Store/Tree/snapshot.js +29 -23
- package/Utils/json.d.ts +166 -26
- package/Utils/json.js +510 -183
- package/package.json +1 -1
- package/Store/Diff/utils/diffTree.utils.d.ts +0 -79
- package/Store/Diff/utils/diffTree.utils.js +0 -398
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { JsonType } from "../../../Utils/json";
|
|
2
|
+
import { Snapshot } from "../../Tree/snapshot";
|
|
3
|
+
import { PROP_DELIMETER, ROUTE_DELIMETER, RouteAndProperty, SplitRouteAndPath, WILDCARD } from "./paths";
|
|
4
|
+
/**
|
|
5
|
+
* The raw state of a DiffTree's routes. A route's state hangs off its parent route's state under
|
|
6
|
+
* `ROUTE_STATE`, keyed by segment, so `Object.keys` of a route's state lists only its own values.
|
|
7
|
+
*/
|
|
8
|
+
export const ROUTE_STATE = Symbol("ROUTE_STATE");
|
|
9
|
+
/**
|
|
10
|
+
* A route's state object, e.g. `/channel/1/`'s; `""` and `/` are the root.
|
|
11
|
+
* @param create Creates the route's state, and its parents', when missing
|
|
12
|
+
* @returns The state, or undefined when it's missing and `create` isn't set
|
|
13
|
+
*/
|
|
14
|
+
export function RouteState(root, route, create = false) {
|
|
15
|
+
let state = root;
|
|
16
|
+
for (let start = 1; state !== undefined && state !== null && start < route.length;) {
|
|
17
|
+
const end = route.indexOf(ROUTE_DELIMETER, start);
|
|
18
|
+
const segment = route.slice(start, end);
|
|
19
|
+
if (create && !Object.hasOwn(state, ROUTE_STATE))
|
|
20
|
+
Object.defineProperty(state, ROUTE_STATE, { value: {} });
|
|
21
|
+
state = create ? state[ROUTE_STATE][segment] ??= {} : state[ROUTE_STATE]?.[segment];
|
|
22
|
+
start = end + 1;
|
|
23
|
+
}
|
|
24
|
+
return state;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Writes a value to a path as it is, without diffing it or keeping keyed values at their homes,
|
|
28
|
+
* creating the path's route when missing. A path to a route (`/channel/1/`) only creates it.
|
|
29
|
+
* @throws When the path's parent doesn't exist
|
|
30
|
+
*/
|
|
31
|
+
export function WritePath(snapshot, root, path, value) {
|
|
32
|
+
const [route, property] = RouteAndProperty(path);
|
|
33
|
+
const state = RouteState(root, route, true);
|
|
34
|
+
if (property === "")
|
|
35
|
+
return;
|
|
36
|
+
const parts = property.split(PROP_DELIMETER);
|
|
37
|
+
const parent = ReadPathParts(state, parts, parts.length - 1);
|
|
38
|
+
if (!parent)
|
|
39
|
+
throw new Error("Unable to write value to path: " + path);
|
|
40
|
+
Assign(snapshot, parent, parts[parts.length - 1], value);
|
|
41
|
+
}
|
|
42
|
+
/** The value at a path, or undefined when any segment of it doesn't exist; see ReadPathFound */
|
|
43
|
+
export function ReadPath(root, path) {
|
|
44
|
+
return ReadPathFound(root, path)[0];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The value at a path, and whether its parent was found: a property missing from an existing
|
|
48
|
+
* parent is found as undefined.
|
|
49
|
+
*/
|
|
50
|
+
export function ReadPathFound(root, path) {
|
|
51
|
+
const [route, property] = RouteAndProperty(path);
|
|
52
|
+
const state = RouteState(root, route);
|
|
53
|
+
if (property === "")
|
|
54
|
+
return [state, !!state];
|
|
55
|
+
const parts = property.split(PROP_DELIMETER);
|
|
56
|
+
const parent = ReadPathParts(state, parts, parts.length - 1);
|
|
57
|
+
return [parent?.[parts[parts.length - 1]], !!parent];
|
|
58
|
+
}
|
|
59
|
+
/** The value at the first `depth` parts of a property path */
|
|
60
|
+
export function ReadPathParts(value, parts, depth) {
|
|
61
|
+
let curr = value;
|
|
62
|
+
for (let x = 0; curr !== undefined && curr !== null && x < depth; x++)
|
|
63
|
+
curr = curr[parts[x]];
|
|
64
|
+
return curr;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The snapshot of the value at a path, whether it was found, and the route it was read through,
|
|
68
|
+
* when there is one. A property path is read from the snapshot of its first segment alone, not of
|
|
69
|
+
* the whole route, so a write to the route's other keys neither rebuilds what it reads nor changes it.
|
|
70
|
+
*/
|
|
71
|
+
export function ReadSnapshot(snapshot, root, path) {
|
|
72
|
+
const [route, property] = SplitRouteAndPath(path);
|
|
73
|
+
const routeState = RouteState(root, route);
|
|
74
|
+
if (!routeState)
|
|
75
|
+
return [undefined, false, undefined];
|
|
76
|
+
if (property === "") {
|
|
77
|
+
const routeSnapshot = Snapshot.Get(snapshot, routeState);
|
|
78
|
+
return [routeSnapshot, !!routeSnapshot, { route, state: routeState, snapshot: routeSnapshot }];
|
|
79
|
+
}
|
|
80
|
+
const dot = property.indexOf(PROP_DELIMETER);
|
|
81
|
+
const key = dot < 0 ? property : property.slice(0, dot);
|
|
82
|
+
const keySnapshot = Snapshot.Child(snapshot, routeState, key);
|
|
83
|
+
const source = { route, state: routeState, key, snapshot: keySnapshot };
|
|
84
|
+
if (dot < 0)
|
|
85
|
+
return [keySnapshot, true, source];
|
|
86
|
+
const [value, valueFound] = ReadPathFound(keySnapshot, property.slice(dot + 1));
|
|
87
|
+
return [value, valueFound, source];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Whether a key's value still has the snapshot taken of it, told by a cache lookup without
|
|
91
|
+
* taking a new one. A primitive value has no snapshot of its own and is compared as it is. A
|
|
92
|
+
* value with no cached snapshot was written since, even where the snapshot was undefined.
|
|
93
|
+
*/
|
|
94
|
+
export function KeyUnchanged(snapshot, state, key, keySnapshot) {
|
|
95
|
+
const value = state[key];
|
|
96
|
+
if (JsonType(value) === "value")
|
|
97
|
+
return value === keySnapshot;
|
|
98
|
+
const cached = Snapshot.Peek(snapshot, value);
|
|
99
|
+
return cached !== undefined && cached === keySnapshot;
|
|
100
|
+
}
|
|
101
|
+
// Removing or replacing a value changes its parent, not the value: its snapshot stays valid. That
|
|
102
|
+
// matters for a slot, whose value is a home still held, and read, elsewhere.
|
|
103
|
+
export function Remove(snapshot, parent, prop) {
|
|
104
|
+
delete parent[prop];
|
|
105
|
+
Snapshot.Invalidate(snapshot, parent);
|
|
106
|
+
}
|
|
107
|
+
export function Assign(snapshot, parent, prop, value) {
|
|
108
|
+
parent[prop] = value;
|
|
109
|
+
Snapshot.Invalidate(snapshot, parent);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Deletes a route's state, child routes included, from its parent route. Every cached snapshot
|
|
113
|
+
* that embedded a value from it, such as one reading a keyed value through its home in the
|
|
114
|
+
* route, is invalidated.
|
|
115
|
+
* @param route A non-root route path, e.g. `/channel/1/`
|
|
116
|
+
*/
|
|
117
|
+
export function DeleteRouteState(snapshot, root, route) {
|
|
118
|
+
const parentRoute = route.slice(0, route.lastIndexOf(ROUTE_DELIMETER, route.length - 2) + 1);
|
|
119
|
+
const segment = route.slice(parentRoute.length, -1);
|
|
120
|
+
const routeStates = RouteState(root, parentRoute)?.[ROUTE_STATE];
|
|
121
|
+
if (!routeStates || !Object.hasOwn(routeStates, segment))
|
|
122
|
+
return;
|
|
123
|
+
InvalidateState(snapshot, routeStates[segment]);
|
|
124
|
+
delete routeStates[segment];
|
|
125
|
+
}
|
|
126
|
+
function InvalidateState(snapshot, value) {
|
|
127
|
+
if (JsonType(value) === "value")
|
|
128
|
+
return;
|
|
129
|
+
Snapshot.Invalidate(snapshot, value);
|
|
130
|
+
const keys = Object.keys(value);
|
|
131
|
+
for (let x = 0; x < keys.length; x++)
|
|
132
|
+
InvalidateState(snapshot, value[keys[x]]);
|
|
133
|
+
const routeStates = value[ROUTE_STATE];
|
|
134
|
+
for (const segment in routeStates)
|
|
135
|
+
InvalidateState(snapshot, routeStates[segment]);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Expands a wildcard path's route against the route state under `root`.
|
|
139
|
+
* @returns Each matching path, keyed by the segment the wildcard matched
|
|
140
|
+
*/
|
|
141
|
+
export function ExpandWildcardPath(root, path) {
|
|
142
|
+
const [route, property] = SplitRouteAndPath(path);
|
|
143
|
+
const parts = route.split(ROUTE_DELIMETER);
|
|
144
|
+
const index = parts.findIndex(part => part.endsWith(WILDCARD));
|
|
145
|
+
const prefix = parts[index].slice(0, -1);
|
|
146
|
+
const parent = RouteState(root, parts.slice(0, index).join(ROUTE_DELIMETER) + ROUTE_DELIMETER);
|
|
147
|
+
const children = parent?.[ROUTE_STATE] ?? {};
|
|
148
|
+
const paths = {};
|
|
149
|
+
for (const segment of Object.keys(children))
|
|
150
|
+
if (segment.startsWith(prefix)) {
|
|
151
|
+
parts[index] = segment;
|
|
152
|
+
paths[segment] = parts.join(ROUTE_DELIMETER) + property;
|
|
153
|
+
}
|
|
154
|
+
return paths;
|
|
155
|
+
}
|
package/Store/Store/store.d.ts
CHANGED
|
@@ -33,6 +33,12 @@ export declare abstract class ReadStore {
|
|
|
33
33
|
*/
|
|
34
34
|
Get<O>(id: string, defaultValue: O): O;
|
|
35
35
|
Has(id: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Whether the store holds the root a path is in: its key, before the first `.`. Push, Splice
|
|
38
|
+
* and Patch take a path into a root, e.g. `rows.t_1`.
|
|
39
|
+
* @param path The path, e.g. `user.items`.
|
|
40
|
+
*/
|
|
41
|
+
protected HasRootOf(path: string): boolean;
|
|
36
42
|
/**
|
|
37
43
|
* Updates the root map with diff results by grouping changes by root path.
|
|
38
44
|
* @param results - Array of diff results to apply
|
|
@@ -62,3 +68,5 @@ export declare abstract class Store extends ReadStore {
|
|
|
62
68
|
abstract Push(key: string, ...data: unknown[]): Promise<void> | void;
|
|
63
69
|
abstract Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<void> | void;
|
|
64
70
|
}
|
|
71
|
+
/** The root key a path is in: its text before the first `.` */
|
|
72
|
+
export declare function RootOf(path: string): string;
|
package/Store/Store/store.js
CHANGED
|
@@ -47,6 +47,14 @@ export class ReadStore {
|
|
|
47
47
|
Has(id) {
|
|
48
48
|
return this.rootMap.has(id);
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether the store holds the root a path is in: its key, before the first `.`. Push, Splice
|
|
52
|
+
* and Patch take a path into a root, e.g. `rows.t_1`.
|
|
53
|
+
* @param path The path, e.g. `user.items`.
|
|
54
|
+
*/
|
|
55
|
+
HasRootOf(path) {
|
|
56
|
+
return this.Has(RootOf(path));
|
|
57
|
+
}
|
|
50
58
|
/**
|
|
51
59
|
* Updates the root map with diff results by grouping changes by root path.
|
|
52
60
|
* @param results - Array of diff results to apply
|
|
@@ -96,3 +104,8 @@ export class ReadStore {
|
|
|
96
104
|
*/
|
|
97
105
|
export class Store extends ReadStore {
|
|
98
106
|
}
|
|
107
|
+
/** The root key a path is in: its text before the first `.` */
|
|
108
|
+
export function RootOf(path) {
|
|
109
|
+
const dot = path.indexOf(".");
|
|
110
|
+
return dot < 0 ? path : path.slice(0, dot);
|
|
111
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncQueue } from "../../Utils/asyncQueue";
|
|
2
2
|
import { WorkerQueue } from "../Diff/workerQueue";
|
|
3
|
-
import { Store } from "./store";
|
|
3
|
+
import { RootOf, Store } from "./store";
|
|
4
4
|
/**
|
|
5
5
|
* StoreAsync class extends the base Store class to provide asynchronous data management operations.
|
|
6
6
|
* This class handles writing, patching, pushing, and splicing data in an asynchronous manner.
|
|
@@ -82,12 +82,13 @@ export class StoreAsync extends Store {
|
|
|
82
82
|
key = key || this.keyFunc?.(data);
|
|
83
83
|
if (!key)
|
|
84
84
|
throw new Error("No key provided for data");
|
|
85
|
-
|
|
85
|
+
// Pending writes by root, so a change to a path in a root being written isn't refused
|
|
86
|
+
this.writeKeySet.add(RootOf(key));
|
|
86
87
|
await this.Schedule({
|
|
87
88
|
method: "writepath",
|
|
88
89
|
arguments: [key, data]
|
|
89
90
|
});
|
|
90
|
-
this.writeKeySet.delete(key);
|
|
91
|
+
this.writeKeySet.delete(RootOf(key));
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
93
94
|
* Patches an existing value in the store with new data asynchronously.
|
|
@@ -100,7 +101,7 @@ export class StoreAsync extends Store {
|
|
|
100
101
|
* the original error if an earlier change failed.
|
|
101
102
|
*/
|
|
102
103
|
async Patch(key, patch) {
|
|
103
|
-
if (!this.
|
|
104
|
+
if (!this.HasRootOf(key) && !this.writeKeySet.has(RootOf(key)))
|
|
104
105
|
throw "Key not found in store";
|
|
105
106
|
await this.Schedule({
|
|
106
107
|
method: "patchpath",
|
|
@@ -116,7 +117,7 @@ export class StoreAsync extends Store {
|
|
|
116
117
|
* the original error if an earlier change failed.
|
|
117
118
|
*/
|
|
118
119
|
async Push(key, ...data) {
|
|
119
|
-
if (!this.
|
|
120
|
+
if (!this.HasRootOf(key) && !this.writeKeySet.has(RootOf(key)))
|
|
120
121
|
throw "Key not found in store";
|
|
121
122
|
await this.Schedule({
|
|
122
123
|
method: "pushpath",
|
|
@@ -135,7 +136,7 @@ export class StoreAsync extends Store {
|
|
|
135
136
|
* the original error if an earlier change failed.
|
|
136
137
|
*/
|
|
137
138
|
async Splice(key, start, deleteCount, ...items) {
|
|
138
|
-
if (!this.
|
|
139
|
+
if (!this.HasRootOf(key) && !this.writeKeySet.has(RootOf(key)))
|
|
139
140
|
throw "Key not found in store";
|
|
140
141
|
await this.Schedule({
|
|
141
142
|
method: "splicepath",
|
package/Store/Store/storeSync.js
CHANGED
|
@@ -60,7 +60,7 @@ export class StoreSync extends Store {
|
|
|
60
60
|
* @throws Will throw an error if the value to be patched is undefined.
|
|
61
61
|
*/
|
|
62
62
|
Patch(key, patch) {
|
|
63
|
-
if (!this.
|
|
63
|
+
if (!this.HasRootOf(key))
|
|
64
64
|
throw "Key not found in store";
|
|
65
65
|
this.diff.PatchPath(key, patch);
|
|
66
66
|
this.Sync();
|
|
@@ -71,7 +71,7 @@ export class StoreSync extends Store {
|
|
|
71
71
|
* @param data The data items to be pushed into the array.
|
|
72
72
|
*/
|
|
73
73
|
Push(key, ...data) {
|
|
74
|
-
if (!this.
|
|
74
|
+
if (!this.HasRootOf(key))
|
|
75
75
|
throw "Key not found in store";
|
|
76
76
|
this.diff.PushPath(key, data);
|
|
77
77
|
this.Sync();
|
|
@@ -85,7 +85,7 @@ export class StoreSync extends Store {
|
|
|
85
85
|
* @param items Optional elements to insert into the array.
|
|
86
86
|
*/
|
|
87
87
|
Splice(key, start, deleteCount, ...items) {
|
|
88
|
-
if (!this.
|
|
88
|
+
if (!this.HasRootOf(key))
|
|
89
89
|
throw "Key not found in store";
|
|
90
90
|
this.diff.SplicePath(key, start, deleteCount ?? Infinity, items);
|
|
91
91
|
this.Sync();
|
|
@@ -109,9 +109,7 @@ function GetPropertyScope(object, prop) {
|
|
|
109
109
|
return object[prop];
|
|
110
110
|
}
|
|
111
111
|
function SetObjectValue(object, prop, value) {
|
|
112
|
-
|
|
113
|
-
object[NODE_VALUE][prop] = value;
|
|
114
|
-
NodeValueSet(object[NODE_VALUE], prop, !propExists, object);
|
|
112
|
+
AssignNodeValue(object[NODE_VALUE], prop, value, object);
|
|
115
113
|
}
|
|
116
114
|
function SetArrayValue(object, prop, value) {
|
|
117
115
|
object[NODE_VALUE][prop] = value;
|
|
@@ -121,25 +119,33 @@ function NodeValueSet(obj, prop, added, wrapper = wrapperCache.get(obj)) {
|
|
|
121
119
|
InvalidateSnapshotForValue(obj);
|
|
122
120
|
if (!wrapper)
|
|
123
121
|
return;
|
|
124
|
-
|
|
122
|
+
// An array's wrapper is an array with no property scopes: its own `length` isn't one, and a
|
|
123
|
+
// diff that only shrinks an array writes nothing but `length`
|
|
124
|
+
const propScopeExists = !!prop && !wrapper[ARRAY_SCOPE] && Object.hasOwn(wrapper, prop);
|
|
125
125
|
if (!prop || added || !propScopeExists)
|
|
126
126
|
ObservableScope.Update(wrapper[ARRAY_SCOPE] ?? wrapper[OBJECT_SCOPE]);
|
|
127
127
|
if (propScopeExists)
|
|
128
128
|
ObservableScope.Update(wrapper[prop]);
|
|
129
129
|
}
|
|
130
|
-
|
|
130
|
+
/** A deleted property: the object's keys changed, so its scope updates, and so does the property's. */
|
|
131
|
+
function RemoveNodeValue(obj, prop) {
|
|
132
|
+
delete obj[prop];
|
|
133
|
+
NodeValueSet(obj, prop, true);
|
|
134
|
+
}
|
|
135
|
+
/** An array spliced in place: its elements changed, so its scope updates. Moved elements keep their nodes. */
|
|
136
|
+
function SplicedNodeValue(array) {
|
|
137
|
+
NodeValueSet(array, "", false);
|
|
138
|
+
}
|
|
139
|
+
/** A property assigned: the object's scope updates when it's new, and the property's does */
|
|
140
|
+
function AssignNodeValue(obj, prop, value, wrapper) {
|
|
131
141
|
const added = !Object.hasOwn(obj, prop);
|
|
132
142
|
obj[prop] = value;
|
|
133
|
-
NodeValueSet(obj, prop, added);
|
|
143
|
+
NodeValueSet(obj, prop, added, wrapper);
|
|
134
144
|
}
|
|
135
145
|
function CreateProxyFactory(alias) {
|
|
136
146
|
// Keyed children are read through their root. Every factory shares the module-level
|
|
137
147
|
// snapshotState, matching wrapperCache; only the resolver differs per factory.
|
|
138
|
-
|
|
139
|
-
const resolveSnapshot = alias && function (child, path) {
|
|
140
|
-
const value = alias(child, false);
|
|
141
|
-
return value === undefined ? undefined : { value, path: path.slice() };
|
|
142
|
-
};
|
|
148
|
+
const resolveSnapshot = alias && (child => alias(child, false));
|
|
143
149
|
function CreateSnapshot(value) {
|
|
144
150
|
return Snapshot.Get(snapshotState, value, resolveSnapshot);
|
|
145
151
|
}
|
|
@@ -410,10 +416,7 @@ export var ObservableNode;
|
|
|
410
416
|
*/
|
|
411
417
|
function ApplyDiff(rootNode, diffResult) {
|
|
412
418
|
const root = rootNode[NODE_VALUE];
|
|
413
|
-
JsonApplyDiff(root, diffResult, AssignNodeValue);
|
|
414
|
-
// A root replacement is applied in place without calling AssignNodeValue
|
|
415
|
-
if (diffResult.length === 1 && diffResult[0].path.length === 0)
|
|
416
|
-
NodeValueSet(root, "", false, rootNode[NODE_WRAPPER]);
|
|
419
|
+
JsonApplyDiff(root, diffResult, AssignNodeValue, RemoveNodeValue, SplicedNodeValue);
|
|
417
420
|
}
|
|
418
421
|
ObservableNode.ApplyDiff = ApplyDiff;
|
|
419
422
|
/**
|
package/Store/Tree/snapshot.d.ts
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Resolves a keyed child to the root entity it should be read through. Returns the entity, or
|
|
3
|
+
* undefined when the child isn't keyed (or has no root), in which case the child itself is used.
|
|
4
4
|
*/
|
|
5
|
-
export type
|
|
6
|
-
/**
|
|
7
|
-
* Resolves a keyed child to the root entity it should be read through, given the child's
|
|
8
|
-
* path. Returns the entity and its path, or undefined when the child isn't keyed (or has no
|
|
9
|
-
* root), in which case the child itself is used. `path` is the walk's own stack, so a
|
|
10
|
-
* resolver must not keep it.
|
|
11
|
-
*/
|
|
12
|
-
export type SnapshotResolver = (child: any, path: Readonly<SnapshotPath>) => {
|
|
13
|
-
value: object;
|
|
14
|
-
path: SnapshotPath;
|
|
15
|
-
} | undefined;
|
|
5
|
+
export type SnapshotResolver = (child: any) => object | undefined;
|
|
16
6
|
/**
|
|
17
7
|
* Snapshot cache state. Each owner (a `DiffTree` instance, or `ObservableNode` at module
|
|
18
8
|
* level) creates its own state, so cached snapshots are never shared between owners.
|
|
@@ -22,28 +12,36 @@ export interface ISnapshotState {
|
|
|
22
12
|
rawToSnapshotCache: WeakMap<object, any>;
|
|
23
13
|
/** Raw value -> every parent whose cached snapshot embedded it */
|
|
24
14
|
hierarchy: WeakMap<object, Set<object>>;
|
|
25
|
-
/** Default resolver for keyed children; a call to `Snapshot.Get` may override it */
|
|
26
|
-
resolve: SnapshotResolver | undefined;
|
|
27
15
|
}
|
|
28
16
|
export declare namespace Snapshot {
|
|
29
17
|
/**
|
|
30
18
|
* Creates a new, empty snapshot cache.
|
|
31
|
-
* @param resolve Optional default resolver for keyed children.
|
|
32
19
|
* @returns A new snapshot state.
|
|
33
20
|
*/
|
|
34
|
-
function Create(
|
|
21
|
+
function Create(): ISnapshotState;
|
|
35
22
|
/**
|
|
36
23
|
* Returns an immutable, cached snapshot of a raw value. The same snapshot is returned
|
|
37
24
|
* until a write invalidates it, and unchanged nested parts are shared between snapshots,
|
|
38
25
|
* so `===` holds for any part that hasn't changed.
|
|
39
26
|
* @param state The snapshot cache to read from and populate.
|
|
40
27
|
* @param value The raw value to snapshot.
|
|
41
|
-
* @param resolve Resolver for keyed children
|
|
42
|
-
*
|
|
43
|
-
* always be seeded with the same path or a resolver may see a different one.
|
|
28
|
+
* @param resolve Resolver for keyed children. Snapshots are cached per raw value, so a value
|
|
29
|
+
* must always be read with the same resolver.
|
|
44
30
|
* @returns The snapshot.
|
|
45
31
|
*/
|
|
46
|
-
function Get<T>(state: ISnapshotState, value: T, resolve?: SnapshotResolver
|
|
32
|
+
function Get<T>(state: ISnapshotState, value: T, resolve?: SnapshotResolver): T;
|
|
33
|
+
/**
|
|
34
|
+
* The snapshot of one property of a raw value, as the value's own snapshot would hold it:
|
|
35
|
+
* cached, keyed children read through their root, and linked to the value so a write to the
|
|
36
|
+
* property invalidates the value's snapshot. Reads one property without building a snapshot of
|
|
37
|
+
* the whole value.
|
|
38
|
+
* @param state The snapshot cache to read from and populate.
|
|
39
|
+
* @param parent The raw value the property is on.
|
|
40
|
+
* @param key The property.
|
|
41
|
+
* @param resolve Resolver for keyed children.
|
|
42
|
+
* @returns The property's snapshot.
|
|
43
|
+
*/
|
|
44
|
+
function Child(state: ISnapshotState, parent: any, key: string | number, resolve?: SnapshotResolver): any;
|
|
47
45
|
/**
|
|
48
46
|
* Invalidates the cached snapshot of a raw value that was just written to, along with
|
|
49
47
|
* every cached ancestor that embedded it.
|
package/Store/Tree/snapshot.js
CHANGED
|
@@ -27,7 +27,7 @@ function InvalidateValue(state, value) {
|
|
|
27
27
|
* unchanged children, so a snapshot handed out never changes and unchanged parts are
|
|
28
28
|
* shared between snapshots. Keyed children are read through their root.
|
|
29
29
|
*/
|
|
30
|
-
function CreateSnapshot(state, resolve, value
|
|
30
|
+
function CreateSnapshot(state, resolve, value) {
|
|
31
31
|
if (JsonType(value) === "value")
|
|
32
32
|
return value;
|
|
33
33
|
const cached = state.rawToSnapshotCache.get(value);
|
|
@@ -37,13 +37,13 @@ function CreateSnapshot(state, resolve, value, path) {
|
|
|
37
37
|
if (Array.isArray(value)) {
|
|
38
38
|
snapshot = new Array(value.length);
|
|
39
39
|
for (let x = 0; x < value.length; x++)
|
|
40
|
-
snapshot[x] = ChildSnapshot(state, resolve, value, x
|
|
40
|
+
snapshot[x] = ChildSnapshot(state, resolve, value, x);
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
43
43
|
snapshot = {};
|
|
44
44
|
const keys = Object.keys(value);
|
|
45
45
|
for (let x = 0; x < keys.length; x++)
|
|
46
|
-
snapshot[keys[x]] = ChildSnapshot(state, resolve, value, keys[x]
|
|
46
|
+
snapshot[keys[x]] = ChildSnapshot(state, resolve, value, keys[x]);
|
|
47
47
|
}
|
|
48
48
|
state.rawToSnapshotCache.set(value, snapshot);
|
|
49
49
|
return snapshot;
|
|
@@ -54,23 +54,17 @@ function CreateSnapshot(state, resolve, value, path) {
|
|
|
54
54
|
* linked too; the parent's own embedded copy stays linked because a positional write
|
|
55
55
|
* (one that changes which entity a slot holds) mutates that copy in place.
|
|
56
56
|
*/
|
|
57
|
-
function ChildSnapshot(state, resolve, parent, key
|
|
57
|
+
function ChildSnapshot(state, resolve, parent, key) {
|
|
58
58
|
const child = parent[key];
|
|
59
59
|
if (JsonType(child) === "value")
|
|
60
60
|
return child;
|
|
61
61
|
CreateSnapshotLink(state, child, parent);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
CreateSnapshotLink(state, resolved.value, parent);
|
|
67
|
-
return CreateSnapshot(state, resolve, resolved.value, resolved.path);
|
|
68
|
-
}
|
|
69
|
-
return CreateSnapshot(state, resolve, child, path);
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
72
|
-
path.pop();
|
|
62
|
+
const resolved = resolve?.(child);
|
|
63
|
+
if (resolved !== undefined && resolved !== child) {
|
|
64
|
+
CreateSnapshotLink(state, resolved, parent);
|
|
65
|
+
return CreateSnapshot(state, resolve, resolved);
|
|
73
66
|
}
|
|
67
|
+
return CreateSnapshot(state, resolve, child);
|
|
74
68
|
}
|
|
75
69
|
function RegisterSnapshot(state, snapshot) {
|
|
76
70
|
if (JsonType(snapshot) !== 'value')
|
|
@@ -80,14 +74,12 @@ export var Snapshot;
|
|
|
80
74
|
(function (Snapshot) {
|
|
81
75
|
/**
|
|
82
76
|
* Creates a new, empty snapshot cache.
|
|
83
|
-
* @param resolve Optional default resolver for keyed children.
|
|
84
77
|
* @returns A new snapshot state.
|
|
85
78
|
*/
|
|
86
|
-
function Create(
|
|
79
|
+
function Create() {
|
|
87
80
|
return {
|
|
88
81
|
rawToSnapshotCache: new WeakMap(),
|
|
89
82
|
hierarchy: new WeakMap(),
|
|
90
|
-
resolve,
|
|
91
83
|
};
|
|
92
84
|
}
|
|
93
85
|
Snapshot.Create = Create;
|
|
@@ -97,15 +89,29 @@ export var Snapshot;
|
|
|
97
89
|
* so `===` holds for any part that hasn't changed.
|
|
98
90
|
* @param state The snapshot cache to read from and populate.
|
|
99
91
|
* @param value The raw value to snapshot.
|
|
100
|
-
* @param resolve Resolver for keyed children
|
|
101
|
-
*
|
|
102
|
-
* always be seeded with the same path or a resolver may see a different one.
|
|
92
|
+
* @param resolve Resolver for keyed children. Snapshots are cached per raw value, so a value
|
|
93
|
+
* must always be read with the same resolver.
|
|
103
94
|
* @returns The snapshot.
|
|
104
95
|
*/
|
|
105
|
-
function Get(state, value, resolve
|
|
106
|
-
return CreateSnapshot(state, resolve, value
|
|
96
|
+
function Get(state, value, resolve) {
|
|
97
|
+
return CreateSnapshot(state, resolve, value);
|
|
107
98
|
}
|
|
108
99
|
Snapshot.Get = Get;
|
|
100
|
+
/**
|
|
101
|
+
* The snapshot of one property of a raw value, as the value's own snapshot would hold it:
|
|
102
|
+
* cached, keyed children read through their root, and linked to the value so a write to the
|
|
103
|
+
* property invalidates the value's snapshot. Reads one property without building a snapshot of
|
|
104
|
+
* the whole value.
|
|
105
|
+
* @param state The snapshot cache to read from and populate.
|
|
106
|
+
* @param parent The raw value the property is on.
|
|
107
|
+
* @param key The property.
|
|
108
|
+
* @param resolve Resolver for keyed children.
|
|
109
|
+
* @returns The property's snapshot.
|
|
110
|
+
*/
|
|
111
|
+
function Child(state, parent, key, resolve) {
|
|
112
|
+
return ChildSnapshot(state, resolve, parent, key);
|
|
113
|
+
}
|
|
114
|
+
Snapshot.Child = Child;
|
|
109
115
|
/**
|
|
110
116
|
* Invalidates the cached snapshot of a raw value that was just written to, along with
|
|
111
117
|
* every cached ancestor that embedded it.
|