fast-tree-builder 2.0.0-beta.0 → 2.0.0-beta.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 CHANGED
@@ -29,7 +29,7 @@
29
29
  - **Arbitary Node Access** – Returns a `Map` that allows constant-time access to any node.
30
30
  - **Tree Validation** – Detects cycles or nodes reachable through multiple paths.
31
31
  - **Reference Validation** – Optionally enforce that all parent/child links are valid.
32
- - **Depth Values** – Optionally includes a depth value in each node.
32
+ - **Depth Values** – Optionally include a depth value in each node.
33
33
 
34
34
  ## Installation
35
35
 
@@ -66,7 +66,7 @@ Builds a tree structure from an iterable list of items.
66
66
 
67
67
  ##### Optional
68
68
 
69
- - `nodeValueMapper`: Function to map an item to a custom value stored in the node. Optional.
69
+ - `nodeValueMapper`: Function to map an item to a custom value stored in the node.
70
70
  - `nodeValueKey`: Key where the item is stored in the output node. Set to `false` to inline the item directly into the node. Defaults to `'value'`.
71
71
  - `nodeParentKey`: Key where the node's parent reference is stored. Set to `false` to omit parent links. Defaults to `'parent'`.
72
72
  - `nodeChildrenKey`: Key where the node's children are stored. Defaults to `'children'`.
package/index.cjs CHANGED
@@ -23,7 +23,7 @@ __export(index_exports, {
23
23
  module.exports = __toCommonJS(index_exports);
24
24
  function buildTree(items, options) {
25
25
  if (!options) {
26
- throw new Error('Missing required "options" parameter.');
26
+ throw new Error(`Missing required 'options' parameter.`);
27
27
  }
28
28
  const {
29
29
  id: idAccessor,
@@ -38,13 +38,13 @@ function buildTree(items, options) {
38
38
  validateReferences = false
39
39
  } = options;
40
40
  if (!idAccessor) {
41
- throw new Error('Option "id" is required.');
41
+ throw new Error(`Option 'id' is required.`);
42
42
  }
43
43
  if (!parentIdAccessor && !childIdsAccessor) {
44
- throw new Error('Either "parentId" or "childIds" must be provided.');
44
+ throw new Error(`Either 'parentId' or 'childIds' must be provided.`);
45
45
  }
46
46
  if (parentIdAccessor && childIdsAccessor) {
47
- throw new Error('"parentId" and "childIds" cannot be used together.');
47
+ throw new Error(`'parentId' and 'childIds' cannot be used together.`);
48
48
  }
49
49
  const roots = [];
50
50
  const nodes = /* @__PURE__ */ new Map();
@@ -53,9 +53,15 @@ function buildTree(items, options) {
53
53
  for (const item of items) {
54
54
  const id = typeof idAccessor === "function" ? idAccessor(item) : item[idAccessor];
55
55
  if (nodes.has(id)) {
56
- throw new Error(`Duplicate identifier "${id}".`);
56
+ throw new Error(`Duplicate identifier '${id}'.`);
57
57
  }
58
58
  const node = nodeValueKey !== false ? { [nodeValueKey]: nodeValueMapper ? nodeValueMapper(item) : item } : { ...nodeValueMapper ? nodeValueMapper(item) : item };
59
+ if (nodeValueKey === false) {
60
+ if (nodeParentKey !== false) {
61
+ delete node[nodeParentKey];
62
+ }
63
+ delete node[nodeChildrenKey];
64
+ }
59
65
  nodes.set(id, node);
60
66
  const parentId = typeof parentIdAccessor === "function" ? parentIdAccessor(item) : item[parentIdAccessor];
61
67
  const parentNode = nodes.get(parentId);
@@ -86,7 +92,7 @@ function buildTree(items, options) {
86
92
  }
87
93
  for (const [parentId, nodes2] of waitingForParent.entries()) {
88
94
  if (validateReferences && parentId != null) {
89
- throw new Error(`Referential integrity violation: parentId "${parentId}" does not match any item in the input.`);
95
+ throw new Error(`Referential integrity violation: parentId '${parentId}' does not match any item in the input.`);
90
96
  }
91
97
  for (const node of nodes2) {
92
98
  roots.push(node);
@@ -98,14 +104,20 @@ function buildTree(items, options) {
98
104
  for (const item of items) {
99
105
  const id = typeof idAccessor === "function" ? idAccessor(item) : item[idAccessor];
100
106
  if (nodes.has(id)) {
101
- throw new Error(`Duplicate identifier "${id}".`);
107
+ throw new Error(`Duplicate identifier '${id}'.`);
102
108
  }
103
109
  const node = nodeValueKey !== false ? { [nodeValueKey]: nodeValueMapper ? nodeValueMapper(item) : item } : { ...nodeValueMapper ? nodeValueMapper(item) : item };
110
+ if (nodeValueKey === false) {
111
+ if (nodeParentKey !== false) {
112
+ delete node[nodeParentKey];
113
+ }
114
+ delete node[nodeChildrenKey];
115
+ }
104
116
  nodes.set(id, node);
105
117
  const childIds = typeof childIdsAccessor === "function" ? childIdsAccessor(item) : item[childIdsAccessor];
106
118
  if (childIds != null) {
107
119
  if (typeof childIds[Symbol.iterator] !== "function") {
108
- throw new Error(`Item "${id}" has invalid children: expected an iterable value.`);
120
+ throw new Error(`Item '${id}' has invalid children: expected an iterable value.`);
109
121
  }
110
122
  node[nodeChildrenKey] = [];
111
123
  for (const childId of childIds) {
@@ -114,12 +126,15 @@ function buildTree(items, options) {
114
126
  node[nodeChildrenKey].push(childNode);
115
127
  if (nodeParentKey !== false) {
116
128
  if (childNode[nodeParentKey] && childNode[nodeParentKey] !== node) {
117
- throw new Error(`Node "${childId}" already has a different parent, refusing to overwrite. Set "nodeParentKey" to false to supress this error.`);
129
+ throw new Error(`Node '${childId}' already has a different parent, refusing to overwrite.`);
118
130
  }
119
131
  childNode[nodeParentKey] = node;
120
132
  }
121
133
  rootCandidates.delete(childId);
122
134
  } else {
135
+ if (waitingChildren.has(childId)) {
136
+ throw new Error(`Multiple parents reference the same unresolved child '${childId}'.`);
137
+ }
123
138
  waitingChildren.set(childId, {
124
139
  parentNode: node,
125
140
  childIndex: node[nodeChildrenKey].length
@@ -137,7 +152,7 @@ function buildTree(items, options) {
137
152
  parentNode[nodeChildrenKey][childIndex] = node;
138
153
  if (nodeParentKey !== false) {
139
154
  if (node[nodeParentKey] && node[nodeParentKey] !== parentNode) {
140
- throw new Error(`Node "${id}" already has a different parent, refusing to overwrite. Set "nodeParentKey" to false to supress this error.`);
155
+ throw new Error(`Node '${id}' already has a different parent, refusing to overwrite.`);
141
156
  }
142
157
  node[nodeParentKey] = parentNode;
143
158
  }
@@ -149,7 +164,7 @@ function buildTree(items, options) {
149
164
  if (waitingChildren.size > 0) {
150
165
  if (validateReferences) {
151
166
  const childId = waitingChildren.keys().next().value;
152
- throw new Error(`Referential integrity violation: child reference "${childId}" does not match any item in the input.`);
167
+ throw new Error(`Referential integrity violation: child reference '${childId}' does not match any item in the input.`);
153
168
  }
154
169
  const pending = Array.from(waitingChildren.values());
155
170
  for (let i = pending.length - 1; i >= 0; i--) {
package/index.d.cts CHANGED
@@ -9,7 +9,7 @@ type TreeNode<TValue, TValueKey extends PropertyKey | false, TParentKey extends
9
9
  } : {});
10
10
  type AccessorReturnType<O, P extends (keyof O) | ((item: O) => any)> = P extends ((item: O) => infer R) ? R : P extends (keyof O) ? O[P] : never;
11
11
  type ObjectKeysOfIterableProperties<T> = {
12
- [K in keyof T]: T[K] extends Iterable<unknown> & object ? K : never;
12
+ [K in keyof T]: T[K] extends (Iterable<unknown> & object) | null | undefined ? K : never;
13
13
  }[keyof T];
14
14
  declare function buildTree<TIdAccessor extends (keyof NoInfer<TInputValue>) | ((item: NoInfer<TInputValue>) => unknown), TNodeValueKey extends PropertyKey | false = 'value', TNodeParentKey extends PropertyKey | false = 'parent', TNodeChildrenKey extends PropertyKey = 'children', TNodeDepthKey extends PropertyKey | false = false, TInputValue extends (TNodeValueKey extends false ? object : TIdAccessor extends PropertyKey ? object : unknown) = any, TMappedValue extends (TNodeValueKey extends false ? object : unknown) = TInputValue>(items: Iterable<TInputValue>, options: {
15
15
  /**
package/index.d.mts CHANGED
@@ -9,7 +9,7 @@ type TreeNode<TValue, TValueKey extends PropertyKey | false, TParentKey extends
9
9
  } : {});
10
10
  type AccessorReturnType<O, P extends (keyof O) | ((item: O) => any)> = P extends ((item: O) => infer R) ? R : P extends (keyof O) ? O[P] : never;
11
11
  type ObjectKeysOfIterableProperties<T> = {
12
- [K in keyof T]: T[K] extends Iterable<unknown> & object ? K : never;
12
+ [K in keyof T]: T[K] extends (Iterable<unknown> & object) | null | undefined ? K : never;
13
13
  }[keyof T];
14
14
  export default function buildTree<TIdAccessor extends (keyof NoInfer<TInputValue>) | ((item: NoInfer<TInputValue>) => unknown), TNodeValueKey extends PropertyKey | false = 'value', TNodeParentKey extends PropertyKey | false = 'parent', TNodeChildrenKey extends PropertyKey = 'children', TNodeDepthKey extends PropertyKey | false = false, TInputValue extends (TNodeValueKey extends false ? object : TIdAccessor extends PropertyKey ? object : unknown) = any, TMappedValue extends (TNodeValueKey extends false ? object : unknown) = TInputValue>(items: Iterable<TInputValue>, options: {
15
15
  /**
package/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  function buildTree(items, options) {
2
2
  if (!options) {
3
- throw new Error('Missing required "options" parameter.');
3
+ throw new Error(`Missing required 'options' parameter.`);
4
4
  }
5
5
  const {
6
6
  id: idAccessor,
@@ -15,13 +15,13 @@ function buildTree(items, options) {
15
15
  validateReferences = false
16
16
  } = options;
17
17
  if (!idAccessor) {
18
- throw new Error('Option "id" is required.');
18
+ throw new Error(`Option 'id' is required.`);
19
19
  }
20
20
  if (!parentIdAccessor && !childIdsAccessor) {
21
- throw new Error('Either "parentId" or "childIds" must be provided.');
21
+ throw new Error(`Either 'parentId' or 'childIds' must be provided.`);
22
22
  }
23
23
  if (parentIdAccessor && childIdsAccessor) {
24
- throw new Error('"parentId" and "childIds" cannot be used together.');
24
+ throw new Error(`'parentId' and 'childIds' cannot be used together.`);
25
25
  }
26
26
  const roots = [];
27
27
  const nodes = /* @__PURE__ */ new Map();
@@ -30,9 +30,15 @@ function buildTree(items, options) {
30
30
  for (const item of items) {
31
31
  const id = typeof idAccessor === "function" ? idAccessor(item) : item[idAccessor];
32
32
  if (nodes.has(id)) {
33
- throw new Error(`Duplicate identifier "${id}".`);
33
+ throw new Error(`Duplicate identifier '${id}'.`);
34
34
  }
35
35
  const node = nodeValueKey !== false ? { [nodeValueKey]: nodeValueMapper ? nodeValueMapper(item) : item } : { ...nodeValueMapper ? nodeValueMapper(item) : item };
36
+ if (nodeValueKey === false) {
37
+ if (nodeParentKey !== false) {
38
+ delete node[nodeParentKey];
39
+ }
40
+ delete node[nodeChildrenKey];
41
+ }
36
42
  nodes.set(id, node);
37
43
  const parentId = typeof parentIdAccessor === "function" ? parentIdAccessor(item) : item[parentIdAccessor];
38
44
  const parentNode = nodes.get(parentId);
@@ -63,7 +69,7 @@ function buildTree(items, options) {
63
69
  }
64
70
  for (const [parentId, nodes2] of waitingForParent.entries()) {
65
71
  if (validateReferences && parentId != null) {
66
- throw new Error(`Referential integrity violation: parentId "${parentId}" does not match any item in the input.`);
72
+ throw new Error(`Referential integrity violation: parentId '${parentId}' does not match any item in the input.`);
67
73
  }
68
74
  for (const node of nodes2) {
69
75
  roots.push(node);
@@ -75,14 +81,20 @@ function buildTree(items, options) {
75
81
  for (const item of items) {
76
82
  const id = typeof idAccessor === "function" ? idAccessor(item) : item[idAccessor];
77
83
  if (nodes.has(id)) {
78
- throw new Error(`Duplicate identifier "${id}".`);
84
+ throw new Error(`Duplicate identifier '${id}'.`);
79
85
  }
80
86
  const node = nodeValueKey !== false ? { [nodeValueKey]: nodeValueMapper ? nodeValueMapper(item) : item } : { ...nodeValueMapper ? nodeValueMapper(item) : item };
87
+ if (nodeValueKey === false) {
88
+ if (nodeParentKey !== false) {
89
+ delete node[nodeParentKey];
90
+ }
91
+ delete node[nodeChildrenKey];
92
+ }
81
93
  nodes.set(id, node);
82
94
  const childIds = typeof childIdsAccessor === "function" ? childIdsAccessor(item) : item[childIdsAccessor];
83
95
  if (childIds != null) {
84
96
  if (typeof childIds[Symbol.iterator] !== "function") {
85
- throw new Error(`Item "${id}" has invalid children: expected an iterable value.`);
97
+ throw new Error(`Item '${id}' has invalid children: expected an iterable value.`);
86
98
  }
87
99
  node[nodeChildrenKey] = [];
88
100
  for (const childId of childIds) {
@@ -91,12 +103,15 @@ function buildTree(items, options) {
91
103
  node[nodeChildrenKey].push(childNode);
92
104
  if (nodeParentKey !== false) {
93
105
  if (childNode[nodeParentKey] && childNode[nodeParentKey] !== node) {
94
- throw new Error(`Node "${childId}" already has a different parent, refusing to overwrite. Set "nodeParentKey" to false to supress this error.`);
106
+ throw new Error(`Node '${childId}' already has a different parent, refusing to overwrite.`);
95
107
  }
96
108
  childNode[nodeParentKey] = node;
97
109
  }
98
110
  rootCandidates.delete(childId);
99
111
  } else {
112
+ if (waitingChildren.has(childId)) {
113
+ throw new Error(`Multiple parents reference the same unresolved child '${childId}'.`);
114
+ }
100
115
  waitingChildren.set(childId, {
101
116
  parentNode: node,
102
117
  childIndex: node[nodeChildrenKey].length
@@ -114,7 +129,7 @@ function buildTree(items, options) {
114
129
  parentNode[nodeChildrenKey][childIndex] = node;
115
130
  if (nodeParentKey !== false) {
116
131
  if (node[nodeParentKey] && node[nodeParentKey] !== parentNode) {
117
- throw new Error(`Node "${id}" already has a different parent, refusing to overwrite. Set "nodeParentKey" to false to supress this error.`);
132
+ throw new Error(`Node '${id}' already has a different parent, refusing to overwrite.`);
118
133
  }
119
134
  node[nodeParentKey] = parentNode;
120
135
  }
@@ -126,7 +141,7 @@ function buildTree(items, options) {
126
141
  if (waitingChildren.size > 0) {
127
142
  if (validateReferences) {
128
143
  const childId = waitingChildren.keys().next().value;
129
- throw new Error(`Referential integrity violation: child reference "${childId}" does not match any item in the input.`);
144
+ throw new Error(`Referential integrity violation: child reference '${childId}' does not match any item in the input.`);
130
145
  }
131
146
  const pending = Array.from(waitingChildren.values());
132
147
  for (let i = pending.length - 1; i >= 0; i--) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-tree-builder",
3
- "version": "2.0.0-beta.0",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "Easily construct highly customizable bi-directional tree structures from iterable data.",
5
5
  "types": "./index.d.mts",
6
6
  "module": "./index.mjs",
@@ -22,7 +22,7 @@
22
22
  "build": "node scripts/build.mjs",
23
23
  "watch": "node scripts/watch.mjs",
24
24
  "test": "mocha",
25
- "coverage": "c8 -r text -r text-summary -r lcov --include \"*.js\" npm test"
25
+ "coverage": "c8 -r text -r text-summary -r lcov --include \"*.mjs\" npm test"
26
26
  },
27
27
  "devDependencies": {
28
28
  "c8": "^10.1.3",