dirvi-lib 0.8.1 → 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.
Files changed (178) hide show
  1. package/dist/app/app-api.d.ts +74 -0
  2. package/dist/app/app-api.d.ts.map +1 -0
  3. package/dist/app/app-api.js +11 -0
  4. package/dist/app/effect-to-action.d.ts +6 -0
  5. package/dist/app/effect-to-action.d.ts.map +1 -0
  6. package/dist/app/effect-to-action.js +74 -0
  7. package/dist/app/index.d.ts +6 -0
  8. package/dist/app/index.d.ts.map +1 -0
  9. package/dist/app/index.js +5 -0
  10. package/dist/app/reducer-action.d.ts +22 -0
  11. package/dist/app/reducer-action.d.ts.map +1 -0
  12. package/dist/app/reducer.d.ts +5 -0
  13. package/dist/app/reducer.d.ts.map +1 -0
  14. package/dist/app/reducer.js +79 -0
  15. package/dist/app/view-api.d.ts +11 -0
  16. package/dist/app/view-api.d.ts.map +1 -0
  17. package/dist/index.d.ts +4 -7
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +4 -7
  20. package/dist/input/application/index.d.ts.map +1 -0
  21. package/dist/input/application/intent-to-effect-no-state.test.d.ts.map +1 -0
  22. package/dist/input/application/intent-to-effect.d.ts +14 -0
  23. package/dist/input/application/intent-to-effect.d.ts.map +1 -0
  24. package/dist/{tree-surfer-ui → input}/application/intent-to-effect.js +36 -17
  25. package/dist/input/application/parse-command.d.ts +14 -0
  26. package/dist/input/application/parse-command.d.ts.map +1 -0
  27. package/dist/{tree-surfer-ui → input}/application/parse-command.js +13 -1
  28. package/dist/input/application/user-input-to-intent.d.ts.map +1 -0
  29. package/dist/input/application/user-input-to-intent.test.d.ts.map +1 -0
  30. package/dist/input/domain/effect.d.ts +55 -0
  31. package/dist/input/domain/effect.d.ts.map +1 -0
  32. package/dist/input/domain/index.d.ts.map +1 -0
  33. package/dist/input/domain/input-mode-state.d.ts.map +1 -0
  34. package/dist/input/domain/intent.d.ts.map +1 -0
  35. package/dist/input/domain/user-input.d.ts.map +1 -0
  36. package/dist/trees/cursor.d.ts.map +1 -0
  37. package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts +10 -0
  38. package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts.map +1 -0
  39. package/dist/trees/fold-node/deprecated/fold-node.impl.js +248 -0
  40. package/dist/trees/fold-node/deprecated/fold-node.test.d.ts +2 -0
  41. package/dist/trees/fold-node/deprecated/fold-node.test.d.ts.map +1 -0
  42. package/dist/trees/fold-node/deprecated/fold-node.test.js +130 -0
  43. package/dist/{tree-surfer/fold-node → trees/fold-node/deprecated}/fold-node.types.d.ts +15 -76
  44. package/dist/trees/fold-node/deprecated/fold-node.types.d.ts.map +1 -0
  45. package/dist/trees/fold-node/fold-node.impl.d.ts +16 -0
  46. package/dist/trees/fold-node/fold-node.impl.d.ts.map +1 -0
  47. package/dist/trees/fold-node/fold-node.impl.js +451 -0
  48. package/dist/trees/fold-node/fold-node.model.d.ts +120 -0
  49. package/dist/trees/fold-node/fold-node.model.d.ts.map +1 -0
  50. package/dist/trees/fold-node/fold-node.model.js +80 -0
  51. package/dist/trees/fold-node/fold-node.types.d.ts +206 -0
  52. package/dist/trees/fold-node/fold-node.types.d.ts.map +1 -0
  53. package/dist/trees/fold-node/index.d.ts +4 -0
  54. package/dist/trees/fold-node/index.d.ts.map +1 -0
  55. package/dist/trees/fold-node/index.js +3 -0
  56. package/dist/trees/index.d.ts +9 -0
  57. package/dist/trees/index.d.ts.map +1 -0
  58. package/dist/trees/index.js +8 -0
  59. package/dist/trees/nav-node/nav-node.impl.d.ts +29 -0
  60. package/dist/trees/nav-node/nav-node.impl.d.ts.map +1 -0
  61. package/dist/{tree-surfer → trees}/nav-node/nav-node.impl.js +89 -42
  62. package/dist/trees/nav-node/nav-node.types.d.ts +103 -0
  63. package/dist/trees/nav-node/nav-node.types.d.ts.map +1 -0
  64. package/dist/trees/state/state.impl.d.ts +8 -0
  65. package/dist/trees/state/state.impl.d.ts.map +1 -0
  66. package/dist/trees/state/state.impl.js +128 -0
  67. package/dist/trees/state/state.schemas.d.ts +25 -0
  68. package/dist/trees/state/state.schemas.d.ts.map +1 -0
  69. package/dist/trees/state/state.schemas.js +17 -0
  70. package/dist/trees/state/state.types.d.ts +36 -0
  71. package/dist/trees/state/state.types.d.ts.map +1 -0
  72. package/dist/trees/state/state.types.js +1 -0
  73. package/dist/trees/tree-node/deprecated/tree-node.arb.d.ts.map +1 -0
  74. package/dist/trees/tree-node/deprecated/tree-node.arb.debug.test.d.ts.map +1 -0
  75. package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.js +1 -1
  76. package/dist/trees/tree-node/deprecated/tree-node.impl.d.ts.map +1 -0
  77. package/dist/trees/tree-node/deprecated/tree-node.schemas.d.ts.map +1 -0
  78. package/dist/trees/tree-node/deprecated/tree-node.test.d.ts.map +1 -0
  79. package/dist/trees/tree-node/deprecated/tree-node.types.d.ts.map +1 -0
  80. package/dist/trees/tree-node/deprecated/tree-node.types.js +1 -0
  81. package/dist/trees/tree-node/index.d.ts +4 -0
  82. package/dist/trees/tree-node/index.d.ts.map +1 -0
  83. package/dist/trees/tree-node/index.js +3 -0
  84. package/dist/trees/tree-node/tree-node.arb.d.ts +104 -0
  85. package/dist/trees/tree-node/tree-node.arb.d.ts.map +1 -0
  86. package/dist/trees/tree-node/tree-node.arb.js +191 -0
  87. package/dist/trees/tree-node/tree-node.impl.d.ts +15 -0
  88. package/dist/trees/tree-node/tree-node.impl.d.ts.map +1 -0
  89. package/dist/trees/tree-node/tree-node.impl.js +286 -0
  90. package/dist/trees/tree-node/tree-node.model.d.ts +78 -0
  91. package/dist/trees/tree-node/tree-node.model.d.ts.map +1 -0
  92. package/dist/trees/tree-node/tree-node.model.js +37 -0
  93. package/dist/trees/tree-node/tree-node.types.d.ts +419 -0
  94. package/dist/trees/tree-node/tree-node.types.d.ts.map +1 -0
  95. package/dist/trees/tree-node/tree-node.types.js +1 -0
  96. package/package.json +1 -1
  97. package/dist/tree-surfer/cursor.d.ts.map +0 -1
  98. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +0 -2
  99. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +0 -1
  100. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +0 -4
  101. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +0 -1
  102. package/dist/tree-surfer/fold-node/fold-node.impl.js +0 -149
  103. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +0 -5
  104. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +0 -1
  105. package/dist/tree-surfer/fold-node/fold-node.schema.js +0 -9
  106. package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +0 -1
  107. package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +0 -1
  108. package/dist/tree-surfer/fold-node/tree-node.test.js +0 -90
  109. package/dist/tree-surfer/index.d.ts +0 -13
  110. package/dist/tree-surfer/index.d.ts.map +0 -1
  111. package/dist/tree-surfer/index.js +0 -12
  112. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +0 -6
  113. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +0 -1
  114. package/dist/tree-surfer/nav-node/nav-node.types.d.ts +0 -70
  115. package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +0 -1
  116. package/dist/tree-surfer/state/state.impl.d.ts +0 -7
  117. package/dist/tree-surfer/state/state.impl.d.ts.map +0 -1
  118. package/dist/tree-surfer/state/state.impl.js +0 -89
  119. package/dist/tree-surfer/state/state.schemas.d.ts +0 -9
  120. package/dist/tree-surfer/state/state.schemas.d.ts.map +0 -1
  121. package/dist/tree-surfer/state/state.schemas.js +0 -8
  122. package/dist/tree-surfer/state/state.types.d.ts +0 -21
  123. package/dist/tree-surfer/state/state.types.d.ts.map +0 -1
  124. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +0 -1
  125. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +0 -1
  126. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +0 -1
  127. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +0 -1
  128. package/dist/tree-surfer/tree-node/tree-node.test.d.ts +0 -2
  129. package/dist/tree-surfer/tree-node/tree-node.test.d.ts.map +0 -1
  130. package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +0 -1
  131. package/dist/tree-surfer-ui/application/index.d.ts.map +0 -1
  132. package/dist/tree-surfer-ui/application/intent-to-effect-no-state.test.d.ts.map +0 -1
  133. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +0 -10
  134. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +0 -1
  135. package/dist/tree-surfer-ui/application/parse-command.d.ts +0 -4
  136. package/dist/tree-surfer-ui/application/parse-command.d.ts.map +0 -1
  137. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +0 -1
  138. package/dist/tree-surfer-ui/application/user-input-to-intent.test.d.ts.map +0 -1
  139. package/dist/tree-surfer-ui/domain/effect.d.ts +0 -43
  140. package/dist/tree-surfer-ui/domain/effect.d.ts.map +0 -1
  141. package/dist/tree-surfer-ui/domain/index.d.ts.map +0 -1
  142. package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +0 -1
  143. package/dist/tree-surfer-ui/domain/intent.d.ts.map +0 -1
  144. package/dist/tree-surfer-ui/domain/user-input.d.ts.map +0 -1
  145. /package/dist/{tree-surfer-ui/domain/effect.js → app/reducer-action.js} +0 -0
  146. /package/dist/{tree-surfer-ui/domain/input-mode-state.js → app/view-api.js} +0 -0
  147. /package/dist/{tree-surfer-ui → input}/application/index.d.ts +0 -0
  148. /package/dist/{tree-surfer-ui → input}/application/index.js +0 -0
  149. /package/dist/{tree-surfer-ui → input}/application/intent-to-effect-no-state.test.d.ts +0 -0
  150. /package/dist/{tree-surfer-ui → input}/application/intent-to-effect-no-state.test.js +0 -0
  151. /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.d.ts +0 -0
  152. /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.js +0 -0
  153. /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.test.d.ts +0 -0
  154. /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.test.js +0 -0
  155. /package/dist/{tree-surfer-ui/domain/intent.js → input/domain/effect.js} +0 -0
  156. /package/dist/{tree-surfer-ui → input}/domain/index.d.ts +0 -0
  157. /package/dist/{tree-surfer-ui → input}/domain/index.js +0 -0
  158. /package/dist/{tree-surfer-ui → input}/domain/input-mode-state.d.ts +0 -0
  159. /package/dist/{tree-surfer-ui/domain/user-input.js → input/domain/input-mode-state.js} +0 -0
  160. /package/dist/{tree-surfer-ui → input}/domain/intent.d.ts +0 -0
  161. /package/dist/{tree-surfer/fold-node/fold-node.arb.js → input/domain/intent.js} +0 -0
  162. /package/dist/{tree-surfer-ui → input}/domain/user-input.d.ts +0 -0
  163. /package/dist/{tree-surfer/fold-node/fold-node.types.js → input/domain/user-input.js} +0 -0
  164. /package/dist/{tree-surfer → trees}/cursor.d.ts +0 -0
  165. /package/dist/{tree-surfer → trees}/cursor.js +0 -0
  166. /package/dist/{tree-surfer/nav-node/nav-node.types.js → trees/fold-node/deprecated/fold-node.types.js} +0 -0
  167. /package/dist/{tree-surfer/state/state.types.js → trees/fold-node/fold-node.types.js} +0 -0
  168. /package/dist/{tree-surfer/tree-node/tree-node.types.js → trees/nav-node/nav-node.types.js} +0 -0
  169. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.d.ts +0 -0
  170. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.d.ts +0 -0
  171. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.js +0 -0
  172. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.impl.d.ts +0 -0
  173. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.impl.js +0 -0
  174. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.schemas.d.ts +0 -0
  175. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.schemas.js +0 -0
  176. /package/dist/{tree-surfer/fold-node → trees/tree-node/deprecated}/tree-node.test.d.ts +0 -0
  177. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.test.js +0 -0
  178. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.types.d.ts +0 -0
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Creates semantic operations for a fold-state tree.
3
+ *
4
+ * Structural children are owned by `FoldNodeApi`; folded children are
5
+ * application-owned FoldNode values stored in `FoldNodeValue`.
6
+ */
7
+ export function createFoldNodeService(foldNodeApi) {
8
+ function createEmptyNode(id) {
9
+ const node = foldNodeApi.createLoadedBranch(id, { foldedChildren: [] }, []);
10
+ /*
11
+ * An empty child collection cannot contain duplicate sibling IDs, so this
12
+ * would indicate a broken TreeNodeApi implementation or changed contract.
13
+ */
14
+ if (node === undefined) {
15
+ throw new Error('Could not create an empty fold node');
16
+ }
17
+ return node;
18
+ }
19
+ /**
20
+ * Returns an array snapshot of loaded structural children.
21
+ *
22
+ * Fold nodes are intended always to be open branches. `undefined` therefore
23
+ * signals an invalid FoldNode supplied to this service, for example one made
24
+ * with `asLeaf` or `asClosedBranch` through the lower-level tree API.
25
+ */
26
+ function getStructuralChildren(node) {
27
+ const children = foldNodeApi.getLoadedChildren(node);
28
+ return children === undefined ? undefined : [...children];
29
+ }
30
+ function getFoldedChildren(node) {
31
+ return foldNodeApi.value(node).foldedChildren;
32
+ }
33
+ function getFoldedChildById(node, id) {
34
+ return getFoldedChildren(node).find((child) => foldNodeApi.id(child) === id);
35
+ }
36
+ function hasFoldedChildWithId(node, id) {
37
+ return getFoldedChildById(node, id) !== undefined;
38
+ }
39
+ /**
40
+ * Replaces only fold-domain data, retaining the node's ID and structural
41
+ * child state.
42
+ */
43
+ function withFoldedChildren(node, foldedChildren) {
44
+ return foldNodeApi.withValue(node, {
45
+ ...foldNodeApi.value(node),
46
+ foldedChildren,
47
+ });
48
+ }
49
+ /**
50
+ * Replaces structural children while requiring that the source node is
51
+ * already an open branch.
52
+ *
53
+ * `withChildren` could convert any tree node into an open branch. That is
54
+ * useful in the generic API, but FoldNodeService wants to preserve its
55
+ * invariant that every FoldNode is already an open branch.
56
+ */
57
+ function withStructuralChildren(node, children) {
58
+ if (foldNodeApi.getLoadedChildren(node) === undefined) {
59
+ return undefined;
60
+ }
61
+ return foldNodeApi.withLoadedChildren(node, children);
62
+ }
63
+ /**
64
+ * Returns whether a fold-state node carries state below itself.
65
+ *
66
+ * A folded node has nested state when it has either:
67
+ *
68
+ * - structural child fold nodes; or
69
+ * - folded direct children.
70
+ */
71
+ function hasNestedFoldState(node) {
72
+ const structuralChildren = getStructuralChildren(node);
73
+ if (structuralChildren === undefined) {
74
+ return undefined;
75
+ }
76
+ return structuralChildren.length > 0 || getFoldedChildren(node).length > 0;
77
+ }
78
+ return {
79
+ createEmptyNode,
80
+ getIfEntryFoldedAtPath(rootNode, path, entryId) {
81
+ return (foldNodeApi.selectAtPath(rootNode, path, (node) => hasFoldedChildWithId(node, entryId)) ?? false);
82
+ },
83
+ foldedEntriesAtPath(rootNode, path) {
84
+ return foldNodeApi.selectAtPath(rootNode, path, (node) => getFoldedChildren(node).map(foldNodeApi.id));
85
+ },
86
+ addFoldedEntryAtPath(rootNode, path, entryId) {
87
+ const rootWithPath = ensurePath(rootNode, path);
88
+ if (rootWithPath === undefined) {
89
+ return undefined;
90
+ }
91
+ return foldNodeApi.updateAtPath(rootWithPath, path, (node) => addFoldedEntry(node, entryId));
92
+ },
93
+ removeFoldedEntryAtPath(rootNode, path, entryId) {
94
+ return foldNodeApi.updateAtPath(rootNode, path, (node) => removeFoldedEntry(node, entryId));
95
+ },
96
+ clearFoldedEntriesAtPath(rootNode, path) {
97
+ return foldNodeApi.updateAtPath(rootNode, path, clearFoldedEntries);
98
+ },
99
+ };
100
+ /**
101
+ * Folds a direct child at one node.
102
+ *
103
+ * If the child already exists as a structural child, it moves to
104
+ * `foldedChildren`, preserving any fold state beneath it. If it does not
105
+ * exist in either collection, a new empty fold node is created.
106
+ */
107
+ function addFoldedEntry(node, entryId) {
108
+ if (hasFoldedChildWithId(node, entryId)) {
109
+ return node;
110
+ }
111
+ const children = getStructuralChildren(node);
112
+ if (children === undefined) {
113
+ return undefined;
114
+ }
115
+ const childIndex = children.findIndex((child) => foldNodeApi.id(child) === entryId);
116
+ const foldedChild = childIndex === -1 ? createEmptyNode(entryId) : children[childIndex];
117
+ if (foldedChild === undefined) {
118
+ return undefined;
119
+ }
120
+ const remainingChildren = childIndex === -1
121
+ ? children
122
+ : [...children.slice(0, childIndex), ...children.slice(childIndex + 1)];
123
+ const nodeWithoutStructuralChild = withStructuralChildren(node, remainingChildren);
124
+ if (nodeWithoutStructuralChild === undefined) {
125
+ return undefined;
126
+ }
127
+ return withFoldedChildren(nodeWithoutStructuralChild, [
128
+ ...getFoldedChildren(nodeWithoutStructuralChild),
129
+ foldedChild,
130
+ ]);
131
+ }
132
+ /**
133
+ * Unfolds a direct child at one node.
134
+ *
135
+ * A folded child that has nested fold state is moved into structural
136
+ * children. A folded child without nested state is omitted entirely.
137
+ */
138
+ function removeFoldedEntry(node, entryId) {
139
+ const foldedChildren = getFoldedChildren(node);
140
+ const foldedChildIndex = foldedChildren.findIndex((child) => foldNodeApi.id(child) === entryId);
141
+ if (foldedChildIndex === -1) {
142
+ return node;
143
+ }
144
+ const foldedChild = foldedChildren[foldedChildIndex];
145
+ if (foldedChild === undefined) {
146
+ return undefined;
147
+ }
148
+ const hasNestedState = hasNestedFoldState(foldedChild);
149
+ if (hasNestedState === undefined) {
150
+ return undefined;
151
+ }
152
+ const remainingFoldedChildren = [
153
+ ...foldedChildren.slice(0, foldedChildIndex),
154
+ ...foldedChildren.slice(foldedChildIndex + 1),
155
+ ];
156
+ const nodeWithoutFoldedChild = withFoldedChildren(node, remainingFoldedChildren);
157
+ if (!hasNestedState) {
158
+ return nodeWithoutFoldedChild;
159
+ }
160
+ const children = getStructuralChildren(nodeWithoutFoldedChild);
161
+ if (children === undefined) {
162
+ return undefined;
163
+ }
164
+ return withStructuralChildren(nodeWithoutFoldedChild, [
165
+ ...children,
166
+ foldedChild,
167
+ ]);
168
+ }
169
+ /**
170
+ * Unfolds all directly folded children at one node.
171
+ *
172
+ * Folded children that contain nested state become structural children;
173
+ * empty folded children are omitted.
174
+ */
175
+ function clearFoldedEntries(node) {
176
+ const foldedChildren = getFoldedChildren(node);
177
+ if (foldedChildren.length === 0) {
178
+ return node;
179
+ }
180
+ const children = getStructuralChildren(node);
181
+ if (children === undefined) {
182
+ return undefined;
183
+ }
184
+ const retainedFoldedChildren = [];
185
+ for (const foldedChild of foldedChildren) {
186
+ const nestedState = hasNestedFoldState(foldedChild);
187
+ if (nestedState === undefined) {
188
+ return undefined;
189
+ }
190
+ if (nestedState) {
191
+ retainedFoldedChildren.push(foldedChild);
192
+ }
193
+ }
194
+ const nodeWithoutFoldedChildren = withFoldedChildren(node, []);
195
+ return withStructuralChildren(nodeWithoutFoldedChildren, [
196
+ ...children,
197
+ ...retainedFoldedChildren,
198
+ ]);
199
+ }
200
+ /**
201
+ * Ensures the path exists as structural fold nodes.
202
+ *
203
+ * A path may not pass through a folded child: folded children represent
204
+ * entries whose contents are collapsed, so they cannot simultaneously be
205
+ * part of the visible structural fold-state path.
206
+ */
207
+ function ensurePath(rootNode, path) {
208
+ return ensurePathAtIndex(rootNode, path, 0);
209
+ }
210
+ function ensurePathAtIndex(node, path, pathIndex) {
211
+ if (pathIndex === path.length) {
212
+ return node;
213
+ }
214
+ const childId = path[pathIndex];
215
+ if (childId === undefined) {
216
+ return undefined;
217
+ }
218
+ /*
219
+ * A folded child is intentionally absent from the structural child tree.
220
+ * Do not create a duplicate FoldNode with the same ID in `children`.
221
+ */
222
+ if (hasFoldedChildWithId(node, childId)) {
223
+ return undefined;
224
+ }
225
+ const children = getStructuralChildren(node);
226
+ if (children === undefined) {
227
+ return undefined;
228
+ }
229
+ const childIndex = children.findIndex((child) => foldNodeApi.id(child) === childId);
230
+ const child = childIndex === -1 ? createEmptyNode(childId) : children[childIndex];
231
+ if (child === undefined) {
232
+ return undefined;
233
+ }
234
+ const updatedChild = ensurePathAtIndex(child, path, pathIndex + 1);
235
+ if (updatedChild === undefined) {
236
+ return undefined;
237
+ }
238
+ if (childIndex === -1) {
239
+ return withStructuralChildren(node, [...children, updatedChild]);
240
+ }
241
+ if (updatedChild === child) {
242
+ return node;
243
+ }
244
+ const updatedChildren = [...children];
245
+ updatedChildren[childIndex] = updatedChild;
246
+ return withStructuralChildren(node, updatedChildren);
247
+ }
248
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fold-node.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold-node.test.d.ts","sourceRoot":"","sources":["../../../../src/trees/fold-node/deprecated/fold-node.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,130 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createTreeNodeApi } from '../../tree-node/tree-node.impl.js';
3
+ import { createFoldNodeService } from './fold-node.impl.js';
4
+ const foldNodeApi = createTreeNodeApi();
5
+ const service = createFoldNodeService(foldNodeApi);
6
+ function createFoldNode(id, options = {}) {
7
+ const node = foldNodeApi.createLoadedBranch(id, {
8
+ foldedChildren: options.foldedChildren ?? [],
9
+ }, options.children ?? []);
10
+ if (node === undefined) {
11
+ throw new Error('Test fixture has duplicate structural child IDs');
12
+ }
13
+ return node;
14
+ }
15
+ function getStructuralChildren(node) {
16
+ const children = foldNodeApi.getLoadedChildren(node);
17
+ if (children === undefined) {
18
+ throw new Error('Expected test FoldNode to be an open branch');
19
+ }
20
+ return [...children];
21
+ }
22
+ function getFoldedChildren(node) {
23
+ return foldNodeApi.value(node).foldedChildren;
24
+ }
25
+ function expectFoldNode(node, expected) {
26
+ expect(foldNodeApi.id(node)).toBe(expected.id);
27
+ expect(getStructuralChildren(node).map(foldNodeApi.id)).toStrictEqual(expected.childIds);
28
+ expect(getFoldedChildren(node).map(foldNodeApi.id)).toStrictEqual(expected.foldedChildIds);
29
+ }
30
+ describe('createFoldNodeService', () => {
31
+ it('UT: adds a new entry to folded children', () => {
32
+ const root = service.createEmptyNode(0);
33
+ const updated = service.addFoldedEntryAtPath(root, [], 0);
34
+ expect(updated).toBeDefined();
35
+ if (updated === undefined) {
36
+ return;
37
+ }
38
+ expectFoldNode(updated, {
39
+ id: 0,
40
+ childIds: [],
41
+ foldedChildIds: [0],
42
+ });
43
+ const foldedChild = getFoldedChildren(updated)[0];
44
+ expect(foldedChild).toBeDefined();
45
+ if (foldedChild === undefined) {
46
+ return;
47
+ }
48
+ expectFoldNode(foldedChild, {
49
+ id: 0,
50
+ childIds: [],
51
+ foldedChildIds: [],
52
+ });
53
+ });
54
+ it('UT: moves an existing structural entry to folded children', () => {
55
+ const child = createFoldNode(1);
56
+ const root = createFoldNode(0, {
57
+ children: [child],
58
+ });
59
+ const updated = service.addFoldedEntryAtPath(root, [], 1);
60
+ expect(updated).toBeDefined();
61
+ if (updated === undefined) {
62
+ return;
63
+ }
64
+ expectFoldNode(updated, {
65
+ id: 0,
66
+ childIds: [],
67
+ foldedChildIds: [1],
68
+ });
69
+ /*
70
+ * Moving an existing child preserves the exact node value, including any
71
+ * fold state nested below it.
72
+ */
73
+ expect(getFoldedChildren(updated)[0]).toBe(child);
74
+ /*
75
+ * The original immutable tree remains unchanged.
76
+ */
77
+ expectFoldNode(root, {
78
+ id: 0,
79
+ childIds: [1],
80
+ foldedChildIds: [],
81
+ });
82
+ });
83
+ it('UT: preserves nested fold state when moving an entry', () => {
84
+ const nestedChild = createFoldNode(2);
85
+ const child = createFoldNode(1, {
86
+ foldedChildren: [nestedChild],
87
+ });
88
+ const root = createFoldNode(0, {
89
+ children: [child],
90
+ });
91
+ const updated = service.addFoldedEntryAtPath(root, [], 1);
92
+ expect(updated).toBeDefined();
93
+ if (updated === undefined) {
94
+ return;
95
+ }
96
+ expectFoldNode(updated, {
97
+ id: 0,
98
+ childIds: [],
99
+ foldedChildIds: [1],
100
+ });
101
+ const movedChild = getFoldedChildren(updated)[0];
102
+ expect(movedChild).toBe(child);
103
+ if (movedChild === undefined) {
104
+ return;
105
+ }
106
+ expectFoldNode(movedChild, {
107
+ id: 1,
108
+ childIds: [],
109
+ foldedChildIds: [2],
110
+ });
111
+ expect(getFoldedChildren(movedChild)[0]).toBe(nestedChild);
112
+ });
113
+ it('UT: rejects a path through a folded entry', () => {
114
+ const foldedChild = createFoldNode(1);
115
+ const root = createFoldNode(0, {
116
+ foldedChildren: [foldedChild],
117
+ });
118
+ const updated = service.addFoldedEntryAtPath(root, [1], 2);
119
+ expect(updated).toBeUndefined();
120
+ /*
121
+ * The failed immutable operation leaves the original root untouched.
122
+ */
123
+ expectFoldNode(root, {
124
+ id: 0,
125
+ childIds: [],
126
+ foldedChildIds: [1],
127
+ });
128
+ expect(getFoldedChildren(root)[0]).toBe(foldedChild);
129
+ });
130
+ });
@@ -1,84 +1,24 @@
1
- import { SerializableKey, TreeNodeApi } from '../tree-node/tree-node.types.js';
1
+ import type { TreeNode, TreeNodeApi, SerializableKey } from '../../tree-node/index.js';
2
2
  /**
3
- * A node in the fold-state tree.
3
+ * Application-specific state stored in each fold-tree node.
4
4
  *
5
- * @remarks
6
- *
7
- * A fold-state tree is a sparse projection of a buffer tree. Apart from its
8
- * root, it stores only nodes that are folded or that have fold information
9
- * somewhere below them. It does not contain buffer entries or buffer-specific
10
- * properties.
11
- *
12
- * The `id` identifies the corresponding buffer entry among its siblings.
13
- *
14
- * `children` contains fold-state nodes for direct buffer entries that are not
15
- * folded at this node but have fold information below them.
16
- *
17
- * `foldedChildren` contains fold-state nodes for direct buffer entries whose contents
18
- * are folded at this node. Each folded entry is represented by a complete
19
- * `FoldNode`, allowing fold state within a folded entry to be preserved
20
- * recursively.
21
- *
22
- * A direct buffer entry with no fold state at or below it is omitted from both
23
- * collections. Consequently, the union of `children` and `foldedChildren` does not
24
- * need to contain every direct buffer child.
25
- *
26
- * The following invariant holds for every fold-state node:
27
- *
28
- * - no ID occurs more than once in `children`;
29
- * - no ID occurs more than once in `foldedChildren`;
30
- * - no ID occurs in both collections;
31
- * - every ID in either collection identifies a direct child in the
32
- * corresponding buffer-tree node.
33
- *
34
- * Every fold node is an open branch from the perspective of `TreeNodeApi`.
35
- * Missing paths may nevertheless be created lazily by `FoldNodeService`.
36
- *
37
- * @typeParam Id - The type of IDs in the actual tree nodes.
38
- *
39
- * @example
40
- *
41
- * ```ts
42
- * type FileId = string;
43
- * type FileFoldNode = FoldNode<FileId>;
44
- *
45
- * const root: FileFoldNode = {
46
- * id: '/',
47
- * children: [],
48
- * foldedChildren: [],
49
- * };
50
- * ```
5
+ * Structural children are owned by TreeNode. `foldedChildren` are semantic
6
+ * fold-state children: direct buffer entries whose contents are folded.
51
7
  */
52
- export type FoldNode<Id extends SerializableKey> = {
53
- id: Id;
54
- children: FoldNode<Id>[];
55
- foldedChildren: FoldNode<Id>[];
56
- };
8
+ export type FoldNodeValue<Id extends SerializableKey> = Readonly<{
9
+ foldedChildren: readonly FoldNode<Id>[];
10
+ }>;
57
11
  /**
58
- * Operations for inspecting and immutably updating a fold-state tree.
59
- *
60
- * @remarks
61
- *
62
- * This API is `TreeNodeApi` specialized for `FoldNode`. It provides structural
63
- * tree operations such as node lookup and immutable path updates.
64
- *
65
- * Paths are relative to the supplied root node:
66
- *
67
- * - `[]` identifies the root;
68
- * - `['child']` identifies a direct child of the root;
69
- * - `['child', 'grandchild']` identifies a descendant.
12
+ * A fold-state node.
70
13
  *
71
- * Path operations are strict. They operate only on nodes that already exist
72
- * in the fold-state tree. Use `FoldNodeService` when missing fold-state nodes
73
- * should be automatically created.
74
- *
75
- * The API does not interpret the `foldedChildren` collection. It only navigates and
76
- * updates the structural `children` tree. The semantic operations for adding
77
- * and removing foldedChildren belong to `FoldNodeService`.
78
- *
79
- * @typeParam Id - The type of IDs in the actual tree nodes.
14
+ * The structural child tree is represented by the opaque TreeNode. The value
15
+ * holds only fold-specific state.
80
16
  */
81
- export type FoldNodeApi<Id extends SerializableKey> = TreeNodeApi<Id, FoldNode<Id>>;
17
+ export type FoldNode<Id extends SerializableKey> = TreeNode<Id, FoldNodeValue<Id>>;
18
+ /**
19
+ * Generic structural operations specialized to a fold-state tree.
20
+ */
21
+ export type FoldNodeApi<Id extends SerializableKey> = TreeNodeApi<Id, FoldNodeValue<Id>>;
82
22
  /**
83
23
  * Semantic operations for managing fold state.
84
24
  *
@@ -188,6 +128,5 @@ export type FoldNodeService<Id extends SerializableKey> = {
188
128
  * `undefined` when the path does not exist.
189
129
  */
190
130
  clearFoldedEntriesAtPath(rootNode: FoldNode<Id>, path: readonly Id[]): FoldNode<Id> | undefined;
191
- clearFoldedEntriesAtPath(rootNode: FoldNode<Id>, path: readonly Id[]): FoldNode<Id> | undefined;
192
131
  };
193
132
  //# sourceMappingURL=fold-node.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold-node.types.d.ts","sourceRoot":"","sources":["../../../../src/trees/fold-node/deprecated/fold-node.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,eAAe,IAAI,QAAQ,CAAC;IAC/D,cAAc,EAAE,SAAS,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;CACzC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,CAAC,EAAE,SAAS,eAAe,IAAI,QAAQ,CACzD,EAAE,EACF,aAAa,CAAC,EAAE,CAAC,CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,EAAE,SAAS,eAAe,IAAI,WAAW,CAC/D,EAAE,EACF,aAAa,CAAC,EAAE,CAAC,CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,eAAe,CAAC,EAAE,SAAS,eAAe,IAAI;IACxD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,EAAE,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEtC;;;;;;;;;;;;;;OAcG;IACH,sBAAsB,CACpB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,OAAO,CAAC;IAEX;;;;OAIG;IACH,mBAAmB,CACjB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,GAClB,EAAE,EAAE,GAAG,SAAS,CAAC;IAEpB;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAClB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAE5B;;;;;;;;;;;;;;;;;OAiBG;IACH,uBAAuB,CACrB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAE5B;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAAwB,CACtB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,GAClB,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;CAC7B,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { SerializableKey, TreeNodeApi } from '../tree-node/index.js';
2
+ import type { FoldsApi } from './fold-node.types.js';
3
+ /**
4
+ * IDs of direct source-tree entries hidden at one source-tree branch.
5
+ */
6
+ type HiddenEntryIds<Id extends SerializableKey> = readonly Id[];
7
+ /**
8
+ * Creates operations for an immutable collection of independently active fold
9
+ * trees.
10
+ *
11
+ * Each fold tree preserves its own hidden-entry definition. An entry is hidden
12
+ * by the aggregate when at least one active fold tree hides it.
13
+ */
14
+ export declare function createFoldsApi<Id extends SerializableKey>(foldTreeApi: TreeNodeApi<Id, HiddenEntryIds<Id>>): FoldsApi<Id>;
15
+ export {};
16
+ //# sourceMappingURL=fold-node.impl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold-node.impl.d.ts","sourceRoot":"","sources":["../../../src/trees/fold-node/fold-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,WAAW,EACZ,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAIV,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,KAAK,cAAc,CAAC,EAAE,SAAS,eAAe,IAAI,SAAS,EAAE,EAAE,CAAC;AA0ChE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,eAAe,EACvD,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC,GAC/C,QAAQ,CAAC,EAAE,CAAC,CAmmBd"}