dirvi-lib 0.7.0 → 0.8.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 (72) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/tree-surfer/cursor.d.ts +52 -26
  5. package/dist/tree-surfer/cursor.d.ts.map +1 -1
  6. package/dist/tree-surfer/cursor.js +19 -44
  7. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +2 -0
  8. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +1 -0
  9. package/dist/tree-surfer/fold-node/fold-node.arb.js +1 -0
  10. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +3 -4
  11. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +1 -1
  12. package/dist/tree-surfer/fold-node/fold-node.impl.js +73 -67
  13. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +3 -5
  14. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +1 -1
  15. package/dist/tree-surfer/fold-node/fold-node.schema.js +2 -9
  16. package/dist/tree-surfer/fold-node/fold-node.types.d.ts +169 -46
  17. package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +1 -1
  18. package/dist/tree-surfer/fold-node/tree-node.test.d.ts +2 -0
  19. package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +1 -0
  20. package/dist/tree-surfer/fold-node/tree-node.test.js +90 -0
  21. package/dist/tree-surfer/index.d.ts +3 -1
  22. package/dist/tree-surfer/index.d.ts.map +1 -1
  23. package/dist/tree-surfer/index.js +3 -1
  24. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +5 -5
  25. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +1 -1
  26. package/dist/tree-surfer/nav-node/nav-node.impl.js +84 -99
  27. package/dist/tree-surfer/nav-node/nav-node.types.d.ts +62 -34
  28. package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +1 -1
  29. package/dist/tree-surfer/nav-node/nav-node.types.js +1 -3
  30. package/dist/tree-surfer/state/state.impl.d.ts +7 -0
  31. package/dist/tree-surfer/state/state.impl.d.ts.map +1 -0
  32. package/dist/tree-surfer/state/state.impl.js +89 -0
  33. package/dist/tree-surfer/state/state.schemas.d.ts +9 -0
  34. package/dist/tree-surfer/state/state.schemas.d.ts.map +1 -0
  35. package/dist/tree-surfer/state/state.schemas.js +8 -0
  36. package/dist/tree-surfer/state/state.types.d.ts +21 -0
  37. package/dist/tree-surfer/state/state.types.d.ts.map +1 -0
  38. package/dist/tree-surfer/state/state.types.js +1 -0
  39. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts +82 -17
  40. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +1 -1
  41. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts +2 -0
  42. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +1 -0
  43. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.js +34 -0
  44. package/dist/tree-surfer/tree-node/tree-node.arb.js +164 -56
  45. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts +1 -1
  46. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +1 -1
  47. package/dist/tree-surfer/tree-node/tree-node.impl.js +41 -48
  48. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts +2 -2
  49. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +1 -1
  50. package/dist/tree-surfer/tree-node/tree-node.schemas.js +2 -2
  51. package/dist/tree-surfer/tree-node/tree-node.test.js +144 -76
  52. package/dist/tree-surfer/tree-node/tree-node.types.d.ts +89 -107
  53. package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +1 -1
  54. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +6 -2
  55. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +1 -1
  56. package/dist/tree-surfer-ui/application/intent-to-effect.js +1 -6
  57. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts +10 -2
  58. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +1 -1
  59. package/dist/tree-surfer-ui/application/user-input-to-intent.js +12 -5
  60. package/dist/tree-surfer-ui/domain/effect.d.ts +9 -8
  61. package/dist/tree-surfer-ui/domain/effect.d.ts.map +1 -1
  62. package/dist/tree-surfer-ui/domain/index.d.ts +1 -1
  63. package/dist/tree-surfer-ui/domain/index.d.ts.map +1 -1
  64. package/dist/tree-surfer-ui/domain/index.js +1 -1
  65. package/dist/tree-surfer-ui/domain/{input-state.d.ts → input-mode-state.d.ts} +2 -2
  66. package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +1 -0
  67. package/package.json +3 -2
  68. package/dist/tree-surfer/state.d.ts +0 -25
  69. package/dist/tree-surfer/state.d.ts.map +0 -1
  70. package/dist/tree-surfer/state.js +0 -118
  71. package/dist/tree-surfer-ui/domain/input-state.d.ts.map +0 -1
  72. /package/dist/tree-surfer-ui/domain/{input-state.js → input-mode-state.js} +0 -0
@@ -1,33 +1,98 @@
1
1
  import * as fc from 'fast-check';
2
2
  import type { TreeNode } from './tree-node.types.js';
3
- type Id = string;
4
- export declare const idArb: fc.Arbitrary<string>;
5
- export type StringNode = TreeNode<Id, StringNode>;
6
- type PathEntry = {
7
- path: string[];
3
+ /**
4
+ * The ID type used by the generated test trees.
5
+ *
6
+ * Short strings keep generated counterexamples readable.
7
+ */
8
+ type TestNodeId = string;
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 declare const testIdArb: fc.Arbitrary<string>;
14
+ /**
15
+ * Tree node whose IDs are strings, without additional domain properties.
16
+ */
17
+ export type StringNode = TreeNode<TestNodeId, StringNode>;
18
+ type Path = TestNodeId[];
19
+ type NodeAtPath = {
20
+ path: Path;
8
21
  node: StringNode;
9
22
  };
10
- type GeneratedNode = {
23
+ type NodeWithReachableNodes = {
11
24
  node: StringNode;
12
- pathEntries: PathEntry[];
25
+ reachableNodes: NodeAtPath[];
13
26
  };
14
- export declare const generatedNodeArb: fc.Arbitrary<GeneratedNode>;
15
- export declare const generatedNodesArrayArb: fc.Arbitrary<GeneratedNode[]>;
16
- export declare const forestAndPathsArb: fc.Arbitrary<{
17
- entries: any[];
18
- pathEntries: PathEntry[];
27
+ /**
28
+ * Generates a leaf node.
29
+ */
30
+ export declare const generatedLeafNodeArb: fc.Arbitrary<NodeWithReachableNodes>;
31
+ /**
32
+ * Generates a closed branch.
33
+ */
34
+ export declare const generatedClosedBranchNodeArb: fc.Arbitrary<NodeWithReachableNodes>;
35
+ /**
36
+ * Generates a leaf, an open branch, or a closed branch.
37
+ *
38
+ * Open branches have unique child IDs. Root IDs are made unique separately
39
+ * when generating a forest.
40
+ *
41
+ * The weighted choice gives recursive open branches a geometric dropoff.
42
+ */
43
+ export declare const generatedNodeArb: fc.Arbitrary<NodeWithReachableNodes>;
44
+ /**
45
+ * Generates an array of generated nodes with unique sibling IDs.
46
+ *
47
+ * The result is suitable for use as the loaded children of an open branch.
48
+ */
49
+ export declare const generatedNodesArrayArb: fc.Arbitrary<NodeWithReachableNodes[]>;
50
+ /**
51
+ * An array of root-level nodes and all paths that can be resolved through
52
+ * those nodes.
53
+ *
54
+ * Paths through closed branches stop at the closed branch because its
55
+ * descendants are not loaded.
56
+ */
57
+ export declare const nodeAndPathsArb: fc.Arbitrary<{
58
+ root: any;
59
+ pathEntries: NodeAtPath[];
19
60
  }>;
20
- export declare const forestAndPathAndExpectedArb: fc.Arbitrary<{
21
- entries: any[];
22
- path: string[];
61
+ /**
62
+ * Generates root-level nodes and a path to one of their reachable nodes.
63
+ *
64
+ * Every generated path is valid for `getAtPath`; unreachable paths are not
65
+ * included.
66
+ */
67
+ export declare const nodeAndPathAndExpectedArb: fc.Arbitrary<{
68
+ root: any;
69
+ path: Path;
23
70
  expected: any;
24
71
  }>;
72
+ /**
73
+ * Generates root-level nodes and a path to one of their branch nodes.
74
+ *
75
+ * Both open and closed branches are included. A path to a closed branch is
76
+ * valid, but a path through a closed branch cannot reach any descendants.
77
+ */
25
78
  export declare const nodesArrayAndBranchPathArb: fc.Arbitrary<{
26
- entries: any[];
27
- path: string[];
79
+ root: any;
80
+ path: Path;
28
81
  expected: any;
29
82
  }>;
83
+ /**
84
+ * Generates a new array of loaded child nodes.
85
+ *
86
+ * The generated nodes have unique IDs, making the result suitable for use as
87
+ * the children of an open branch.
88
+ */
30
89
  export declare const newBranchesArb: fc.Arbitrary<any[]>;
90
+ /**
91
+ * Generates either a new array of loaded child nodes or `null`.
92
+ *
93
+ * An array represents an open branch, including an open branch with no
94
+ * children. `null` represents a closed branch.
95
+ */
31
96
  export declare const newBranchesOrNullArb: fc.Arbitrary<any[] | null>;
32
97
  export {};
33
98
  //# sourceMappingURL=tree-node.arb.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree-node.arb.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.arb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,KAAK,EAAE,GAAG,MAAM,CAAC;AAEjB,eAAO,MAAM,KAAK,sBAGhB,CAAC;AAGH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;AAElD,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,SAAS,EAAE,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CA8DnD,CAAC;AAEP,eAAO,MAAM,sBAAsB,+BAIjC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;EAK7B,CAAC;AAEF,eAAO,MAAM,2BAA2B;;;;EAQrC,CAAC;AAEJ,eAAO,MAAM,0BAA0B;;;;EAYpC,CAAC;AAEJ,eAAO,MAAM,cAAc,qBAE1B,CAAC;AAEF,eAAO,MAAM,oBAAoB,4BAA8C,CAAC"}
1
+ {"version":3,"file":"tree-node.arb.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/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;AASzB;;;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;AAqEF;;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,4BAGhC,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/tree-surfer/tree-node/tree-node.arb.debug.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { describe, it } from 'vitest';
2
+ import * as fc from 'fast-check';
3
+ import { generatedNodeArb, generatedNodesArrayArb, } from './tree-node.arb.js';
4
+ /**
5
+ * Used for looking at the tree distributions generated by the arbs.
6
+ */
7
+ describe('print generated tree node arbitraries', () => {
8
+ it.only('generated nodes', () => {
9
+ const samples = fc.sample(generatedNodeArb, {
10
+ numRuns: 10,
11
+ seed: 12345,
12
+ });
13
+ for (const [index, generated] of samples.entries()) {
14
+ console.log(`\n--- node ${index + 1} ---`);
15
+ console.dir(generated.node, {
16
+ depth: Infinity,
17
+ colors: false,
18
+ });
19
+ }
20
+ });
21
+ it.only('generated root-level node arrays', () => {
22
+ const samples = fc.sample(generatedNodesArrayArb, {
23
+ numRuns: 5,
24
+ seed: 12345,
25
+ });
26
+ for (const [index, generatedNodes] of samples.entries()) {
27
+ console.log(`\n--- root array ${index + 1} ---`);
28
+ console.dir(generatedNodes.map(({ node }) => node), {
29
+ depth: Infinity,
30
+ colors: false,
31
+ });
32
+ }
33
+ });
34
+ });
@@ -1,80 +1,188 @@
1
1
  import * as fc from 'fast-check';
2
- export const idArb = fc.string({
2
+ /**
3
+ * Characters used by 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),
3
12
  minLength: 1,
4
13
  maxLength: 8,
5
14
  });
6
- export const generatedNodeArb = fc.letrec((tie) => ({
7
- node: fc.oneof(idArb.map((id) => {
8
- const node = { id: id };
9
- return {
10
- node,
11
- pathEntries: [
12
- {
13
- path: [id],
14
- node,
15
- },
16
- ],
17
- };
18
- }), fc
19
- .tuple(idArb, fc.oneof(fc.constant(null), fc.uniqueArray(tie('node'), {
15
+ /**
16
+ * Generates the metadata for a leaf node.
17
+ *
18
+ * @param id - The ID assigned to the leaf.
19
+ * @returns The leaf and the path that resolves to it.
20
+ */
21
+ const generatedLeafNodeWithReachableNodes = (id) => {
22
+ const node = { id };
23
+ return {
24
+ node,
25
+ reachableNodes: [{ path: [], node }],
26
+ };
27
+ };
28
+ /**
29
+ * Generates the metadata for a closed branch.
30
+ *
31
+ * A closed branch contributes no descendant paths because its children are not
32
+ * loaded.
33
+ *
34
+ * @param id - The ID assigned to the branch.
35
+ * @returns The closed branch and the path that resolves to it.
36
+ */
37
+ const generatedClosedBranchWithReachableNodes = (id) => {
38
+ const node = {
39
+ id,
40
+ children: null,
41
+ };
42
+ return {
43
+ node,
44
+ reachableNodes: [{ path: [], node }],
45
+ };
46
+ };
47
+ /**
48
+ * Generates the metadata for an open branch and its descendants.
49
+ *
50
+ * @param id - The ID assigned to the branch.
51
+ * @param children - The generated children of the branch.
52
+ * @returns The open branch and paths to the branch and all its descendants.
53
+ */
54
+ const generatedOpenBranchWithReachableNodes = (id, children) => {
55
+ const node = {
56
+ id,
57
+ children: children.map(({ node }) => node),
58
+ };
59
+ const descendantNodes = children.flatMap(({ node: child, reachableNodes }) => reachableNodes.map(({ path, node }) => ({
60
+ path: [child.id, ...path],
61
+ node,
62
+ })));
63
+ return {
64
+ node,
65
+ reachableNodes: [{ path: [], node }, ...descendantNodes],
66
+ };
67
+ };
68
+ /**
69
+ * Generates a leaf node.
70
+ */
71
+ export const generatedLeafNodeArb = testIdArb.map(generatedLeafNodeWithReachableNodes);
72
+ /**
73
+ * Generates a closed branch.
74
+ */
75
+ export const generatedClosedBranchNodeArb = testIdArb.map(generatedClosedBranchWithReachableNodes);
76
+ /**
77
+ * Generates a leaf or a closed branch.
78
+ *
79
+ * These node types terminate a generated path because they have no reachable
80
+ * descendants.
81
+ */
82
+ const generatedTerminalNodeArb = fc.oneof(generatedLeafNodeArb, generatedClosedBranchNodeArb);
83
+ /**
84
+ * Generates a leaf, an open branch, or a closed branch.
85
+ *
86
+ * Open branches have unique child IDs. Root IDs are made unique separately
87
+ * when generating a forest.
88
+ *
89
+ * The weighted choice gives recursive open branches a geometric dropoff.
90
+ */
91
+ export const generatedNodeArb = fc.letrec((tie) => {
92
+ const generatedChildNodeArb = tie('node');
93
+ /**
94
+ * Generates the loaded children of an open branch.
95
+ *
96
+ * Sibling IDs must be unique so that child lookup is unambiguous. The
97
+ * maximum length also limits the branching factor of recursive examples.
98
+ */
99
+ const generatedOpenChildrenArb = fc.uniqueArray(generatedChildNodeArb, {
20
100
  minLength: 0,
101
+ maxLength: 4,
21
102
  size: 'medium',
22
- // @ts-ignore
23
- selector: (generated) => generated.node.id,
24
- })))
25
- .map(([id, children]) => {
26
- const node = {
27
- id,
28
- children:
29
- // @ts-ignore
30
- children === null ? null : children.map((child) => child.node),
31
- };
32
- const descendantPathEntries = children === null
33
- ? []
34
- : children.flatMap((child) =>
35
- // @ts-ignore
36
- child.pathEntries.map(({ path, node }) => ({
37
- path: [id, ...path],
38
- node,
39
- })));
40
- return {
41
- node,
42
- pathEntries: [
43
- {
44
- path: [id],
45
- node,
46
- },
47
- ...descendantPathEntries,
48
- ],
49
- };
50
- })),
51
- })).node;
103
+ selector: ({ node }) => node.id,
104
+ });
105
+ /**
106
+ * Generates an open branch and its reachable descendants.
107
+ */
108
+ const generatedOpenBranchNodeArb = fc
109
+ .tuple(testIdArb, generatedOpenChildrenArb)
110
+ .map(([id, children]) => generatedOpenBranchWithReachableNodes(id, children));
111
+ return {
112
+ node: fc
113
+ .integer({
114
+ min: 0,
115
+ max: 9999,
116
+ })
117
+ .chain((choice) =>
118
+ // 5046 works for getAtPath tests, but too heavy for modifyAtPath tests
119
+ choice >= 5050
120
+ ? generatedOpenBranchNodeArb
121
+ : generatedTerminalNodeArb),
122
+ };
123
+ }).node;
124
+ /**
125
+ * Generates an array of generated nodes with unique sibling IDs.
126
+ *
127
+ * The result is suitable for use as the loaded children of an open branch.
128
+ */
52
129
  export const generatedNodesArrayArb = fc.uniqueArray(generatedNodeArb, {
53
130
  minLength: 0,
54
131
  size: 'small',
55
132
  selector: (generated) => generated.node.id,
56
133
  });
57
- export const forestAndPathsArb = generatedNodesArrayArb.map((generatedNodes) => ({
58
- entries: generatedNodes.map((generated) => generated.node),
59
- pathEntries: generatedNodes.flatMap((generated) => generated.pathEntries),
134
+ /**
135
+ * An array of root-level nodes and all paths that can be resolved through
136
+ * those nodes.
137
+ *
138
+ * Paths through closed branches stop at the closed branch because its
139
+ * descendants are not loaded.
140
+ */
141
+ export const nodeAndPathsArb = generatedNodeArb.map(({ node: root, reachableNodes }) => ({
142
+ root,
143
+ pathEntries: reachableNodes,
60
144
  }));
61
- export const forestAndPathAndExpectedArb = forestAndPathsArb
145
+ /**
146
+ * Generates root-level nodes and a path to one of their reachable nodes.
147
+ *
148
+ * Every generated path is valid for `getAtPath`; unreachable paths are not
149
+ * included.
150
+ */
151
+ export const nodeAndPathAndExpectedArb = nodeAndPathsArb
62
152
  .filter(({ pathEntries }) => pathEntries.length > 0)
63
- .chain(({ entries, pathEntries }) => fc.constantFrom(...pathEntries).map(({ path, node }) => ({
64
- entries,
153
+ .chain(({ root, pathEntries }) => fc.constantFrom(...pathEntries).map(({ path, node }) => ({
154
+ root,
65
155
  path,
66
156
  expected: node,
67
157
  })));
68
- export const nodesArrayAndBranchPathArb = forestAndPathsArb
69
- .map(({ entries, pathEntries }) => ({
70
- entries,
158
+ /**
159
+ * Generates root-level nodes and a path to one of their branch nodes.
160
+ *
161
+ * Both open and closed branches are included. A path to a closed branch is
162
+ * valid, but a path through a closed branch cannot reach any descendants.
163
+ */
164
+ export const nodesArrayAndBranchPathArb = nodeAndPathsArb
165
+ .map(({ root, pathEntries }) => ({
166
+ root,
71
167
  branchPathEntries: pathEntries.filter(({ node }) => 'children' in node),
72
168
  }))
73
169
  .filter(({ branchPathEntries }) => branchPathEntries.length > 0)
74
- .chain(({ entries, branchPathEntries }) => fc.constantFrom(...branchPathEntries).map(({ path, node }) => ({
75
- entries,
170
+ .chain(({ root, branchPathEntries }) => fc.constantFrom(...branchPathEntries).map(({ path, node }) => ({
171
+ root,
76
172
  path,
77
173
  expected: node,
78
174
  })));
175
+ /**
176
+ * Generates a new array of loaded child nodes.
177
+ *
178
+ * The generated nodes have unique IDs, making the result suitable for use as
179
+ * the children of an open branch.
180
+ */
79
181
  export const newBranchesArb = generatedNodesArrayArb.map((generatedNodes) => generatedNodes.map(({ node }) => node));
182
+ /**
183
+ * Generates either a new array of loaded child nodes or `null`.
184
+ *
185
+ * An array represents an open branch, including an open branch with no
186
+ * children. `null` represents a closed branch.
187
+ */
80
188
  export const newBranchesOrNullArb = fc.oneof(newBranchesArb, fc.constant(null));
@@ -10,5 +10,5 @@ import type { SerializableKey, TreeNode, TreeNodeApi } from './tree-node.types.j
10
10
  * Node IDs are compared using JavaScript value equality. Sibling IDs
11
11
  * should be unique.
12
12
  */
13
- export declare function createTreeNodeApi<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>>(): TreeNodeApi<Id, ChildNode>;
13
+ export declare function createTreeNodeApi<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(): TreeNodeApi<Id, Node>;
14
14
  //# sourceMappingURL=tree-node.impl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree-node.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/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,SAAS,SAAS,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC,KACtC,WAAW,CAAC,EAAE,EAAE,SAAS,CAAC,CA0G9B"}
1
+ {"version":3,"file":"tree-node.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/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"}
@@ -11,7 +11,6 @@
11
11
  */
12
12
  export function createTreeNodeApi() {
13
13
  const treeNodeApi = {
14
- // Type Narrowers:
15
14
  isLeaf(node) {
16
15
  return !('children' in node);
17
16
  },
@@ -30,60 +29,54 @@ export function createTreeNodeApi() {
30
29
  getChildById(node, id) {
31
30
  return node.children.find((child) => child.id === id);
32
31
  },
33
- getAtPath(entries, path, selector) {
34
- if (path.length === 0) {
35
- return undefined;
36
- }
37
- // Accumulator
38
- let node = entries.find((candidate) => candidate.id === path[0]);
39
- for (let index = 1; node !== undefined && index < path.length; index += 1) {
40
- if (treeNodeApi.isOpenBranch(node)) {
41
- node = treeNodeApi.getChildById(node, path[index]);
32
+ getAtPath(root, path, selector) {
33
+ let node = root;
34
+ for (const id of path) {
35
+ if (!treeNodeApi.isBranch(node) || !treeNodeApi.isOpenBranch(node)) {
36
+ return undefined;
42
37
  }
43
- else {
44
- node = undefined;
38
+ const child = treeNodeApi.getChildById(node, id);
39
+ if (child === undefined) {
40
+ return undefined;
45
41
  }
42
+ node = child;
46
43
  }
47
- return node === undefined ? undefined : selector(node);
44
+ return selector(node);
48
45
  },
49
- modifyAtPath(entries, path, modifier) {
46
+ modifyAtPath(root, path, modifier) {
50
47
  if (path.length === 0) {
51
- return undefined;
48
+ return modifier(root);
52
49
  }
53
- // Only cloned arrays/nodes are modified below.
54
- const updatedEntries = [...entries];
55
- let currentEntries = updatedEntries;
56
- for (let index = 0; index < path.length; index += 1) {
57
- const id = path[index];
58
- const childIndex = currentEntries.findIndex((candidate) => candidate.id === id);
59
- if (childIndex === -1) {
60
- return undefined;
61
- }
62
- const child = currentEntries[childIndex];
63
- if (index === path.length - 1) {
64
- const replacement = modifier(child);
65
- if (replacement === undefined) {
66
- return undefined;
67
- }
68
- currentEntries[childIndex] = replacement;
69
- return updatedEntries;
70
- }
71
- if (!treeNodeApi.isBranch(child) || !treeNodeApi.isOpenBranch(child)) {
72
- // The path cannot continue through a leaf or closed branch.
73
- return undefined;
74
- }
75
- // Clone the child array before modifying it.
76
- const updatedChildren = [...treeNodeApi.getChildren(child)];
77
- // Clone the ancestor node and attach the cloned child array.
78
- currentEntries[childIndex] = {
79
- ...child,
80
- children: updatedChildren,
81
- };
82
- // Continue traversal through the cloned children.
83
- currentEntries = updatedChildren;
84
- }
85
- return undefined;
50
+ return modifyChildAtPath(root, path, modifier);
86
51
  },
87
52
  };
53
+ function modifyChildAtPath(node, path, modifier) {
54
+ const childId = path[0];
55
+ if (childId === undefined ||
56
+ !treeNodeApi.isBranch(node) ||
57
+ !treeNodeApi.isOpenBranch(node)) {
58
+ return undefined;
59
+ }
60
+ const childIndex = node.children.findIndex((child) => child.id === childId);
61
+ if (childIndex === -1) {
62
+ return undefined;
63
+ }
64
+ const child = node.children[childIndex];
65
+ if (child === undefined) {
66
+ return undefined;
67
+ }
68
+ const updatedChild = path.length === 1
69
+ ? modifier(child)
70
+ : modifyChildAtPath(child, path.slice(1), modifier);
71
+ if (updatedChild === undefined) {
72
+ return undefined;
73
+ }
74
+ const children = [...node.children];
75
+ children[childIndex] = updatedChild;
76
+ return {
77
+ ...node,
78
+ children,
79
+ };
80
+ }
88
81
  return treeNodeApi;
89
82
  }
@@ -16,7 +16,7 @@ export declare const serializableKeySchema: z.ZodUnion<readonly [z.ZodString, z.
16
16
  *
17
17
  * The concrete application can extend these schemas with additional fields.
18
18
  */
19
- export declare function createTreeNodeSchemas<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>>(idSchema: z.ZodType<Id>, childSchema: z.ZodType<ChildNode>): {
19
+ export declare function createTreeNodeSchemas<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(idSchema: z.ZodType<Id>, nodeSchema: z.ZodType<Node>): {
20
20
  leafSchema: z.ZodObject<{
21
21
  id: z.ZodType<Id, unknown, z.core.$ZodTypeInternals<Id, unknown>>;
22
22
  }, z.core.$strip>;
@@ -26,7 +26,7 @@ export declare function createTreeNodeSchemas<Id extends SerializableKey, ChildN
26
26
  }, z.core.$strip>;
27
27
  openBranchSchema: z.ZodObject<{
28
28
  id: z.ZodType<Id, unknown, z.core.$ZodTypeInternals<Id, unknown>>;
29
- children: z.ZodArray<z.ZodType<ChildNode, unknown, z.core.$ZodTypeInternals<ChildNode, unknown>>>;
29
+ children: z.ZodArray<z.ZodType<Node, unknown, z.core.$ZodTypeInternals<Node, unknown>>>;
30
30
  }, z.core.$strip>;
31
31
  };
32
32
  //# sourceMappingURL=tree-node.schemas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree-node.schemas.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/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,SAAS,SAAS,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC,EACzC,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;;;;;;;;;;;;EAoB3D"}
1
+ {"version":3,"file":"tree-node.schemas.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/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"}
@@ -15,7 +15,7 @@ export const serializableKeySchema = z.union([z.string(), z.number().finite()]);
15
15
  *
16
16
  * The concrete application can extend these schemas with additional fields.
17
17
  */
18
- export function createTreeNodeSchemas(idSchema, childSchema) {
18
+ export function createTreeNodeSchemas(idSchema, nodeSchema) {
19
19
  const leafSchema = z.object({
20
20
  id: idSchema,
21
21
  });
@@ -25,7 +25,7 @@ export function createTreeNodeSchemas(idSchema, childSchema) {
25
25
  });
26
26
  const openBranchSchema = z.object({
27
27
  id: idSchema,
28
- children: z.array(childSchema),
28
+ children: z.array(nodeSchema),
29
29
  });
30
30
  return {
31
31
  leafSchema,