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,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":""}
@@ -1,6 +1,6 @@
1
1
  import { describe, it } from 'vitest';
2
2
  import * as fc from 'fast-check';
3
- import { generatedNodeArb, generatedNodesArrayArb, } from './tree-node.arb.js';
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,5 @@
1
+ export type * from './tree-node.model.js';
2
+ export { serializableKeySchema, createTreeNodeModelSchema } from './tree-node.model.js';
3
+ export type * from './tree-node.types.js';
4
+ export { createTreeNodeApi } from './tree-node.impl.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/trees/tree-node/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,sBAAsB,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACxF,mBAAmB,sBAAsB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { serializableKeySchema, createTreeNodeModelSchema } from './tree-node.model.js';
2
+ export { createTreeNodeApi } from './tree-node.impl.js';
@@ -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"}