fast-tree-builder 0.1.4 → 1.0.0-alpha.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 (5) hide show
  1. package/README.md +36 -11
  2. package/index.cjs +135 -59
  3. package/index.d.ts +3 -1
  4. package/index.js +135 -59
  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,14 @@ 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`.
229
254
  - `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
255
 
231
256
  Returns
@@ -238,7 +263,7 @@ An object with the following properties:
238
263
  Throws `Error` when:
239
264
 
240
265
  - A duplicate identifier is recieved,
241
- - or `validateParentKeys` is set and an invalid parent key is recieved,
266
+ - or `validRootKeys` is set and an invalid parent key is recieved,
242
267
  - or `validateTree` is set to `true` and a cyclic graph is the result.
243
268
 
244
269
  ## Comparison with other tree building libraries
package/index.cjs CHANGED
@@ -1,76 +1,152 @@
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, 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 (validRootKeys) {
53
+ const validParentKeys = new Set(validRootKeys);
54
+ for (const [key, node] of danglingNodes.entries()) {
55
+ if (!validParentKeys.has(key)) {
56
+ throw new Error(`Invalid parent key "${key}"`);
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;
47
- }
48
- }
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}"`);
56
- }
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];
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);
61
75
  }
62
- roots.push(root);
63
76
  }
64
77
  }
65
78
  }
66
79
  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];
80
+ const knownNodes = new Set();
81
+ const incompleteNodes = new Set();
82
+ for (const item of items) {
83
+ const keyOfNode = typeof key === 'function' ? key(item) : item[key];
84
+ const keyOfChildNodes = typeof childrenKey === 'function' ? childrenKey(item) : item[childrenKey];
85
+ if (knownNodes.has(keyOfNode)) {
86
+ throw new Error(`Duplicate identifier detected for "${keyOfNode}"`);
87
+ }
88
+ knownNodes.add(keyOfNode);
89
+ incompleteNodes.delete(keyOfNode);
90
+ let node = nodes.get(keyOfNode);
91
+ if (!node) {
92
+ node = {};
93
+ danglingNodes.set(keyOfNode, node);
94
+ }
95
+ // Set the data of the node
96
+ const nodeData = typeof mapNodeData === 'function' ? mapNodeData(item) : item;
97
+ if (nodeDataKey !== false) {
98
+ node[nodeDataKey] = nodeData;
99
+ }
100
+ else {
101
+ Object.assign(node, nodeData);
102
+ }
103
+ // Link children to this node
104
+ if (keyOfChildNodes) {
105
+ node[nodeChildrenKey] = [];
106
+ for (const keyOfChildNode of keyOfChildNodes) {
107
+ let childNode = danglingNodes.get(keyOfChildNode);
108
+ if (childNode) {
109
+ nodes.set(keyOfChildNode, childNode);
110
+ danglingNodes.delete(keyOfChildNode);
111
+ // Set the parent on child node
112
+ if (nodeParentKey !== false) {
113
+ childNode[nodeParentKey] = node;
114
+ }
115
+ }
116
+ else if (nodes.has(keyOfChildNode)) {
117
+ throw new Error(`Duplicate parent detected for "${keyOfChildNode}"`);
118
+ }
119
+ else {
120
+ // We create a new temporary node
121
+ childNode = {};
122
+ // Set the parent on child node
123
+ if (nodeParentKey !== false) {
124
+ childNode[nodeParentKey] = node;
125
+ }
126
+ nodes.set(keyOfChildNode, childNode);
127
+ incompleteNodes.add(keyOfChildNode);
128
+ }
129
+ node[nodeChildrenKey].push(childNode);
130
+ }
131
+ }
132
+ }
133
+ if (incompleteNodes.size > 0) {
134
+ throw new Error(`Some nodes miss their referenced children (${incompleteNodes.size}).`);
135
+ }
136
+ if (validRootKeys) {
137
+ const validParentKeys = new Set(validRootKeys);
138
+ for (const [key, node] of danglingNodes.entries()) {
139
+ if (!validParentKeys.has(key)) {
140
+ throw new Error(`Invalid parent key "${key}"`);
72
141
  }
73
- roots.push(root);
142
+ roots.push(node);
143
+ nodes.set(key, node);
144
+ }
145
+ }
146
+ else {
147
+ for (const [key, node] of danglingNodes.entries()) {
148
+ roots.push(node);
149
+ nodes.set(key, node);
74
150
  }
75
151
  }
76
152
  }
package/index.d.ts CHANGED
@@ -17,15 +17,17 @@ 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>;
29
31
  validateTree?: boolean;
30
32
  }): {
31
33
  roots: TreeNode<M extends undefined ? T : M, D, P, C>[];
package/index.js CHANGED
@@ -1,74 +1,150 @@
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, 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 (validRootKeys) {
51
+ const validParentKeys = new Set(validRootKeys);
52
+ for (const [key, node] of danglingNodes.entries()) {
53
+ if (!validParentKeys.has(key)) {
54
+ throw new Error(`Invalid parent key "${key}"`);
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;
45
- }
46
- }
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}"`);
54
- }
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];
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);
59
73
  }
60
- roots.push(root);
61
74
  }
62
75
  }
63
76
  }
64
77
  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];
78
+ const knownNodes = new Set();
79
+ const incompleteNodes = new Set();
80
+ for (const item of items) {
81
+ const keyOfNode = typeof key === 'function' ? key(item) : item[key];
82
+ const keyOfChildNodes = typeof childrenKey === 'function' ? childrenKey(item) : item[childrenKey];
83
+ if (knownNodes.has(keyOfNode)) {
84
+ throw new Error(`Duplicate identifier detected for "${keyOfNode}"`);
85
+ }
86
+ knownNodes.add(keyOfNode);
87
+ incompleteNodes.delete(keyOfNode);
88
+ let node = nodes.get(keyOfNode);
89
+ if (!node) {
90
+ node = {};
91
+ danglingNodes.set(keyOfNode, node);
92
+ }
93
+ // Set the data of the node
94
+ const nodeData = typeof mapNodeData === 'function' ? mapNodeData(item) : item;
95
+ if (nodeDataKey !== false) {
96
+ node[nodeDataKey] = nodeData;
97
+ }
98
+ else {
99
+ Object.assign(node, nodeData);
100
+ }
101
+ // Link children to this node
102
+ if (keyOfChildNodes) {
103
+ node[nodeChildrenKey] = [];
104
+ for (const keyOfChildNode of keyOfChildNodes) {
105
+ let childNode = danglingNodes.get(keyOfChildNode);
106
+ if (childNode) {
107
+ nodes.set(keyOfChildNode, childNode);
108
+ danglingNodes.delete(keyOfChildNode);
109
+ // Set the parent on child node
110
+ if (nodeParentKey !== false) {
111
+ childNode[nodeParentKey] = node;
112
+ }
113
+ }
114
+ else if (nodes.has(keyOfChildNode)) {
115
+ throw new Error(`Duplicate parent detected for "${keyOfChildNode}"`);
116
+ }
117
+ else {
118
+ // We create a new temporary node
119
+ childNode = {};
120
+ // Set the parent on child node
121
+ if (nodeParentKey !== false) {
122
+ childNode[nodeParentKey] = node;
123
+ }
124
+ nodes.set(keyOfChildNode, childNode);
125
+ incompleteNodes.add(keyOfChildNode);
126
+ }
127
+ node[nodeChildrenKey].push(childNode);
128
+ }
129
+ }
130
+ }
131
+ if (incompleteNodes.size > 0) {
132
+ throw new Error(`Some nodes miss their referenced children (${incompleteNodes.size}).`);
133
+ }
134
+ if (validRootKeys) {
135
+ const validParentKeys = new Set(validRootKeys);
136
+ for (const [key, node] of danglingNodes.entries()) {
137
+ if (!validParentKeys.has(key)) {
138
+ throw new Error(`Invalid parent key "${key}"`);
70
139
  }
71
- roots.push(root);
140
+ roots.push(node);
141
+ nodes.set(key, node);
142
+ }
143
+ }
144
+ else {
145
+ for (const [key, node] of danglingNodes.entries()) {
146
+ roots.push(node);
147
+ nodes.set(key, node);
72
148
  }
73
149
  }
74
150
  }
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.0",
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": {