fast-tree-builder 0.1.4 → 1.0.0-alpha.1
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.
- package/README.md +38 -11
- package/index.cjs +133 -58
- package/index.d.ts +4 -1
- package/index.js +133 -58
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
- You have a list of items,
|
|
13
13
|
- where each item is identifiable by a unique value,
|
|
14
|
-
- and the items are connected via a parent relation.
|
|
14
|
+
- and the items are connected via a *parent* OR a *childred* relation.
|
|
15
15
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
|
|
22
22
|
- **Robust TypeScript Type Definitions**: Leverage type safety through extensive TypeScript type definitions. The package includes precise type annotations to improve code reliability and developer workflow.
|
|
23
23
|
|
|
24
|
-
- **Fully Customizable Node Structure**: Tailor the structure of the nodes in the built tree to meet your specific requirements. You have the freedom to define data, parent, and children key names according to your application's needs. To avoid circular references parent links can be turned off which helps generating JSON data.
|
|
24
|
+
- **Fully Customizable Node Structure**: Tailor the structure of the nodes in the built tree to meet your specific requirements. You have the freedom to define data, parent, and children key names according to your application's needs. To avoid circular references, parent links can be turned off which helps generating JSON data.
|
|
25
25
|
|
|
26
26
|
- **Works on Any Iterable Data**: Designed to handle arrays, sets, and other iterable data structures efficiently, ensuring broad applicability.
|
|
27
27
|
|
|
28
28
|
- **No Sorting Required**: The algorithm does not require your input data to be sorted, saving you preprocessing time and effort.
|
|
29
29
|
|
|
30
|
-
- **Flexible Key and Parent Key Types**: You can use any JavaScript value for identifying items. Relations checked with strict (`
|
|
30
|
+
- **Flexible Key and Parent Key Types**: You can use any JavaScript value for identifying items. Relations checked with strict (`childKey === parentKey`) comparison.
|
|
31
31
|
|
|
32
32
|
- **Multiple Root Nodes**: Can efficiently construct trees with multiple root nodes, accommodating scenarios that necessitate distinct, separate tree structures within the same dataset.
|
|
33
33
|
|
|
@@ -60,6 +60,8 @@ Here are some examples showcasing the usage of `fast-tree-builder` and their exp
|
|
|
60
60
|
|
|
61
61
|
```typescript
|
|
62
62
|
import buildTree from 'fast-tree-builder';
|
|
63
|
+
// OR
|
|
64
|
+
const { default: buildTree } = require('fast-tree-builder');
|
|
63
65
|
|
|
64
66
|
const items = [
|
|
65
67
|
{ id: 1, parent: null, name: 'Root 1' },
|
|
@@ -70,8 +72,10 @@ const items = [
|
|
|
70
72
|
];
|
|
71
73
|
|
|
72
74
|
const { roots, nodes } = buildTree(items, {
|
|
75
|
+
// the input items:
|
|
73
76
|
key: 'id',
|
|
74
77
|
parentKey: 'parent',
|
|
78
|
+
// the built node:
|
|
75
79
|
nodeDataKey: 'data',
|
|
76
80
|
nodeParentKey: 'parent',
|
|
77
81
|
nodeChildrenKey: 'children',
|
|
@@ -113,7 +117,27 @@ console.log(nodes);
|
|
|
113
117
|
```
|
|
114
118
|
|
|
115
119
|
|
|
116
|
-
### Example 2:
|
|
120
|
+
### Example 2: Build tree by children
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import buildTree from 'fast-tree-builder';
|
|
124
|
+
|
|
125
|
+
const items = [
|
|
126
|
+
{ id: 1, children: [3, 4], name: 'Root 1' },
|
|
127
|
+
{ id: 2, children: [5], name: 'Root 2' },
|
|
128
|
+
{ id: 3, name: 'Child 1.1' },
|
|
129
|
+
{ id: 4, name: 'Child 1.2' },
|
|
130
|
+
{ id: 5, name: 'Child 2.1' },
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const { roots, nodes } = buildTree(items, {
|
|
134
|
+
mode: 'children'
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> Produces the same output as **Example 1**.
|
|
139
|
+
|
|
140
|
+
### Example 3: Customized Node Structure
|
|
117
141
|
|
|
118
142
|
```typescript
|
|
119
143
|
import buildTree from 'fast-tree-builder';
|
|
@@ -171,7 +195,7 @@ console.log(nodes);
|
|
|
171
195
|
```
|
|
172
196
|
|
|
173
197
|
|
|
174
|
-
### Example
|
|
198
|
+
### Example 4: Crazy ideas
|
|
175
199
|
|
|
176
200
|
```typescript
|
|
177
201
|
import buildTree from 'fast-tree-builder';
|
|
@@ -219,13 +243,15 @@ Parameters
|
|
|
219
243
|
|
|
220
244
|
- `items`: An iterable data structure containing the items to build the tree from.
|
|
221
245
|
- `options`: An object specifying the build options. It has the following properties:
|
|
246
|
+
- `mode`: (Optional) Defines the item connection method. `children` means an item defines its children in a list, and connects that way; `parent` means an item defines its parent, and connects to it that way. Defaults to `parent`.
|
|
222
247
|
- `key`: (Optional) The key used to identify items. It can be a string, number, symbol, or a function that extracts the key from an item. Defaults to `'id'`.
|
|
223
|
-
- `parentKey`: (Optional) The key used to identify the parent of each item. It can be a string
|
|
224
|
-
- `nodeDataKey`: (Optional) The key used to store the item's data in each node. It can be a string
|
|
225
|
-
- `nodeParentKey`: (Optional) The key used to store the parent node in each node. It can be a string
|
|
226
|
-
- `nodeChildrenKey`: (Optional) The key used to store the children nodes in each node. It can be a string
|
|
248
|
+
- `parentKey`: (Optional) The key used to identify the parent of each item. It can be a `string`, `number`, `symbol`, or a `function` that extracts the parent key from an item. Defaults to `'parent'`.
|
|
249
|
+
- `nodeDataKey`: (Optional) The key used to store the item's data in each node. It can be a `string`, `number`, `symbol`, or `false` if the data should be merged directly into the node. Defaults to `'data'`.
|
|
250
|
+
- `nodeParentKey`: (Optional) The key used to store the parent node in each node. It can be a `string`, `number`, `symbol`, or `false` if the parent node should not be included. Defaults to `'parent'`.
|
|
251
|
+
- `nodeChildrenKey`: (Optional) The key used to store the children nodes in each node. It can be a `string`, `number`, `symbol`. Defaults to `'children'`.
|
|
227
252
|
- `mapNodeData`: (Optional) A function that maps an item to its corresponding node data. It allows transforming the item before assigning it to the node. Defaults to `undefined`.
|
|
228
|
-
- `
|
|
253
|
+
- `validRootKeys`: (Optional) An iterable containing key values that can be accepted as root nodes. If provided, any item with a key not present in this iterable will cause an error to be thrown. Defaults to `undefined`.
|
|
254
|
+
- `validRootParentKeys`: (Optional) Only available when `mode` is set to `parent`. An iterable containing key values that can be accepted the parent field values of root nodes. If provided, any root node with a parent key not present in this iterable will cause an error to be thrown. Defaults to `undefined`.
|
|
229
255
|
- `validateTree`: (Optional) A boolean flag that determines whether to validate the resulting data structure. If the structure is a cyclic graph, an `Error` will be thrown. Requires additional `O(n)` time to compute. Defaults to `false`.
|
|
230
256
|
|
|
231
257
|
Returns
|
|
@@ -238,7 +264,8 @@ An object with the following properties:
|
|
|
238
264
|
Throws `Error` when:
|
|
239
265
|
|
|
240
266
|
- A duplicate identifier is recieved,
|
|
241
|
-
- or `
|
|
267
|
+
- or `validRootKeys` is set and an invalid key is recieved,
|
|
268
|
+
- or `validRootParentKeys` is set and an invalid parent key is recieved,
|
|
242
269
|
- or `validateTree` is set to `true` and a cyclic graph is the result.
|
|
243
270
|
|
|
244
271
|
## Comparison with other tree building libraries
|
package/index.cjs
CHANGED
|
@@ -1,76 +1,151 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
function buildTree(items, options = {}) {
|
|
4
|
-
const { key = 'id', parentKey = 'parent', nodeDataKey = 'data', nodeParentKey = 'parent', nodeChildrenKey = 'children', mapNodeData,
|
|
4
|
+
const { mode = 'parent', key = 'id', parentKey = 'parent', childrenKey = 'children', nodeDataKey = 'data', nodeParentKey = 'parent', nodeChildrenKey = 'children', mapNodeData, validRootKeys, validRootParentKeys, validateTree = false, } = options;
|
|
5
|
+
const roots = [];
|
|
5
6
|
const nodes = new Map();
|
|
6
7
|
const danglingNodes = new Map();
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
danglingNodes.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
if (mode === 'parent') {
|
|
9
|
+
for (const item of items) {
|
|
10
|
+
const keyOfNode = typeof key === 'function' ? key(item) : item[key];
|
|
11
|
+
const keyOfParentNode = typeof parentKey === 'function' ? parentKey(item) : item[parentKey];
|
|
12
|
+
if (nodes.has(keyOfNode)) {
|
|
13
|
+
throw new Error(`Duplicate identifier detected for "${keyOfNode}"`);
|
|
14
|
+
}
|
|
15
|
+
// Current node can be new or already created by a child item as its parent
|
|
16
|
+
let node = danglingNodes.get(keyOfNode);
|
|
17
|
+
if (node) {
|
|
18
|
+
danglingNodes.delete(keyOfNode);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
node = {};
|
|
22
|
+
}
|
|
23
|
+
nodes.set(keyOfNode, node);
|
|
24
|
+
// Set the data of the node
|
|
25
|
+
const nodeData = typeof mapNodeData === 'function' ? mapNodeData(item) : item;
|
|
26
|
+
if (nodeDataKey !== false) {
|
|
27
|
+
node[nodeDataKey] = nodeData;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
Object.assign(node, nodeData);
|
|
31
|
+
}
|
|
32
|
+
// Link this node to its parent
|
|
33
|
+
let parentNode = nodes.get(keyOfParentNode) ?? danglingNodes.get(keyOfParentNode);
|
|
34
|
+
if (!parentNode) {
|
|
35
|
+
// No parent node exists yet, create as dangling node
|
|
36
|
+
parentNode = {};
|
|
37
|
+
// Track as dangling node, we dont know yet if it really exists
|
|
38
|
+
danglingNodes.set(keyOfParentNode, parentNode);
|
|
39
|
+
}
|
|
40
|
+
// When no children added yet
|
|
41
|
+
if (!parentNode[nodeChildrenKey]) {
|
|
42
|
+
parentNode[nodeChildrenKey] = [];
|
|
43
|
+
}
|
|
44
|
+
// Add as child
|
|
45
|
+
parentNode[nodeChildrenKey].push(node);
|
|
46
|
+
// Set the parent on this node
|
|
47
|
+
if (nodeParentKey !== false) {
|
|
48
|
+
node[nodeParentKey] = parentNode;
|
|
49
|
+
}
|
|
19
50
|
}
|
|
20
|
-
nodes
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
51
|
+
// Children of dangling nodes will become the root nodes
|
|
52
|
+
if (validRootParentKeys) {
|
|
53
|
+
const validParentKeys = new Set(validRootParentKeys);
|
|
54
|
+
for (const [parentKey, node] of danglingNodes.entries()) {
|
|
55
|
+
if (!validParentKeys.has(parentKey)) {
|
|
56
|
+
throw new Error(`Invalid parent key "${parentKey}" found for a root node.`);
|
|
57
|
+
}
|
|
58
|
+
for (const root of node[nodeChildrenKey]) {
|
|
59
|
+
// Root nodes does not have a parent, unlink the dangling node
|
|
60
|
+
if (nodeParentKey !== false) {
|
|
61
|
+
delete root[nodeParentKey];
|
|
62
|
+
}
|
|
63
|
+
roots.push(root);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
25
66
|
}
|
|
26
67
|
else {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
danglingNodes.set(keyOfParentNode, parent);
|
|
37
|
-
}
|
|
38
|
-
// When no children added yet
|
|
39
|
-
if (!parent[nodeChildrenKey]) {
|
|
40
|
-
parent[nodeChildrenKey] = [];
|
|
41
|
-
}
|
|
42
|
-
// Add as child
|
|
43
|
-
parent[nodeChildrenKey].push(node);
|
|
44
|
-
// Set the parent on this node
|
|
45
|
-
if (nodeParentKey !== false) {
|
|
46
|
-
node[nodeParentKey] = parent;
|
|
68
|
+
for (const node of danglingNodes.values()) {
|
|
69
|
+
for (const root of node[nodeChildrenKey]) {
|
|
70
|
+
// Root nodes does not have a parent, unlink the dangling node
|
|
71
|
+
if (nodeParentKey !== false) {
|
|
72
|
+
delete root[nodeParentKey];
|
|
73
|
+
}
|
|
74
|
+
roots.push(root);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
47
77
|
}
|
|
48
78
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
79
|
+
else {
|
|
80
|
+
if (validRootParentKeys) {
|
|
81
|
+
throw new Error(`Option "validRootParentKeys" cannot be used when mode is set to "children".`);
|
|
82
|
+
}
|
|
83
|
+
const knownNodes = new Set();
|
|
84
|
+
const incompleteNodes = new Set();
|
|
85
|
+
for (const item of items) {
|
|
86
|
+
const keyOfNode = typeof key === 'function' ? key(item) : item[key];
|
|
87
|
+
const keyOfChildNodes = typeof childrenKey === 'function' ? childrenKey(item) : item[childrenKey];
|
|
88
|
+
if (knownNodes.has(keyOfNode)) {
|
|
89
|
+
throw new Error(`Duplicate identifier detected for "${keyOfNode}"`);
|
|
56
90
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
91
|
+
knownNodes.add(keyOfNode);
|
|
92
|
+
incompleteNodes.delete(keyOfNode);
|
|
93
|
+
let node = nodes.get(keyOfNode);
|
|
94
|
+
if (!node) {
|
|
95
|
+
node = {};
|
|
96
|
+
danglingNodes.set(keyOfNode, node);
|
|
97
|
+
}
|
|
98
|
+
// Set the data of the node
|
|
99
|
+
const nodeData = typeof mapNodeData === 'function' ? mapNodeData(item) : item;
|
|
100
|
+
if (nodeDataKey !== false) {
|
|
101
|
+
node[nodeDataKey] = nodeData;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
Object.assign(node, nodeData);
|
|
105
|
+
}
|
|
106
|
+
// Link children to this node
|
|
107
|
+
if (keyOfChildNodes) {
|
|
108
|
+
node[nodeChildrenKey] = [];
|
|
109
|
+
for (const keyOfChildNode of keyOfChildNodes) {
|
|
110
|
+
let childNode = danglingNodes.get(keyOfChildNode);
|
|
111
|
+
if (childNode) {
|
|
112
|
+
nodes.set(keyOfChildNode, childNode);
|
|
113
|
+
danglingNodes.delete(keyOfChildNode);
|
|
114
|
+
// Set the parent on child node
|
|
115
|
+
if (nodeParentKey !== false) {
|
|
116
|
+
childNode[nodeParentKey] = node;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (nodes.has(keyOfChildNode)) {
|
|
120
|
+
throw new Error(`Duplicate parent detected for "${keyOfChildNode}"`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// We create a new temporary node
|
|
124
|
+
childNode = {};
|
|
125
|
+
// Set the parent on child node
|
|
126
|
+
if (nodeParentKey !== false) {
|
|
127
|
+
childNode[nodeParentKey] = node;
|
|
128
|
+
}
|
|
129
|
+
nodes.set(keyOfChildNode, childNode);
|
|
130
|
+
incompleteNodes.add(keyOfChildNode);
|
|
131
|
+
}
|
|
132
|
+
node[nodeChildrenKey].push(childNode);
|
|
61
133
|
}
|
|
62
|
-
roots.push(root);
|
|
63
134
|
}
|
|
64
135
|
}
|
|
136
|
+
if (incompleteNodes.size > 0) {
|
|
137
|
+
throw new Error(`Some nodes miss their referenced children (${incompleteNodes.size}).`);
|
|
138
|
+
}
|
|
139
|
+
for (const [key, node] of danglingNodes.entries()) {
|
|
140
|
+
roots.push(node);
|
|
141
|
+
nodes.set(key, node);
|
|
142
|
+
}
|
|
65
143
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
delete root[nodeParentKey];
|
|
72
|
-
}
|
|
73
|
-
roots.push(root);
|
|
144
|
+
if (validRootKeys) {
|
|
145
|
+
const validKeys = new Set(validRootKeys);
|
|
146
|
+
for (const key of roots.keys()) {
|
|
147
|
+
if (!validKeys.has(key)) {
|
|
148
|
+
throw new Error(`A root node has an unexpected key "${key}"`);
|
|
74
149
|
}
|
|
75
150
|
}
|
|
76
151
|
}
|
package/index.d.ts
CHANGED
|
@@ -17,15 +17,18 @@ type TreeNode<T, D extends string | number | symbol | false, P extends string |
|
|
|
17
17
|
}));
|
|
18
18
|
type KeyReturnType<T, P extends keyof T | ((item: T) => any)> = P extends ((item: T) => infer R) ? R : P extends keyof T ? T[P] : never;
|
|
19
19
|
declare function buildTree<T extends (D extends false ? object : unknown), K extends keyof T | ((item: T) => any), M extends (D extends false ? object : unknown) = T, D extends string | number | symbol | false = 'data', P extends string | number | symbol | false = 'parent', C extends string | number | symbol = 'children'>(items: Iterable<T>, options?: {
|
|
20
|
+
mode?: 'parent' | 'children';
|
|
20
21
|
key?: K;
|
|
21
22
|
parentKey?: keyof T | ((item: T) => KeyReturnType<T, K>);
|
|
23
|
+
childrenKey?: keyof T | ((item: T) => KeyReturnType<T, K>);
|
|
22
24
|
nodeDataKey?: D;
|
|
23
25
|
nodeParentKey?: P;
|
|
24
26
|
nodeChildrenKey?: C;
|
|
25
27
|
mapNodeData?: {
|
|
26
28
|
(item: T): M;
|
|
27
29
|
};
|
|
28
|
-
|
|
30
|
+
validRootKeys?: Iterable<unknown>;
|
|
31
|
+
validRootParentKeys?: Iterable<unknown>;
|
|
29
32
|
validateTree?: boolean;
|
|
30
33
|
}): {
|
|
31
34
|
roots: TreeNode<M extends undefined ? T : M, D, P, C>[];
|
package/index.js
CHANGED
|
@@ -1,74 +1,149 @@
|
|
|
1
1
|
function buildTree(items, options = {}) {
|
|
2
|
-
const { key = 'id', parentKey = 'parent', nodeDataKey = 'data', nodeParentKey = 'parent', nodeChildrenKey = 'children', mapNodeData,
|
|
2
|
+
const { mode = 'parent', key = 'id', parentKey = 'parent', childrenKey = 'children', nodeDataKey = 'data', nodeParentKey = 'parent', nodeChildrenKey = 'children', mapNodeData, validRootKeys, validRootParentKeys, validateTree = false, } = options;
|
|
3
|
+
const roots = [];
|
|
3
4
|
const nodes = new Map();
|
|
4
5
|
const danglingNodes = new Map();
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
danglingNodes.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
if (mode === 'parent') {
|
|
7
|
+
for (const item of items) {
|
|
8
|
+
const keyOfNode = typeof key === 'function' ? key(item) : item[key];
|
|
9
|
+
const keyOfParentNode = typeof parentKey === 'function' ? parentKey(item) : item[parentKey];
|
|
10
|
+
if (nodes.has(keyOfNode)) {
|
|
11
|
+
throw new Error(`Duplicate identifier detected for "${keyOfNode}"`);
|
|
12
|
+
}
|
|
13
|
+
// Current node can be new or already created by a child item as its parent
|
|
14
|
+
let node = danglingNodes.get(keyOfNode);
|
|
15
|
+
if (node) {
|
|
16
|
+
danglingNodes.delete(keyOfNode);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
node = {};
|
|
20
|
+
}
|
|
21
|
+
nodes.set(keyOfNode, node);
|
|
22
|
+
// Set the data of the node
|
|
23
|
+
const nodeData = typeof mapNodeData === 'function' ? mapNodeData(item) : item;
|
|
24
|
+
if (nodeDataKey !== false) {
|
|
25
|
+
node[nodeDataKey] = nodeData;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
Object.assign(node, nodeData);
|
|
29
|
+
}
|
|
30
|
+
// Link this node to its parent
|
|
31
|
+
let parentNode = nodes.get(keyOfParentNode) ?? danglingNodes.get(keyOfParentNode);
|
|
32
|
+
if (!parentNode) {
|
|
33
|
+
// No parent node exists yet, create as dangling node
|
|
34
|
+
parentNode = {};
|
|
35
|
+
// Track as dangling node, we dont know yet if it really exists
|
|
36
|
+
danglingNodes.set(keyOfParentNode, parentNode);
|
|
37
|
+
}
|
|
38
|
+
// When no children added yet
|
|
39
|
+
if (!parentNode[nodeChildrenKey]) {
|
|
40
|
+
parentNode[nodeChildrenKey] = [];
|
|
41
|
+
}
|
|
42
|
+
// Add as child
|
|
43
|
+
parentNode[nodeChildrenKey].push(node);
|
|
44
|
+
// Set the parent on this node
|
|
45
|
+
if (nodeParentKey !== false) {
|
|
46
|
+
node[nodeParentKey] = parentNode;
|
|
47
|
+
}
|
|
17
48
|
}
|
|
18
|
-
nodes
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
49
|
+
// Children of dangling nodes will become the root nodes
|
|
50
|
+
if (validRootParentKeys) {
|
|
51
|
+
const validParentKeys = new Set(validRootParentKeys);
|
|
52
|
+
for (const [parentKey, node] of danglingNodes.entries()) {
|
|
53
|
+
if (!validParentKeys.has(parentKey)) {
|
|
54
|
+
throw new Error(`Invalid parent key "${parentKey}" found for a root node.`);
|
|
55
|
+
}
|
|
56
|
+
for (const root of node[nodeChildrenKey]) {
|
|
57
|
+
// Root nodes does not have a parent, unlink the dangling node
|
|
58
|
+
if (nodeParentKey !== false) {
|
|
59
|
+
delete root[nodeParentKey];
|
|
60
|
+
}
|
|
61
|
+
roots.push(root);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
23
64
|
}
|
|
24
65
|
else {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
danglingNodes.set(keyOfParentNode, parent);
|
|
35
|
-
}
|
|
36
|
-
// When no children added yet
|
|
37
|
-
if (!parent[nodeChildrenKey]) {
|
|
38
|
-
parent[nodeChildrenKey] = [];
|
|
39
|
-
}
|
|
40
|
-
// Add as child
|
|
41
|
-
parent[nodeChildrenKey].push(node);
|
|
42
|
-
// Set the parent on this node
|
|
43
|
-
if (nodeParentKey !== false) {
|
|
44
|
-
node[nodeParentKey] = parent;
|
|
66
|
+
for (const node of danglingNodes.values()) {
|
|
67
|
+
for (const root of node[nodeChildrenKey]) {
|
|
68
|
+
// Root nodes does not have a parent, unlink the dangling node
|
|
69
|
+
if (nodeParentKey !== false) {
|
|
70
|
+
delete root[nodeParentKey];
|
|
71
|
+
}
|
|
72
|
+
roots.push(root);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
45
75
|
}
|
|
46
76
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
77
|
+
else {
|
|
78
|
+
if (validRootParentKeys) {
|
|
79
|
+
throw new Error(`Option "validRootParentKeys" cannot be used when mode is set to "children".`);
|
|
80
|
+
}
|
|
81
|
+
const knownNodes = new Set();
|
|
82
|
+
const incompleteNodes = new Set();
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
const keyOfNode = typeof key === 'function' ? key(item) : item[key];
|
|
85
|
+
const keyOfChildNodes = typeof childrenKey === 'function' ? childrenKey(item) : item[childrenKey];
|
|
86
|
+
if (knownNodes.has(keyOfNode)) {
|
|
87
|
+
throw new Error(`Duplicate identifier detected for "${keyOfNode}"`);
|
|
54
88
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
knownNodes.add(keyOfNode);
|
|
90
|
+
incompleteNodes.delete(keyOfNode);
|
|
91
|
+
let node = nodes.get(keyOfNode);
|
|
92
|
+
if (!node) {
|
|
93
|
+
node = {};
|
|
94
|
+
danglingNodes.set(keyOfNode, node);
|
|
95
|
+
}
|
|
96
|
+
// Set the data of the node
|
|
97
|
+
const nodeData = typeof mapNodeData === 'function' ? mapNodeData(item) : item;
|
|
98
|
+
if (nodeDataKey !== false) {
|
|
99
|
+
node[nodeDataKey] = nodeData;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
Object.assign(node, nodeData);
|
|
103
|
+
}
|
|
104
|
+
// Link children to this node
|
|
105
|
+
if (keyOfChildNodes) {
|
|
106
|
+
node[nodeChildrenKey] = [];
|
|
107
|
+
for (const keyOfChildNode of keyOfChildNodes) {
|
|
108
|
+
let childNode = danglingNodes.get(keyOfChildNode);
|
|
109
|
+
if (childNode) {
|
|
110
|
+
nodes.set(keyOfChildNode, childNode);
|
|
111
|
+
danglingNodes.delete(keyOfChildNode);
|
|
112
|
+
// Set the parent on child node
|
|
113
|
+
if (nodeParentKey !== false) {
|
|
114
|
+
childNode[nodeParentKey] = node;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (nodes.has(keyOfChildNode)) {
|
|
118
|
+
throw new Error(`Duplicate parent detected for "${keyOfChildNode}"`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// We create a new temporary node
|
|
122
|
+
childNode = {};
|
|
123
|
+
// Set the parent on child node
|
|
124
|
+
if (nodeParentKey !== false) {
|
|
125
|
+
childNode[nodeParentKey] = node;
|
|
126
|
+
}
|
|
127
|
+
nodes.set(keyOfChildNode, childNode);
|
|
128
|
+
incompleteNodes.add(keyOfChildNode);
|
|
129
|
+
}
|
|
130
|
+
node[nodeChildrenKey].push(childNode);
|
|
59
131
|
}
|
|
60
|
-
roots.push(root);
|
|
61
132
|
}
|
|
62
133
|
}
|
|
134
|
+
if (incompleteNodes.size > 0) {
|
|
135
|
+
throw new Error(`Some nodes miss their referenced children (${incompleteNodes.size}).`);
|
|
136
|
+
}
|
|
137
|
+
for (const [key, node] of danglingNodes.entries()) {
|
|
138
|
+
roots.push(node);
|
|
139
|
+
nodes.set(key, node);
|
|
140
|
+
}
|
|
63
141
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
delete root[nodeParentKey];
|
|
70
|
-
}
|
|
71
|
-
roots.push(root);
|
|
142
|
+
if (validRootKeys) {
|
|
143
|
+
const validKeys = new Set(validRootKeys);
|
|
144
|
+
for (const key of roots.keys()) {
|
|
145
|
+
if (!validKeys.has(key)) {
|
|
146
|
+
throw new Error(`A root node has an unexpected key "${key}"`);
|
|
72
147
|
}
|
|
73
148
|
}
|
|
74
149
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-tree-builder",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
4
|
"description": "Efficiently construct highly customizable bi-directional tree structures from iterable data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -16,18 +16,17 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"prepack": "npm run build && npm run test",
|
|
18
18
|
"postversion": "git push && git push --tags",
|
|
19
|
-
"clean": "
|
|
20
|
-
"build": "node scripts/build
|
|
19
|
+
"clean": "node scripts/clean.js",
|
|
20
|
+
"build": "node scripts/build.js",
|
|
21
21
|
"watch": "node scripts/watch.js",
|
|
22
22
|
"test": "mocha",
|
|
23
23
|
"coverage": "c8 -r text -r text-summary -r lcov --include \"*.js\" npm test"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"c8": "^
|
|
26
|
+
"c8": "^9.1.0",
|
|
27
27
|
"chokidar": "^3.5.3",
|
|
28
28
|
"mocha": "^10.2.0",
|
|
29
|
-
"
|
|
30
|
-
"typescript": "^5.1.3"
|
|
29
|
+
"typescript": "^5.3.3"
|
|
31
30
|
},
|
|
32
31
|
"homepage": "https://github.com/lionel87/fast-tree-builder#readme",
|
|
33
32
|
"repository": {
|