dirvi-lib 0.8.0 → 0.8.2
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/dist/app/app-api.d.ts +74 -0
- package/dist/app/app-api.d.ts.map +1 -0
- package/dist/app/app-api.js +11 -0
- package/dist/app/effect-to-action.d.ts +6 -0
- package/dist/app/effect-to-action.d.ts.map +1 -0
- package/dist/app/effect-to-action.js +74 -0
- package/dist/app/index.d.ts +6 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +5 -0
- package/dist/app/reducer-action.d.ts +22 -0
- package/dist/app/reducer-action.d.ts.map +1 -0
- package/dist/app/reducer.d.ts +5 -0
- package/dist/app/reducer.d.ts.map +1 -0
- package/dist/app/reducer.js +79 -0
- package/dist/app/view-api.d.ts +11 -0
- package/dist/app/view-api.d.ts.map +1 -0
- package/dist/index.d.ts +4 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -7
- package/dist/input/application/index.d.ts.map +1 -0
- package/dist/input/application/intent-to-effect-no-state.test.d.ts.map +1 -0
- package/dist/input/application/intent-to-effect.d.ts +14 -0
- package/dist/input/application/intent-to-effect.d.ts.map +1 -0
- package/dist/{tree-surfer-ui → input}/application/intent-to-effect.js +36 -17
- package/dist/input/application/parse-command.d.ts +14 -0
- package/dist/input/application/parse-command.d.ts.map +1 -0
- package/dist/{tree-surfer-ui → input}/application/parse-command.js +13 -1
- package/dist/input/application/user-input-to-intent.d.ts.map +1 -0
- package/dist/input/application/user-input-to-intent.test.d.ts.map +1 -0
- package/dist/input/domain/effect.d.ts +55 -0
- package/dist/input/domain/effect.d.ts.map +1 -0
- package/dist/input/domain/index.d.ts.map +1 -0
- package/dist/input/domain/input-mode-state.d.ts.map +1 -0
- package/dist/input/domain/intent.d.ts.map +1 -0
- package/dist/input/domain/user-input.d.ts.map +1 -0
- package/dist/trees/cursor.d.ts.map +1 -0
- package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts +10 -0
- package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts.map +1 -0
- package/dist/trees/fold-node/deprecated/fold-node.impl.js +248 -0
- package/dist/trees/fold-node/deprecated/fold-node.test.d.ts +2 -0
- package/dist/trees/fold-node/deprecated/fold-node.test.d.ts.map +1 -0
- package/dist/trees/fold-node/deprecated/fold-node.test.js +130 -0
- package/dist/{tree-surfer/fold-node → trees/fold-node/deprecated}/fold-node.types.d.ts +15 -76
- package/dist/trees/fold-node/deprecated/fold-node.types.d.ts.map +1 -0
- package/dist/trees/fold-node/fold-node.impl.d.ts +16 -0
- package/dist/trees/fold-node/fold-node.impl.d.ts.map +1 -0
- package/dist/trees/fold-node/fold-node.impl.js +451 -0
- package/dist/trees/fold-node/fold-node.model.d.ts +120 -0
- package/dist/trees/fold-node/fold-node.model.d.ts.map +1 -0
- package/dist/trees/fold-node/fold-node.model.js +80 -0
- package/dist/trees/fold-node/fold-node.types.d.ts +206 -0
- package/dist/trees/fold-node/fold-node.types.d.ts.map +1 -0
- package/dist/trees/fold-node/index.d.ts +4 -0
- package/dist/trees/fold-node/index.d.ts.map +1 -0
- package/dist/trees/fold-node/index.js +3 -0
- package/dist/trees/index.d.ts +9 -0
- package/dist/trees/index.d.ts.map +1 -0
- package/dist/trees/index.js +8 -0
- package/dist/trees/nav-node/nav-node.impl.d.ts +29 -0
- package/dist/trees/nav-node/nav-node.impl.d.ts.map +1 -0
- package/dist/{tree-surfer → trees}/nav-node/nav-node.impl.js +89 -42
- package/dist/trees/nav-node/nav-node.types.d.ts +103 -0
- package/dist/trees/nav-node/nav-node.types.d.ts.map +1 -0
- package/dist/trees/state/state.impl.d.ts +8 -0
- package/dist/trees/state/state.impl.d.ts.map +1 -0
- package/dist/trees/state/state.impl.js +128 -0
- package/dist/trees/state/state.schemas.d.ts +25 -0
- package/dist/trees/state/state.schemas.d.ts.map +1 -0
- package/dist/trees/state/state.schemas.js +17 -0
- package/dist/trees/state/state.types.d.ts +36 -0
- package/dist/trees/state/state.types.d.ts.map +1 -0
- package/dist/trees/state/state.types.js +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.arb.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.arb.debug.test.d.ts.map +1 -0
- package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.js +1 -1
- package/dist/trees/tree-node/deprecated/tree-node.impl.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.schemas.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.test.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.types.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.types.js +1 -0
- package/dist/trees/tree-node/index.d.ts +4 -0
- package/dist/trees/tree-node/index.d.ts.map +1 -0
- package/dist/trees/tree-node/index.js +3 -0
- package/dist/trees/tree-node/tree-node.arb.d.ts +104 -0
- package/dist/trees/tree-node/tree-node.arb.d.ts.map +1 -0
- package/dist/trees/tree-node/tree-node.arb.js +191 -0
- package/dist/trees/tree-node/tree-node.impl.d.ts +15 -0
- package/dist/trees/tree-node/tree-node.impl.d.ts.map +1 -0
- package/dist/trees/tree-node/tree-node.impl.js +286 -0
- package/dist/trees/tree-node/tree-node.model.d.ts +78 -0
- package/dist/trees/tree-node/tree-node.model.d.ts.map +1 -0
- package/dist/trees/tree-node/tree-node.model.js +37 -0
- package/dist/trees/tree-node/tree-node.types.d.ts +419 -0
- package/dist/trees/tree-node/tree-node.types.d.ts.map +1 -0
- package/dist/trees/tree-node/tree-node.types.js +1 -0
- package/package.json +1 -1
- package/dist/tree-surfer/cursor.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +0 -2
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +0 -4
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/fold-node.impl.js +0 -149
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +0 -5
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/fold-node.schema.js +0 -9
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/tree-node.test.js +0 -90
- package/dist/tree-surfer/index.d.ts +0 -13
- package/dist/tree-surfer/index.d.ts.map +0 -1
- package/dist/tree-surfer/index.js +0 -12
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +0 -6
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +0 -1
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts +0 -70
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +0 -1
- package/dist/tree-surfer/state/state.impl.d.ts +0 -7
- package/dist/tree-surfer/state/state.impl.d.ts.map +0 -1
- package/dist/tree-surfer/state/state.impl.js +0 -89
- package/dist/tree-surfer/state/state.schemas.d.ts +0 -9
- package/dist/tree-surfer/state/state.schemas.d.ts.map +0 -1
- package/dist/tree-surfer/state/state.schemas.js +0 -8
- package/dist/tree-surfer/state/state.types.d.ts +0 -21
- package/dist/tree-surfer/state/state.types.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.test.d.ts +0 -2
- package/dist/tree-surfer/tree-node/tree-node.test.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/index.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/intent-to-effect-no-state.test.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +0 -10
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/parse-command.d.ts +0 -4
- package/dist/tree-surfer-ui/application/parse-command.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/user-input-to-intent.test.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/effect.d.ts +0 -43
- package/dist/tree-surfer-ui/domain/effect.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/index.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/intent.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/user-input.d.ts.map +0 -1
- /package/dist/{tree-surfer-ui/domain/effect.js → app/reducer-action.js} +0 -0
- /package/dist/{tree-surfer-ui/domain/input-mode-state.js → app/view-api.js} +0 -0
- /package/dist/{tree-surfer-ui → input}/application/index.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/application/index.js +0 -0
- /package/dist/{tree-surfer-ui → input}/application/intent-to-effect-no-state.test.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/application/intent-to-effect-no-state.test.js +0 -0
- /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.js +0 -0
- /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.test.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.test.js +0 -0
- /package/dist/{tree-surfer-ui/domain/intent.js → input/domain/effect.js} +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/index.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/index.js +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/input-mode-state.d.ts +0 -0
- /package/dist/{tree-surfer-ui/domain/user-input.js → input/domain/input-mode-state.js} +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/intent.d.ts +0 -0
- /package/dist/{tree-surfer/fold-node/fold-node.arb.js → input/domain/intent.js} +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/user-input.d.ts +0 -0
- /package/dist/{tree-surfer/fold-node/fold-node.types.js → input/domain/user-input.js} +0 -0
- /package/dist/{tree-surfer → trees}/cursor.d.ts +0 -0
- /package/dist/{tree-surfer → trees}/cursor.js +0 -0
- /package/dist/{tree-surfer/nav-node/nav-node.types.js → trees/fold-node/deprecated/fold-node.types.js} +0 -0
- /package/dist/{tree-surfer/state/state.types.js → trees/fold-node/fold-node.types.js} +0 -0
- /package/dist/{tree-surfer/tree-node/tree-node.types.js → trees/nav-node/nav-node.types.js} +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.js +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.impl.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.impl.js +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.schemas.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.schemas.js +0 -0
- /package/dist/{tree-surfer/fold-node → trees/tree-node/deprecated}/tree-node.test.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.test.js +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.types.d.ts +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates operations for navigating and resynchronizing tree-surfer state.
|
|
3
|
+
*/
|
|
4
|
+
export function createStateApi(treeNodeApi, cursorApi) {
|
|
5
|
+
/**
|
|
6
|
+
* Reloads one node's direct children and recursively reloads descendant
|
|
7
|
+
* branches that were open in the old state.
|
|
8
|
+
*
|
|
9
|
+
* The node itself retains its existing application value. The supplied
|
|
10
|
+
* loader refreshes only its direct child collection.
|
|
11
|
+
*
|
|
12
|
+
* A branch that was previously closed remains closed: its children were not
|
|
13
|
+
* loaded in the old state, so there is no descendant loading state to
|
|
14
|
+
* preserve or refresh.
|
|
15
|
+
*/
|
|
16
|
+
async function reload(oldRoot, parentPath, loadBranches) {
|
|
17
|
+
const oldChildren = treeNodeApi.getLoadedChildren(oldRoot);
|
|
18
|
+
/*
|
|
19
|
+
* The old node was either a leaf or a closed branch. In either case, it
|
|
20
|
+
* had no loaded child collection to resynchronize.
|
|
21
|
+
*/
|
|
22
|
+
if (oldChildren === undefined) {
|
|
23
|
+
return { root: oldRoot };
|
|
24
|
+
}
|
|
25
|
+
const loadedChildren = await loadBranches(parentPath);
|
|
26
|
+
/*
|
|
27
|
+
* `withChildren` retains oldRoot's ID and application value, replacing
|
|
28
|
+
* only its structural child collection.
|
|
29
|
+
*
|
|
30
|
+
* Failure means the loader supplied duplicate sibling IDs, violating the
|
|
31
|
+
* TreeNodeApi contract. This is a loader/data-integrity error rather than
|
|
32
|
+
* an ordinary missing-path condition.
|
|
33
|
+
*/
|
|
34
|
+
let newRoot = treeNodeApi.withLoadedChildren(oldRoot, loadedChildren);
|
|
35
|
+
if (newRoot === undefined) {
|
|
36
|
+
throw new Error('Cannot resync tree: loaded branch contains duplicate sibling IDs');
|
|
37
|
+
}
|
|
38
|
+
/*
|
|
39
|
+
* Recurse only into entries that were open in the old state and which
|
|
40
|
+
* still exist in the freshly loaded child collection.
|
|
41
|
+
*
|
|
42
|
+
* This preserves fresh values from loaded children while restoring their
|
|
43
|
+
* previously loaded descendant collections.
|
|
44
|
+
*/
|
|
45
|
+
for (const oldEntry of oldChildren) {
|
|
46
|
+
const oldEntryChildren = treeNodeApi.getLoadedChildren(oldEntry);
|
|
47
|
+
/*
|
|
48
|
+
* A leaf or closed branch did not have loaded descendants in old state.
|
|
49
|
+
*/
|
|
50
|
+
if (oldEntryChildren === undefined) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const oldEntryId = treeNodeApi.id(oldEntry);
|
|
54
|
+
const currentChildren = treeNodeApi.getLoadedChildren(newRoot);
|
|
55
|
+
if (currentChildren === undefined) {
|
|
56
|
+
throw new Error('Cannot resync tree: replacement root unexpectedly has no children');
|
|
57
|
+
}
|
|
58
|
+
const currentChildrenArray = [...currentChildren];
|
|
59
|
+
const currentChildIndex = currentChildrenArray.findIndex((child) => treeNodeApi.id(child) === oldEntryId);
|
|
60
|
+
if (currentChildIndex === -1) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const currentChild = currentChildrenArray[currentChildIndex];
|
|
64
|
+
if (currentChild === undefined) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (treeNodeApi.kind(currentChild) === 'leaf') {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const reloadedChild = await reload(oldEntry, [...parentPath, oldEntryId], loadBranches);
|
|
71
|
+
const reloadedGrandchildren = treeNodeApi.getLoadedChildren(reloadedChild.root);
|
|
72
|
+
if (reloadedGrandchildren === undefined) {
|
|
73
|
+
throw new Error('Cannot resync tree: reloaded open branch has no children');
|
|
74
|
+
}
|
|
75
|
+
const updatedCurrentChild = treeNodeApi.withLoadedChildren(currentChild, reloadedGrandchildren);
|
|
76
|
+
if (updatedCurrentChild === undefined) {
|
|
77
|
+
throw new Error('Cannot resync tree: reloaded descendants contain duplicate sibling IDs');
|
|
78
|
+
}
|
|
79
|
+
const updatedChildren = [...currentChildrenArray];
|
|
80
|
+
updatedChildren[currentChildIndex] = updatedCurrentChild;
|
|
81
|
+
const updatedRoot = treeNodeApi.withLoadedChildren(newRoot, updatedChildren);
|
|
82
|
+
if (updatedRoot === undefined) {
|
|
83
|
+
throw new Error('Cannot resync tree: updated branch contains duplicate sibling IDs');
|
|
84
|
+
}
|
|
85
|
+
newRoot = updatedRoot;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
root: newRoot,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Determines the cursor after resynchronization.
|
|
93
|
+
*
|
|
94
|
+
* Current policy: return the root cursor.
|
|
95
|
+
*
|
|
96
|
+
* Future policy:
|
|
97
|
+
*
|
|
98
|
+
* 1. Keep the exact cursor if it survives.
|
|
99
|
+
* 2. Otherwise choose the deepest surviving ancestor.
|
|
100
|
+
* 3. Otherwise choose the nearest preceding surviving cursor.
|
|
101
|
+
* 4. Otherwise choose the first valid cursor.
|
|
102
|
+
* 5. If no entry cursor exists, use the root cursor.
|
|
103
|
+
*/
|
|
104
|
+
function resyncCursor(_oldState, _newRoot, _newFolds) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
getNodeAtCursor(state) {
|
|
109
|
+
const path = cursorApi.getPath(state.cursor);
|
|
110
|
+
return treeNodeApi.selectAtPath(state.root, path, (node) => node);
|
|
111
|
+
},
|
|
112
|
+
async resync(oldState, loadBranches) {
|
|
113
|
+
const reloaded = await reload(oldState.root, [], loadBranches);
|
|
114
|
+
/*
|
|
115
|
+
* Fold-state resynchronization is not implemented yet. Keep the existing
|
|
116
|
+
* fold definitions while the source tree is reloaded.
|
|
117
|
+
*/
|
|
118
|
+
const folds = oldState.folds;
|
|
119
|
+
const root = reloaded.root;
|
|
120
|
+
return {
|
|
121
|
+
...oldState,
|
|
122
|
+
root,
|
|
123
|
+
folds,
|
|
124
|
+
cursor: resyncCursor(oldState, root, folds),
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Cursor } from '../cursor.js';
|
|
3
|
+
import type { FoldsModel } from '../fold-node/index.js';
|
|
4
|
+
import type { SerializableKey, TreeNodeModel } from '../tree-node/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Serializable persistence/interchange model for tree-surfer state.
|
|
7
|
+
*
|
|
8
|
+
* This is intentionally distinct from runtime `State`: its root and folds use
|
|
9
|
+
* public structural models, while runtime state holds opaque TreeNode and
|
|
10
|
+
* Folds values.
|
|
11
|
+
*/
|
|
12
|
+
export type StateModel<Id extends SerializableKey, Value> = Readonly<{
|
|
13
|
+
root: TreeNodeModel<Id, Value>;
|
|
14
|
+
folds: FoldsModel<Id>;
|
|
15
|
+
cursor: Cursor<Id>;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Creates a schema for the serializable StateModel DTO.
|
|
19
|
+
*
|
|
20
|
+
* This validates only the persistence representation. Applications must decode
|
|
21
|
+
* the validated root and folds models through their specialized TreeNodeApi and
|
|
22
|
+
* FoldsApi before constructing opaque runtime State.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createStateModelSchema<Id extends SerializableKey, Value>(rootSchema: z.ZodType<TreeNodeModel<Id, Value>>, foldsSchema: z.ZodType<FoldsModel<Id>>, cursorSchema: z.ZodType<Cursor<Id>>): z.ZodType<StateModel<Id, Value>>;
|
|
25
|
+
//# sourceMappingURL=state.schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.schemas.d.ts","sourceRoot":"","sources":["../../../src/trees/state/state.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE5E;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CACpB,EAAE,SAAS,eAAe,EAC1B,KAAK,IACH,QAAQ,CAAC;IACX,IAAI,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC/B,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;CACpB,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,EAAE,SAAS,eAAe,EAC1B,KAAK,EAEL,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,EAC/C,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,EACtC,YAAY,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,GAClC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAQlC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a schema for the serializable StateModel DTO.
|
|
4
|
+
*
|
|
5
|
+
* This validates only the persistence representation. Applications must decode
|
|
6
|
+
* the validated root and folds models through their specialized TreeNodeApi and
|
|
7
|
+
* FoldsApi before constructing opaque runtime State.
|
|
8
|
+
*/
|
|
9
|
+
export function createStateModelSchema(rootSchema, foldsSchema, cursorSchema) {
|
|
10
|
+
return z
|
|
11
|
+
.object({
|
|
12
|
+
root: rootSchema,
|
|
13
|
+
folds: foldsSchema,
|
|
14
|
+
cursor: cursorSchema,
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Cursor } from '../cursor.js';
|
|
2
|
+
import type { Folds } from '../fold-node/index.js';
|
|
3
|
+
import type { SerializableKey, TreeNode } from '../tree-node/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* State for navigating a tree and its associated fold-state tree.
|
|
6
|
+
*
|
|
7
|
+
* Invariant: `root` is an open branch.
|
|
8
|
+
*
|
|
9
|
+
* Note: This cannot be represented through a structural intersection because
|
|
10
|
+
* TreeNode is opaque; enforce it when constructing State, using
|
|
11
|
+
* TreeNodeApi.state or TreeNodeApi.getChildren.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam Id - Tree-node sibling ID type.
|
|
14
|
+
* @typeParam Value - Application-owned value stored in every tree node.
|
|
15
|
+
*/
|
|
16
|
+
export type State<Id extends SerializableKey, Value> = Readonly<{
|
|
17
|
+
root: TreeNode<Id, Value>;
|
|
18
|
+
folds: Folds<Id>;
|
|
19
|
+
cursor: Cursor<Id>;
|
|
20
|
+
}>;
|
|
21
|
+
export type StateApi<Id extends SerializableKey, Value> = Readonly<{
|
|
22
|
+
getNodeAtCursor(state: State<Id, Value>): TreeNode<Id, Value> | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Reloads the root's children and all descendant branches that were loaded
|
|
25
|
+
* in the previous state.
|
|
26
|
+
*
|
|
27
|
+
* `loadBranches([])` loads the root's direct children.
|
|
28
|
+
* `loadBranches(['src'])` loads the children of `src`, where `src` is the
|
|
29
|
+
* ID of a root child.
|
|
30
|
+
*
|
|
31
|
+
* Returned nodes must be valid TreeNode values created or decoded through
|
|
32
|
+
* the specialized TreeNodeApi for `Id` and `Value`.
|
|
33
|
+
*/
|
|
34
|
+
resync(oldState: State<Id, Value>, loadBranches: (path: readonly Id[]) => Promise<readonly TreeNode<Id, Value>[]>): Promise<State<Id, Value>>;
|
|
35
|
+
}>;
|
|
36
|
+
//# sourceMappingURL=state.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.types.d.ts","sourceRoot":"","sources":["../../../src/trees/state/state.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEvE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,IAAI,QAAQ,CAAC;IAC9D,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,MAAM,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,IAAI,QAAQ,CAAC;IACjE,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;IAE1E;;;;;;;;;;OAUG;IACH,MAAM,CACJ,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,EAC1B,YAAY,EAAE,CACZ,IAAI,EAAE,SAAS,EAAE,EAAE,KAChB,OAAO,CAAC,SAAS,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,GAC3C,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;CAC9B,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.arb.d.ts","sourceRoot":"","sources":["../../../../src/trees/tree-node/deprecated/tree-node.arb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;GAIG;AACH,KAAK,UAAU,GAAG,MAAM,CAAC;AAOzB;;;GAGG;AACH,eAAO,MAAM,SAAS,sBAIpB,CAAC;AAEH;;GAEG;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAC1D,KAAK,IAAI,GAAG,UAAU,EAAE,CAAC;AAEzB,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,cAAc,EAAE,UAAU,EAAE,CAAC;CAC9B,CAAC;AAuEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,sCAEhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,4BAA4B,sCAExC,CAAC;AAaF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,SAAS,CAAC,sBAAsB,CA0C5D,CAAC;AAEP;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,wCAIjC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;EAK3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;;EAQnC,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B;;;;EAYpC,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,cAAc,qBAE1B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,4BAA8C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.arb.debug.test.d.ts","sourceRoot":"","sources":["../../../../src/trees/tree-node/deprecated/tree-node.arb.debug.test.ts"],"names":[],"mappings":""}
|
package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.js
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it } from 'vitest';
|
|
2
2
|
import * as fc from 'fast-check';
|
|
3
|
-
import { generatedNodeArb, generatedNodesArrayArb
|
|
3
|
+
import { generatedNodeArb, generatedNodesArrayArb } from './tree-node.arb.js';
|
|
4
4
|
/**
|
|
5
5
|
* Used for looking at the tree distributions generated by the arbs.
|
|
6
6
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.impl.d.ts","sourceRoot":"","sources":["../../../../src/trees/tree-node/deprecated/tree-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,KAC5B,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAqGzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.schemas.d.ts","sourceRoot":"","sources":["../../../../src/trees/tree-node/deprecated/tree-node.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEjE;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,iDAA6C,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAC/B,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;EAoBrD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.test.d.ts","sourceRoot":"","sources":["../../../../src/trees/tree-node/deprecated/tree-node.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.types.d.ts","sourceRoot":"","sources":["../../../../src/trees/tree-node/deprecated/tree-node.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,MAAM,QAAQ,CAClB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B,YAAY,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AAEhD,+BAA+B;AAC/B,MAAM,MAAM,YAAY,CAAC,EAAE,SAAS,eAAe,IAAI;IACrD,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,cAAc,CACxB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,oBAAoB,CAAC,EAAE,CAAC,CAAC;AAE5D,uDAAuD;AACvD,MAAM,MAAM,kBAAkB,CAC5B,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B;IACF,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,IAAI,EAAE,CAAC;CAClB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,oBAAoB,CAAC,EAAE,SAAS,eAAe,IAAI;IAC7D,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,IAAI,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,WAAW,CACrB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B;IACF,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;IACpD,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC9D,cAAc,CACZ,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,GAC7B,IAAI,IAAI,oBAAoB,CAAC,EAAE,CAAC,CAAC;IACpC,YAAY,CACV,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,GAC7B,IAAI,IAAI,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAExC;;;;;;;;OAQG;IACH,WAAW,CAAC,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEhE;;;;;;OAMG;IACH,YAAY,CAAC,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAE3E;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,MAAM,EACd,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,GAC/B,MAAM,GAAG,SAAS,CAAC;IAEtB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CACV,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,SAAS,GACzC,IAAI,GAAG,SAAS,CAAC;CACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/trees/tree-node/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as fc from 'fast-check';
|
|
2
|
+
import { TreeNodeModel } from './tree-node.model.js';
|
|
3
|
+
/**
|
|
4
|
+
* The ID and Values types used for the generated test trees.
|
|
5
|
+
*
|
|
6
|
+
* Short strings keep generated counterexamples readable. Values just have to
|
|
7
|
+
* be something easily checked for equality.
|
|
8
|
+
*/
|
|
9
|
+
type TestNodeId = string;
|
|
10
|
+
type TestValue = number;
|
|
11
|
+
type TestNodeModel = TreeNodeModel<TestNodeId, TestValue>;
|
|
12
|
+
type ModelAndPath = {
|
|
13
|
+
path: TestNodeId[];
|
|
14
|
+
model: TestNodeModel;
|
|
15
|
+
};
|
|
16
|
+
type ModelWithReachableModels = {
|
|
17
|
+
model: TestNodeModel;
|
|
18
|
+
/**
|
|
19
|
+
* Includes this model at `[]` and every descendant reachable through loaded
|
|
20
|
+
* branches. Descendants behind unloaded branches are intentionally absent.
|
|
21
|
+
*/
|
|
22
|
+
reachableModels: ModelAndPath[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Generates valid string IDs for test nodes. Note that the API just does
|
|
26
|
+
* comparisons on the IDs so we can safely restrict to any subset.
|
|
27
|
+
*/
|
|
28
|
+
export declare const testIdArb: fc.Arbitrary<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Generates serializable application values for test nodes.
|
|
31
|
+
*/
|
|
32
|
+
export declare const testValueArb: fc.Arbitrary<number>;
|
|
33
|
+
/**
|
|
34
|
+
* Generates a leaf model.
|
|
35
|
+
*/
|
|
36
|
+
export declare const generatedLeafModelArb: fc.Arbitrary<ModelWithReachableModels>;
|
|
37
|
+
/**
|
|
38
|
+
* Generates an unloaded-branch model.
|
|
39
|
+
*/
|
|
40
|
+
export declare const generatedUnloadedBranchModelArb: fc.Arbitrary<ModelWithReachableModels>;
|
|
41
|
+
/**
|
|
42
|
+
* Generates a leaf, an unloaded branch, or a loaded branch.
|
|
43
|
+
*
|
|
44
|
+
* Loaded branches have unique child IDs. Root IDs are made unique separately
|
|
45
|
+
* when generating a forest.
|
|
46
|
+
*
|
|
47
|
+
* The weighted choice gives recursive open branches a geometric dropoff.
|
|
48
|
+
*/
|
|
49
|
+
export declare const generatedNodeArb: fc.Arbitrary<ModelWithReachableModels>;
|
|
50
|
+
/**
|
|
51
|
+
* Generates an array of generated nodes with unique sibling IDs.
|
|
52
|
+
*
|
|
53
|
+
* The result is suitable for use as the loaded children of an open branch.
|
|
54
|
+
*/
|
|
55
|
+
export declare const generatedNodesArrayArb: fc.Arbitrary<ModelWithReachableModels[]>;
|
|
56
|
+
/**
|
|
57
|
+
* An array of root-level nodes and all paths that can be resolved through
|
|
58
|
+
* those nodes.
|
|
59
|
+
*
|
|
60
|
+
* Paths through closed branches stop at the closed branch because its
|
|
61
|
+
* descendants are not loaded.
|
|
62
|
+
*/
|
|
63
|
+
export declare const nodeAndPathsArb: fc.Arbitrary<{
|
|
64
|
+
root: TestNodeModel;
|
|
65
|
+
pathEntries: ModelAndPath[];
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Generates root-level nodes and a path to one of their reachable nodes.
|
|
69
|
+
*
|
|
70
|
+
* Every generated path is valid for `getAtPath`; unreachable paths are not
|
|
71
|
+
* included.
|
|
72
|
+
*/
|
|
73
|
+
export declare const nodeAndPathAndExpectedArb: fc.Arbitrary<{
|
|
74
|
+
root: TestNodeModel;
|
|
75
|
+
path: string[];
|
|
76
|
+
expected: TestNodeModel;
|
|
77
|
+
}>;
|
|
78
|
+
/**
|
|
79
|
+
* Generates root-level nodes and a path to one of their branch nodes.
|
|
80
|
+
*
|
|
81
|
+
* Both open and closed branches are included. A path to a closed branch is
|
|
82
|
+
* valid, but a path through a closed branch cannot reach any descendants.
|
|
83
|
+
*/
|
|
84
|
+
export declare const nodesArrayAndBranchPathArb: fc.Arbitrary<{
|
|
85
|
+
root: TestNodeModel;
|
|
86
|
+
path: string[];
|
|
87
|
+
expected: TestNodeModel;
|
|
88
|
+
}>;
|
|
89
|
+
/**
|
|
90
|
+
* Generates a new array of loaded child nodes.
|
|
91
|
+
*
|
|
92
|
+
* The generated nodes have unique IDs, making the result suitable for use as
|
|
93
|
+
* the children of an open branch.
|
|
94
|
+
*/
|
|
95
|
+
export declare const newBranchesArb: fc.Arbitrary<TestNodeModel[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Generates either a new array of loaded child nodes or `null`.
|
|
98
|
+
*
|
|
99
|
+
* An array represents an open branch, including an open branch with no
|
|
100
|
+
* children. `null` represents a closed branch.
|
|
101
|
+
*/
|
|
102
|
+
export declare const newBranchesOrNullArb: fc.Arbitrary<TestNodeModel[] | null>;
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=tree-node.arb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.arb.d.ts","sourceRoot":"","sources":["../../../src/trees/tree-node/tree-node.arb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAGjC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;;GAKG;AACH,KAAK,UAAU,GAAG,MAAM,CAAC;AACzB,KAAK,SAAS,GAAG,MAAM,CAAC;AAGxB,KAAK,aAAa,GAAG,aAAa,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAE1D,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,KAAK,EAAE,aAAa,CAAC;CACtB,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB;;;OAGG;IACH,eAAe,EAAE,YAAY,EAAE,CAAC;CACjC,CAAC;AAOF;;;GAGG;AACH,eAAO,MAAM,SAAS,sBAIpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY,sBAGvB,CAAC;AAoEH;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,SAAS,CAAC,wBAAwB,CAGjB,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,+BAA+B,EAAE,EAAE,CAAC,SAAS,CAAC,wBAAwB,CAGjB,CAAC;AAanE;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,SAAS,CAAC,wBAAwB,CAwC9D,CAAC;AAEP;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,0CAIjC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;EAK3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;;EAQnC,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B;;;;EAYpC,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,cAAc,+BAE1B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,sCAA8C,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import * as fc from 'fast-check';
|
|
2
|
+
/**
|
|
3
|
+
* Characters used for readable generated node IDs.
|
|
4
|
+
*/
|
|
5
|
+
const smallLetterCharacters = Array.from('abcdefghijklmnopqrstuvwxyz');
|
|
6
|
+
/**
|
|
7
|
+
* Generates valid string IDs for test nodes. Note that the API just does
|
|
8
|
+
* comparisons on the IDs so we can safely restrict to any subset.
|
|
9
|
+
*/
|
|
10
|
+
export const testIdArb = fc.string({
|
|
11
|
+
unit: fc.constantFrom(...smallLetterCharacters),
|
|
12
|
+
minLength: 1,
|
|
13
|
+
maxLength: 8,
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Generates serializable application values for test nodes.
|
|
17
|
+
*/
|
|
18
|
+
export const testValueArb = fc.integer({
|
|
19
|
+
min: -10_000,
|
|
20
|
+
max: 10_000,
|
|
21
|
+
});
|
|
22
|
+
const generatedLeafModel = (id, value) => {
|
|
23
|
+
const model = {
|
|
24
|
+
id,
|
|
25
|
+
value,
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
model,
|
|
29
|
+
reachableModels: [{ path: [], model }],
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
function generatedUnloadedBranchModel(id, value) {
|
|
33
|
+
const model = {
|
|
34
|
+
id,
|
|
35
|
+
value,
|
|
36
|
+
children: null,
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
model,
|
|
40
|
+
reachableModels: [{ path: [], model }],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates the metadata for a loaded branch and all descendants reachable
|
|
45
|
+
* through its loaded child collection.
|
|
46
|
+
*
|
|
47
|
+
* Child-array order is not semantically meaningful to the tree API. The
|
|
48
|
+
* generator happens to use an array because `TreeNodeModel` is serializable.
|
|
49
|
+
*/
|
|
50
|
+
function generatedLoadedBranchModel(id, value, children) {
|
|
51
|
+
const model = {
|
|
52
|
+
id,
|
|
53
|
+
value,
|
|
54
|
+
children: children.map(({ model: child }) => child),
|
|
55
|
+
};
|
|
56
|
+
const reachableDescendants = children.flatMap(({ model: childModel, reachableModels }) => reachableModels.map(({ path, model: reachableModel }) => ({
|
|
57
|
+
path: [childModel.id, ...path],
|
|
58
|
+
model: reachableModel,
|
|
59
|
+
})));
|
|
60
|
+
return {
|
|
61
|
+
model,
|
|
62
|
+
reachableModels: [
|
|
63
|
+
{ path: [], model },
|
|
64
|
+
...reachableDescendants,
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generates a leaf model.
|
|
70
|
+
*/
|
|
71
|
+
export const generatedLeafModelArb = fc
|
|
72
|
+
.tuple(testIdArb, testValueArb)
|
|
73
|
+
.map(([id, value]) => generatedLeafModel(id, value));
|
|
74
|
+
/**
|
|
75
|
+
* Generates an unloaded-branch model.
|
|
76
|
+
*/
|
|
77
|
+
export const generatedUnloadedBranchModelArb = fc
|
|
78
|
+
.tuple(testIdArb, testValueArb)
|
|
79
|
+
.map(([id, value]) => generatedUnloadedBranchModel(id, value));
|
|
80
|
+
/**
|
|
81
|
+
* Generates either kind of terminal model.
|
|
82
|
+
*
|
|
83
|
+
* Both leaves and unloaded branches terminate path traversal.
|
|
84
|
+
*/
|
|
85
|
+
const generatedTerminalModelArb = fc.oneof(generatedLeafModelArb, generatedUnloadedBranchModelArb);
|
|
86
|
+
/**
|
|
87
|
+
* Generates a leaf, an unloaded branch, or a loaded branch.
|
|
88
|
+
*
|
|
89
|
+
* Loaded branches have unique child IDs. Root IDs are made unique separately
|
|
90
|
+
* when generating a forest.
|
|
91
|
+
*
|
|
92
|
+
* The weighted choice gives recursive open branches a geometric dropoff.
|
|
93
|
+
*/
|
|
94
|
+
export const generatedNodeArb = fc.letrec((tie) => {
|
|
95
|
+
const generatedChildModelArb = tie('node');
|
|
96
|
+
/**
|
|
97
|
+
* Generates the loaded children of an open branch.
|
|
98
|
+
*
|
|
99
|
+
* Sibling IDs must be unique so that child lookup is unambiguous. The
|
|
100
|
+
* maximum length also limits the branching factor of recursive examples.
|
|
101
|
+
*/
|
|
102
|
+
const generatedOpenChildrenArb = fc.uniqueArray(generatedChildModelArb, {
|
|
103
|
+
minLength: 0,
|
|
104
|
+
maxLength: 4,
|
|
105
|
+
size: 'medium',
|
|
106
|
+
selector: ({ model }) => model.id,
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* Generates an open branch and its reachable descendants.
|
|
110
|
+
*/
|
|
111
|
+
const generatedLoadedBranchModelArb = fc
|
|
112
|
+
.tuple(testIdArb, testValueArb, generatedOpenChildrenArb)
|
|
113
|
+
.map(([id, value, children]) => generatedLoadedBranchModel(id, value, children));
|
|
114
|
+
return {
|
|
115
|
+
node: fc
|
|
116
|
+
.integer({
|
|
117
|
+
min: 0,
|
|
118
|
+
max: 9999,
|
|
119
|
+
})
|
|
120
|
+
.chain((choice) =>
|
|
121
|
+
// 5046 works for getAtPath tests, but too heavy for modifyAtPath tests
|
|
122
|
+
choice >= 5050
|
|
123
|
+
? generatedLoadedBranchModelArb
|
|
124
|
+
: generatedTerminalModelArb),
|
|
125
|
+
};
|
|
126
|
+
}).node;
|
|
127
|
+
/**
|
|
128
|
+
* Generates an array of generated nodes with unique sibling IDs.
|
|
129
|
+
*
|
|
130
|
+
* The result is suitable for use as the loaded children of an open branch.
|
|
131
|
+
*/
|
|
132
|
+
export const generatedNodesArrayArb = fc.uniqueArray(generatedNodeArb, {
|
|
133
|
+
minLength: 0,
|
|
134
|
+
size: 'small',
|
|
135
|
+
selector: (generated) => generated.model.id,
|
|
136
|
+
});
|
|
137
|
+
/**
|
|
138
|
+
* An array of root-level nodes and all paths that can be resolved through
|
|
139
|
+
* those nodes.
|
|
140
|
+
*
|
|
141
|
+
* Paths through closed branches stop at the closed branch because its
|
|
142
|
+
* descendants are not loaded.
|
|
143
|
+
*/
|
|
144
|
+
export const nodeAndPathsArb = generatedNodeArb.map(({ model: root, reachableModels }) => ({
|
|
145
|
+
root,
|
|
146
|
+
pathEntries: reachableModels,
|
|
147
|
+
}));
|
|
148
|
+
/**
|
|
149
|
+
* Generates root-level nodes and a path to one of their reachable nodes.
|
|
150
|
+
*
|
|
151
|
+
* Every generated path is valid for `getAtPath`; unreachable paths are not
|
|
152
|
+
* included.
|
|
153
|
+
*/
|
|
154
|
+
export const nodeAndPathAndExpectedArb = nodeAndPathsArb
|
|
155
|
+
.filter(({ pathEntries }) => pathEntries.length > 0)
|
|
156
|
+
.chain(({ root, pathEntries }) => fc.constantFrom(...pathEntries).map(({ path, model }) => ({
|
|
157
|
+
root,
|
|
158
|
+
path,
|
|
159
|
+
expected: model,
|
|
160
|
+
})));
|
|
161
|
+
/**
|
|
162
|
+
* Generates root-level nodes and a path to one of their branch nodes.
|
|
163
|
+
*
|
|
164
|
+
* Both open and closed branches are included. A path to a closed branch is
|
|
165
|
+
* valid, but a path through a closed branch cannot reach any descendants.
|
|
166
|
+
*/
|
|
167
|
+
export const nodesArrayAndBranchPathArb = nodeAndPathsArb
|
|
168
|
+
.map(({ root, pathEntries }) => ({
|
|
169
|
+
root,
|
|
170
|
+
branchPathEntries: pathEntries.filter(({ model }) => 'children' in model),
|
|
171
|
+
}))
|
|
172
|
+
.filter(({ branchPathEntries }) => branchPathEntries.length > 0)
|
|
173
|
+
.chain(({ root, branchPathEntries }) => fc.constantFrom(...branchPathEntries).map(({ path, model }) => ({
|
|
174
|
+
root,
|
|
175
|
+
path,
|
|
176
|
+
expected: model,
|
|
177
|
+
})));
|
|
178
|
+
/**
|
|
179
|
+
* Generates a new array of loaded child nodes.
|
|
180
|
+
*
|
|
181
|
+
* The generated nodes have unique IDs, making the result suitable for use as
|
|
182
|
+
* the children of an open branch.
|
|
183
|
+
*/
|
|
184
|
+
export const newBranchesArb = generatedNodesArrayArb.map((generatedNodes) => generatedNodes.map(({ model }) => model));
|
|
185
|
+
/**
|
|
186
|
+
* Generates either a new array of loaded child nodes or `null`.
|
|
187
|
+
*
|
|
188
|
+
* An array represents an open branch, including an open branch with no
|
|
189
|
+
* children. `null` represents a closed branch.
|
|
190
|
+
*/
|
|
191
|
+
export const newBranchesOrNullArb = fc.oneof(newBranchesArb, fc.constant(null));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TreeNodeApi } from './tree-node.types.js';
|
|
2
|
+
import type { SerializableKey } from './tree-node.model.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates an API for constructing, inspecting, and immutably updating opaque
|
|
5
|
+
* tree nodes.
|
|
6
|
+
*
|
|
7
|
+
* The returned nodes are ordinary JavaScript objects at runtime. Their
|
|
8
|
+
* structural representation is private to this module; consumers interact
|
|
9
|
+
* with them only through the returned API.
|
|
10
|
+
*
|
|
11
|
+
* Tree updates preserve object identity for unrelated nodes. New ancestor
|
|
12
|
+
* nodes and child collections are created only along an updated path.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createTreeNodeApi<Id extends SerializableKey, Value>(): TreeNodeApi<Id, Value>;
|
|
15
|
+
//# sourceMappingURL=tree-node.impl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.impl.d.ts","sourceRoot":"","sources":["../../../src/trees/tree-node/tree-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EAGZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AAqJ3E;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,SAAS,eAAe,EAC1B,KAAK,KACF,WAAW,CAAC,EAAE,EAAE,KAAK,CAAC,CAiR1B"}
|