dirvi-lib 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) 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 +120 -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 +40 -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 +115 -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/feedback/feedback.impl.d.ts +9 -0
  18. package/dist/feedback/feedback.impl.d.ts.map +1 -0
  19. package/dist/feedback/feedback.impl.js +34 -0
  20. package/dist/feedback/feedback.types.d.ts +24 -0
  21. package/dist/feedback/feedback.types.d.ts.map +1 -0
  22. package/dist/feedback/index.d.ts +3 -0
  23. package/dist/feedback/index.d.ts.map +1 -0
  24. package/dist/feedback/index.js +1 -0
  25. package/dist/index.d.ts +5 -7
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +5 -7
  28. package/dist/input/application/index.d.ts.map +1 -0
  29. package/dist/input/application/intent-to-effect-no-state.test.d.ts.map +1 -0
  30. package/dist/input/application/intent-to-effect.d.ts +14 -0
  31. package/dist/input/application/intent-to-effect.d.ts.map +1 -0
  32. package/dist/{tree-surfer-ui → input}/application/intent-to-effect.js +66 -42
  33. package/dist/input/application/parse-command.d.ts +14 -0
  34. package/dist/input/application/parse-command.d.ts.map +1 -0
  35. package/dist/input/application/parse-command.js +92 -0
  36. package/dist/input/application/parse-normal-command.d.ts +12 -0
  37. package/dist/input/application/parse-normal-command.d.ts.map +1 -0
  38. package/dist/input/application/parse-normal-command.js +89 -0
  39. package/dist/input/application/tokenize-normal-command.d.ts +24 -0
  40. package/dist/input/application/tokenize-normal-command.d.ts.map +1 -0
  41. package/dist/input/application/tokenize-normal-command.js +110 -0
  42. package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.d.ts +4 -2
  43. package/dist/input/application/user-input-to-intent.d.ts.map +1 -0
  44. package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.js +9 -6
  45. package/dist/input/application/user-input-to-intent.test.d.ts.map +1 -0
  46. package/dist/input/domain/effect.d.ts +84 -0
  47. package/dist/input/domain/effect.d.ts.map +1 -0
  48. package/dist/input/domain/index.d.ts.map +1 -0
  49. package/dist/input/domain/input-mode-state.d.ts.map +1 -0
  50. package/dist/input/domain/intent.d.ts.map +1 -0
  51. package/dist/input/domain/user-input.d.ts.map +1 -0
  52. package/dist/trees/cursor.d.ts.map +1 -0
  53. package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts +10 -0
  54. package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts.map +1 -0
  55. package/dist/trees/fold-node/deprecated/fold-node.impl.js +248 -0
  56. package/dist/trees/fold-node/deprecated/fold-node.test.d.ts +2 -0
  57. package/dist/trees/fold-node/deprecated/fold-node.test.d.ts.map +1 -0
  58. package/dist/trees/fold-node/deprecated/fold-node.test.js +130 -0
  59. package/dist/{tree-surfer/fold-node → trees/fold-node/deprecated}/fold-node.types.d.ts +15 -76
  60. package/dist/trees/fold-node/deprecated/fold-node.types.d.ts.map +1 -0
  61. package/dist/trees/fold-node/fold-node.impl.d.ts +16 -0
  62. package/dist/trees/fold-node/fold-node.impl.d.ts.map +1 -0
  63. package/dist/trees/fold-node/fold-node.impl.js +472 -0
  64. package/dist/trees/fold-node/fold-node.model.d.ts +120 -0
  65. package/dist/trees/fold-node/fold-node.model.d.ts.map +1 -0
  66. package/dist/trees/fold-node/fold-node.model.js +80 -0
  67. package/dist/trees/fold-node/fold-node.types.d.ts +214 -0
  68. package/dist/trees/fold-node/fold-node.types.d.ts.map +1 -0
  69. package/dist/trees/fold-node/index.d.ts +4 -0
  70. package/dist/trees/fold-node/index.d.ts.map +1 -0
  71. package/dist/trees/fold-node/index.js +3 -0
  72. package/dist/trees/index.d.ts +9 -0
  73. package/dist/trees/index.d.ts.map +1 -0
  74. package/dist/trees/index.js +8 -0
  75. package/dist/trees/nav-node/nav-node.impl.d.ts +29 -0
  76. package/dist/trees/nav-node/nav-node.impl.d.ts.map +1 -0
  77. package/dist/{tree-surfer → trees}/nav-node/nav-node.impl.js +89 -42
  78. package/dist/trees/nav-node/nav-node.types.d.ts +103 -0
  79. package/dist/trees/nav-node/nav-node.types.d.ts.map +1 -0
  80. package/dist/trees/nav-node/nav-node.types.js +1 -0
  81. package/dist/trees/state/state.impl.d.ts +8 -0
  82. package/dist/trees/state/state.impl.d.ts.map +1 -0
  83. package/dist/trees/state/state.impl.js +128 -0
  84. package/dist/trees/state/state.schemas.d.ts +25 -0
  85. package/dist/trees/state/state.schemas.d.ts.map +1 -0
  86. package/dist/trees/state/state.schemas.js +17 -0
  87. package/dist/trees/state/state.types.d.ts +36 -0
  88. package/dist/trees/state/state.types.d.ts.map +1 -0
  89. package/dist/trees/state/state.types.js +1 -0
  90. package/dist/trees/tree-node/deprecated/tree-node.arb.d.ts.map +1 -0
  91. package/dist/trees/tree-node/deprecated/tree-node.arb.debug.test.d.ts.map +1 -0
  92. package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.js +1 -1
  93. package/dist/trees/tree-node/deprecated/tree-node.impl.d.ts.map +1 -0
  94. package/dist/trees/tree-node/deprecated/tree-node.schemas.d.ts.map +1 -0
  95. package/dist/trees/tree-node/deprecated/tree-node.test.d.ts.map +1 -0
  96. package/dist/trees/tree-node/deprecated/tree-node.types.d.ts.map +1 -0
  97. package/dist/trees/tree-node/deprecated/tree-node.types.js +1 -0
  98. package/dist/trees/tree-node/index.d.ts +5 -0
  99. package/dist/trees/tree-node/index.d.ts.map +1 -0
  100. package/dist/trees/tree-node/index.js +2 -0
  101. package/dist/trees/tree-node/tree-node.arb.d.ts +104 -0
  102. package/dist/trees/tree-node/tree-node.arb.d.ts.map +1 -0
  103. package/dist/trees/tree-node/tree-node.arb.js +191 -0
  104. package/dist/trees/tree-node/tree-node.impl.d.ts +15 -0
  105. package/dist/trees/tree-node/tree-node.impl.d.ts.map +1 -0
  106. package/dist/trees/tree-node/tree-node.impl.js +286 -0
  107. package/dist/trees/tree-node/tree-node.model.d.ts +78 -0
  108. package/dist/trees/tree-node/tree-node.model.d.ts.map +1 -0
  109. package/dist/trees/tree-node/tree-node.model.js +37 -0
  110. package/dist/trees/tree-node/tree-node.types.d.ts +419 -0
  111. package/dist/trees/tree-node/tree-node.types.d.ts.map +1 -0
  112. package/dist/trees/tree-node/tree-node.types.js +1 -0
  113. package/package.json +1 -1
  114. package/dist/tree-surfer/cursor.d.ts.map +0 -1
  115. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +0 -2
  116. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +0 -1
  117. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +0 -4
  118. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +0 -1
  119. package/dist/tree-surfer/fold-node/fold-node.impl.js +0 -149
  120. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +0 -5
  121. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +0 -1
  122. package/dist/tree-surfer/fold-node/fold-node.schema.js +0 -9
  123. package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +0 -1
  124. package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +0 -1
  125. package/dist/tree-surfer/fold-node/tree-node.test.js +0 -90
  126. package/dist/tree-surfer/index.d.ts +0 -13
  127. package/dist/tree-surfer/index.d.ts.map +0 -1
  128. package/dist/tree-surfer/index.js +0 -12
  129. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +0 -6
  130. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +0 -1
  131. package/dist/tree-surfer/nav-node/nav-node.types.d.ts +0 -70
  132. package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +0 -1
  133. package/dist/tree-surfer/state/state.impl.d.ts +0 -7
  134. package/dist/tree-surfer/state/state.impl.d.ts.map +0 -1
  135. package/dist/tree-surfer/state/state.impl.js +0 -89
  136. package/dist/tree-surfer/state/state.schemas.d.ts +0 -9
  137. package/dist/tree-surfer/state/state.schemas.d.ts.map +0 -1
  138. package/dist/tree-surfer/state/state.schemas.js +0 -8
  139. package/dist/tree-surfer/state/state.types.d.ts +0 -21
  140. package/dist/tree-surfer/state/state.types.d.ts.map +0 -1
  141. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +0 -1
  142. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +0 -1
  143. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +0 -1
  144. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +0 -1
  145. package/dist/tree-surfer/tree-node/tree-node.test.d.ts +0 -2
  146. package/dist/tree-surfer/tree-node/tree-node.test.d.ts.map +0 -1
  147. package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +0 -1
  148. package/dist/tree-surfer-ui/application/index.d.ts.map +0 -1
  149. package/dist/tree-surfer-ui/application/intent-to-effect-no-state.test.d.ts.map +0 -1
  150. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +0 -10
  151. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +0 -1
  152. package/dist/tree-surfer-ui/application/parse-command.d.ts +0 -4
  153. package/dist/tree-surfer-ui/application/parse-command.d.ts.map +0 -1
  154. package/dist/tree-surfer-ui/application/parse-command.js +0 -48
  155. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +0 -1
  156. package/dist/tree-surfer-ui/application/user-input-to-intent.test.d.ts.map +0 -1
  157. package/dist/tree-surfer-ui/domain/effect.d.ts +0 -43
  158. package/dist/tree-surfer-ui/domain/effect.d.ts.map +0 -1
  159. package/dist/tree-surfer-ui/domain/index.d.ts.map +0 -1
  160. package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +0 -1
  161. package/dist/tree-surfer-ui/domain/intent.d.ts.map +0 -1
  162. package/dist/tree-surfer-ui/domain/user-input.d.ts.map +0 -1
  163. /package/dist/{tree-surfer-ui/domain/effect.js → app/reducer-action.js} +0 -0
  164. /package/dist/{tree-surfer-ui/domain/input-mode-state.js → app/view-api.js} +0 -0
  165. /package/dist/{tree-surfer-ui/domain/intent.js → feedback/feedback.types.js} +0 -0
  166. /package/dist/{tree-surfer-ui → input}/application/index.d.ts +0 -0
  167. /package/dist/{tree-surfer-ui → input}/application/index.js +0 -0
  168. /package/dist/{tree-surfer-ui → input}/application/intent-to-effect-no-state.test.d.ts +0 -0
  169. /package/dist/{tree-surfer-ui → input}/application/intent-to-effect-no-state.test.js +0 -0
  170. /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.test.d.ts +0 -0
  171. /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.test.js +0 -0
  172. /package/dist/{tree-surfer-ui/domain/user-input.js → input/domain/effect.js} +0 -0
  173. /package/dist/{tree-surfer-ui → input}/domain/index.d.ts +0 -0
  174. /package/dist/{tree-surfer-ui → input}/domain/index.js +0 -0
  175. /package/dist/{tree-surfer-ui → input}/domain/input-mode-state.d.ts +0 -0
  176. /package/dist/{tree-surfer/fold-node/fold-node.arb.js → input/domain/input-mode-state.js} +0 -0
  177. /package/dist/{tree-surfer-ui → input}/domain/intent.d.ts +0 -0
  178. /package/dist/{tree-surfer/fold-node/fold-node.types.js → input/domain/intent.js} +0 -0
  179. /package/dist/{tree-surfer-ui → input}/domain/user-input.d.ts +0 -0
  180. /package/dist/{tree-surfer/nav-node/nav-node.types.js → input/domain/user-input.js} +0 -0
  181. /package/dist/{tree-surfer → trees}/cursor.d.ts +0 -0
  182. /package/dist/{tree-surfer → trees}/cursor.js +0 -0
  183. /package/dist/{tree-surfer/state/state.types.js → trees/fold-node/deprecated/fold-node.types.js} +0 -0
  184. /package/dist/{tree-surfer/tree-node/tree-node.types.js → trees/fold-node/fold-node.types.js} +0 -0
  185. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.d.ts +0 -0
  186. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.d.ts +0 -0
  187. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.js +0 -0
  188. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.impl.d.ts +0 -0
  189. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.impl.js +0 -0
  190. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.schemas.d.ts +0 -0
  191. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.schemas.js +0 -0
  192. /package/dist/{tree-surfer/fold-node → trees/tree-node/deprecated}/tree-node.test.d.ts +0 -0
  193. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.test.js +0 -0
  194. /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.types.d.ts +0 -0
@@ -0,0 +1,472 @@
1
+ function toData(folds) {
2
+ return folds;
3
+ }
4
+ function fromData(data) {
5
+ return data;
6
+ }
7
+ function isValidFoldIndex(index) {
8
+ return Number.isSafeInteger(index) && index >= 0;
9
+ }
10
+ function hasUniqueValues(values) {
11
+ return new Set(values).size === values.length;
12
+ }
13
+ /**
14
+ * Creates operations for an immutable collection of independently active fold
15
+ * trees.
16
+ *
17
+ * Each fold tree preserves its own hidden-entry definition. An entry is hidden
18
+ * by the aggregate when at least one active fold tree hides it.
19
+ */
20
+ export function createFoldsApi(foldTreeApi) {
21
+ function createEmptyFoldTree(rootId) {
22
+ const tree = foldTreeApi.createLoadedBranch(rootId, [], []);
23
+ /*
24
+ * An empty child collection cannot contain duplicate IDs. Failure would
25
+ * therefore indicate a broken TreeNodeApi implementation or contract.
26
+ */
27
+ if (tree === undefined) {
28
+ throw new Error('Could not create an empty fold tree');
29
+ }
30
+ return tree;
31
+ }
32
+ function getSlotAtIndex(folds, index) {
33
+ if (!isValidFoldIndex(index)) {
34
+ return undefined;
35
+ }
36
+ return toData(folds).slots[index];
37
+ }
38
+ function withSlotAtIndex(folds, index, foldData) {
39
+ if (!isValidFoldIndex(index)) {
40
+ throw new RangeError('Fold index must be a non-negative safe integer');
41
+ }
42
+ const data = toData(folds);
43
+ const slots = [...data.slots];
44
+ while (slots.length <= index) {
45
+ slots.push(undefined);
46
+ }
47
+ slots[index] = foldData;
48
+ /*
49
+ * `setAtIndex` permits replacing the primary slot. Other public operations
50
+ * preserve the invariant that slot zero is always occupied.
51
+ */
52
+ return fromData({
53
+ slots: slots,
54
+ });
55
+ }
56
+ function updateSlotAtIndex(folds, index, update) {
57
+ const foldData = getSlotAtIndex(folds, index);
58
+ if (foldData === undefined) {
59
+ return undefined;
60
+ }
61
+ const updatedFoldData = update(foldData);
62
+ if (updatedFoldData === foldData) {
63
+ return folds;
64
+ }
65
+ return withSlotAtIndex(folds, index, updatedFoldData);
66
+ }
67
+ function removeSlotAtIndex(folds, index) {
68
+ const data = toData(folds);
69
+ const slots = [...data.slots];
70
+ slots[index] = undefined;
71
+ /*
72
+ * Trailing unoccupied slots carry no information, so omit them from the
73
+ * immutable runtime representation.
74
+ */
75
+ while (slots.length > 1 && slots[slots.length - 1] === undefined) {
76
+ slots.pop();
77
+ }
78
+ return fromData({
79
+ slots: slots,
80
+ });
81
+ }
82
+ function getLoadedChildren(tree) {
83
+ const children = foldTreeApi.getLoadedChildren(tree);
84
+ return children === undefined ? undefined : [...children];
85
+ }
86
+ function createEmptyFoldNode(id) {
87
+ const tree = foldTreeApi.createLoadedBranch(id, [], []);
88
+ if (tree === undefined) {
89
+ throw new Error('Could not create an empty fold node');
90
+ }
91
+ return tree;
92
+ }
93
+ /**
94
+ * Creates structural fold-tree nodes for `path` where they do not already
95
+ * exist. A fold tree structurally retains descendants even when an entry is
96
+ * hidden at its parent, so paths may pass through hidden entry IDs.
97
+ */
98
+ function ensurePath(tree, path) {
99
+ if (path.length === 0) {
100
+ return tree;
101
+ }
102
+ const children = getLoadedChildren(tree);
103
+ if (children === undefined) {
104
+ return undefined;
105
+ }
106
+ const childId = path[0];
107
+ if (childId === undefined) {
108
+ return undefined;
109
+ }
110
+ const childIndex = children.findIndex((child) => foldTreeApi.id(child) === childId);
111
+ const child = childIndex === -1 ? createEmptyFoldNode(childId) : children[childIndex];
112
+ if (child === undefined) {
113
+ return undefined;
114
+ }
115
+ const updatedChild = ensurePath(child, path.slice(1));
116
+ if (updatedChild === undefined) {
117
+ return undefined;
118
+ }
119
+ if (childIndex === -1) {
120
+ return foldTreeApi.withLoadedChildren(tree, [...children, updatedChild]);
121
+ }
122
+ if (updatedChild === child) {
123
+ return tree;
124
+ }
125
+ const updatedChildren = [...children];
126
+ updatedChildren[childIndex] = updatedChild;
127
+ return foldTreeApi.withLoadedChildren(tree, updatedChildren);
128
+ }
129
+ function isEmptyFoldNode(tree) {
130
+ const children = getLoadedChildren(tree);
131
+ if (children === undefined) {
132
+ return undefined;
133
+ }
134
+ return foldTreeApi.value(tree).length === 0 && children.length === 0;
135
+ }
136
+ /**
137
+ * Updates one existing fold-tree node and prunes empty sparse descendants on
138
+ * the path back to the root.
139
+ *
140
+ * The root is always retained, even if it becomes empty.
141
+ */
142
+ function updateAtPathAndPrune(tree, path, update) {
143
+ if (path.length === 0) {
144
+ return update(tree);
145
+ }
146
+ const children = getLoadedChildren(tree);
147
+ if (children === undefined) {
148
+ return undefined;
149
+ }
150
+ const childId = path[0];
151
+ if (childId === undefined) {
152
+ return undefined;
153
+ }
154
+ const childIndex = children.findIndex((child) => foldTreeApi.id(child) === childId);
155
+ if (childIndex === -1) {
156
+ return undefined;
157
+ }
158
+ const child = children[childIndex];
159
+ if (child === undefined) {
160
+ return undefined;
161
+ }
162
+ const updatedChild = updateAtPathAndPrune(child, path.slice(1), update);
163
+ if (updatedChild === undefined) {
164
+ return undefined;
165
+ }
166
+ if (updatedChild === child) {
167
+ return tree;
168
+ }
169
+ const childIsEmpty = isEmptyFoldNode(updatedChild);
170
+ if (childIsEmpty === undefined) {
171
+ return undefined;
172
+ }
173
+ const updatedChildren = [...children];
174
+ if (childIsEmpty) {
175
+ updatedChildren.splice(childIndex, 1);
176
+ }
177
+ else {
178
+ updatedChildren[childIndex] = updatedChild;
179
+ }
180
+ return foldTreeApi.withLoadedChildren(tree, updatedChildren);
181
+ }
182
+ function foldTreeToModel(tree) {
183
+ return foldTreeApi.match(tree, {
184
+ leaf: () => {
185
+ throw new Error('Fold trees must contain loaded branch nodes');
186
+ },
187
+ unloadedBranch: () => {
188
+ throw new Error('Fold trees must contain loaded branch nodes');
189
+ },
190
+ loadedBranch: ({ id, value, children }) => ({
191
+ id,
192
+ hiddenEntryIds: [...value],
193
+ children: [...children].map(foldTreeToModel),
194
+ }),
195
+ });
196
+ }
197
+ function foldSlotToModel(foldData) {
198
+ return {
199
+ name: foldData.name,
200
+ description: foldData.description,
201
+ active: foldData.active,
202
+ tree: foldTreeToModel(foldData.tree),
203
+ };
204
+ }
205
+ function foldTreeFromModel(model) {
206
+ if (!hasUniqueValues(model.hiddenEntryIds)) {
207
+ return undefined;
208
+ }
209
+ const children = [];
210
+ for (const childModel of model.children) {
211
+ const child = foldTreeFromModel(childModel);
212
+ if (child === undefined) {
213
+ return undefined;
214
+ }
215
+ children.push(child);
216
+ }
217
+ return foldTreeApi.createLoadedBranch(model.id, [...model.hiddenEntryIds], children);
218
+ }
219
+ const api = {
220
+ create(rootId, initialPrimaryInfo) {
221
+ return fromData({
222
+ slots: [
223
+ {
224
+ name: initialPrimaryInfo.name,
225
+ description: initialPrimaryInfo.description,
226
+ active: true,
227
+ tree: createEmptyFoldTree(rootId),
228
+ },
229
+ ],
230
+ });
231
+ },
232
+ getIndexByName(folds, name) {
233
+ const { slots } = toData(folds);
234
+ let matchingIndex;
235
+ for (let index = 0; index < slots.length; index += 1) {
236
+ const foldData = slots[index];
237
+ if (foldData === undefined || foldData.name !== name) {
238
+ continue;
239
+ }
240
+ /*
241
+ * Names are intended to be durable user-facing references. Until
242
+ * creation, rename, and restoration enforce unique names, do not
243
+ * choose an arbitrary matching fold tree.
244
+ */
245
+ if (matchingIndex !== undefined) {
246
+ return undefined;
247
+ }
248
+ matchingIndex = index;
249
+ }
250
+ return matchingIndex;
251
+ },
252
+ getInfoAtIndex(folds, index) {
253
+ const foldData = getSlotAtIndex(folds, index);
254
+ if (foldData === undefined) {
255
+ return undefined;
256
+ }
257
+ return {
258
+ name: foldData.name,
259
+ description: foldData.description,
260
+ };
261
+ },
262
+ isActiveAtIndex(folds, index) {
263
+ return getSlotAtIndex(folds, index)?.active;
264
+ },
265
+ hasAtIndex(folds, index) {
266
+ return getSlotAtIndex(folds, index) !== undefined;
267
+ },
268
+ *indexes(folds) {
269
+ const { slots } = toData(folds);
270
+ for (let index = 0; index < slots.length; index += 1) {
271
+ if (slots[index] !== undefined) {
272
+ yield index;
273
+ }
274
+ }
275
+ },
276
+ // TODO: Enforce unique names
277
+ setAtIndex(folds, index, foldData) {
278
+ if (!isValidFoldIndex(index)) {
279
+ return undefined;
280
+ }
281
+ const tree = foldTreeFromModel(foldData.tree);
282
+ if (tree === undefined) {
283
+ return undefined;
284
+ }
285
+ return withSlotAtIndex(folds, index, {
286
+ name: foldData.name,
287
+ description: foldData.description,
288
+ active: foldData.active,
289
+ tree,
290
+ });
291
+ },
292
+ setAdditionalFoldAtIndex(folds, index, rootId, info) {
293
+ if (!isValidFoldIndex(index) || index === 0) {
294
+ return undefined;
295
+ }
296
+ return withSlotAtIndex(folds, index, {
297
+ name: info.name,
298
+ description: info.description,
299
+ active: true,
300
+ tree: createEmptyFoldTree(rootId),
301
+ });
302
+ },
303
+ removeAdditionalFoldAtIndex(folds, index) {
304
+ if (!isValidFoldIndex(index) || index === 0) {
305
+ return undefined;
306
+ }
307
+ if (getSlotAtIndex(folds, index) === undefined) {
308
+ return folds;
309
+ }
310
+ return removeSlotAtIndex(folds, index);
311
+ },
312
+ updateInfoAtIndex(folds, index, update) {
313
+ return updateSlotAtIndex(folds, index, (foldData) => {
314
+ const name = update.name ?? foldData.name;
315
+ const description = update.description === undefined
316
+ ? foldData.description
317
+ : update.description;
318
+ if (name === foldData.name && description === foldData.description) {
319
+ return foldData;
320
+ }
321
+ return {
322
+ ...foldData,
323
+ name,
324
+ description,
325
+ };
326
+ });
327
+ },
328
+ activateAtIndex(folds, index) {
329
+ return updateSlotAtIndex(folds, index, (foldData) => foldData.active ? foldData : { ...foldData, active: true });
330
+ },
331
+ deactivateAtIndex(folds, index) {
332
+ return updateSlotAtIndex(folds, index, (foldData) => !foldData.active ? foldData : { ...foldData, active: false });
333
+ },
334
+ toggleActiveAtIndex(folds, index) {
335
+ return updateSlotAtIndex(folds, index, (foldData) => ({
336
+ ...foldData,
337
+ active: !foldData.active,
338
+ }));
339
+ },
340
+ isEntryHiddenInFoldAtPath(folds, index, path, entryId) {
341
+ const foldData = getSlotAtIndex(folds, index);
342
+ if (foldData === undefined) {
343
+ return false;
344
+ }
345
+ return (foldTreeApi.selectAtPath(foldData.tree, path, (node) => foldTreeApi.value(node).includes(entryId)) ?? false);
346
+ },
347
+ isEntryHiddenAtPath(folds, path, entryId) {
348
+ for (const index of api.indexes(folds)) {
349
+ if (api.isActiveAtIndex(folds, index) === true &&
350
+ api.isEntryHiddenInFoldAtPath(folds, index, path, entryId)) {
351
+ return true;
352
+ }
353
+ }
354
+ return false;
355
+ },
356
+ hiddenEntryIdsAtPath(folds, index, path) {
357
+ const foldData = getSlotAtIndex(folds, index);
358
+ if (foldData === undefined) {
359
+ return undefined;
360
+ }
361
+ return foldTreeApi.selectAtPath(foldData.tree, path, (node) => [
362
+ ...foldTreeApi.value(node),
363
+ ]);
364
+ },
365
+ hideEntryAtPath(folds, index, path, entryId) {
366
+ const foldData = getSlotAtIndex(folds, index);
367
+ if (foldData === undefined) {
368
+ return undefined;
369
+ }
370
+ if (api.isEntryHiddenInFoldAtPath(folds, index, path, entryId)) {
371
+ return folds;
372
+ }
373
+ const treeWithPath = ensurePath(foldData.tree, path);
374
+ if (treeWithPath === undefined) {
375
+ return undefined;
376
+ }
377
+ const updatedTree = updateAtPathAndPrune(treeWithPath, path, (node) => foldTreeApi.withValue(node, [...foldTreeApi.value(node), entryId]));
378
+ if (updatedTree === undefined) {
379
+ return undefined;
380
+ }
381
+ return withSlotAtIndex(folds, index, {
382
+ ...foldData,
383
+ tree: updatedTree,
384
+ });
385
+ },
386
+ showEntryAtPath(folds, index, path, entryId) {
387
+ const foldData = getSlotAtIndex(folds, index);
388
+ if (foldData === undefined) {
389
+ return undefined;
390
+ }
391
+ const updatedTree = updateAtPathAndPrune(foldData.tree, path, (node) => {
392
+ const hiddenEntryIds = foldTreeApi.value(node);
393
+ if (!hiddenEntryIds.includes(entryId)) {
394
+ return node;
395
+ }
396
+ return foldTreeApi.withValue(node, hiddenEntryIds.filter((id) => id !== entryId));
397
+ });
398
+ if (updatedTree === undefined) {
399
+ return undefined;
400
+ }
401
+ if (updatedTree === foldData.tree) {
402
+ return folds;
403
+ }
404
+ return withSlotAtIndex(folds, index, {
405
+ ...foldData,
406
+ tree: updatedTree,
407
+ });
408
+ },
409
+ showAllHiddenEntriesAtPath(folds, index, path) {
410
+ const foldData = getSlotAtIndex(folds, index);
411
+ if (foldData === undefined) {
412
+ return undefined;
413
+ }
414
+ const updatedTree = updateAtPathAndPrune(foldData.tree, path, (node) => {
415
+ if (foldTreeApi.value(node).length === 0) {
416
+ return node;
417
+ }
418
+ return foldTreeApi.withValue(node, []);
419
+ });
420
+ if (updatedTree === undefined) {
421
+ return undefined;
422
+ }
423
+ if (updatedTree === foldData.tree) {
424
+ return folds;
425
+ }
426
+ return withSlotAtIndex(folds, index, {
427
+ ...foldData,
428
+ tree: updatedTree,
429
+ });
430
+ },
431
+ toModel(folds) {
432
+ const { slots } = toData(folds);
433
+ const modelSlots = [
434
+ foldSlotToModel(slots[0]),
435
+ ...slots
436
+ .slice(1)
437
+ .map((foldData) => foldData === undefined ? null : foldSlotToModel(foldData)),
438
+ ];
439
+ return {
440
+ slots: modelSlots,
441
+ };
442
+ },
443
+ fromModel(model) {
444
+ const slots = [];
445
+ for (const foldSlotModel of model.slots) {
446
+ if (foldSlotModel === null) {
447
+ slots.push(undefined);
448
+ continue;
449
+ }
450
+ const tree = foldTreeFromModel(foldSlotModel.tree);
451
+ if (tree === undefined) {
452
+ return undefined;
453
+ }
454
+ slots.push({
455
+ name: foldSlotModel.name,
456
+ description: foldSlotModel.description,
457
+ active: foldSlotModel.active,
458
+ tree,
459
+ });
460
+ }
461
+ const primarySlot = slots[0];
462
+ if (primarySlot === undefined) {
463
+ return undefined;
464
+ }
465
+ const foldSlots = [primarySlot, ...slots.slice(1)];
466
+ return fromData({
467
+ slots: foldSlots,
468
+ });
469
+ },
470
+ };
471
+ return api;
472
+ }
@@ -0,0 +1,120 @@
1
+ import { z } from 'zod';
2
+ import type { SerializableKey } from '../tree-node/index.js';
3
+ /**
4
+ * Serializable semantic representation of one fold-state tree node.
5
+ *
6
+ * This is a persistence/interchange model. It is not the runtime
7
+ * representation of the fold tree.
8
+ *
9
+ * A fold node represents one source-tree node. Its `hiddenEntryIds` are IDs of
10
+ * direct source-tree entries hidden at that node. Its `children` are sparse
11
+ * fold-state descendants.
12
+ *
13
+ * `children` does not contain every source-tree child. It contains only
14
+ * children which have fold state at or below them.
15
+ *
16
+ * A child fold-node ID is allowed to also appear in `hiddenEntryIds`. For
17
+ * example:
18
+ *
19
+ * ```text
20
+ * root
21
+ * hiddenEntryIds: ["src"]
22
+ * children:
23
+ * - id: "src"
24
+ * hiddenEntryIds: ["lib"]
25
+ * ```
26
+ *
27
+ * This means `src` is hidden directly under `root`, while fold state below
28
+ * `src` is retained for when it is shown again.
29
+ */
30
+ export type FoldNodeModel<Id extends SerializableKey> = Readonly<{
31
+ /**
32
+ * The source-tree node ID represented by this fold-state node.
33
+ */
34
+ id: Id;
35
+ /**
36
+ * IDs of direct source-tree entries hidden at this node.
37
+ *
38
+ * These IDs are semantically a set: duplicates are invalid and iteration
39
+ * order has no meaning.
40
+ */
41
+ hiddenEntryIds: readonly Id[];
42
+ /**
43
+ * Sparse child fold-state nodes.
44
+ *
45
+ * This is semantically keyed by child ID. Array order has no meaning.
46
+ */
47
+ children: readonly FoldNodeModel<Id>[];
48
+ }>;
49
+ /**
50
+ * Creates a schema for one serialized fold-state tree.
51
+ *
52
+ * The schema validates fold-tree-local invariants only:
53
+ *
54
+ * - every hidden entry ID is unique within its fold node;
55
+ * - every child fold-node ID is unique within its parent.
56
+ *
57
+ * It intentionally does not validate:
58
+ *
59
+ * - that an ID exists in a particular source tree;
60
+ * - that a child fold node corresponds to a direct source-tree child;
61
+ * - whether the represented source-tree node is a branch;
62
+ * - whether an entry is currently loaded in the source tree.
63
+ *
64
+ * Those rules require access to the source tree and belong in a fold service,
65
+ * not in this serialization schema.
66
+ */
67
+ export declare function createFoldNodeModelSchema<Id extends SerializableKey>(idSchema: z.ZodType<Id>): z.ZodType<FoldNodeModel<Id>>;
68
+ /**
69
+ * Serializable metadata and fold-state tree for one occupied fold slot.
70
+ *
71
+ * `description: null` means that this fold tree has no description.
72
+ * An empty string is a supplied description with no text.
73
+ */
74
+ export type FoldSlotModel<Id extends SerializableKey> = Readonly<{
75
+ /**
76
+ * User-facing name of this fold tree.
77
+ */
78
+ name: string;
79
+ /**
80
+ * User-facing description of this fold tree, or `null` when it has none.
81
+ */
82
+ description: string | null;
83
+ /**
84
+ * Whether this fold tree currently contributes to hiding entries.
85
+ *
86
+ * An inactive fold tree retains its fold definition but does not affect the
87
+ * visible navigation projection.
88
+ */
89
+ active: boolean;
90
+ /**
91
+ * The fold-state tree stored in this slot.
92
+ */
93
+ tree: FoldNodeModel<Id>;
94
+ }>;
95
+ /**
96
+ * Serializable representation of Folds.
97
+ *
98
+ * `null` represents an unoccupied non-primary slot. Do not use `undefined`:
99
+ * JSON serialization converts undefined array entries to null anyway.
100
+ */
101
+ export type FoldsModel<Id extends SerializableKey> = Readonly<{
102
+ slots: readonly [FoldSlotModel<Id>, ...(FoldSlotModel<Id> | null)[]];
103
+ }>;
104
+ /**
105
+ * Creates a schema for the serializable fold collection.
106
+ *
107
+ * This validates:
108
+ *
109
+ * - there is always a primary slot at index zero;
110
+ * - every occupied slot has a name, a description or `null`, active state,
111
+ * and a valid fold tree;
112
+ * - every non-primary slot is either an occupied fold slot or `null`.
113
+ *
114
+ * A slot's `description: null` means that its fold tree has no description.
115
+ *
116
+ * It does not validate fold-tree names for uniqueness. That is a domain rule
117
+ * only if the fold service decides names must be unique.
118
+ */
119
+ export declare function createFoldsModelSchema<Id extends SerializableKey>(idSchema: z.ZodType<Id>): z.ZodType<FoldsModel<Id>>;
120
+ //# sourceMappingURL=fold-node.model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold-node.model.d.ts","sourceRoot":"","sources":["../../../src/trees/fold-node/fold-node.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAY7D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,eAAe,IAAI,QAAQ,CAAC;IAC/D;;OAEG;IACH,EAAE,EAAE,EAAE,CAAC;IAEP;;;;;OAKG;IACH,cAAc,EAAE,SAAS,EAAE,EAAE,CAAC;IAE9B;;;;OAIG;IACH,QAAQ,EAAE,SAAS,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC;CACxC,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,SAAS,eAAe,EAClE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,GACtB,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CA0B9B;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,eAAe,IAAI,QAAQ,CAAC;IAC/D;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAE3B;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC;CACzB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,UAAU,CAAC,EAAE,SAAS,eAAe,IAAI,QAAQ,CAAC;IAC5D,KAAK,EAAE,SAAS,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;CACtE,CAAC,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,SAAS,eAAe,EAC/D,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,GACtB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAmB3B"}
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Returns whether every value appears at most once.
4
+ *
5
+ * `SerializableKey` values are strings or numbers, so `Set` equality is the
6
+ * appropriate equality relation for fold-node IDs.
7
+ */
8
+ function hasUniqueValues(values) {
9
+ return new Set(values).size === values.length;
10
+ }
11
+ /**
12
+ * Creates a schema for one serialized fold-state tree.
13
+ *
14
+ * The schema validates fold-tree-local invariants only:
15
+ *
16
+ * - every hidden entry ID is unique within its fold node;
17
+ * - every child fold-node ID is unique within its parent.
18
+ *
19
+ * It intentionally does not validate:
20
+ *
21
+ * - that an ID exists in a particular source tree;
22
+ * - that a child fold node corresponds to a direct source-tree child;
23
+ * - whether the represented source-tree node is a branch;
24
+ * - whether an entry is currently loaded in the source tree.
25
+ *
26
+ * Those rules require access to the source tree and belong in a fold service,
27
+ * not in this serialization schema.
28
+ */
29
+ export function createFoldNodeModelSchema(idSchema) {
30
+ let nodeSchema;
31
+ const hiddenEntryIdsSchema = z.array(idSchema).refine(hasUniqueValues, {
32
+ message: 'Hidden entry IDs must be unique within a fold node',
33
+ path: ['hiddenEntryIds'],
34
+ });
35
+ nodeSchema = z.lazy(() => z
36
+ .object({
37
+ id: idSchema,
38
+ hiddenEntryIds: hiddenEntryIdsSchema,
39
+ children: z.array(nodeSchema),
40
+ })
41
+ .strict()
42
+ .refine((node) => hasUniqueValues(node.children.map((child) => child.id)), {
43
+ message: 'Child fold-node IDs must be unique within a fold node',
44
+ path: ['children'],
45
+ }));
46
+ return nodeSchema;
47
+ }
48
+ /**
49
+ * Creates a schema for the serializable fold collection.
50
+ *
51
+ * This validates:
52
+ *
53
+ * - there is always a primary slot at index zero;
54
+ * - every occupied slot has a name, a description or `null`, active state,
55
+ * and a valid fold tree;
56
+ * - every non-primary slot is either an occupied fold slot or `null`.
57
+ *
58
+ * A slot's `description: null` means that its fold tree has no description.
59
+ *
60
+ * It does not validate fold-tree names for uniqueness. That is a domain rule
61
+ * only if the fold service decides names must be unique.
62
+ */
63
+ export function createFoldsModelSchema(idSchema) {
64
+ const foldNodeSchema = createFoldNodeModelSchema(idSchema);
65
+ const foldSlotSchema = z
66
+ .object({
67
+ name: z.string(),
68
+ description: z.string().nullable(),
69
+ active: z.boolean(),
70
+ tree: foldNodeSchema,
71
+ })
72
+ .strict();
73
+ return z
74
+ .object({
75
+ slots: z
76
+ .tuple([foldSlotSchema])
77
+ .rest(z.union([foldSlotSchema, z.null()])),
78
+ })
79
+ .strict();
80
+ }