j-templates 8.1.2 → 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/SYNTAX_PRIMER.md +3 -4
- package/Store/Diff/diffTree.d.ts +39 -33
- package/Store/Diff/diffTree.js +147 -435
- package/Store/Diff/diffTree.types.d.ts +5 -79
- package/Store/Diff/diffTree.types.js +1 -3
- package/Store/Diff/diffTreeWorker.d.ts +4 -0
- package/Store/Diff/diffTreeWorker.js +17 -39
- package/Store/Diff/diffTreeWriter.d.ts +27 -38
- package/Store/Diff/diffTreeWriter.js +44 -95
- package/Store/Diff/index.d.ts +2 -0
- package/Store/Diff/index.js +2 -0
- package/Store/Diff/utils/diffTree.types.d.ts +113 -0
- package/Store/Diff/utils/diffTree.types.js +1 -0
- 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/pushStore.d.ts +1 -5
- package/Store/Store/pushStore.js +3 -22
- package/Store/Store/store.d.ts +10 -4
- package/Store/Store/store.js +25 -11
- package/Store/Store/storeAsync.d.ts +4 -5
- package/Store/Store/storeAsync.js +19 -36
- package/Store/Store/storeSync.d.ts +14 -9
- package/Store/Store/storeSync.js +27 -22
- package/Store/Tree/observableNode.d.ts +2 -23
- package/Store/Tree/observableNode.js +46 -183
- package/Store/Tree/snapshot.d.ts +68 -0
- package/Store/Tree/snapshot.js +147 -0
- package/Utils/json.d.ts +180 -10
- package/Utils/json.js +532 -67
- package/package.json +1 -1
- package/Store/Diff/diffSync.d.ts +0 -36
- package/Store/Diff/diffSync.js +0 -43
|
@@ -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
|
+
}
|
|
@@ -43,12 +43,8 @@ export declare class PushStore extends ReadStore {
|
|
|
43
43
|
* @param keyFunc Optional function to generate a key for a given data value, used to
|
|
44
44
|
* resolve root objects. Pass the same key logic the `DiffTreeWriter` was constructed
|
|
45
45
|
* with, if any.
|
|
46
|
-
* @param cloneData Whether to deep-clone the values the store adopts (false by default).
|
|
47
|
-
* A `Worker` or `MessagePort` already delivers a structured-clone copy of every message.
|
|
48
|
-
* A same-thread `EventTarget` delivers the writer's own objects, so either the writer
|
|
49
|
-
* posts copies or this is true; otherwise the store and the writer's tree share objects.
|
|
50
46
|
*/
|
|
51
|
-
constructor(eventTarget: EventTarget, keyFunc?: (value: any) => string | undefined
|
|
47
|
+
constructor(eventTarget: EventTarget, keyFunc?: (value: any) => string | undefined);
|
|
52
48
|
/**
|
|
53
49
|
* Stops listening to the source and destroys the error scope. The source stays open (a
|
|
54
50
|
* worker keeps running); closing or terminating it is up to its owner.
|
package/Store/Store/pushStore.js
CHANGED
|
@@ -43,35 +43,16 @@ export class PushStore extends ReadStore {
|
|
|
43
43
|
* @param keyFunc Optional function to generate a key for a given data value, used to
|
|
44
44
|
* resolve root objects. Pass the same key logic the `DiffTreeWriter` was constructed
|
|
45
45
|
* with, if any.
|
|
46
|
-
* @param cloneData Whether to deep-clone the values the store adopts (false by default).
|
|
47
|
-
* A `Worker` or `MessagePort` already delivers a structured-clone copy of every message.
|
|
48
|
-
* A same-thread `EventTarget` delivers the writer's own objects, so either the writer
|
|
49
|
-
* posts copies or this is true; otherwise the store and the writer's tree share objects.
|
|
50
46
|
*/
|
|
51
|
-
constructor(eventTarget, keyFunc
|
|
52
|
-
super(keyFunc
|
|
47
|
+
constructor(eventTarget, keyFunc) {
|
|
48
|
+
super(keyFunc);
|
|
53
49
|
this.eventTarget = eventTarget;
|
|
54
50
|
this.error = null;
|
|
55
51
|
this.errorScope = ObservableScope.Basic(() => this.error);
|
|
56
52
|
this.messageHandler = (event) => {
|
|
57
53
|
const messageData = event.data;
|
|
58
54
|
if (messageData && messageData.type === DIFF_TREE_EVENT) {
|
|
59
|
-
|
|
60
|
-
for (let x = 0; x < data.length; x++) {
|
|
61
|
-
const result = data[x];
|
|
62
|
-
switch (result.type) {
|
|
63
|
-
case "diff": {
|
|
64
|
-
this.UpdateRootMap(result.result);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
case "splice": {
|
|
68
|
-
const spliceResult = result.result;
|
|
69
|
-
this.UpdateRootMap(spliceResult.diffResult);
|
|
70
|
-
this.SpliceRootObject(spliceResult.path[0], spliceResult.start, spliceResult.deleteCount, spliceResult.items);
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
55
|
+
this.UpdateRootMap(messageData.data);
|
|
75
56
|
if (this.error !== null) {
|
|
76
57
|
this.error = null;
|
|
77
58
|
ObservableScope.Update(this.errorScope);
|
package/Store/Store/store.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { JsonDiffResult } from "../../Utils/json";
|
|
|
9
9
|
*/
|
|
10
10
|
export declare abstract class ReadStore {
|
|
11
11
|
protected keyFunc?: (value: any) => string | undefined;
|
|
12
|
-
private cloneData;
|
|
13
12
|
private rootMap;
|
|
14
13
|
private createNode;
|
|
15
14
|
/**
|
|
@@ -17,7 +16,7 @@ export declare abstract class ReadStore {
|
|
|
17
16
|
* @param keyFunc Optional function to generate a key for a given data value.
|
|
18
17
|
* When provided, enables alias functionality where values can reference other root objects.
|
|
19
18
|
*/
|
|
20
|
-
constructor(keyFunc?: (value: any) => string | undefined
|
|
19
|
+
constructor(keyFunc?: (value: any) => string | undefined);
|
|
21
20
|
/**
|
|
22
21
|
* Retrieves a value from the store by id.
|
|
23
22
|
* @template O - The type of the value to retrieve
|
|
@@ -34,13 +33,18 @@ export declare abstract class ReadStore {
|
|
|
34
33
|
*/
|
|
35
34
|
Get<O>(id: string, defaultValue: O): O;
|
|
36
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;
|
|
37
42
|
/**
|
|
38
43
|
* Updates the root map with diff results by grouping changes by root path.
|
|
39
44
|
* @param results - Array of diff results to apply
|
|
40
45
|
* @protected
|
|
41
46
|
*/
|
|
42
47
|
protected UpdateRootMap(results: JsonDiffResult): void;
|
|
43
|
-
protected SpliceRootObject(rootPath: string | number, start: number, deleteCount: number, items: any[]): void;
|
|
44
48
|
/**
|
|
45
49
|
* Updates a specific root object with diff results.
|
|
46
50
|
* Creates the root object if it doesn't exist.
|
|
@@ -62,5 +66,7 @@ export declare abstract class Store extends ReadStore {
|
|
|
62
66
|
abstract Write(data: unknown, key?: string): Promise<void> | void;
|
|
63
67
|
abstract Patch(key: string, patch: unknown): Promise<void> | void;
|
|
64
68
|
abstract Push(key: string, ...data: unknown[]): Promise<void> | void;
|
|
65
|
-
abstract Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<
|
|
69
|
+
abstract Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<void> | void;
|
|
66
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
|
@@ -14,14 +14,15 @@ export class ReadStore {
|
|
|
14
14
|
* @param keyFunc Optional function to generate a key for a given data value.
|
|
15
15
|
* When provided, enables alias functionality where values can reference other root objects.
|
|
16
16
|
*/
|
|
17
|
-
constructor(keyFunc
|
|
17
|
+
constructor(keyFunc) {
|
|
18
18
|
this.keyFunc = keyFunc;
|
|
19
|
-
this.cloneData = cloneData;
|
|
20
19
|
this.rootMap = new Map();
|
|
21
20
|
const aliasFunc = keyFunc &&
|
|
22
21
|
((value, reactive = true) => {
|
|
23
22
|
const key = keyFunc(value);
|
|
24
|
-
|
|
23
|
+
// A route key names an entity in a DiffTree route instance, which never reaches the
|
|
24
|
+
// store: read the embedded copy
|
|
25
|
+
if (key === undefined || key.startsWith("/"))
|
|
25
26
|
return undefined;
|
|
26
27
|
const rootObject = this.rootMap.get(key);
|
|
27
28
|
if (rootObject === undefined)
|
|
@@ -46,6 +47,14 @@ export class ReadStore {
|
|
|
46
47
|
Has(id) {
|
|
47
48
|
return this.rootMap.has(id);
|
|
48
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
|
+
}
|
|
49
58
|
/**
|
|
50
59
|
* Updates the root map with diff results by grouping changes by root path.
|
|
51
60
|
* @param results - Array of diff results to apply
|
|
@@ -61,10 +70,6 @@ export class ReadStore {
|
|
|
61
70
|
this.UpdateRootObject(rootGroup[0].path[0], rootGroup);
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
|
-
SpliceRootObject(rootPath, start, deleteCount, items) {
|
|
65
|
-
const rootObject = this.rootMap.get(rootPath);
|
|
66
|
-
ObservableNode.ApplySplice(rootObject[rootPath], start, deleteCount, items, this.cloneData);
|
|
67
|
-
}
|
|
68
73
|
/**
|
|
69
74
|
* Updates a specific root object with diff results.
|
|
70
75
|
* Creates the root object if it doesn't exist.
|
|
@@ -76,14 +81,18 @@ export class ReadStore {
|
|
|
76
81
|
UpdateRootObject(rootPath, results) {
|
|
77
82
|
const rootObject = this.rootMap.get(rootPath);
|
|
78
83
|
if (rootObject === undefined) {
|
|
79
|
-
if (results.
|
|
80
|
-
throw `Unable to initialize root path ${rootPath} with
|
|
81
|
-
const rootData =
|
|
84
|
+
if (results[0].type !== "write" || results[0].path.length > 1)
|
|
85
|
+
throw `Unable to initialize root path ${rootPath} with initial path ${results[0].path}`;
|
|
86
|
+
const rootData = JsonDeepClone(results[0].value);
|
|
82
87
|
const newRootObject = this.createNode({ [rootPath]: rootData }, false);
|
|
83
88
|
this.rootMap.set(rootPath, newRootObject);
|
|
89
|
+
// Later entries for the same root (e.g. route summaries written one instance at a time)
|
|
90
|
+
// apply to the root just created
|
|
91
|
+
if (results.length > 1)
|
|
92
|
+
ObservableNode.ApplyDiff(newRootObject, results.slice(1));
|
|
84
93
|
return;
|
|
85
94
|
}
|
|
86
|
-
ObservableNode.ApplyDiff(rootObject, results
|
|
95
|
+
ObservableNode.ApplyDiff(rootObject, results);
|
|
87
96
|
}
|
|
88
97
|
}
|
|
89
98
|
/**
|
|
@@ -95,3 +104,8 @@ export class ReadStore {
|
|
|
95
104
|
*/
|
|
96
105
|
export class Store extends ReadStore {
|
|
97
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
|
+
}
|
|
@@ -24,7 +24,7 @@ import { Store } from "./store";
|
|
|
24
24
|
* await store.Push("users", { id: 1 }, { id: 2 });
|
|
25
25
|
*
|
|
26
26
|
* // Splicing an array asynchronously
|
|
27
|
-
*
|
|
27
|
+
* await store.Splice("users", 0, 1, { id: 3 });
|
|
28
28
|
*
|
|
29
29
|
* // Cleaning up resources
|
|
30
30
|
* store.Destroy();
|
|
@@ -41,7 +41,7 @@ export declare class StoreAsync extends Store {
|
|
|
41
41
|
/**
|
|
42
42
|
* Creates an instance of StoreAsync.
|
|
43
43
|
* @param diffWorker A `Worker` running a `DiffTree` connected via `ConnectWorkerToDiffTree`
|
|
44
|
-
* (e.g. a bundled `defaultDiffTreeWorker` entry). Any `keyFunc`/
|
|
44
|
+
* (e.g. a bundled `defaultDiffTreeWorker` entry). Any `keyFunc`/routes the diff needs
|
|
45
45
|
* must be baked into that worker's own entry file.
|
|
46
46
|
* @param keyFunc Optional function to generate a key for a given data value, used locally by
|
|
47
47
|
* this store to resolve root objects. Pass the same key logic the worker's `DiffTree` was
|
|
@@ -88,12 +88,11 @@ export declare class StoreAsync extends Store {
|
|
|
88
88
|
* @param start The position at which to start changing the array.
|
|
89
89
|
* @param deleteCount Optional number of elements to delete. If not provided, all elements from start to end will be deleted.
|
|
90
90
|
* @param items Optional elements to insert into the array.
|
|
91
|
-
* @returns A promise that resolves
|
|
92
|
-
* applied to the store.
|
|
91
|
+
* @returns A promise that resolves once the splice has been applied to the store.
|
|
93
92
|
* @throws Will throw if the key isn't in the store and has no pending Write, or reject with
|
|
94
93
|
* the original error if an earlier change failed.
|
|
95
94
|
*/
|
|
96
|
-
Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<
|
|
95
|
+
Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<void>;
|
|
97
96
|
/**
|
|
98
97
|
* Returns a promise that resolves once every change made before this call has been applied
|
|
99
98
|
* to the store. Changes made after this call are sent in a later batch, so they aren't
|
|
@@ -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.
|
|
@@ -26,7 +26,7 @@ import { Store } from "./store";
|
|
|
26
26
|
* await store.Push("users", { id: 1 }, { id: 2 });
|
|
27
27
|
*
|
|
28
28
|
* // Splicing an array asynchronously
|
|
29
|
-
*
|
|
29
|
+
* await store.Splice("users", 0, 1, { id: 3 });
|
|
30
30
|
*
|
|
31
31
|
* // Cleaning up resources
|
|
32
32
|
* store.Destroy();
|
|
@@ -38,17 +38,17 @@ export class StoreAsync extends Store {
|
|
|
38
38
|
/**
|
|
39
39
|
* Creates an instance of StoreAsync.
|
|
40
40
|
* @param diffWorker A `Worker` running a `DiffTree` connected via `ConnectWorkerToDiffTree`
|
|
41
|
-
* (e.g. a bundled `defaultDiffTreeWorker` entry). Any `keyFunc`/
|
|
41
|
+
* (e.g. a bundled `defaultDiffTreeWorker` entry). Any `keyFunc`/routes the diff needs
|
|
42
42
|
* must be baked into that worker's own entry file.
|
|
43
43
|
* @param keyFunc Optional function to generate a key for a given data value, used locally by
|
|
44
44
|
* this store to resolve root objects. Pass the same key logic the worker's `DiffTree` was
|
|
45
45
|
* constructed with, if any.
|
|
46
46
|
*/
|
|
47
47
|
constructor(diffWorker, keyFunc) {
|
|
48
|
-
super(keyFunc
|
|
48
|
+
super(keyFunc);
|
|
49
49
|
this.writeKeySet = new Set();
|
|
50
50
|
this.currentBatch = [];
|
|
51
|
-
this.currentBatchPromise = Promise.resolve(
|
|
51
|
+
this.currentBatchPromise = Promise.resolve();
|
|
52
52
|
this.workerQueue = new WorkerQueue(diffWorker);
|
|
53
53
|
this.asyncQueue = new AsyncQueue(true);
|
|
54
54
|
}
|
|
@@ -59,29 +59,13 @@ export class StoreAsync extends Store {
|
|
|
59
59
|
await Promise.resolve();
|
|
60
60
|
if (batch === this.currentBatch)
|
|
61
61
|
this.currentBatch = [];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
switch (result.type) {
|
|
66
|
-
case "diff": {
|
|
67
|
-
this.UpdateRootMap(result.result);
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
case "splice": {
|
|
71
|
-
const spliceResult = result.result;
|
|
72
|
-
this.UpdateRootMap(spliceResult.diffResult);
|
|
73
|
-
this.SpliceRootObject(spliceResult.path[0], spliceResult.start, spliceResult.deleteCount, spliceResult.items);
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return results;
|
|
62
|
+
// The worker's Commit() diff covers every change in the batch
|
|
63
|
+
const diff = await this.workerQueue.Push(batch);
|
|
64
|
+
this.UpdateRootMap(diff);
|
|
79
65
|
});
|
|
80
66
|
}
|
|
81
|
-
const batchIndex = this.currentBatch.length;
|
|
82
67
|
this.currentBatch.push(method);
|
|
83
|
-
|
|
84
|
-
return batchResult[batchIndex].result;
|
|
68
|
+
await this.currentBatchPromise;
|
|
85
69
|
}
|
|
86
70
|
/**
|
|
87
71
|
* Writes data to the store asynchronously.
|
|
@@ -98,12 +82,13 @@ export class StoreAsync extends Store {
|
|
|
98
82
|
key = key || this.keyFunc?.(data);
|
|
99
83
|
if (!key)
|
|
100
84
|
throw new Error("No key provided for data");
|
|
101
|
-
|
|
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));
|
|
102
87
|
await this.Schedule({
|
|
103
|
-
method: "
|
|
88
|
+
method: "writepath",
|
|
104
89
|
arguments: [key, data]
|
|
105
90
|
});
|
|
106
|
-
this.writeKeySet.delete(key);
|
|
91
|
+
this.writeKeySet.delete(RootOf(key));
|
|
107
92
|
}
|
|
108
93
|
/**
|
|
109
94
|
* Patches an existing value in the store with new data asynchronously.
|
|
@@ -116,7 +101,7 @@ export class StoreAsync extends Store {
|
|
|
116
101
|
* the original error if an earlier change failed.
|
|
117
102
|
*/
|
|
118
103
|
async Patch(key, patch) {
|
|
119
|
-
if (!this.
|
|
104
|
+
if (!this.HasRootOf(key) && !this.writeKeySet.has(RootOf(key)))
|
|
120
105
|
throw "Key not found in store";
|
|
121
106
|
await this.Schedule({
|
|
122
107
|
method: "patchpath",
|
|
@@ -132,7 +117,7 @@ export class StoreAsync extends Store {
|
|
|
132
117
|
* the original error if an earlier change failed.
|
|
133
118
|
*/
|
|
134
119
|
async Push(key, ...data) {
|
|
135
|
-
if (!this.
|
|
120
|
+
if (!this.HasRootOf(key) && !this.writeKeySet.has(RootOf(key)))
|
|
136
121
|
throw "Key not found in store";
|
|
137
122
|
await this.Schedule({
|
|
138
123
|
method: "pushpath",
|
|
@@ -146,19 +131,17 @@ export class StoreAsync extends Store {
|
|
|
146
131
|
* @param start The position at which to start changing the array.
|
|
147
132
|
* @param deleteCount Optional number of elements to delete. If not provided, all elements from start to end will be deleted.
|
|
148
133
|
* @param items Optional elements to insert into the array.
|
|
149
|
-
* @returns A promise that resolves
|
|
150
|
-
* applied to the store.
|
|
134
|
+
* @returns A promise that resolves once the splice has been applied to the store.
|
|
151
135
|
* @throws Will throw if the key isn't in the store and has no pending Write, or reject with
|
|
152
136
|
* the original error if an earlier change failed.
|
|
153
137
|
*/
|
|
154
138
|
async Splice(key, start, deleteCount, ...items) {
|
|
155
|
-
if (!this.
|
|
139
|
+
if (!this.HasRootOf(key) && !this.writeKeySet.has(RootOf(key)))
|
|
156
140
|
throw "Key not found in store";
|
|
157
|
-
|
|
141
|
+
await this.Schedule({
|
|
158
142
|
method: "splicepath",
|
|
159
|
-
arguments: [key, start, deleteCount, items]
|
|
143
|
+
arguments: [key, start, deleteCount ?? Infinity, items]
|
|
160
144
|
});
|
|
161
|
-
return spliceResult.spliceResult;
|
|
162
145
|
}
|
|
163
146
|
/**
|
|
164
147
|
* Returns a promise that resolves once every change made before this call has been applied
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IRouteDefinitions } from "../Diff/utils/diffTree.types";
|
|
2
2
|
import { Store } from "./store";
|
|
3
3
|
/**
|
|
4
4
|
* StoreSync class extends the base Store class to provide synchronous data management operations.
|
|
@@ -22,25 +22,26 @@ import { Store } from "./store";
|
|
|
22
22
|
* store.Push("user.array", { id: 1 }, { id: 2 });
|
|
23
23
|
*
|
|
24
24
|
* // Splicing an array
|
|
25
|
-
*
|
|
25
|
+
* store.Splice("user.array", 0, 1, { id: 3 });
|
|
26
26
|
*
|
|
27
27
|
* @see Store
|
|
28
28
|
* @see StoreAsync
|
|
29
|
-
* @see
|
|
29
|
+
* @see DiffTree
|
|
30
30
|
*/
|
|
31
31
|
export declare class StoreSync extends Store {
|
|
32
32
|
/**
|
|
33
|
-
* The
|
|
33
|
+
* The tree each change is applied to. Its `Commit()` diff keeps the store in sync.
|
|
34
34
|
* @private
|
|
35
35
|
*/
|
|
36
36
|
private diff;
|
|
37
37
|
/**
|
|
38
38
|
* Creates an instance of StoreSync.
|
|
39
39
|
* @param keyFunc Optional function to generate a key for a given data value.
|
|
40
|
-
* @param
|
|
41
|
-
*
|
|
40
|
+
* @param routes Optional route definitions. A projection in the root route (`"/"`) is
|
|
41
|
+
* readable through `Get` at `$projection_<key>`.
|
|
42
|
+
* @throws When projections depend on each other in a cycle.
|
|
42
43
|
*/
|
|
43
|
-
constructor(keyFunc?: (value: any) => string | undefined,
|
|
44
|
+
constructor(keyFunc?: (value: any) => string | undefined, routes?: IRouteDefinitions);
|
|
44
45
|
/**
|
|
45
46
|
* Writes data to the store synchronously.
|
|
46
47
|
* @param data The data to be written. Can be of any type.
|
|
@@ -68,7 +69,11 @@ export declare class StoreSync extends Store {
|
|
|
68
69
|
* @param start The position at which to start changing the array.
|
|
69
70
|
* @param deleteCount Optional number of elements to delete. If not provided, all elements from start to end will be deleted.
|
|
70
71
|
* @param items Optional elements to insert into the array.
|
|
71
|
-
* @returns The array of deleted elements.
|
|
72
72
|
*/
|
|
73
|
-
Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]):
|
|
73
|
+
Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): void;
|
|
74
|
+
/**
|
|
75
|
+
* Runs projections and applies everything changed since the last sync to the store.
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
private Sync;
|
|
74
79
|
}
|
package/Store/Store/storeSync.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DiffSync } from "../Diff/diffSync";
|
|
1
|
+
import { DiffTree } from "../Diff/diffTree";
|
|
3
2
|
import { Store } from "./store";
|
|
4
3
|
/**
|
|
5
4
|
* StoreSync class extends the base Store class to provide synchronous data management operations.
|
|
@@ -23,22 +22,23 @@ import { Store } from "./store";
|
|
|
23
22
|
* store.Push("user.array", { id: 1 }, { id: 2 });
|
|
24
23
|
*
|
|
25
24
|
* // Splicing an array
|
|
26
|
-
*
|
|
25
|
+
* store.Splice("user.array", 0, 1, { id: 3 });
|
|
27
26
|
*
|
|
28
27
|
* @see Store
|
|
29
28
|
* @see StoreAsync
|
|
30
|
-
* @see
|
|
29
|
+
* @see DiffTree
|
|
31
30
|
*/
|
|
32
31
|
export class StoreSync extends Store {
|
|
33
32
|
/**
|
|
34
33
|
* Creates an instance of StoreSync.
|
|
35
34
|
* @param keyFunc Optional function to generate a key for a given data value.
|
|
36
|
-
* @param
|
|
37
|
-
*
|
|
35
|
+
* @param routes Optional route definitions. A projection in the root route (`"/"`) is
|
|
36
|
+
* readable through `Get` at `$projection_<key>`.
|
|
37
|
+
* @throws When projections depend on each other in a cycle.
|
|
38
38
|
*/
|
|
39
|
-
constructor(keyFunc,
|
|
39
|
+
constructor(keyFunc, routes) {
|
|
40
40
|
super(keyFunc);
|
|
41
|
-
this.diff = new
|
|
41
|
+
this.diff = new DiffTree(keyFunc, routes);
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* Writes data to the store synchronously.
|
|
@@ -50,8 +50,8 @@ export class StoreSync extends Store {
|
|
|
50
50
|
key = key || this.keyFunc?.(data);
|
|
51
51
|
if (!key)
|
|
52
52
|
throw "No key provided for data";
|
|
53
|
-
|
|
54
|
-
this.
|
|
53
|
+
this.diff.WritePath(key, data);
|
|
54
|
+
this.Sync();
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* Patches an existing value in the store with new data synchronously.
|
|
@@ -60,10 +60,10 @@ 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
|
-
|
|
66
|
-
this.
|
|
65
|
+
this.diff.PatchPath(key, patch);
|
|
66
|
+
this.Sync();
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Pushes data into an array stored at the specified key synchronously.
|
|
@@ -71,10 +71,10 @@ 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
|
-
|
|
77
|
-
this.
|
|
76
|
+
this.diff.PushPath(key, data);
|
|
77
|
+
this.Sync();
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
80
|
* Splices an array stored at the specified key with new data synchronously.
|
|
@@ -83,14 +83,19 @@ export class StoreSync extends Store {
|
|
|
83
83
|
* @param start The position at which to start changing the array.
|
|
84
84
|
* @param deleteCount Optional number of elements to delete. If not provided, all elements from start to end will be deleted.
|
|
85
85
|
* @param items Optional elements to insert into the array.
|
|
86
|
-
* @returns The array of deleted elements.
|
|
87
86
|
*/
|
|
88
87
|
Splice(key, start, deleteCount, ...items) {
|
|
89
|
-
if (!this.
|
|
88
|
+
if (!this.HasRootOf(key))
|
|
90
89
|
throw "Key not found in store";
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
this.diff.SplicePath(key, start, deleteCount ?? Infinity, items);
|
|
91
|
+
this.Sync();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Runs projections and applies everything changed since the last sync to the store.
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
Sync() {
|
|
98
|
+
this.diff.RunProjections();
|
|
99
|
+
this.UpdateRootMap(this.diff.Commit());
|
|
95
100
|
}
|
|
96
101
|
}
|