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.
Files changed (5) hide show
  1. package/README.md +38 -11
  2. package/index.cjs +133 -58
  3. package/index.d.ts +4 -1
  4. package/index.js +133 -58
  5. 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 (`key === parentKey`) comparison.
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: Customized Node Structure
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 3: Crazy ideas
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, number, symbol, or a function that extracts the parent key from an item. Defaults to `'parent'`.
224
- - `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'`.
225
- - `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'`.
226
- - `nodeChildrenKey`: (Optional) The key used to store the children nodes in each node. It can be a string, number, symbol. Defaults to `'children'`.
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
- - `validateParentKeys`: (Optional) An iterable containing parent key values that can be accepted as root nodes. If provided, any item with a parent key not present in this iterable will cause an error to be thrown. Defaults to `undefined`.
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 `validateParentKeys` is set and an invalid parent key is recieved,
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, validateParentKeys, validateTree = false, } = options;
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
- for (const item of items) {
8
- const keyOfNode = typeof key === 'function' ? key(item) : item[key];
9
- if (nodes.has(keyOfNode)) {
10
- throw new Error(`Duplicate identifier detected for "${keyOfNode}"`);
11
- }
12
- // Current node can be new or already created by a child item as its parent
13
- let node = danglingNodes.get(keyOfNode);
14
- if (node) {
15
- danglingNodes.delete(keyOfNode);
16
- }
17
- else {
18
- node = {};
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.set(keyOfNode, node);
21
- // Set the data of the node
22
- const nodeData = typeof mapNodeData === 'function' ? mapNodeData(item) : item;
23
- if (nodeDataKey !== false) {
24
- node[nodeDataKey] = nodeData;
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
- Object.assign(node, nodeData);
28
- }
29
- // Link this node to its parent
30
- const keyOfParentNode = typeof parentKey === 'function' ? parentKey(item) : item[parentKey];
31
- let parent = nodes.get(keyOfParentNode) ?? danglingNodes.get(keyOfParentNode);
32
- if (!parent) {
33
- // No parent node exists yet, create as dangling node
34
- parent = {};
35
- // Track as dangling node, we dont know yet if it really exists
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
- // Children of dangling nodes will become the root nodes
50
- const roots = [];
51
- if (validateParentKeys) {
52
- const validParentKeys = new Set(validateParentKeys);
53
- for (const [key, node] of danglingNodes.entries()) {
54
- if (!validParentKeys.has(key)) {
55
- throw new Error(`Invalid parent key "${key}"`);
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
- for (const root of node[nodeChildrenKey]) {
58
- // Root nodes does not have a parent, unlink the dangling node
59
- if (nodeParentKey !== false) {
60
- delete root[nodeParentKey];
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
- else {
67
- for (const node of danglingNodes.values()) {
68
- for (const root of node[nodeChildrenKey]) {
69
- // Root nodes does not have a parent, unlink the dangling node
70
- if (nodeParentKey !== false) {
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
- validateParentKeys?: Iterable<unknown>;
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, validateParentKeys, validateTree = false, } = options;
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
- for (const item of items) {
6
- const keyOfNode = typeof key === 'function' ? key(item) : item[key];
7
- if (nodes.has(keyOfNode)) {
8
- throw new Error(`Duplicate identifier detected for "${keyOfNode}"`);
9
- }
10
- // Current node can be new or already created by a child item as its parent
11
- let node = danglingNodes.get(keyOfNode);
12
- if (node) {
13
- danglingNodes.delete(keyOfNode);
14
- }
15
- else {
16
- node = {};
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.set(keyOfNode, node);
19
- // Set the data of the node
20
- const nodeData = typeof mapNodeData === 'function' ? mapNodeData(item) : item;
21
- if (nodeDataKey !== false) {
22
- node[nodeDataKey] = nodeData;
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
- Object.assign(node, nodeData);
26
- }
27
- // Link this node to its parent
28
- const keyOfParentNode = typeof parentKey === 'function' ? parentKey(item) : item[parentKey];
29
- let parent = nodes.get(keyOfParentNode) ?? danglingNodes.get(keyOfParentNode);
30
- if (!parent) {
31
- // No parent node exists yet, create as dangling node
32
- parent = {};
33
- // Track as dangling node, we dont know yet if it really exists
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
- // Children of dangling nodes will become the root nodes
48
- const roots = [];
49
- if (validateParentKeys) {
50
- const validParentKeys = new Set(validateParentKeys);
51
- for (const [key, node] of danglingNodes.entries()) {
52
- if (!validParentKeys.has(key)) {
53
- throw new Error(`Invalid parent key "${key}"`);
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
- for (const root of node[nodeChildrenKey]) {
56
- // Root nodes does not have a parent, unlink the dangling node
57
- if (nodeParentKey !== false) {
58
- delete root[nodeParentKey];
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
- else {
65
- for (const node of danglingNodes.values()) {
66
- for (const root of node[nodeChildrenKey]) {
67
- // Root nodes does not have a parent, unlink the dangling node
68
- if (nodeParentKey !== false) {
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.4",
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": "rimraf --glob \"!(.*).{d.ts,js,cjs,mjs}\" build coverage",
20
- "build": "node scripts/build-esm.js && node scripts/build-cjs.js",
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": "^8.0.0",
26
+ "c8": "^9.1.0",
27
27
  "chokidar": "^3.5.3",
28
28
  "mocha": "^10.2.0",
29
- "rimraf": "^5.0.1",
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": {