dirvi-lib 0.11.0-beta.6 → 0.11.0-beta.8

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 (158) hide show
  1. package/dist/app/app-api.d.ts +7 -25
  2. package/dist/app/app-api.d.ts.map +1 -1
  3. package/dist/app/app-api.js +1 -11
  4. package/dist/app/index.d.ts +1 -0
  5. package/dist/app/index.d.ts.map +1 -1
  6. package/dist/app/index.js +1 -0
  7. package/dist/app/node-presentation.d.ts +13 -0
  8. package/dist/app/node-presentation.d.ts.map +1 -0
  9. package/dist/app/reducer/effect-to-action.d.ts +2 -2
  10. package/dist/app/reducer/effect-to-action.d.ts.map +1 -1
  11. package/dist/app/reducer/effect-to-action.js +87 -32
  12. package/dist/app/reducer/reducer-action.d.ts +12 -6
  13. package/dist/app/reducer/reducer-action.d.ts.map +1 -1
  14. package/dist/app/reducer/reducer.d.ts +4 -2
  15. package/dist/app/reducer/reducer.d.ts.map +1 -1
  16. package/dist/app/reducer/reducer.js +102 -68
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/input/application/intent-to-effect.d.ts +2 -2
  21. package/dist/input/application/intent-to-effect.d.ts.map +1 -1
  22. package/dist/input/application/intent-to-effect.js +8 -9
  23. package/dist/input/application/parse-ex-command.d.ts +1 -1
  24. package/dist/input/application/parse-ex-command.d.ts.map +1 -1
  25. package/dist/input/application/parse-normal-command.d.ts +1 -1
  26. package/dist/input/application/parse-normal-command.d.ts.map +1 -1
  27. package/dist/input/domain/effect.d.ts +1 -2
  28. package/dist/input/domain/effect.d.ts.map +1 -1
  29. package/dist/state-and-nav/cursor.d.ts +89 -0
  30. package/dist/state-and-nav/cursor.d.ts.map +1 -0
  31. package/dist/state-and-nav/cursor.js +84 -0
  32. package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.impl.d.ts +1 -1
  33. package/dist/state-and-nav/fold-node/deprecated/fold-node.impl.d.ts.map +1 -0
  34. package/dist/state-and-nav/fold-node/deprecated/fold-node.test.d.ts.map +1 -0
  35. package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.test.js +2 -2
  36. package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.types.d.ts +2 -2
  37. package/dist/state-and-nav/fold-node/deprecated/fold-node.types.d.ts.map +1 -0
  38. package/dist/{trees → state-and-nav}/fold-node/fold-node.impl.d.ts +3 -3
  39. package/dist/state-and-nav/fold-node/fold-node.impl.d.ts.map +1 -0
  40. package/dist/{trees → state-and-nav}/fold-node/fold-node.impl.js +1 -1
  41. package/dist/{trees → state-and-nav}/fold-node/fold-node.model.d.ts +1 -1
  42. package/dist/state-and-nav/fold-node/fold-node.model.d.ts.map +1 -0
  43. package/dist/{trees → state-and-nav}/fold-node/fold-node.types.d.ts +2 -2
  44. package/dist/state-and-nav/fold-node/fold-node.types.d.ts.map +1 -0
  45. package/dist/state-and-nav/fold-node/index.d.ts.map +1 -0
  46. package/dist/state-and-nav/index.d.ts +16 -0
  47. package/dist/state-and-nav/index.d.ts.map +1 -0
  48. package/dist/state-and-nav/index.js +6 -0
  49. package/dist/state-and-nav/nav/nav.ordered.impl.d.ts +12 -0
  50. package/dist/state-and-nav/nav/nav.ordered.impl.d.ts.map +1 -0
  51. package/dist/state-and-nav/nav/nav.ordered.impl.js +77 -0
  52. package/dist/state-and-nav/nav/nav.traversal.d.ts +11 -0
  53. package/dist/state-and-nav/nav/nav.traversal.d.ts.map +1 -0
  54. package/dist/state-and-nav/nav/nav.traversal.js +207 -0
  55. package/dist/{trees/nav-node/nav-node.types.d.ts → state-and-nav/nav/nav.types.d.ts} +32 -22
  56. package/dist/state-and-nav/nav/nav.types.d.ts.map +1 -0
  57. package/dist/state-and-nav/nav/nav.unordered.impl.d.ts +13 -0
  58. package/dist/state-and-nav/nav/nav.unordered.impl.d.ts.map +1 -0
  59. package/dist/state-and-nav/nav/nav.unordered.impl.js +77 -0
  60. package/dist/state-and-nav/state/state.impl.d.ts +9 -0
  61. package/dist/state-and-nav/state/state.impl.d.ts.map +1 -0
  62. package/dist/{trees → state-and-nav}/state/state.impl.js +62 -9
  63. package/dist/state-and-nav/state/state.schemas.d.ts +2 -0
  64. package/dist/state-and-nav/state/state.schemas.d.ts.map +1 -0
  65. package/dist/state-and-nav/state/state.schemas.js +39 -0
  66. package/dist/state-and-nav/state/state.types.d.ts +72 -0
  67. package/dist/state-and-nav/state/state.types.d.ts.map +1 -0
  68. package/dist/state-and-nav/tree/deprecated/tree-node.arb.d.ts.map +1 -0
  69. package/dist/state-and-nav/tree/deprecated/tree-node.arb.debug.test.d.ts.map +1 -0
  70. package/dist/state-and-nav/tree/deprecated/tree-node.impl.d.ts.map +1 -0
  71. package/dist/state-and-nav/tree/deprecated/tree-node.schemas.d.ts.map +1 -0
  72. package/dist/state-and-nav/tree/deprecated/tree-node.test.d.ts.map +1 -0
  73. package/dist/state-and-nav/tree/deprecated/tree-node.types.d.ts.map +1 -0
  74. package/dist/state-and-nav/tree/index.d.ts +7 -0
  75. package/dist/state-and-nav/tree/index.d.ts.map +1 -0
  76. package/dist/state-and-nav/tree/index.js +1 -0
  77. package/dist/state-and-nav/tree/tree-node.arb.d.ts +94 -0
  78. package/dist/state-and-nav/tree/tree-node.arb.d.ts.map +1 -0
  79. package/dist/state-and-nav/tree/tree-node.arb.debug.test.d.ts +2 -0
  80. package/dist/state-and-nav/tree/tree-node.arb.debug.test.d.ts.map +1 -0
  81. package/dist/state-and-nav/tree/tree-node.arb.debug.test.js +54 -0
  82. package/dist/state-and-nav/tree/tree-node.arb.js +188 -0
  83. package/dist/state-and-nav/tree/tree-node.model.d.ts +193 -0
  84. package/dist/state-and-nav/tree/tree-node.model.d.ts.map +1 -0
  85. package/dist/state-and-nav/tree/tree-node.model.js +110 -0
  86. package/dist/state-and-nav/tree/tree-node.test.d.ts +2 -0
  87. package/dist/state-and-nav/tree/tree-node.test.d.ts.map +1 -0
  88. package/dist/state-and-nav/tree/tree-node.test.js +234 -0
  89. package/dist/{trees/tree-node/tree-node.impl.d.ts → state-and-nav/tree/tree.impl.d.ts} +3 -3
  90. package/dist/state-and-nav/tree/tree.impl.d.ts.map +1 -0
  91. package/dist/{trees/tree-node/tree-node.impl.js → state-and-nav/tree/tree.impl.js} +19 -16
  92. package/dist/{trees/tree-node/tree-node.types.d.ts → state-and-nav/tree/tree.types.d.ts} +64 -44
  93. package/dist/state-and-nav/tree/tree.types.d.ts.map +1 -0
  94. package/dist/state-and-nav/tree/tree.types.js +1 -0
  95. package/package.json +1 -1
  96. package/dist/trees/cursor.d.ts +0 -59
  97. package/dist/trees/cursor.d.ts.map +0 -1
  98. package/dist/trees/cursor.js +0 -55
  99. package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts.map +0 -1
  100. package/dist/trees/fold-node/deprecated/fold-node.test.d.ts.map +0 -1
  101. package/dist/trees/fold-node/deprecated/fold-node.types.d.ts.map +0 -1
  102. package/dist/trees/fold-node/fold-node.impl.d.ts.map +0 -1
  103. package/dist/trees/fold-node/fold-node.model.d.ts.map +0 -1
  104. package/dist/trees/fold-node/fold-node.types.d.ts.map +0 -1
  105. package/dist/trees/fold-node/index.d.ts.map +0 -1
  106. package/dist/trees/index.d.ts +0 -9
  107. package/dist/trees/index.d.ts.map +0 -1
  108. package/dist/trees/index.js +0 -8
  109. package/dist/trees/nav-node/nav-node.impl.d.ts +0 -29
  110. package/dist/trees/nav-node/nav-node.impl.d.ts.map +0 -1
  111. package/dist/trees/nav-node/nav-node.impl.js +0 -223
  112. package/dist/trees/nav-node/nav-node.types.d.ts.map +0 -1
  113. package/dist/trees/state/state.impl.d.ts +0 -8
  114. package/dist/trees/state/state.impl.d.ts.map +0 -1
  115. package/dist/trees/state/state.schemas.d.ts +0 -25
  116. package/dist/trees/state/state.schemas.d.ts.map +0 -1
  117. package/dist/trees/state/state.schemas.js +0 -17
  118. package/dist/trees/state/state.types.d.ts +0 -36
  119. package/dist/trees/state/state.types.d.ts.map +0 -1
  120. package/dist/trees/tree-node/deprecated/tree-node.arb.d.ts.map +0 -1
  121. package/dist/trees/tree-node/deprecated/tree-node.arb.debug.test.d.ts.map +0 -1
  122. package/dist/trees/tree-node/deprecated/tree-node.impl.d.ts.map +0 -1
  123. package/dist/trees/tree-node/deprecated/tree-node.schemas.d.ts.map +0 -1
  124. package/dist/trees/tree-node/deprecated/tree-node.test.d.ts.map +0 -1
  125. package/dist/trees/tree-node/deprecated/tree-node.types.d.ts.map +0 -1
  126. package/dist/trees/tree-node/index.d.ts +0 -5
  127. package/dist/trees/tree-node/index.d.ts.map +0 -1
  128. package/dist/trees/tree-node/index.js +0 -2
  129. package/dist/trees/tree-node/tree-node.arb.d.ts +0 -104
  130. package/dist/trees/tree-node/tree-node.arb.d.ts.map +0 -1
  131. package/dist/trees/tree-node/tree-node.arb.js +0 -188
  132. package/dist/trees/tree-node/tree-node.impl.d.ts.map +0 -1
  133. package/dist/trees/tree-node/tree-node.model.d.ts +0 -78
  134. package/dist/trees/tree-node/tree-node.model.d.ts.map +0 -1
  135. package/dist/trees/tree-node/tree-node.model.js +0 -37
  136. package/dist/trees/tree-node/tree-node.types.d.ts.map +0 -1
  137. /package/dist/{trees/fold-node/deprecated/fold-node.types.js → app/node-presentation.js} +0 -0
  138. /package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.impl.js +0 -0
  139. /package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.test.d.ts +0 -0
  140. /package/dist/{trees/fold-node → state-and-nav/fold-node/deprecated}/fold-node.types.js +0 -0
  141. /package/dist/{trees → state-and-nav}/fold-node/fold-node.model.js +0 -0
  142. /package/dist/{trees/nav-node/nav-node.types.js → state-and-nav/fold-node/fold-node.types.js} +0 -0
  143. /package/dist/{trees → state-and-nav}/fold-node/index.d.ts +0 -0
  144. /package/dist/{trees → state-and-nav}/fold-node/index.js +0 -0
  145. /package/dist/{trees/state/state.types.js → state-and-nav/nav/nav.types.js} +0 -0
  146. /package/dist/{trees/tree-node/deprecated/tree-node.types.js → state-and-nav/state/state.types.js} +0 -0
  147. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.arb.d.ts +0 -0
  148. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.arb.debug.test.d.ts +0 -0
  149. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.arb.debug.test.js +0 -0
  150. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.arb.js +0 -0
  151. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.impl.d.ts +0 -0
  152. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.impl.js +0 -0
  153. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.schemas.d.ts +0 -0
  154. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.schemas.js +0 -0
  155. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.test.d.ts +0 -0
  156. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.test.js +0 -0
  157. /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.types.d.ts +0 -0
  158. /package/dist/{trees/tree-node → state-and-nav/tree/deprecated}/tree-node.types.js +0 -0
@@ -1,7 +1,13 @@
1
+ function getStateData(state) {
2
+ return state;
3
+ }
4
+ function createState(data) {
5
+ return data;
6
+ }
1
7
  /**
2
8
  * Creates operations for navigating and resynchronizing tree-surfer state.
3
9
  */
4
- export function createStateApi(treeNodeApi, cursorApi) {
10
+ export function createStateApi(treeNodeApi, foldsApi, cursorApi) {
5
11
  /**
6
12
  * Reloads one node's direct children and recursively reloads descendant
7
13
  * branches that were open in the old state.
@@ -102,27 +108,74 @@ export function createStateApi(treeNodeApi, cursorApi) {
102
108
  * 5. If no entry cursor exists, use the root cursor.
103
109
  */
104
110
  function resyncCursor(_oldState, _newRoot, _newFolds) {
105
- return [];
111
+ return { path: [] };
106
112
  }
107
113
  return {
114
+ treeNodeApi,
115
+ foldsApi,
116
+ cursorApi,
117
+ create(root, folds, cursor) {
118
+ return treeNodeApi.kind(root) === 'loaded-branch'
119
+ ? createState({ root, folds, cursor })
120
+ : undefined;
121
+ },
122
+ getRoot(state) {
123
+ return getStateData(state).root;
124
+ },
125
+ getFolds(state) {
126
+ return getStateData(state).folds;
127
+ },
128
+ getCursor(state) {
129
+ return getStateData(state).cursor;
130
+ },
131
+ update(state, changes) {
132
+ const data = getStateData(state);
133
+ const root = changes.root ?? data.root;
134
+ const folds = changes.folds ?? data.folds;
135
+ const cursor = changes.cursor ?? data.cursor;
136
+ if (treeNodeApi.kind(root) !== 'loaded-branch') {
137
+ return undefined;
138
+ }
139
+ if (root === data.root &&
140
+ folds === data.folds &&
141
+ cursor === data.cursor) {
142
+ return state;
143
+ }
144
+ return createState({ root, folds, cursor });
145
+ },
108
146
  getNodeAtCursor(state) {
109
- const path = cursorApi.getPath(state.cursor);
110
- return treeNodeApi.selectAtPath(state.root, path, (node) => node);
147
+ const { root, cursor } = getStateData(state);
148
+ /*
149
+ * A fold cursor identifies a display item, not a single source node.
150
+ * Its representative path must not silently select its first child.
151
+ */
152
+ if (!cursorApi.isNodeCursor(cursor)) {
153
+ return undefined;
154
+ }
155
+ return treeNodeApi.selectAtPath(root, cursor.path, (node) => node);
156
+ },
157
+ isCursorRoot(state) {
158
+ const { cursor } = getStateData(state);
159
+ return cursorApi.isNodeCursor(cursor) && cursor.path.length === 0;
160
+ },
161
+ cursorPath(state) {
162
+ const { cursor } = getStateData(state);
163
+ return cursorApi.getPath(cursor);
111
164
  },
112
165
  async resync(oldState, loadBranches) {
113
- const reloaded = await reload(oldState.root, [], loadBranches);
166
+ const oldStateData = getStateData(oldState);
167
+ const reloaded = await reload(oldStateData.root, [], loadBranches);
114
168
  /*
115
169
  * Fold-state resynchronization is not implemented yet. Keep the existing
116
170
  * fold definitions while the source tree is reloaded.
117
171
  */
118
- const folds = oldState.folds;
172
+ const folds = oldStateData.folds;
119
173
  const root = reloaded.root;
120
- return {
121
- ...oldState,
174
+ return createState({
122
175
  root,
123
176
  folds,
124
177
  cursor: resyncCursor(oldState, root, folds),
125
- };
178
+ });
126
179
  },
127
180
  };
128
181
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=state.schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.schemas.d.ts","sourceRoot":"","sources":["../../../src/state-and-nav/state/state.schemas.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ export {};
2
+ // import { z } from 'zod';
3
+ // import type { Cursor } from './unordered-nav/cursor.js';
4
+ // import type { FoldsModel } from './fold-node/index.js';
5
+ // import type { SerializableKey, TreeNodeModel } from './tree/index.js';
6
+ //
7
+ // /**
8
+ // * Serializable persistence/interchange model for tree-surfer state.
9
+ // *
10
+ // * This is intentionally distinct from runtime `State`: its root and folds use
11
+ // * public structural models, while runtime state holds opaque TreeNode and
12
+ // * Folds values.
13
+ // */
14
+ // export type StateModel<Id extends SerializableKey, Value> = Readonly<{
15
+ // root: TreeNodeModel<Id, Value>;
16
+ // folds: FoldsModel<Id>;
17
+ // cursor: Cursor<Id>;
18
+ // }>;
19
+ //
20
+ // /**
21
+ // * Creates a schema for the serializable StateModel DTO.
22
+ // *
23
+ // * This validates only the persistence representation. Applications must decode
24
+ // * the validated root and folds models through their specialized TreeNodeApi and
25
+ // * FoldsApi before constructing opaque runtime State.
26
+ // */
27
+ // export function createStateModelSchema<Id extends SerializableKey, Value>(
28
+ // rootSchema: z.ZodType<TreeNodeModel<Id, Value>>,
29
+ // foldsSchema: z.ZodType<FoldsModel<Id>>,
30
+ // cursorSchema: z.ZodType<Cursor<Id>>,
31
+ // ): z.ZodType<StateModel<Id, Value>> {
32
+ // return z
33
+ // .object({
34
+ // root: rootSchema,
35
+ // folds: foldsSchema,
36
+ // cursor: cursorSchema,
37
+ // })
38
+ // .strict();
39
+ // }
@@ -0,0 +1,72 @@
1
+ import type { SerializableKey, TreeNode, TreeMethods } from '../tree/index.js';
2
+ import type { FoldMethods, Folds } from '../fold-node/index.js';
3
+ import type { Cursor, CursorMethods } from '../cursor.js';
4
+ declare const stateBrand: unique symbol;
5
+ /**
6
+ * Opaque type for state of the tree, folds, and cursor.
7
+ *
8
+ * Used by consumers as React State.
9
+ */
10
+ export type State<Id extends SerializableKey, Value> = Readonly<{
11
+ [stateBrand]: Readonly<{
12
+ id: Id;
13
+ value: Value;
14
+ }>;
15
+ }>;
16
+ export type StateApi<Id extends SerializableKey, Value> = Readonly<{
17
+ treeNodeApi: TreeMethods<Id, Value>;
18
+ foldsApi: FoldMethods<Id>;
19
+ cursorApi: CursorMethods<Id>;
20
+ /**
21
+ * Creates state whose root is a loaded branch.
22
+ *
23
+ * Returns undefined when root is a leaf or an unloaded branch.
24
+ */
25
+ create(root: TreeNode<Id, Value>, folds: Folds<Id>, cursor: Cursor<Id>): State<Id, Value> | undefined;
26
+ getRoot(state: State<Id, Value>): TreeNode<Id, Value>;
27
+ getFolds(state: State<Id, Value>): Folds<Id>;
28
+ getCursor(state: State<Id, Value>): Cursor<Id>;
29
+ /**
30
+ * Immutably replaces supplied state components.
31
+ *
32
+ * Returns undefined when the replacement root is not a loaded branch.
33
+ * Returns the original state when all components retain their references.
34
+ */
35
+ update(state: State<Id, Value>, changes: Readonly<{
36
+ root?: TreeNode<Id, Value>;
37
+ folds?: Folds<Id>;
38
+ cursor?: Cursor<Id>;
39
+ }>): State<Id, Value> | undefined;
40
+ /**
41
+ * Returns a mutable root-relative path.
42
+ *
43
+ * For a fold cursor, returns the path of its first hidden child.
44
+ * This representative path does not identify the fold marker itself.
45
+ */
46
+ cursorPath(state: State<Id, Value>): Id[];
47
+ /**
48
+ * Tests whether the cursor is the root node cursor: { path: [] }.
49
+ */
50
+ isCursorRoot(state: State<Id, Value>): boolean;
51
+ /**
52
+ * Resolves a node cursor against the source tree.
53
+ *
54
+ * Returns undefined for fold cursors or paths absent from the loaded tree.
55
+ * This does not check visibility in a navigation projection.
56
+ */
57
+ getNodeAtCursor(state: State<Id, Value>): TreeNode<Id, Value> | undefined;
58
+ /**
59
+ * Reloads the root's children and all descendant branches that were loaded
60
+ * in the previous state.
61
+ *
62
+ * `loadBranches([])` loads the root's direct children.
63
+ * `loadBranches(['src'])` loads the children of `src`, where `src` is the
64
+ * ID of a root child.
65
+ *
66
+ * Returned nodes must be valid TreeNode values created or decoded through
67
+ * the specialized TreeNodeApi for `Id` and `Value`.
68
+ */
69
+ resync(oldState: State<Id, Value>, loadBranches: (path: readonly Id[]) => Promise<readonly TreeNode<Id, Value>[]>): Promise<State<Id, Value>>;
70
+ }>;
71
+ export {};
72
+ //# sourceMappingURL=state.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.types.d.ts","sourceRoot":"","sources":["../../../src/state-and-nav/state/state.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,CAAC,MAAM,UAAU,EAAE,OAAO,MAAM,CAAC;AAExC;;;;GAIG;AACH,MAAM,MAAM,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,IAAI,QAAQ,CAAC;IAC9D,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC;QACrB,EAAE,EAAE,EAAE,CAAC;QACP,KAAK,EAAE,KAAK,CAAC;KACd,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,MAAM,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,IAAI,QAAQ,CAAC;IACjE,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACpC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;IAC1B,SAAS,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC;IAE7B;;;;OAIG;IACH,MAAM,CACJ,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,EACzB,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,EAChB,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GACjB,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;IAEhC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7C,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IAE/C;;;;;OAKG;IACH,MAAM,CACJ,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,EACvB,OAAO,EAAE,QAAQ,CAAC;QAChB,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC3B,KAAK,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;KACrB,CAAC,GACD,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;IAEhC;;;;;OAKG;IACH,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC;IAC1C;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC;IAC/C;;;;;OAKG;IACH,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
+ {"version":3,"file":"tree-node.arb.d.ts","sourceRoot":"","sources":["../../../../src/state-and-nav/tree/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/state-and-nav/tree/deprecated/tree-node.arb.debug.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-node.impl.d.ts","sourceRoot":"","sources":["../../../../src/state-and-nav/tree/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/state-and-nav/tree/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/state-and-nav/tree/deprecated/tree-node.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-node.types.d.ts","sourceRoot":"","sources":["../../../../src/state-and-nav/tree/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,7 @@
1
+ /**
2
+ * To be used as the dependencies of State and Navigation.
3
+ */
4
+ export type { SerializableKey } from './tree-node.model.js';
5
+ export type * from './tree.types.js';
6
+ export { createTreeMethods } from './tree.impl.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/state-and-nav/tree/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,mBAAmB,iBAAiB,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ export { createTreeMethods } from './tree.impl.js';
@@ -0,0 +1,94 @@
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 Id = string;
10
+ type Value = number;
11
+ type ModelAndPath = {
12
+ model: TreeNodeModel<Id, Value>;
13
+ path: Id[];
14
+ };
15
+ /**
16
+ * A model and a pair of a model and its path for every reachable path in the
17
+ * model
18
+ *
19
+ * Includes this model at `[]` and every descendant reachable through loaded
20
+ * branches. Descendants behind unloaded branches are intentionally absent.
21
+ */
22
+ type ModelWithReachableModels = {
23
+ model: TreeNodeModel<Id, Value>;
24
+ reachableModels: ModelAndPath[];
25
+ };
26
+ /**
27
+ * Generates valid string IDs for test nodes. Note that the API just does
28
+ * comparisons on the IDs so we can safely restrict to any subset.
29
+ */
30
+ export declare const idArb: fc.Arbitrary<string>;
31
+ /**
32
+ * Generates serializable application values for test nodes.
33
+ */
34
+ export declare const valueArb: fc.Arbitrary<number>;
35
+ /**
36
+ * Generates a leaf model.
37
+ */
38
+ export declare const leafModelWRMArb: fc.Arbitrary<ModelWithReachableModels>;
39
+ /**
40
+ * Generates an unloaded-branch model.
41
+ */
42
+ export declare const unloadedBranchModelWRMArb: fc.Arbitrary<ModelWithReachableModels>;
43
+ /**
44
+ * Generates a leaf, an unloaded branch, or a loaded branch.
45
+ *
46
+ * Loaded branches have unique child IDs. Root IDs are made unique separately
47
+ * when generating a forest.
48
+ *
49
+ * The weighted choice gives recursive loaded branches a geometric drop off.
50
+ */
51
+ export declare const modelWithReachableModelsArb: fc.Arbitrary<ModelWithReachableModels>;
52
+ /**
53
+ * Generates an array of generated models with unique sibling IDs.
54
+ *
55
+ * The result is suitable for use as the loaded children of a loaded branch.
56
+ */
57
+ export declare const siblingModelsWRMArb: fc.Arbitrary<ModelWithReachableModels[]>;
58
+ /**
59
+ * Generates root-level models and a path to one of their reachable models.
60
+ *
61
+ * Every generated path is valid for `getAtPath`; unreachable paths are not
62
+ * included.
63
+ */
64
+ export declare const modelAndReachablePathArb: fc.Arbitrary<{
65
+ root: TreeNodeModel<string, number>;
66
+ path: string[];
67
+ expected: TreeNodeModel<string, number>;
68
+ }>;
69
+ /**
70
+ * Generates a root model and a path to one of its reachable branch models.
71
+ *
72
+ * Both loaded and unloaded branches are included.
73
+ */
74
+ export declare const modelAndBranchPathArb: fc.Arbitrary<{
75
+ root: TreeNodeModel<string, number>;
76
+ path: string[];
77
+ expected: TreeNodeModel<string, number>;
78
+ }>;
79
+ /**
80
+ * Generates a new array of loaded child models.
81
+ *
82
+ * The generated models have unique IDs, making the result suitable for use as
83
+ * the children of a loaded branch.
84
+ */
85
+ export declare const childrenModelsArb: fc.Arbitrary<TreeNodeModel<string, number>[]>;
86
+ /**
87
+ * Generates either a new array of loaded child models or `null`.
88
+ *
89
+ * An array represents a loaded branch, including a loaded branch with no
90
+ * children. `null` represents an unloaded branch.
91
+ */
92
+ export declare const childrenStateModelArb: fc.Arbitrary<TreeNodeModel<string, number>[] | null>;
93
+ export {};
94
+ //# sourceMappingURL=tree-node.arb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-node.arb.d.ts","sourceRoot":"","sources":["../../../src/state-and-nav/tree/tree-node.arb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;;GAKG;AACH,KAAK,EAAE,GAAG,MAAM,CAAC;AACjB,KAAK,KAAK,GAAG,MAAM,CAAC;AAEpB,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAChC,IAAI,EAAE,EAAE,EAAE,CAAC;CACZ,CAAC;AAEF;;;;;;GAMG;AACH,KAAK,wBAAwB,GAAG;IAC9B,KAAK,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAChC,eAAe,EAAE,YAAY,EAAE,CAAC;CACjC,CAAC;AAWF;;;GAGG;AACH,eAAO,MAAM,KAAK,sBAIhB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,sBAGnB,CAAC;AAyEH;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,EAAE,CAAC,SAAS,CAAC,wBAAwB,CAEhB,CAAC;AAEpD;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAE,EAAE,CAAC,SAAS,CAAC,wBAAwB,CAGd,CAAC;AAUhE;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,EAAE,EAAE,CAAC,SAAS,CAAC,wBAAwB,CAyCtE,CAAC;AASV;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,0CAI9B,CAAC;AAIH;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;;EAOpC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;EAc/B,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,+CAE7B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,sDAAiD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tree-node.arb.debug.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-node.arb.debug.test.d.ts","sourceRoot":"","sources":["../../../src/state-and-nav/tree/tree-node.arb.debug.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { describe, it } from 'vitest';
2
+ import * as fc from 'fast-check';
3
+ import { modelWithReachableModelsArb, siblingModelsWRMArb } from './tree-node.arb.js';
4
+ /**
5
+ * Manual inspection of generated models and their reachable paths.
6
+ *
7
+ * Remove `.skip` temporarily to run these inspections.
8
+ * These are debugging aids, not assertions about the arbitraries.
9
+ */
10
+ describe.skip('inspect tree-node arbitraries', () => {
11
+ const seed = 12345;
12
+ it('prints generated models and reachable paths', () => {
13
+ const samples = fc.sample(modelWithReachableModelsArb, {
14
+ numRuns: 10,
15
+ seed,
16
+ });
17
+ for (const [index, generated] of samples.entries()) {
18
+ console.log(`\n--- model ${index + 1} ---`);
19
+ console.dir(generated.model, {
20
+ depth: Infinity,
21
+ colors: false,
22
+ });
23
+ console.log(`reachable nodes: ${generated.reachableModels.length}`, `maximum depth: ${Math.max(...generated.reachableModels.map(({ path }) => path.length))}`);
24
+ console.log('reachable paths:');
25
+ console.dir(generated.reachableModels.map(({ path, model }) => ({
26
+ path,
27
+ id: model.id,
28
+ kind: !('children' in model)
29
+ ? 'leaf'
30
+ : model.children === null
31
+ ? 'unloaded-branch'
32
+ : 'loaded-branch',
33
+ })), {
34
+ depth: Infinity,
35
+ colors: false,
36
+ });
37
+ }
38
+ });
39
+ it('prints generated sibling collections', () => {
40
+ const samples = fc.sample(siblingModelsWRMArb, {
41
+ numRuns: 5,
42
+ seed,
43
+ });
44
+ for (const [index, generatedNodes] of samples.entries()) {
45
+ console.log(`\n--- sibling collection ${index + 1} ---`);
46
+ console.dir(generatedNodes.map(({ model }) => model), {
47
+ depth: Infinity,
48
+ colors: false,
49
+ });
50
+ console.log('sibling IDs:', generatedNodes.map(({ model }) => model.id));
51
+ console.log('total reachable nodes:', generatedNodes.reduce((count, generated) => count + generated.reachableModels.length, 0));
52
+ }
53
+ });
54
+ });
@@ -0,0 +1,188 @@
1
+ import * as fc from 'fast-check';
2
+ // =============================================================================
3
+ // ID and Value Arbitraries
4
+ // =============================================================================
5
+ /**
6
+ * Characters used for readable generated node IDs.
7
+ */
8
+ const smallLetterCharacters = Array.from('abcdefghijklmnopqrstuvwxyz');
9
+ /**
10
+ * Generates valid string IDs for test nodes. Note that the API just does
11
+ * comparisons on the IDs so we can safely restrict to any subset.
12
+ */
13
+ export const idArb = fc.string({
14
+ unit: fc.constantFrom(...smallLetterCharacters),
15
+ minLength: 1,
16
+ maxLength: 8,
17
+ });
18
+ /**
19
+ * Generates serializable application values for test nodes.
20
+ */
21
+ export const valueArb = fc.integer({
22
+ min: -10_000,
23
+ max: 10_000,
24
+ });
25
+ // =============================================================================
26
+ // Generating Models with Reachable Models
27
+ // =============================================================================
28
+ // Helpers
29
+ const createLeafModel = (id, value) => {
30
+ const model = {
31
+ id,
32
+ value,
33
+ };
34
+ return {
35
+ model,
36
+ reachableModels: [{ path: [], model }],
37
+ };
38
+ };
39
+ function createUnloadedBranchModel(id, value) {
40
+ const model = {
41
+ id,
42
+ value,
43
+ children: null,
44
+ };
45
+ return {
46
+ model,
47
+ reachableModels: [{ path: [], model }],
48
+ };
49
+ }
50
+ /**
51
+ * Creates the metadata for a loaded branch and all descendants reachable
52
+ * through its loaded child collection.
53
+ *
54
+ * Child-array order is preserved, both in the model and in the enumeration
55
+ * of reachable descendants.
56
+ */
57
+ function createLoadedBranchModel(id, value, children) {
58
+ const model = {
59
+ id,
60
+ value,
61
+ children: children.map(({ model: child }) => child),
62
+ };
63
+ const reachableDescendants = children.flatMap(({ model: childModel, reachableModels }) => reachableModels.map(({ path, model: reachableModel }) => ({
64
+ path: [childModel.id, ...path],
65
+ model: reachableModel,
66
+ })));
67
+ return {
68
+ model,
69
+ reachableModels: [{ path: [], model }, ...reachableDescendants],
70
+ };
71
+ }
72
+ // Arbs
73
+ /**
74
+ * Generates a leaf model.
75
+ */
76
+ export const leafModelWRMArb = fc
77
+ .tuple(idArb, valueArb)
78
+ .map(([id, value]) => createLeafModel(id, value));
79
+ /**
80
+ * Generates an unloaded-branch model.
81
+ */
82
+ export const unloadedBranchModelWRMArb = fc
83
+ .tuple(idArb, valueArb)
84
+ .map(([id, value]) => createUnloadedBranchModel(id, value));
85
+ /**
86
+ * Generates either kind of 'terminal' model.
87
+ *
88
+ * Both leaves and unloaded branches terminate path traversal.
89
+ */
90
+ const terminalModelWRMArb = fc.oneof(leafModelWRMArb, unloadedBranchModelWRMArb);
91
+ /**
92
+ * Generates a leaf, an unloaded branch, or a loaded branch.
93
+ *
94
+ * Loaded branches have unique child IDs. Root IDs are made unique separately
95
+ * when generating a forest.
96
+ *
97
+ * The weighted choice gives recursive loaded branches a geometric drop off.
98
+ */
99
+ export const modelWithReachableModelsArb = fc.letrec((tie) => {
100
+ const generatedChildModelArb = tie('node');
101
+ /**
102
+ * Generates the loaded children of a loaded branch.
103
+ *
104
+ * Sibling IDs must be unique so that child lookup is unambiguous. The
105
+ * maximum length also limits the branching factor of recursive examples.
106
+ */
107
+ const generatedLoadedChildrenArb = fc.uniqueArray(generatedChildModelArb, {
108
+ minLength: 0,
109
+ maxLength: 4,
110
+ size: 'medium',
111
+ selector: ({ model }) => model.id,
112
+ });
113
+ /**
114
+ * Generates a loaded branch and its reachable descendants.
115
+ */
116
+ const generatedLoadedBranchModelArb = fc
117
+ .tuple(idArb, valueArb, generatedLoadedChildrenArb)
118
+ .map(([id, value, children]) => createLoadedBranchModel(id, value, children));
119
+ return {
120
+ node: fc
121
+ .integer({
122
+ min: 0,
123
+ max: 9999,
124
+ })
125
+ .chain((choice) =>
126
+ // 5046 works for getAtPath tests, but too heavy for modifyAtPath tests
127
+ choice >= 5050
128
+ ? generatedLoadedBranchModelArb
129
+ : terminalModelWRMArb),
130
+ };
131
+ }).node;
132
+ // =============================================================================
133
+ // Generating Models
134
+ // =============================================================================
135
+ // Prepares inputs for particular tests.
136
+ // Generating Replacement Child State
137
+ /**
138
+ * Generates an array of generated models with unique sibling IDs.
139
+ *
140
+ * The result is suitable for use as the loaded children of a loaded branch.
141
+ */
142
+ export const siblingModelsWRMArb = fc.uniqueArray(modelWithReachableModelsArb, {
143
+ minLength: 0,
144
+ size: 'small',
145
+ selector: (generated) => generated.model.id,
146
+ });
147
+ // Generating Models with a Selected Reachable Target
148
+ /**
149
+ * Generates root-level models and a path to one of their reachable models.
150
+ *
151
+ * Every generated path is valid for `getAtPath`; unreachable paths are not
152
+ * included.
153
+ */
154
+ export const modelAndReachablePathArb = modelWithReachableModelsArb.chain(({ model: root, reachableModels }) => fc.constantFrom(...reachableModels).map(({ path, model }) => ({
155
+ root,
156
+ path,
157
+ expected: model,
158
+ })));
159
+ /**
160
+ * Generates a root model and a path to one of its reachable branch models.
161
+ *
162
+ * Both loaded and unloaded branches are included.
163
+ */
164
+ export const modelAndBranchPathArb = modelWithReachableModelsArb
165
+ .map(({ model: root, reachableModels }) => ({
166
+ root,
167
+ branchPathEntries: reachableModels.filter(({ model }) => 'children' in model),
168
+ }))
169
+ .filter(({ branchPathEntries }) => branchPathEntries.length > 0)
170
+ .chain(({ root, branchPathEntries }) => fc.constantFrom(...branchPathEntries).map(({ path, model }) => ({
171
+ root,
172
+ path,
173
+ expected: model,
174
+ })));
175
+ /**
176
+ * Generates a new array of loaded child models.
177
+ *
178
+ * The generated models have unique IDs, making the result suitable for use as
179
+ * the children of a loaded branch.
180
+ */
181
+ export const childrenModelsArb = siblingModelsWRMArb.map((generatedNodes) => generatedNodes.map(({ model }) => model));
182
+ /**
183
+ * Generates either a new array of loaded child models or `null`.
184
+ *
185
+ * An array represents a loaded branch, including a loaded branch with no
186
+ * children. `null` represents an unloaded branch.
187
+ */
188
+ export const childrenStateModelArb = fc.oneof(childrenModelsArb, fc.constant(null));