fast-tree-builder 2.0.0-beta.2 → 2.0.0-beta.4
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 +17 -17
- package/index.cjs +42 -42
- package/index.d.cts +18 -18
- package/index.d.mts +18 -18
- package/index.mjs +42 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
## Features
|
|
20
20
|
|
|
21
21
|
- **Supports `parentId` and `childIds` Models** – Choose your relation style via options.
|
|
22
|
-
- **Fully Typed** –
|
|
22
|
+
- **Fully Typed** – Carefully written TypeScript types for the built tree.
|
|
23
23
|
- **Highly Customizable** – Design the node structure as you like.
|
|
24
24
|
- **Any Iterable Accepted** – Works on arrays, sets, or any iterable type.
|
|
25
25
|
- **Flexible ID Types** – Anything can be an identifier; relations matched with `childId === parentId`.
|
|
@@ -59,18 +59,18 @@ Builds a tree structure from an iterable list of items.
|
|
|
59
59
|
|
|
60
60
|
- `id`: A key or function used to extract the unique identifier from each item.
|
|
61
61
|
|
|
62
|
-
##### One of
|
|
62
|
+
##### One of
|
|
63
63
|
|
|
64
64
|
- `parentId`: A key or function that access the parent ID of the item.
|
|
65
65
|
- `childIds`: A key or function that access an iterable of child IDs for the item.
|
|
66
66
|
|
|
67
67
|
##### Optional
|
|
68
68
|
|
|
69
|
-
- `
|
|
70
|
-
- `
|
|
71
|
-
- `
|
|
72
|
-
- `
|
|
73
|
-
- `
|
|
69
|
+
- `valueResolver`: Function to transform an item to a custom value stored in the node. Defaults to use the input item as is.
|
|
70
|
+
- `valueKey`: 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
|
+
- `parentKey`: Key where the node's parent reference is stored in the output node. Set to `false` to omit parent links. Defaults to `'parent'`.
|
|
72
|
+
- `childrenKey`: Key where the node's children are stored in the output node. Defaults to `'children'`.
|
|
73
|
+
- `depthKey`: Key where the node's depth (with root = 0) is stored in the output node. Set to `false` to omit depth values. Automatically enables `validateTree` when a string value is set here. Defaults to `false`.
|
|
74
74
|
- `validateReferences`: When `true`, verifies all `parentId` or `childIds` resolve to real items. Errors are thrown on invalid references. Defaults to `false`.
|
|
75
75
|
- `validateTree`: When `true`, verifies that the final structure is a valid tree (no cycles or nodes reachable via multipla paths). Errors are thrown if the check fails. Defaults to `false`.
|
|
76
76
|
|
|
@@ -88,7 +88,7 @@ Builds a tree structure from an iterable list of items.
|
|
|
88
88
|
- Missing required `id`, `parentId`/`childIds`, or `options` parameter
|
|
89
89
|
- Duplicate item identifiers in input
|
|
90
90
|
- Invalid reference (if `validateReferences` is enabled)
|
|
91
|
-
- Cycle or structural error (if `validateTree` is enabled or `
|
|
91
|
+
- Cycle or structural error (if `validateTree` is enabled or `depthKey` is string)
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
## Usage
|
|
@@ -115,9 +115,9 @@ const { roots, nodes } = buildTree(items, {
|
|
|
115
115
|
id: 'id',
|
|
116
116
|
parentId: 'parent',
|
|
117
117
|
// the built node:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
valueKey: 'value',
|
|
119
|
+
parentKey: 'parent',
|
|
120
|
+
childrenKey: 'children',
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
console.log(roots[0].value.name);
|
|
@@ -193,10 +193,10 @@ const items = [
|
|
|
193
193
|
const { roots, nodes } = buildTree(items, {
|
|
194
194
|
id: item => item.key?.n,
|
|
195
195
|
parentId: item => item.parentKey?.n,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
valueResolver: item => ({ title: item.name }),
|
|
197
|
+
valueKey: false, // merge item data into node
|
|
198
|
+
parentKey: 'up',
|
|
199
|
+
childrenKey: 'down',
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
console.log(roots[0].title);
|
|
@@ -251,8 +251,8 @@ const items = [
|
|
|
251
251
|
const { roots, nodes } = buildTree(items, {
|
|
252
252
|
id: item => item.substring(2, 4),
|
|
253
253
|
parentKey: item => item.substring(0, 2),
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
valueResolver: item => ({ name: item.substring(4) }),
|
|
255
|
+
valueKey: false, // merge item data into node
|
|
256
256
|
});
|
|
257
257
|
|
|
258
258
|
console.log(roots[0].name);
|
package/index.cjs
CHANGED
|
@@ -29,11 +29,11 @@ function buildTree(items, options) {
|
|
|
29
29
|
id: idAccessor,
|
|
30
30
|
parentId: parentIdAccessor,
|
|
31
31
|
childIds: childIdsAccessor,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
valueResolver,
|
|
33
|
+
valueKey = "value",
|
|
34
|
+
parentKey = "parent",
|
|
35
|
+
childrenKey = "children",
|
|
36
|
+
depthKey = false,
|
|
37
37
|
validateTree = false,
|
|
38
38
|
validateReferences = false
|
|
39
39
|
} = options;
|
|
@@ -55,21 +55,21 @@ function buildTree(items, options) {
|
|
|
55
55
|
if (nodes.has(id)) {
|
|
56
56
|
throw new Error(`Duplicate identifier '${id}'.`);
|
|
57
57
|
}
|
|
58
|
-
const node =
|
|
59
|
-
if (
|
|
60
|
-
if (
|
|
61
|
-
delete node[
|
|
58
|
+
const node = valueKey !== false ? { [valueKey]: valueResolver ? valueResolver(item) : item } : { ...valueResolver ? valueResolver(item) : item };
|
|
59
|
+
if (valueKey === false) {
|
|
60
|
+
if (parentKey !== false) {
|
|
61
|
+
delete node[parentKey];
|
|
62
62
|
}
|
|
63
|
-
delete node[
|
|
63
|
+
delete node[childrenKey];
|
|
64
64
|
}
|
|
65
65
|
nodes.set(id, node);
|
|
66
66
|
const parentId = typeof parentIdAccessor === "function" ? parentIdAccessor(item) : item[parentIdAccessor];
|
|
67
67
|
const parentNode = nodes.get(parentId);
|
|
68
68
|
if (parentNode) {
|
|
69
|
-
parentNode[
|
|
70
|
-
parentNode[
|
|
71
|
-
if (
|
|
72
|
-
node[
|
|
69
|
+
parentNode[childrenKey] ||= [];
|
|
70
|
+
parentNode[childrenKey].push(node);
|
|
71
|
+
if (parentKey !== false) {
|
|
72
|
+
node[parentKey] = parentNode;
|
|
73
73
|
}
|
|
74
74
|
} else {
|
|
75
75
|
const siblings = waitingForParent.get(parentId);
|
|
@@ -81,10 +81,10 @@ function buildTree(items, options) {
|
|
|
81
81
|
}
|
|
82
82
|
const children = waitingForParent.get(id);
|
|
83
83
|
if (children) {
|
|
84
|
-
node[
|
|
85
|
-
if (
|
|
84
|
+
node[childrenKey] = children;
|
|
85
|
+
if (parentKey !== false) {
|
|
86
86
|
for (const child of children) {
|
|
87
|
-
child[
|
|
87
|
+
child[parentKey] = node;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
waitingForParent.delete(id);
|
|
@@ -106,12 +106,12 @@ function buildTree(items, options) {
|
|
|
106
106
|
if (nodes.has(id)) {
|
|
107
107
|
throw new Error(`Duplicate identifier '${id}'.`);
|
|
108
108
|
}
|
|
109
|
-
const node =
|
|
110
|
-
if (
|
|
111
|
-
if (
|
|
112
|
-
delete node[
|
|
109
|
+
const node = valueKey !== false ? { [valueKey]: valueResolver ? valueResolver(item) : item } : { ...valueResolver ? valueResolver(item) : item };
|
|
110
|
+
if (valueKey === false) {
|
|
111
|
+
if (parentKey !== false) {
|
|
112
|
+
delete node[parentKey];
|
|
113
113
|
}
|
|
114
|
-
delete node[
|
|
114
|
+
delete node[childrenKey];
|
|
115
115
|
}
|
|
116
116
|
nodes.set(id, node);
|
|
117
117
|
const childIds = typeof childIdsAccessor === "function" ? childIdsAccessor(item) : item[childIdsAccessor];
|
|
@@ -119,16 +119,16 @@ function buildTree(items, options) {
|
|
|
119
119
|
if (typeof childIds[Symbol.iterator] !== "function") {
|
|
120
120
|
throw new Error(`Item '${id}' has invalid children: expected an iterable value.`);
|
|
121
121
|
}
|
|
122
|
-
node[
|
|
122
|
+
node[childrenKey] = [];
|
|
123
123
|
for (const childId of childIds) {
|
|
124
124
|
const childNode = nodes.get(childId);
|
|
125
125
|
if (childNode) {
|
|
126
|
-
node[
|
|
127
|
-
if (
|
|
128
|
-
if (childNode[
|
|
126
|
+
node[childrenKey].push(childNode);
|
|
127
|
+
if (parentKey !== false) {
|
|
128
|
+
if (childNode[parentKey] && childNode[parentKey] !== node) {
|
|
129
129
|
throw new Error(`Node '${childId}' already has a different parent, refusing to overwrite.`);
|
|
130
130
|
}
|
|
131
|
-
childNode[
|
|
131
|
+
childNode[parentKey] = node;
|
|
132
132
|
}
|
|
133
133
|
rootCandidates.delete(childId);
|
|
134
134
|
} else {
|
|
@@ -137,24 +137,24 @@ function buildTree(items, options) {
|
|
|
137
137
|
}
|
|
138
138
|
waitingChildren.set(childId, {
|
|
139
139
|
parentNode: node,
|
|
140
|
-
childIndex: node[
|
|
140
|
+
childIndex: node[childrenKey].length
|
|
141
141
|
});
|
|
142
|
-
node[
|
|
142
|
+
node[childrenKey].push(null);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
-
if (node[
|
|
146
|
-
delete node[
|
|
145
|
+
if (node[childrenKey].length === 0) {
|
|
146
|
+
delete node[childrenKey];
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
const parentDescriptor = waitingChildren.get(id);
|
|
150
150
|
if (parentDescriptor) {
|
|
151
151
|
const { parentNode, childIndex } = parentDescriptor;
|
|
152
|
-
parentNode[
|
|
153
|
-
if (
|
|
154
|
-
if (node[
|
|
152
|
+
parentNode[childrenKey][childIndex] = node;
|
|
153
|
+
if (parentKey !== false) {
|
|
154
|
+
if (node[parentKey] && node[parentKey] !== parentNode) {
|
|
155
155
|
throw new Error(`Node '${id}' already has a different parent, refusing to overwrite.`);
|
|
156
156
|
}
|
|
157
|
-
node[
|
|
157
|
+
node[parentKey] = parentNode;
|
|
158
158
|
}
|
|
159
159
|
waitingChildren.delete(id);
|
|
160
160
|
} else {
|
|
@@ -169,9 +169,9 @@ function buildTree(items, options) {
|
|
|
169
169
|
const pending = Array.from(waitingChildren.values());
|
|
170
170
|
for (let i = pending.length - 1; i >= 0; i--) {
|
|
171
171
|
const { parentNode, childIndex } = pending[i];
|
|
172
|
-
parentNode[
|
|
173
|
-
if (parentNode[
|
|
174
|
-
delete parentNode[
|
|
172
|
+
parentNode[childrenKey].splice(childIndex, 1);
|
|
173
|
+
if (parentNode[childrenKey].length === 0) {
|
|
174
|
+
delete parentNode[childrenKey];
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
}
|
|
@@ -179,7 +179,7 @@ function buildTree(items, options) {
|
|
|
179
179
|
roots.push(node);
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
-
const withDepth = typeof
|
|
182
|
+
const withDepth = typeof depthKey === "string" || typeof depthKey === "symbol" || typeof depthKey === "number";
|
|
183
183
|
if (validateTree || withDepth) {
|
|
184
184
|
if (roots.length === 0 && nodes.size > 0) {
|
|
185
185
|
throw new Error("Tree validation failed: detected a cycle.");
|
|
@@ -195,10 +195,10 @@ function buildTree(items, options) {
|
|
|
195
195
|
}
|
|
196
196
|
visited.add(node);
|
|
197
197
|
if (withDepth) {
|
|
198
|
-
node[
|
|
198
|
+
node[depthKey] = depth;
|
|
199
199
|
}
|
|
200
|
-
if (node[
|
|
201
|
-
for (const child of node[
|
|
200
|
+
if (node[childrenKey]) {
|
|
201
|
+
for (const child of node[childrenKey]) {
|
|
202
202
|
stack.push({ node: child, depth: depth + 1 });
|
|
203
203
|
}
|
|
204
204
|
}
|
package/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
type TreeNode<TValue, TValueKey extends PropertyKey | false, TParentKey extends PropertyKey | false, TChildrenKey extends PropertyKey, TDepthKey extends PropertyKey | false> = (TValueKey extends false ? Omit<TValue, Exclude<TParentKey, false> | TChildrenKey | Exclude<TDepthKey, false>> : {
|
|
2
2
|
[k in Exclude<TValueKey, false>]: TValue;
|
|
3
3
|
}) & (TParentKey extends false ? {} : {
|
|
4
|
-
[k in Exclude<TParentKey, false>]
|
|
4
|
+
[k in Exclude<TParentKey, false>]?: TreeNode<TValue, TValueKey, TParentKey, TChildrenKey, TDepthKey>;
|
|
5
5
|
}) & {
|
|
6
6
|
[k in TChildrenKey]?: TreeNode<TValue, TValueKey, TParentKey, TChildrenKey, TDepthKey>[];
|
|
7
7
|
} & (TDepthKey extends false ? {} : {
|
|
@@ -11,51 +11,51 @@ type AccessorReturnType<O, P extends (keyof O) | ((item: O) => any)> = P extends
|
|
|
11
11
|
type ObjectKeysOfIterableProperties<T> = {
|
|
12
12
|
[K in keyof T]: T[K] extends (Iterable<unknown> & object) | null | undefined ? K : never;
|
|
13
13
|
}[keyof T];
|
|
14
|
-
declare function buildTree<TIdAccessor extends (keyof NoInfer<TInputValue>) | ((item: NoInfer<TInputValue>) => unknown),
|
|
14
|
+
declare function buildTree<TIdAccessor extends (keyof NoInfer<TInputValue>) | ((item: NoInfer<TInputValue>) => unknown), TValueKey extends PropertyKey | false = 'value', TParentKey extends PropertyKey | false = 'parent', TChildrenKey extends PropertyKey = 'children', TDepthKey extends PropertyKey | false = false, TInputValue extends (TValueKey extends false ? object : TIdAccessor extends PropertyKey ? object : unknown) = any, TResolvedValue extends (TValueKey extends false ? object : unknown) = TInputValue>(items: Iterable<TInputValue>, options: {
|
|
15
15
|
/**
|
|
16
16
|
* A string key or function used to get the item's unique identifier.
|
|
17
17
|
*/
|
|
18
18
|
id: TIdAccessor;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Function to transform an item to a custom value stored in the node.
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
(item: NoInfer<TInputValue>):
|
|
22
|
+
valueResolver?: {
|
|
23
|
+
(item: NoInfer<TInputValue>): TResolvedValue;
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Key where the item is stored in the output node.
|
|
27
27
|
*
|
|
28
|
-
* Set to `false` to
|
|
28
|
+
* Set to `false` to inline the item directly into the node.
|
|
29
29
|
*
|
|
30
30
|
* Defaults to `'value'`.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
valueKey?: TValueKey;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Key where the node's parent reference is stored in the output node.
|
|
35
35
|
*
|
|
36
36
|
* Set to `false` to omit parent links.
|
|
37
37
|
*
|
|
38
38
|
* Defaults to `'parent'`.
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
parentKey?: TParentKey;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* Key where the node's children are stored in the output node.
|
|
43
43
|
*
|
|
44
44
|
* Defaults to `'children'`.
|
|
45
45
|
*/
|
|
46
|
-
|
|
46
|
+
childrenKey?: TChildrenKey;
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
48
|
+
* Key where the node's depth is stored in the output node.
|
|
49
49
|
* Root nodes have a depth of 0.
|
|
50
50
|
*
|
|
51
51
|
* Set to `false` to omit depth values.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
* and both operations also share the same traversal logic.
|
|
53
|
+
* Automatically enables `validateTree` when a string value is set here:
|
|
54
|
+
* Depth assignment requires a valid tree structure, and both operations also share the same traversal logic.
|
|
55
55
|
*
|
|
56
56
|
* Defaults to `false`.
|
|
57
57
|
*/
|
|
58
|
-
|
|
58
|
+
depthKey?: TDepthKey;
|
|
59
59
|
/**
|
|
60
60
|
* Validates that the final structure forms a tree.
|
|
61
61
|
*
|
|
@@ -107,8 +107,8 @@ declare function buildTree<TIdAccessor extends (keyof NoInfer<TInputValue>) | ((
|
|
|
107
107
|
*/
|
|
108
108
|
childIds?: never;
|
|
109
109
|
})): {
|
|
110
|
-
roots: TreeNode<
|
|
111
|
-
nodes: Map<AccessorReturnType<TInputValue, TIdAccessor>, TreeNode<
|
|
110
|
+
roots: TreeNode<TResolvedValue extends undefined ? TInputValue : TResolvedValue, TValueKey, TParentKey, TChildrenKey, TDepthKey>[];
|
|
111
|
+
nodes: Map<AccessorReturnType<TInputValue, TIdAccessor>, TreeNode<TResolvedValue extends undefined ? TInputValue : TResolvedValue, TValueKey, TParentKey, TChildrenKey, TDepthKey>>;
|
|
112
112
|
};
|
|
113
113
|
declare const _default: { default: typeof buildTree };
|
|
114
114
|
export = _default;
|
package/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
type TreeNode<TValue, TValueKey extends PropertyKey | false, TParentKey extends PropertyKey | false, TChildrenKey extends PropertyKey, TDepthKey extends PropertyKey | false> = (TValueKey extends false ? Omit<TValue, Exclude<TParentKey, false> | TChildrenKey | Exclude<TDepthKey, false>> : {
|
|
2
2
|
[k in Exclude<TValueKey, false>]: TValue;
|
|
3
3
|
}) & (TParentKey extends false ? {} : {
|
|
4
|
-
[k in Exclude<TParentKey, false>]
|
|
4
|
+
[k in Exclude<TParentKey, false>]?: TreeNode<TValue, TValueKey, TParentKey, TChildrenKey, TDepthKey>;
|
|
5
5
|
}) & {
|
|
6
6
|
[k in TChildrenKey]?: TreeNode<TValue, TValueKey, TParentKey, TChildrenKey, TDepthKey>[];
|
|
7
7
|
} & (TDepthKey extends false ? {} : {
|
|
@@ -11,51 +11,51 @@ type AccessorReturnType<O, P extends (keyof O) | ((item: O) => any)> = P extends
|
|
|
11
11
|
type ObjectKeysOfIterableProperties<T> = {
|
|
12
12
|
[K in keyof T]: T[K] extends (Iterable<unknown> & object) | null | undefined ? K : never;
|
|
13
13
|
}[keyof T];
|
|
14
|
-
export default function buildTree<TIdAccessor extends (keyof NoInfer<TInputValue>) | ((item: NoInfer<TInputValue>) => unknown),
|
|
14
|
+
export default function buildTree<TIdAccessor extends (keyof NoInfer<TInputValue>) | ((item: NoInfer<TInputValue>) => unknown), TValueKey extends PropertyKey | false = 'value', TParentKey extends PropertyKey | false = 'parent', TChildrenKey extends PropertyKey = 'children', TDepthKey extends PropertyKey | false = false, TInputValue extends (TValueKey extends false ? object : TIdAccessor extends PropertyKey ? object : unknown) = any, TResolvedValue extends (TValueKey extends false ? object : unknown) = TInputValue>(items: Iterable<TInputValue>, options: {
|
|
15
15
|
/**
|
|
16
16
|
* A string key or function used to get the item's unique identifier.
|
|
17
17
|
*/
|
|
18
18
|
id: TIdAccessor;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Function to transform an item to a custom value stored in the node.
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
(item: NoInfer<TInputValue>):
|
|
22
|
+
valueResolver?: {
|
|
23
|
+
(item: NoInfer<TInputValue>): TResolvedValue;
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Key where the item is stored in the output node.
|
|
27
27
|
*
|
|
28
|
-
* Set to `false` to
|
|
28
|
+
* Set to `false` to inline the item directly into the node.
|
|
29
29
|
*
|
|
30
30
|
* Defaults to `'value'`.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
valueKey?: TValueKey;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Key where the node's parent reference is stored in the output node.
|
|
35
35
|
*
|
|
36
36
|
* Set to `false` to omit parent links.
|
|
37
37
|
*
|
|
38
38
|
* Defaults to `'parent'`.
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
parentKey?: TParentKey;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* Key where the node's children are stored in the output node.
|
|
43
43
|
*
|
|
44
44
|
* Defaults to `'children'`.
|
|
45
45
|
*/
|
|
46
|
-
|
|
46
|
+
childrenKey?: TChildrenKey;
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
48
|
+
* Key where the node's depth is stored in the output node.
|
|
49
49
|
* Root nodes have a depth of 0.
|
|
50
50
|
*
|
|
51
51
|
* Set to `false` to omit depth values.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
* and both operations also share the same traversal logic.
|
|
53
|
+
* Automatically enables `validateTree` when a string value is set here:
|
|
54
|
+
* Depth assignment requires a valid tree structure, and both operations also share the same traversal logic.
|
|
55
55
|
*
|
|
56
56
|
* Defaults to `false`.
|
|
57
57
|
*/
|
|
58
|
-
|
|
58
|
+
depthKey?: TDepthKey;
|
|
59
59
|
/**
|
|
60
60
|
* Validates that the final structure forms a tree.
|
|
61
61
|
*
|
|
@@ -107,7 +107,7 @@ export default function buildTree<TIdAccessor extends (keyof NoInfer<TInputValue
|
|
|
107
107
|
*/
|
|
108
108
|
childIds?: never;
|
|
109
109
|
})): {
|
|
110
|
-
roots: TreeNode<
|
|
111
|
-
nodes: Map<AccessorReturnType<TInputValue, TIdAccessor>, TreeNode<
|
|
110
|
+
roots: TreeNode<TResolvedValue extends undefined ? TInputValue : TResolvedValue, TValueKey, TParentKey, TChildrenKey, TDepthKey>[];
|
|
111
|
+
nodes: Map<AccessorReturnType<TInputValue, TIdAccessor>, TreeNode<TResolvedValue extends undefined ? TInputValue : TResolvedValue, TValueKey, TParentKey, TChildrenKey, TDepthKey>>;
|
|
112
112
|
};
|
|
113
113
|
export {};
|
package/index.mjs
CHANGED
|
@@ -6,11 +6,11 @@ function buildTree(items, options) {
|
|
|
6
6
|
id: idAccessor,
|
|
7
7
|
parentId: parentIdAccessor,
|
|
8
8
|
childIds: childIdsAccessor,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
valueResolver,
|
|
10
|
+
valueKey = "value",
|
|
11
|
+
parentKey = "parent",
|
|
12
|
+
childrenKey = "children",
|
|
13
|
+
depthKey = false,
|
|
14
14
|
validateTree = false,
|
|
15
15
|
validateReferences = false
|
|
16
16
|
} = options;
|
|
@@ -32,21 +32,21 @@ function buildTree(items, options) {
|
|
|
32
32
|
if (nodes.has(id)) {
|
|
33
33
|
throw new Error(`Duplicate identifier '${id}'.`);
|
|
34
34
|
}
|
|
35
|
-
const node =
|
|
36
|
-
if (
|
|
37
|
-
if (
|
|
38
|
-
delete node[
|
|
35
|
+
const node = valueKey !== false ? { [valueKey]: valueResolver ? valueResolver(item) : item } : { ...valueResolver ? valueResolver(item) : item };
|
|
36
|
+
if (valueKey === false) {
|
|
37
|
+
if (parentKey !== false) {
|
|
38
|
+
delete node[parentKey];
|
|
39
39
|
}
|
|
40
|
-
delete node[
|
|
40
|
+
delete node[childrenKey];
|
|
41
41
|
}
|
|
42
42
|
nodes.set(id, node);
|
|
43
43
|
const parentId = typeof parentIdAccessor === "function" ? parentIdAccessor(item) : item[parentIdAccessor];
|
|
44
44
|
const parentNode = nodes.get(parentId);
|
|
45
45
|
if (parentNode) {
|
|
46
|
-
parentNode[
|
|
47
|
-
parentNode[
|
|
48
|
-
if (
|
|
49
|
-
node[
|
|
46
|
+
parentNode[childrenKey] ||= [];
|
|
47
|
+
parentNode[childrenKey].push(node);
|
|
48
|
+
if (parentKey !== false) {
|
|
49
|
+
node[parentKey] = parentNode;
|
|
50
50
|
}
|
|
51
51
|
} else {
|
|
52
52
|
const siblings = waitingForParent.get(parentId);
|
|
@@ -58,10 +58,10 @@ function buildTree(items, options) {
|
|
|
58
58
|
}
|
|
59
59
|
const children = waitingForParent.get(id);
|
|
60
60
|
if (children) {
|
|
61
|
-
node[
|
|
62
|
-
if (
|
|
61
|
+
node[childrenKey] = children;
|
|
62
|
+
if (parentKey !== false) {
|
|
63
63
|
for (const child of children) {
|
|
64
|
-
child[
|
|
64
|
+
child[parentKey] = node;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
waitingForParent.delete(id);
|
|
@@ -83,12 +83,12 @@ function buildTree(items, options) {
|
|
|
83
83
|
if (nodes.has(id)) {
|
|
84
84
|
throw new Error(`Duplicate identifier '${id}'.`);
|
|
85
85
|
}
|
|
86
|
-
const node =
|
|
87
|
-
if (
|
|
88
|
-
if (
|
|
89
|
-
delete node[
|
|
86
|
+
const node = valueKey !== false ? { [valueKey]: valueResolver ? valueResolver(item) : item } : { ...valueResolver ? valueResolver(item) : item };
|
|
87
|
+
if (valueKey === false) {
|
|
88
|
+
if (parentKey !== false) {
|
|
89
|
+
delete node[parentKey];
|
|
90
90
|
}
|
|
91
|
-
delete node[
|
|
91
|
+
delete node[childrenKey];
|
|
92
92
|
}
|
|
93
93
|
nodes.set(id, node);
|
|
94
94
|
const childIds = typeof childIdsAccessor === "function" ? childIdsAccessor(item) : item[childIdsAccessor];
|
|
@@ -96,16 +96,16 @@ function buildTree(items, options) {
|
|
|
96
96
|
if (typeof childIds[Symbol.iterator] !== "function") {
|
|
97
97
|
throw new Error(`Item '${id}' has invalid children: expected an iterable value.`);
|
|
98
98
|
}
|
|
99
|
-
node[
|
|
99
|
+
node[childrenKey] = [];
|
|
100
100
|
for (const childId of childIds) {
|
|
101
101
|
const childNode = nodes.get(childId);
|
|
102
102
|
if (childNode) {
|
|
103
|
-
node[
|
|
104
|
-
if (
|
|
105
|
-
if (childNode[
|
|
103
|
+
node[childrenKey].push(childNode);
|
|
104
|
+
if (parentKey !== false) {
|
|
105
|
+
if (childNode[parentKey] && childNode[parentKey] !== node) {
|
|
106
106
|
throw new Error(`Node '${childId}' already has a different parent, refusing to overwrite.`);
|
|
107
107
|
}
|
|
108
|
-
childNode[
|
|
108
|
+
childNode[parentKey] = node;
|
|
109
109
|
}
|
|
110
110
|
rootCandidates.delete(childId);
|
|
111
111
|
} else {
|
|
@@ -114,24 +114,24 @@ function buildTree(items, options) {
|
|
|
114
114
|
}
|
|
115
115
|
waitingChildren.set(childId, {
|
|
116
116
|
parentNode: node,
|
|
117
|
-
childIndex: node[
|
|
117
|
+
childIndex: node[childrenKey].length
|
|
118
118
|
});
|
|
119
|
-
node[
|
|
119
|
+
node[childrenKey].push(null);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
if (node[
|
|
123
|
-
delete node[
|
|
122
|
+
if (node[childrenKey].length === 0) {
|
|
123
|
+
delete node[childrenKey];
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
const parentDescriptor = waitingChildren.get(id);
|
|
127
127
|
if (parentDescriptor) {
|
|
128
128
|
const { parentNode, childIndex } = parentDescriptor;
|
|
129
|
-
parentNode[
|
|
130
|
-
if (
|
|
131
|
-
if (node[
|
|
129
|
+
parentNode[childrenKey][childIndex] = node;
|
|
130
|
+
if (parentKey !== false) {
|
|
131
|
+
if (node[parentKey] && node[parentKey] !== parentNode) {
|
|
132
132
|
throw new Error(`Node '${id}' already has a different parent, refusing to overwrite.`);
|
|
133
133
|
}
|
|
134
|
-
node[
|
|
134
|
+
node[parentKey] = parentNode;
|
|
135
135
|
}
|
|
136
136
|
waitingChildren.delete(id);
|
|
137
137
|
} else {
|
|
@@ -146,9 +146,9 @@ function buildTree(items, options) {
|
|
|
146
146
|
const pending = Array.from(waitingChildren.values());
|
|
147
147
|
for (let i = pending.length - 1; i >= 0; i--) {
|
|
148
148
|
const { parentNode, childIndex } = pending[i];
|
|
149
|
-
parentNode[
|
|
150
|
-
if (parentNode[
|
|
151
|
-
delete parentNode[
|
|
149
|
+
parentNode[childrenKey].splice(childIndex, 1);
|
|
150
|
+
if (parentNode[childrenKey].length === 0) {
|
|
151
|
+
delete parentNode[childrenKey];
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -156,7 +156,7 @@ function buildTree(items, options) {
|
|
|
156
156
|
roots.push(node);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
const withDepth = typeof
|
|
159
|
+
const withDepth = typeof depthKey === "string" || typeof depthKey === "symbol" || typeof depthKey === "number";
|
|
160
160
|
if (validateTree || withDepth) {
|
|
161
161
|
if (roots.length === 0 && nodes.size > 0) {
|
|
162
162
|
throw new Error("Tree validation failed: detected a cycle.");
|
|
@@ -172,10 +172,10 @@ function buildTree(items, options) {
|
|
|
172
172
|
}
|
|
173
173
|
visited.add(node);
|
|
174
174
|
if (withDepth) {
|
|
175
|
-
node[
|
|
175
|
+
node[depthKey] = depth;
|
|
176
176
|
}
|
|
177
|
-
if (node[
|
|
178
|
-
for (const child of node[
|
|
177
|
+
if (node[childrenKey]) {
|
|
178
|
+
for (const child of node[childrenKey]) {
|
|
179
179
|
stack.push({ node: child, depth: depth + 1 });
|
|
180
180
|
}
|
|
181
181
|
}
|
package/package.json
CHANGED