j-templates 8.2.0 → 9.0.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/Node/vNode.js +2 -0
- 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/observableScope.d.ts +1 -0
- package/Store/Tree/observableScope.js +4 -0
- 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
package/package.json
CHANGED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { JsonDiffResult } from "../../../Utils/json";
|
|
2
|
-
import { ISnapshotState, SnapshotPath, SnapshotResolver } from "../../Tree/snapshot";
|
|
3
|
-
export declare const ROOT_ROUTE = "/";
|
|
4
|
-
export declare const PROJECTION_PREFIX = "$projection_";
|
|
5
|
-
export declare const ROUTE_STATE: unique symbol;
|
|
6
|
-
type KeyFunc = {
|
|
7
|
-
(value: any): string | undefined;
|
|
8
|
-
};
|
|
9
|
-
/** Returns the keyFunc for a route path (`/channel/1/`, or `""`/`/` for the root) */
|
|
10
|
-
export type RouteKeyFunc = {
|
|
11
|
-
(route: string): KeyFunc | undefined;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* The path a keyed value lives at: its key joined to the route it was found in. A key
|
|
15
|
-
* starting with `/` is a route path from the root.
|
|
16
|
-
* @returns The path, or undefined when the value isn't keyed
|
|
17
|
-
*/
|
|
18
|
-
export declare function ResolveKey(keyFunc: KeyFunc | undefined, route: string, value: any): string;
|
|
19
|
-
export declare function ExpandKeyedDiffFromValue(route: string, path: string, keyFunc: KeyFunc, value: any, keyedDiff: {
|
|
20
|
-
path: string;
|
|
21
|
-
value: any;
|
|
22
|
-
}[]): void;
|
|
23
|
-
/**
|
|
24
|
-
* Applies a diff to `diffRoot`, then each keyed value and rebased row it produced, until none are
|
|
25
|
-
* left. Each rebase moves a row past one keyed value, so the rows run out.
|
|
26
|
-
*/
|
|
27
|
-
export declare function ApplyDiff(snapshot: ISnapshotState, treeRootState: any, routeKeyFunc: RouteKeyFunc, route: string, diffRoot: any, diff: JsonDiffResult): void;
|
|
28
|
-
export declare function ApplyPathValues(snapshot: ISnapshotState, root: any, pathValues: {
|
|
29
|
-
path: string;
|
|
30
|
-
value: any;
|
|
31
|
-
}[]): void;
|
|
32
|
-
/**
|
|
33
|
-
* A snapshot resolver reading each keyed child through the path it lives at, keyed with the
|
|
34
|
-
* keyFunc of the route the snapshot walk is in.
|
|
35
|
-
*/
|
|
36
|
-
export declare function CreateKeyResolver(root: any, routeKeyFunc: RouteKeyFunc): SnapshotResolver;
|
|
37
|
-
export declare function ApplyValueToPath(snapshot: ISnapshotState, root: any, routeKeyFunc: RouteKeyFunc, basePath: string, path: string, value: any): void;
|
|
38
|
-
/**
|
|
39
|
-
* Converts a route path to the snapshot path of the value it names, following how
|
|
40
|
-
* `WritePath` stores route state: `/channel/1/item.v` is `[ROUTE_STATE, "channel", ROUTE_STATE, "1", "item", "v"]`.
|
|
41
|
-
*/
|
|
42
|
-
export declare function ToSnapshotPath(path: string): SnapshotPath;
|
|
43
|
-
/** The route path a snapshot path is in: each segment following a `ROUTE_STATE`, e.g. `/channel/1/` */
|
|
44
|
-
export declare function SnapshotPathRoute(path: Readonly<SnapshotPath>): string;
|
|
45
|
-
export declare function WritePath(snapshot: ISnapshotState, root: any, path: string, value: any, guardPath?: boolean, pathIndex?: number): void;
|
|
46
|
-
export declare function ReadPath(root: any, path: string, pathIndex?: number): any;
|
|
47
|
-
export declare function ReadPathFound(root: any, path: string, pathIndex?: number): any[];
|
|
48
|
-
export declare function SplitRouteAndPath(path: string): readonly [string, string];
|
|
49
|
-
export declare function GetKeyedPath(root: any, dotPath: string, keyFunc?: (data: any) => string): string;
|
|
50
|
-
/**
|
|
51
|
-
* Deletes a route's state, child routes included, from its parent route. Every cached snapshot
|
|
52
|
-
* that embedded a value from it, such as one reading a keyed value through its home in the
|
|
53
|
-
* route, is invalidated.
|
|
54
|
-
* @param route A non-root route path, e.g. `/channel/1/`
|
|
55
|
-
*/
|
|
56
|
-
export declare function DeleteRouteState(snapshot: ISnapshotState, root: any, route: string): void;
|
|
57
|
-
/** Whether a path's route has a wildcard segment, e.g. a `row_*` segment */
|
|
58
|
-
export declare function IsWildcardPath(path: string): boolean;
|
|
59
|
-
/**
|
|
60
|
-
* Whether a route matches a route pattern segment by segment. A pattern segment ending in `*`
|
|
61
|
-
* matches any segment starting with the text before it: a `row_*` segment matches `row_1`.
|
|
62
|
-
*/
|
|
63
|
-
export declare function RouteMatches(pattern: string, route: string): boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Expands a wildcard path's route against the route state under `root`.
|
|
66
|
-
* @returns Each matching path, keyed by the segment the wildcard matched
|
|
67
|
-
*/
|
|
68
|
-
export declare function ExpandWildcardPath(root: any, path: string): {
|
|
69
|
-
[segment: string]: string;
|
|
70
|
-
};
|
|
71
|
-
/**
|
|
72
|
-
* Throws when a read path's wildcard is invalid: more than one wildcard segment, a `*` that
|
|
73
|
-
* doesn't end its segment, or a `*` in the property path.
|
|
74
|
-
*/
|
|
75
|
-
export declare function ValidateWildcardPath(path: string): void;
|
|
76
|
-
/** Whether `path` is `basePath` or a property below it: `/a.b` is within `/a`, `/ab` isn't */
|
|
77
|
-
export declare function IsPathWithin(path: string, basePath: string): boolean;
|
|
78
|
-
export declare function JoinPaths(basePath: string, targetPath: string): string;
|
|
79
|
-
export {};
|
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
import { JsonApplyDiff, JsonDeepClone, JsonDiff, JsonType } from "../../../Utils/json";
|
|
2
|
-
import { Snapshot } from "../../Tree/snapshot";
|
|
3
|
-
export const ROOT_ROUTE = "/";
|
|
4
|
-
export const PROJECTION_PREFIX = "$projection_";
|
|
5
|
-
export const ROUTE_STATE = Symbol("ROUTE_STATE");
|
|
6
|
-
const ROUTE_DELIMETER = "/";
|
|
7
|
-
const PROP_DELIMETER = ".";
|
|
8
|
-
const WILDCARD = "*";
|
|
9
|
-
/**
|
|
10
|
-
* The path a keyed value lives at: its key joined to the route it was found in. A key
|
|
11
|
-
* starting with `/` is a route path from the root.
|
|
12
|
-
* @returns The path, or undefined when the value isn't keyed
|
|
13
|
-
*/
|
|
14
|
-
export function ResolveKey(keyFunc, route, value) {
|
|
15
|
-
const key = keyFunc?.(value);
|
|
16
|
-
return key ? JoinPaths(route, key) : undefined;
|
|
17
|
-
}
|
|
18
|
-
export function ExpandKeyedDiffFromValue(route, path, keyFunc, value, keyedDiff) {
|
|
19
|
-
const type = JsonType(value);
|
|
20
|
-
if (type === 'value')
|
|
21
|
-
return;
|
|
22
|
-
const filterPrefix = JoinPaths(route, path);
|
|
23
|
-
const key = keyFunc(value);
|
|
24
|
-
if (key) {
|
|
25
|
-
const [keyRoute] = SplitRouteAndPath(key);
|
|
26
|
-
const invalidKeyRoute = keyRoute && !keyRoute.startsWith('/') && route.endsWith(keyRoute);
|
|
27
|
-
const fullKeyPath = JoinPaths(route, key);
|
|
28
|
-
if (!invalidKeyRoute && fullKeyPath && !IsPathWithin(fullKeyPath, filterPrefix))
|
|
29
|
-
keyedDiff.push({
|
|
30
|
-
path: fullKeyPath,
|
|
31
|
-
value: value
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
const keys = type === "array" ? undefined : Object.keys(value);
|
|
35
|
-
const length = keys ? keys.length : value.length;
|
|
36
|
-
for (let x = 0; x < length; x++) {
|
|
37
|
-
const child = value[keys ? keys[x] : x];
|
|
38
|
-
ExpandKeyedDiffFromValue(route, path, keyFunc, child, keyedDiff);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Applies a diff to `diffRoot`, then each keyed value and rebased row it produced, until none are
|
|
43
|
-
* left. Each rebase moves a row past one keyed value, so the rows run out.
|
|
44
|
-
*/
|
|
45
|
-
export function ApplyDiff(snapshot, treeRootState, routeKeyFunc, route, diffRoot, diff) {
|
|
46
|
-
const keyFunc = routeKeyFunc(route);
|
|
47
|
-
const fixupDiff = [];
|
|
48
|
-
for (let x = 0; keyFunc && x < diff.length; x++) {
|
|
49
|
-
const row = diff[x];
|
|
50
|
-
const path = row.path;
|
|
51
|
-
let curr = diffRoot;
|
|
52
|
-
let key = null;
|
|
53
|
-
let keyIndex = -1;
|
|
54
|
-
let y = 0;
|
|
55
|
-
for (; curr && y < path.length; y++) {
|
|
56
|
-
const currKey = keyFunc(curr);
|
|
57
|
-
if (currKey) {
|
|
58
|
-
key = currKey;
|
|
59
|
-
keyIndex = y;
|
|
60
|
-
}
|
|
61
|
-
curr = curr[path[y]];
|
|
62
|
-
}
|
|
63
|
-
if (y < path.length && key) {
|
|
64
|
-
const joinedKey = JoinPaths(route, key);
|
|
65
|
-
const keyedValue = ReadPath(treeRootState, joinedKey);
|
|
66
|
-
if (keyedValue) {
|
|
67
|
-
const write = {
|
|
68
|
-
type: "write",
|
|
69
|
-
value: keyedValue,
|
|
70
|
-
path: path.slice(0, keyIndex)
|
|
71
|
-
};
|
|
72
|
-
fixupDiff.push(write);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
JsonApplyDiff(diffRoot, fixupDiff, function (parent, prop, value) {
|
|
77
|
-
parent[prop] = value;
|
|
78
|
-
Snapshot.Invalidate(snapshot, parent);
|
|
79
|
-
});
|
|
80
|
-
// The raw value can be a stale copy missing paths the diff's snapshot has; the rebased rows
|
|
81
|
-
// below write those to the value's home
|
|
82
|
-
JsonApplyDiff(diffRoot, diff, function (parent, prop, value) {
|
|
83
|
-
parent[prop] = value;
|
|
84
|
-
Snapshot.Invalidate(snapshot, parent);
|
|
85
|
-
});
|
|
86
|
-
if (!keyFunc)
|
|
87
|
-
return;
|
|
88
|
-
const rebasedRouteMap = {};
|
|
89
|
-
const keyedWrites = [];
|
|
90
|
-
for (let x = 0; x < diff.length; x++) {
|
|
91
|
-
const diffRow = diff[x];
|
|
92
|
-
const path = diffRow.path;
|
|
93
|
-
const pathStr = path.join('.');
|
|
94
|
-
const routeAndPath = JoinPaths(route, pathStr);
|
|
95
|
-
if (diffRow.type === "write")
|
|
96
|
-
ExpandKeyedDiffFromValue(route, pathStr, keyFunc, diffRow.value, keyedWrites);
|
|
97
|
-
let curr = diffRoot;
|
|
98
|
-
let key = null;
|
|
99
|
-
let keyIndex = -1;
|
|
100
|
-
let keyCurr = null;
|
|
101
|
-
for (let y = 0; curr && y < path.length; y++) {
|
|
102
|
-
const currKey = keyFunc(curr);
|
|
103
|
-
if (currKey) {
|
|
104
|
-
key = currKey;
|
|
105
|
-
keyIndex = y;
|
|
106
|
-
keyCurr = curr;
|
|
107
|
-
}
|
|
108
|
-
curr = curr[path[y]];
|
|
109
|
-
}
|
|
110
|
-
if (key) {
|
|
111
|
-
const joinedPath = JoinPaths(route, key);
|
|
112
|
-
if (!IsPathWithin(routeAndPath, joinedPath)) {
|
|
113
|
-
const exists = !!ReadPath(treeRootState, joinedPath);
|
|
114
|
-
const [targetRoute, targetPath] = SplitRouteAndPath(joinedPath);
|
|
115
|
-
rebasedRouteMap[targetRoute] ??= [];
|
|
116
|
-
if (!exists) {
|
|
117
|
-
rebasedRouteMap[targetRoute].push({
|
|
118
|
-
type: 'write',
|
|
119
|
-
path: targetPath.split('.'),
|
|
120
|
-
value: keyCurr
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
const rebasedDiffRow = diffRow.type === "move" ? {
|
|
125
|
-
...diffRow,
|
|
126
|
-
path: targetPath.split('.').concat(diffRow.path.slice(keyIndex))
|
|
127
|
-
} : {
|
|
128
|
-
...diffRow,
|
|
129
|
-
path: targetPath.split('.').concat(diffRow.path.slice(keyIndex))
|
|
130
|
-
};
|
|
131
|
-
if (rebasedDiffRow.type === "move" || rebasedDiffRow.value !== ReadPath(treeRootState, JoinPaths(targetRoute, rebasedDiffRow.path.join('.'))))
|
|
132
|
-
rebasedRouteMap[targetRoute].push(rebasedDiffRow);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
for (let x = 0; x < keyedWrites.length; x++) {
|
|
138
|
-
const [keyedRoute, keyedPath] = SplitRouteAndPath(keyedWrites[x].path);
|
|
139
|
-
ApplyValueToPath(snapshot, treeRootState, routeKeyFunc, keyedRoute, keyedPath, keyedWrites[x].value);
|
|
140
|
-
}
|
|
141
|
-
for (const rebasedRoute in rebasedRouteMap) {
|
|
142
|
-
const rebasedRoot = ReadPath(treeRootState, rebasedRoute);
|
|
143
|
-
ApplyDiff(snapshot, treeRootState, routeKeyFunc, rebasedRoute, rebasedRoot, rebasedRouteMap[rebasedRoute]);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
export function ApplyPathValues(snapshot, root, pathValues) {
|
|
147
|
-
for (let x = 0; x < pathValues.length; x++)
|
|
148
|
-
WritePath(snapshot, root, pathValues[x].path, JsonDeepClone(pathValues[x].value));
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* A snapshot resolver reading each keyed child through the path it lives at, keyed with the
|
|
152
|
-
* keyFunc of the route the snapshot walk is in.
|
|
153
|
-
*/
|
|
154
|
-
export function CreateKeyResolver(root, routeKeyFunc) {
|
|
155
|
-
return function (child, snapshotPath) {
|
|
156
|
-
const route = SnapshotPathRoute(snapshotPath);
|
|
157
|
-
const keyedPath = ResolveKey(routeKeyFunc(route), route, child);
|
|
158
|
-
if (!keyedPath)
|
|
159
|
-
return undefined;
|
|
160
|
-
const value = ReadPath(root, keyedPath);
|
|
161
|
-
return value === undefined ? undefined : { value, path: ToSnapshotPath(keyedPath) };
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* The snapshot of `routeRoot[property]`, read through its home value when it's keyed, as a
|
|
166
|
-
* snapshot of the whole route would hold it.
|
|
167
|
-
*/
|
|
168
|
-
function ResolvedSnapshot(snapshot, root, routeKeyFunc, routeRoot, route, property) {
|
|
169
|
-
const value = routeRoot[property];
|
|
170
|
-
if (JsonType(value) === "value")
|
|
171
|
-
return value;
|
|
172
|
-
const resolver = CreateKeyResolver(root, routeKeyFunc);
|
|
173
|
-
const path = ToSnapshotPath(JoinPaths(route, property));
|
|
174
|
-
const resolved = resolver(value, path);
|
|
175
|
-
return resolved ? Snapshot.Get(snapshot, resolved.value, resolver, resolved.path) : Snapshot.Get(snapshot, value, resolver, path);
|
|
176
|
-
}
|
|
177
|
-
export function ApplyValueToPath(snapshot, root, routeKeyFunc, basePath, path, value) {
|
|
178
|
-
const [route, property] = SplitRouteAndPath(path);
|
|
179
|
-
if (!property)
|
|
180
|
-
throw new Error("Unable to write to root path");
|
|
181
|
-
let combinedRoute = JoinPaths(basePath, route);
|
|
182
|
-
let diffRoot = ReadPath(root, combinedRoute);
|
|
183
|
-
if (diffRoot === undefined) {
|
|
184
|
-
WritePath(snapshot, root, combinedRoute, undefined);
|
|
185
|
-
diffRoot = ReadPath(root, combinedRoute);
|
|
186
|
-
}
|
|
187
|
-
// Only the written value is snapshotted. A snapshot of the whole route would cache values that
|
|
188
|
-
// embed keys whose home values this apply hasn't written yet, and nothing would update them.
|
|
189
|
-
const propertyIndex = property.indexOf(PROP_DELIMETER);
|
|
190
|
-
const rootProperty = propertyIndex < 0 ? property : property.slice(0, propertyIndex);
|
|
191
|
-
const currentValue = ReadPath(ResolvedSnapshot(snapshot, root, routeKeyFunc, diffRoot, combinedRoute, rootProperty), propertyIndex < 0 ? "" : property.slice(propertyIndex + 1));
|
|
192
|
-
const diff = JsonDiff(value, currentValue, property);
|
|
193
|
-
ApplyDiff(snapshot, root, routeKeyFunc, combinedRoute, diffRoot, diff);
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Converts a route path to the snapshot path of the value it names, following how
|
|
197
|
-
* `WritePath` stores route state: `/channel/1/item.v` is `[ROUTE_STATE, "channel", ROUTE_STATE, "1", "item", "v"]`.
|
|
198
|
-
*/
|
|
199
|
-
export function ToSnapshotPath(path) {
|
|
200
|
-
const snapshotPath = [];
|
|
201
|
-
const [route, dotPath] = SplitRouteAndPath(path);
|
|
202
|
-
const routeParts = route.split(ROUTE_DELIMETER);
|
|
203
|
-
for (let x = 0; x < routeParts.length; x++)
|
|
204
|
-
routeParts[x] !== "" && snapshotPath.push(ROUTE_STATE, routeParts[x]);
|
|
205
|
-
if (dotPath !== "")
|
|
206
|
-
snapshotPath.push(...dotPath.split(PROP_DELIMETER));
|
|
207
|
-
return snapshotPath;
|
|
208
|
-
}
|
|
209
|
-
/** The route path a snapshot path is in: each segment following a `ROUTE_STATE`, e.g. `/channel/1/` */
|
|
210
|
-
export function SnapshotPathRoute(path) {
|
|
211
|
-
let route = ROUTE_DELIMETER;
|
|
212
|
-
for (let x = 0; x < path.length - 1; x++)
|
|
213
|
-
if (path[x] === ROUTE_STATE)
|
|
214
|
-
route += `${String(path[x + 1])}${ROUTE_DELIMETER}`;
|
|
215
|
-
return route;
|
|
216
|
-
}
|
|
217
|
-
export function WritePath(snapshot, root, path, value, guardPath = true, pathIndex = 0) {
|
|
218
|
-
const startingSlash = path[pathIndex] === ROUTE_DELIMETER;
|
|
219
|
-
if (startingSlash && path.includes(ROUTE_DELIMETER, pathIndex + 1)) {
|
|
220
|
-
const routeKey = path.slice(pathIndex + 1, path.indexOf(ROUTE_DELIMETER, pathIndex + 1));
|
|
221
|
-
if (!Object.hasOwn(root, ROUTE_STATE))
|
|
222
|
-
Object.defineProperty(root, ROUTE_STATE, {
|
|
223
|
-
value: {}
|
|
224
|
-
});
|
|
225
|
-
root[ROUTE_STATE][routeKey] ??= {};
|
|
226
|
-
WritePath(snapshot, root[ROUTE_STATE][routeKey], path, value, guardPath, pathIndex + routeKey.length + 1);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
const sliceIndex = startingSlash ? pathIndex + 1 : pathIndex;
|
|
230
|
-
const dotPath = path.slice(sliceIndex);
|
|
231
|
-
if (guardPath && dotPath.startsWith(PROJECTION_PREFIX))
|
|
232
|
-
throw new Error(`Cannot write to ${path}: ${PROJECTION_PREFIX} keys are reserved for projection results.`);
|
|
233
|
-
if (dotPath === '')
|
|
234
|
-
return;
|
|
235
|
-
const parts = dotPath.split(PROP_DELIMETER);
|
|
236
|
-
let curr = root;
|
|
237
|
-
let x = 0;
|
|
238
|
-
for (; curr && x < parts.length - 1; x++)
|
|
239
|
-
curr = curr[parts[x]];
|
|
240
|
-
if (!curr)
|
|
241
|
-
throw new Error("Unable to write value to path: " + path);
|
|
242
|
-
if (curr[parts[x]])
|
|
243
|
-
Snapshot.Invalidate(snapshot, curr[parts[x]]);
|
|
244
|
-
curr[parts[x]] = value;
|
|
245
|
-
Snapshot.Invalidate(snapshot, curr);
|
|
246
|
-
}
|
|
247
|
-
export function ReadPath(root, path, pathIndex = 0) {
|
|
248
|
-
if (root === undefined)
|
|
249
|
-
return undefined;
|
|
250
|
-
if (path === '')
|
|
251
|
-
return root;
|
|
252
|
-
const startingSlash = path[pathIndex] === ROUTE_DELIMETER;
|
|
253
|
-
if (startingSlash && pathIndex === path.length - 1)
|
|
254
|
-
return root;
|
|
255
|
-
if (startingSlash && path.includes(ROUTE_DELIMETER, pathIndex + 1)) {
|
|
256
|
-
const routeKey = path.slice(pathIndex + 1, path.indexOf(ROUTE_DELIMETER, pathIndex + 1));
|
|
257
|
-
const nextRoot = root[ROUTE_STATE]?.[routeKey];
|
|
258
|
-
return ReadPath(nextRoot, path, pathIndex + routeKey.length + 1);
|
|
259
|
-
}
|
|
260
|
-
const sliceIndex = startingSlash ? pathIndex + 1 : pathIndex;
|
|
261
|
-
const dotPath = path.slice(sliceIndex);
|
|
262
|
-
const parts = dotPath.split(PROP_DELIMETER);
|
|
263
|
-
let curr = root;
|
|
264
|
-
let x = 0;
|
|
265
|
-
for (; curr && x < parts.length - 1; x++)
|
|
266
|
-
curr = curr[parts[x]];
|
|
267
|
-
return curr?.[parts[x]];
|
|
268
|
-
}
|
|
269
|
-
export function ReadPathFound(root, path, pathIndex = 0) {
|
|
270
|
-
if (root === undefined)
|
|
271
|
-
return [undefined, false];
|
|
272
|
-
if (path === '')
|
|
273
|
-
return [root, !!root];
|
|
274
|
-
const startingSlash = path[pathIndex] === ROUTE_DELIMETER;
|
|
275
|
-
if (startingSlash && pathIndex === path.length - 1)
|
|
276
|
-
return [root, !!root];
|
|
277
|
-
if (startingSlash && path.includes(ROUTE_DELIMETER, pathIndex + 1)) {
|
|
278
|
-
const routeKey = path.slice(pathIndex + 1, path.indexOf(ROUTE_DELIMETER, pathIndex + 1));
|
|
279
|
-
const nextRoot = root[ROUTE_STATE]?.[routeKey];
|
|
280
|
-
return ReadPathFound(nextRoot, path, pathIndex + routeKey.length + 1);
|
|
281
|
-
}
|
|
282
|
-
const sliceIndex = startingSlash ? pathIndex + 1 : pathIndex;
|
|
283
|
-
const dotPath = path.slice(sliceIndex);
|
|
284
|
-
const parts = dotPath.split(PROP_DELIMETER);
|
|
285
|
-
let curr = root;
|
|
286
|
-
let x = 0;
|
|
287
|
-
for (; curr && x < parts.length - 1; x++)
|
|
288
|
-
curr = curr[parts[x]];
|
|
289
|
-
return [curr?.[parts[x]], !!curr];
|
|
290
|
-
}
|
|
291
|
-
export function SplitRouteAndPath(path) {
|
|
292
|
-
const lastSlash = path.lastIndexOf(ROUTE_DELIMETER);
|
|
293
|
-
const route = path.slice(0, lastSlash + 1);
|
|
294
|
-
const propertyPath = path.slice(lastSlash + 1);
|
|
295
|
-
return [route, propertyPath];
|
|
296
|
-
}
|
|
297
|
-
export function GetKeyedPath(root, dotPath, keyFunc) {
|
|
298
|
-
if (!keyFunc)
|
|
299
|
-
return undefined;
|
|
300
|
-
const parts = dotPath.split(PROP_DELIMETER);
|
|
301
|
-
let keyPath = keyFunc(root);
|
|
302
|
-
let curr = root;
|
|
303
|
-
for (let x = 0; curr && x < parts.length - 1; x++) {
|
|
304
|
-
curr = curr[parts[x]];
|
|
305
|
-
keyPath = keyFunc(root) ?? keyPath;
|
|
306
|
-
}
|
|
307
|
-
return keyPath;
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Deletes a route's state, child routes included, from its parent route. Every cached snapshot
|
|
311
|
-
* that embedded a value from it, such as one reading a keyed value through its home in the
|
|
312
|
-
* route, is invalidated.
|
|
313
|
-
* @param route A non-root route path, e.g. `/channel/1/`
|
|
314
|
-
*/
|
|
315
|
-
export function DeleteRouteState(snapshot, root, route) {
|
|
316
|
-
const parentRoute = route.slice(0, route.lastIndexOf(ROUTE_DELIMETER, route.length - 2) + 1);
|
|
317
|
-
const segment = route.slice(parentRoute.length, -1);
|
|
318
|
-
const routeStates = ReadPath(root, parentRoute)?.[ROUTE_STATE];
|
|
319
|
-
if (!routeStates || !Object.hasOwn(routeStates, segment))
|
|
320
|
-
return;
|
|
321
|
-
InvalidateState(snapshot, routeStates[segment]);
|
|
322
|
-
delete routeStates[segment];
|
|
323
|
-
}
|
|
324
|
-
function InvalidateState(snapshot, value) {
|
|
325
|
-
if (JsonType(value) === "value")
|
|
326
|
-
return;
|
|
327
|
-
Snapshot.Invalidate(snapshot, value);
|
|
328
|
-
const keys = Object.keys(value);
|
|
329
|
-
for (let x = 0; x < keys.length; x++)
|
|
330
|
-
InvalidateState(snapshot, value[keys[x]]);
|
|
331
|
-
const routeStates = value[ROUTE_STATE];
|
|
332
|
-
for (const segment in routeStates)
|
|
333
|
-
InvalidateState(snapshot, routeStates[segment]);
|
|
334
|
-
}
|
|
335
|
-
/** Whether a path's route has a wildcard segment, e.g. a `row_*` segment */
|
|
336
|
-
export function IsWildcardPath(path) {
|
|
337
|
-
return SplitRouteAndPath(path)[0].includes(WILDCARD);
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Whether a route matches a route pattern segment by segment. A pattern segment ending in `*`
|
|
341
|
-
* matches any segment starting with the text before it: a `row_*` segment matches `row_1`.
|
|
342
|
-
*/
|
|
343
|
-
export function RouteMatches(pattern, route) {
|
|
344
|
-
if (pattern === route)
|
|
345
|
-
return true;
|
|
346
|
-
// Every route but a few fails on the text before the wildcard, so that's checked before splitting
|
|
347
|
-
const wildcard = pattern.indexOf(WILDCARD);
|
|
348
|
-
if (wildcard < 0 || !route.startsWith(pattern.slice(0, wildcard)))
|
|
349
|
-
return false;
|
|
350
|
-
const patternParts = pattern.split(ROUTE_DELIMETER);
|
|
351
|
-
const routeParts = route.split(ROUTE_DELIMETER);
|
|
352
|
-
if (patternParts.length !== routeParts.length)
|
|
353
|
-
return false;
|
|
354
|
-
for (let x = 0; x < patternParts.length; x++)
|
|
355
|
-
if (patternParts[x].endsWith(WILDCARD) ? !routeParts[x].startsWith(patternParts[x].slice(0, -1)) : patternParts[x] !== routeParts[x])
|
|
356
|
-
return false;
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Expands a wildcard path's route against the route state under `root`.
|
|
361
|
-
* @returns Each matching path, keyed by the segment the wildcard matched
|
|
362
|
-
*/
|
|
363
|
-
export function ExpandWildcardPath(root, path) {
|
|
364
|
-
const [route, property] = SplitRouteAndPath(path);
|
|
365
|
-
const parts = route.split(ROUTE_DELIMETER);
|
|
366
|
-
const index = parts.findIndex(part => part.endsWith(WILDCARD));
|
|
367
|
-
const prefix = parts[index].slice(0, -1);
|
|
368
|
-
const parent = ReadPath(root, parts.slice(0, index).join(ROUTE_DELIMETER) + ROUTE_DELIMETER);
|
|
369
|
-
const children = parent?.[ROUTE_STATE] ?? {};
|
|
370
|
-
const paths = {};
|
|
371
|
-
for (const segment of Object.keys(children))
|
|
372
|
-
if (segment.startsWith(prefix)) {
|
|
373
|
-
parts[index] = segment;
|
|
374
|
-
paths[segment] = parts.join(ROUTE_DELIMETER) + property;
|
|
375
|
-
}
|
|
376
|
-
return paths;
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Throws when a read path's wildcard is invalid: more than one wildcard segment, a `*` that
|
|
380
|
-
* doesn't end its segment, or a `*` in the property path.
|
|
381
|
-
*/
|
|
382
|
-
export function ValidateWildcardPath(path) {
|
|
383
|
-
const [route, property] = SplitRouteAndPath(path);
|
|
384
|
-
const wildcards = route.split(ROUTE_DELIMETER).filter(part => part.includes(WILDCARD));
|
|
385
|
-
if (property.includes(WILDCARD) || wildcards.length > 1 || wildcards.some(part => part.indexOf(WILDCARD) !== part.length - 1))
|
|
386
|
-
throw new Error(`Invalid wildcard read ${path}: only one route segment may end in ${WILDCARD}.`);
|
|
387
|
-
}
|
|
388
|
-
/** Whether `path` is `basePath` or a property below it: `/a.b` is within `/a`, `/ab` isn't */
|
|
389
|
-
export function IsPathWithin(path, basePath) {
|
|
390
|
-
return path === basePath || path.startsWith(basePath + PROP_DELIMETER);
|
|
391
|
-
}
|
|
392
|
-
export function JoinPaths(basePath, targetPath) {
|
|
393
|
-
if (targetPath.startsWith(ROUTE_DELIMETER))
|
|
394
|
-
return targetPath;
|
|
395
|
-
if (basePath === '')
|
|
396
|
-
return `/${targetPath}`;
|
|
397
|
-
return basePath.concat(targetPath);
|
|
398
|
-
}
|