tree-processor 0.8.1 → 0.9.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.
package/README.en.md ADDED
@@ -0,0 +1,1307 @@
1
+ # tree-processor
2
+
3
+ <div align="right">
4
+
5
+ [English](https://github.com/knott11/tree-processor/blob/main/README.en.md) | [中文](https://github.com/knott11/tree-processor/blob/main/README.md)
6
+
7
+ </div>
8
+
9
+ <div align="center">
10
+
11
+ ![npm version](https://img.shields.io/npm/v/tree-processor?style=flat-square)
12
+ ![npm downloads (2 months)](https://img.shields.io/badge/downloads-1.3K-brightgreen?style=flat-square&label=2mo)
13
+ ![bundle size](https://img.shields.io/badge/bundle-8.4KB-blue?style=flat-square)
14
+ ![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)
15
+ ![coverage](https://img.shields.io/badge/coverage-99%25-brightgreen?style=flat-square)
16
+
17
+ A lightweight tree-structured data processing utility library written in TypeScript, supporting tree-shaking, with each format bundle size approximately **8.2-8.5 KB** (ESM: 8.24 KB, CJS: 8.51 KB, UMD: 8.52 KB).
18
+
19
+
20
+ </div>
21
+
22
+ ## 📋 Table of Contents
23
+
24
+ - [Features](#-features)
25
+ - [Use Cases](#-use-cases)
26
+ - [Installation](#-installation)
27
+ - [Quick Start](#-quick-start)
28
+ - [API Documentation](#-api-documentation)
29
+ - [Traversal Methods](#traversal-methods)
30
+ - [Search Methods](#search-methods)
31
+ - [Access Methods](#access-methods)
32
+ - [Modification Methods](#modification-methods)
33
+ - [Conversion Methods](#conversion-methods)
34
+ - [Query Methods](#query-methods)
35
+ - [Validation Methods](#validation-methods)
36
+ - [Custom Field Names](#custom-field-names)
37
+ - [Testing](#testing)
38
+ - [Development](#development)
39
+
40
+ ## ✨ Features
41
+
42
+ - **Lightweight** - Each format bundle size is only 8.2-8.5 KB (ESM: 8.24 KB, CJS: 8.51 KB, UMD: 8.52 KB), minimal impact on project size
43
+ - **Tree-shaking Support** - Supports on-demand imports, only bundles the code you actually use, further reducing bundle size
44
+ - **Full TypeScript Support** - Provides complete type definitions and IntelliSense, improving development experience
45
+ - **Flexible Custom Field Names** - Supports custom children and id field names, adapting to various data structures
46
+ - **Zero Dependencies** - No external dependencies, ready to use out of the box, no need to worry about dependency conflicts
47
+ - **Comprehensive Test Coverage** - Contains 328 test cases with 99%+ test coverage (99% statement coverage, 98.41% branch coverage, 100% function coverage, 98.99% line coverage), covering basic functionality, edge cases, error handling, and complex scenarios
48
+ - **Rich API** - Provides 32+ methods, including array-like APIs (map, filter, find, some, every, includes, at, indexOf, etc.), and tree-specific operations (get parent/child nodes, depth calculation, data validation, format conversion, etc.), covering complete scenarios for traversal, search, modification, conversion, and validation
49
+
50
+ **Supported methods:** mapTree, forEachTree, filterTree, findTree, pushTree, unshiftTree, popTree, shiftTree, someTree, everyTree, includesTree, atTree, indexOfTree, atIndexOfTree, dedupTree, removeTree, getParentTree, getChildrenTree, getSiblingsTree, getNodeDepthMap, getNodeDepth, isLeafNode, isRootNode, isEmptyTreeData, isEmptySingleTreeData, isTreeData, isSingleTreeData, isValidTreeNode, isTreeNodeWithCircularCheck, isSafeTreeDepth, convertToArrayTree, convertBackTree, convertToMapTree, convertToLevelArrayTree, convertToObjectTree. The last parameter of each method can customize the property names for children and id.
51
+
52
+ ### 💡 Use Cases
53
+
54
+ - **Navigation Systems** - Multi-level menus, route configuration expansion, collapse, search, and filtering
55
+ - **File Systems** - File directory traversal, search, move, and delete
56
+ - **Permission Systems** - Organizational structure, role permission tree structure management and validation
57
+ - **Framework Development** - Component trees, route trees, and other tree structure construction and management
58
+ - **Data Management** - Category management, comment systems, tree selectors, and other data operations
59
+
60
+ ## 📦 Installation
61
+
62
+ ```bash
63
+ npm install tree-processor
64
+ # or
65
+ yarn add tree-processor
66
+ # or
67
+ pnpm add tree-processor
68
+ ```
69
+
70
+ ## 🚀 Quick Start
71
+
72
+ ```javascript
73
+ import { mapTree, findTree, filterTree } from 'tree-processor'
74
+
75
+ const treeData = [
76
+ {
77
+ id: 1,
78
+ name: 'node1',
79
+ children: [
80
+ { id: 2, name: 'node2' },
81
+ { id: 3, name: 'node3' },
82
+ ],
83
+ },
84
+ ]
85
+
86
+ // Get all node names
87
+ const names = mapTree(treeData, (node) => node.name)
88
+ console.log(names) // ['node1', 'node2', 'node3']
89
+
90
+ // Find a node
91
+ const node = findTree(treeData, (n) => n.id === 2)
92
+ console.log(node) // { id: 2, name: 'node2' }
93
+
94
+ // Filter nodes
95
+ const filtered = filterTree(treeData, (n) => n.id > 1)
96
+ console.log(filtered) // [{ id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
97
+ ```
98
+
99
+ ## 📖 API Documentation
100
+
101
+ ### Import Methods
102
+
103
+ #### Default Import (Recommended for scenarios requiring multiple methods)
104
+
105
+ ```javascript
106
+ // ES Module
107
+ import t from 'tree-processor'
108
+
109
+ // CommonJS
110
+ const t = require('tree-processor')
111
+ ```
112
+
113
+ #### Named Import (Recommended for scenarios requiring only a few methods, supports tree-shaking)
114
+
115
+ ```javascript
116
+ // ES Module - Named import of individual methods
117
+ import { mapTree, filterTree, findTree } from 'tree-processor'
118
+
119
+ // ES Module - Import types
120
+ import type { TreeNode, TreeData, FieldNames } from 'tree-processor'
121
+
122
+ // CommonJS - Named import
123
+ const { mapTree, filterTree } = require('tree-processor')
124
+ ```
125
+
126
+ ### Sample Data
127
+
128
+ The following sample data will be used for demonstrations of all subsequent methods:
129
+
130
+ ```javascript
131
+ const treeData = [
132
+ {
133
+ id: 1,
134
+ name: 'node1',
135
+ children: [
136
+ {
137
+ id: 2,
138
+ name: 'node2',
139
+ children: [
140
+ { id: 4, name: 'node4' },
141
+ { id: 5, name: 'node5' },
142
+ ],
143
+ },
144
+ {
145
+ id: 3,
146
+ name: 'node3',
147
+ children: [{ id: 6, name: 'node6' }],
148
+ },
149
+ ],
150
+ },
151
+ ];
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Traversal Methods
157
+
158
+ ### mapTree
159
+
160
+ Traverse tree-structured data and execute a callback function for each node, returning a mapped array.
161
+
162
+ ```javascript
163
+ // Get all node names
164
+ const nodeNames = t.mapTree(treeData, (node) => node.name)
165
+ console.log(nodeNames) // ['node1', 'node2', 'node4', 'node5', 'node3', 'node6']
166
+
167
+ // Get all node IDs
168
+ const nodeIds = t.mapTree(treeData, (node) => node.id)
169
+ console.log(nodeIds) // [1, 2, 4, 5, 3, 6]
170
+
171
+ // Modify node data
172
+ const modifiedNodes = t.mapTree(treeData, (node) => ({
173
+ ...node,
174
+ label: node.name
175
+ }))
176
+ console.log(modifiedNodes) // Returns a new array containing the label field
177
+ ```
178
+
179
+ ### forEachTree
180
+
181
+ Traverse tree-structured data and execute a callback function for each node. Unlike mapTree, this doesn't return a value and has better performance, suitable for scenarios where you only need to traverse without returning results.
182
+
183
+ ```javascript
184
+ // Traverse all nodes and print
185
+ t.forEachTree(treeData, (node) => {
186
+ console.log(node)
187
+ })
188
+
189
+ // Modify node properties
190
+ t.forEachTree(treeData, (node) => {
191
+ node.visited = true
192
+ node.timestamp = Date.now()
193
+ })
194
+
195
+ // Count nodes
196
+ let nodeCount = 0
197
+ t.forEachTree(treeData, () => {
198
+ nodeCount++
199
+ })
200
+ console.log(nodeCount) // Total number of nodes
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Search Methods
206
+
207
+ ### filterTree
208
+
209
+ Filter tree-structured data and return nodes that meet the conditions.
210
+
211
+ ```javascript
212
+ // Filter nodes whose names contain 'node'
213
+ const filteredNodes = t.filterTree(treeData, (node) => {
214
+ return node.name.includes('node')
215
+ })
216
+ console.log(filteredNodes) // Returns an array of nodes that meet the conditions
217
+
218
+ // Filter nodes with ID greater than 2
219
+ const nodesWithLargeId = t.filterTree(treeData, (node) => node.id > 2)
220
+ console.log(nodesWithLargeId) // Returns an array of nodes with ID greater than 2
221
+
222
+ // Filter nodes without children (leaf nodes)
223
+ const leafNodes = t.filterTree(treeData, (node) => {
224
+ return !node.children || node.children.length === 0
225
+ })
226
+ console.log(leafNodes) // Returns all leaf nodes
227
+ ```
228
+
229
+ ### findTree
230
+
231
+ Find the first node in tree-structured data that meets the condition. Returns null if not found.
232
+
233
+ ```javascript
234
+ // Find node with ID 2
235
+ const foundNode = t.findTree(treeData, (node) => node.id === 2)
236
+ console.log(foundNode) // Returns the found node object, or null if not found
237
+
238
+ // Find node with name 'node3'
239
+ const node3 = t.findTree(treeData, (node) => node.name === 'node3')
240
+ console.log(node3) // { id: 3, name: 'node3', children: [...] }
241
+
242
+ // Find a non-existent node
243
+ const nodeNotFound = t.findTree(treeData, (node) => node.id === 999)
244
+ console.log(nodeNotFound) // null
245
+ ```
246
+
247
+ ### includesTree
248
+
249
+ Check if tree-structured data contains a node with the specified ID.
250
+
251
+ ```javascript
252
+ const nodeId = 2
253
+ const hasNode = t.includesTree(treeData, nodeId)
254
+
255
+ console.log(hasNode) // true means it contains the node, false means it doesn't
256
+ ```
257
+
258
+ ### someTree
259
+
260
+ Check if there exists a node in tree-structured data that meets the condition. Returns true if at least one node meets the condition.
261
+
262
+ ```javascript
263
+ // Check if there exists a node with name 'node2'
264
+ const hasNode2 = t.someTree(treeData, node => node.name === 'node2')
265
+ console.log(hasNode2) // true
266
+
267
+ // Check if there exists a node with ID greater than 10
268
+ const hasLargeId = t.someTree(treeData, node => node.id > 10)
269
+ console.log(hasLargeId) // false
270
+ ```
271
+
272
+ ### everyTree
273
+
274
+ Check if all nodes in tree-structured data meet the condition. Returns true only if all nodes meet the condition.
275
+
276
+ ```javascript
277
+ // Check if all node IDs are greater than 0
278
+ const allIdsPositive = t.everyTree(treeData, node => node.id > 0)
279
+ console.log(allIdsPositive) // true
280
+
281
+ // Check if all nodes have a name property
282
+ const allHaveName = t.everyTree(treeData, node => node.name)
283
+ console.log(allHaveName) // Returns true or false based on actual data
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Access Methods
289
+
290
+ ### atTree
291
+
292
+ Get a node by parent node ID and child node index. Supports negative indices, same as the array at method. Returns null if not found.
293
+
294
+ ```javascript
295
+ // Get the first child node (index 0) of the node with ID 1
296
+ const firstChildNode = t.atTree(treeData, 1, 0)
297
+ console.log(firstChildNode) // Returns the first child node
298
+
299
+ // Get the last child node (negative index)
300
+ const lastChildNode = t.atTree(treeData, 1, -1)
301
+ console.log(lastChildNode) // Returns the last child node
302
+
303
+ // Index out of range returns null
304
+ const nodeNotFound = t.atTree(treeData, 1, 10)
305
+ console.log(nodeNotFound) // null
306
+ ```
307
+
308
+ ### indexOfTree
309
+
310
+ Returns an array representing the index path from the root node to the node with the targetId. Returns null if not found. The return value can be passed to the second parameter of atIndexOfTree for value retrieval.
311
+
312
+ ```javascript
313
+ // Get the index path of the node with ID 4
314
+ const nodePath = t.indexOfTree(treeData, 4)
315
+ console.log(nodePath) // [0, 0, 0] means root node -> first child -> first child
316
+
317
+ // Node not found returns null
318
+ const pathNotFound = t.indexOfTree(treeData, 999)
319
+ console.log(pathNotFound) // null
320
+
321
+ // Use with atIndexOfTree
322
+ const indexPath = t.indexOfTree(treeData, 4)
323
+ const nodeByPath = t.atIndexOfTree(treeData, indexPath)
324
+ console.log(nodeByPath) // Gets the node with ID 4
325
+ ```
326
+
327
+ ### atIndexOfTree
328
+
329
+ Get a node by index path. Returns null if the path is invalid or out of range.
330
+
331
+ ```javascript
332
+ // Get node by index path
333
+ const nodeByIndexPath = t.atIndexOfTree(treeData, [0, 1, 0])
334
+ console.log(nodeByIndexPath) // Returns the node object at the corresponding path
335
+
336
+ // Use with indexOfTree
337
+ const targetPath = t.indexOfTree(treeData, 4)
338
+ const targetNode = t.atIndexOfTree(treeData, targetPath)
339
+ console.log(targetNode) // Gets the node with ID 4
340
+
341
+ // Invalid path returns null
342
+ const invalidPath = t.atIndexOfTree(treeData, [999])
343
+ console.log(invalidPath) // null
344
+ ```
345
+
346
+ ---
347
+
348
+ ## Modification Methods
349
+
350
+ ### pushTree
351
+
352
+ Add a child node to the end under the specified node. Returns true if successful, false if the target node is not found.
353
+
354
+ ```javascript
355
+ // Add a new child node under the node with ID 1
356
+ const addSuccess = t.pushTree(treeData, 1, { id: 7, name: 'node7' })
357
+ console.log(addSuccess) // true
358
+ console.log(treeData) // New node has been added to the end of the children array
359
+
360
+ // Try to add under a non-existent node
361
+ const addFailed = t.pushTree(treeData, 999, { id: 8, name: 'node8' })
362
+ console.log(addFailed) // false, target node not found
363
+ ```
364
+
365
+ ### unshiftTree
366
+
367
+ Add a child node to the beginning under the specified node. Returns true if successful, false if the target node is not found.
368
+
369
+ ```javascript
370
+ // Add a new child node to the beginning under the node with ID 1
371
+ const unshiftSuccess = t.unshiftTree(treeData, 1, { id: 7, name: 'node7' })
372
+ console.log(unshiftSuccess) // true
373
+ console.log(treeData) // New node has been added to the beginning of the children array
374
+ ```
375
+
376
+ ### popTree
377
+
378
+ Remove the last child node under the specified node. Returns the removed node, or false if the node doesn't exist or has no children.
379
+
380
+ ```javascript
381
+ // Remove the last child node under the node with ID 1
382
+ const removedNode = t.popTree(treeData, 1)
383
+ console.log(removedNode) // Returns the removed node object, or false
384
+
385
+ // Try to remove from a non-existent node
386
+ const popFailed = t.popTree(treeData, 999)
387
+ console.log(popFailed) // false
388
+ ```
389
+
390
+ ### shiftTree
391
+
392
+ Remove the first child node under the specified node. Returns the removed node, or false if the node doesn't exist or has no children.
393
+
394
+ ```javascript
395
+ // Remove the first child node under the node with ID 1
396
+ const shiftedNode = t.shiftTree(treeData, 1)
397
+ console.log(shiftedNode) // Returns the removed node object, or false
398
+ ```
399
+
400
+ ### removeTree
401
+
402
+ Remove the node with the specified ID from tree-structured data, including root nodes and child nodes.
403
+
404
+ ```javascript
405
+ const nodeIdToRemove = 2
406
+ const removeSuccess = t.removeTree(treeData, nodeIdToRemove)
407
+
408
+ console.log(removeSuccess) // true means successful removal, false means node not found
409
+ console.log(treeData) // Tree structure after removal
410
+ ```
411
+
412
+ ### dedupTree
413
+
414
+ Tree-structured object array deduplication method that removes duplicate nodes based on the specified key. Keeps the first occurrence of the node.
415
+
416
+ ```javascript
417
+ // Deduplicate based on id field
418
+ const uniqueTreeData = t.dedupTree(treeData, 'id')
419
+ console.log(uniqueTreeData) // Returns deduplicated tree-structured data
420
+
421
+ // Deduplicate based on name field
422
+ const uniqueByNameTree = t.dedupTree(treeData, 'name')
423
+ console.log(uniqueByNameTree) // Returns data deduplicated by name
424
+ ```
425
+
426
+ ---
427
+
428
+ ## Conversion Methods
429
+
430
+ ### convertToArrayTree
431
+
432
+ Flatten tree-structured data into an array. The returned array contains nodes without the `children` field.
433
+
434
+ ```javascript
435
+ // Flatten tree structure into an array
436
+ const array = t.convertToArrayTree(treeData)
437
+ console.log(array)
438
+ // [
439
+ // { id: 1, name: 'node1' },
440
+ // { id: 2, name: 'node2' },
441
+ // { id: 4, name: 'node4' },
442
+ // { id: 5, name: 'node5' },
443
+ // { id: 3, name: 'node3' },
444
+ // { id: 6, name: 'node6' }
445
+ // ]
446
+
447
+ // Note: Returned nodes do not contain the children field
448
+ array.forEach(node => {
449
+ console.log(node.children) // undefined
450
+ })
451
+
452
+ // Support custom field names
453
+ const customTree = [
454
+ {
455
+ nodeId: 1,
456
+ name: 'node1',
457
+ subNodes: [
458
+ { nodeId: 2, name: 'node2' }
459
+ ]
460
+ }
461
+ ]
462
+ const customArray = t.convertToArrayTree(customTree, {
463
+ children: 'subNodes',
464
+ id: 'nodeId'
465
+ })
466
+ console.log(customArray) // Flattened array without subNodes field
467
+ ```
468
+
469
+ ### convertToMapTree
470
+
471
+ Convert tree-structured data to a Map, where the key is the node ID and the value is the node object (without the `children` field). Suitable for scenarios requiring fast node lookup by ID.
472
+
473
+ ```javascript
474
+ // Convert tree structure to Map
475
+ const map = t.convertToMapTree(treeData)
476
+ console.log(map instanceof Map) // true
477
+ console.log(map.size) // 6
478
+
479
+ // Quickly find node by ID
480
+ const node = map.get(2)
481
+ console.log(node) // { id: 2, name: 'node2' }
482
+ console.log(node.children) // undefined (does not contain children field)
483
+
484
+ // Support custom field names
485
+ const customTree = [
486
+ {
487
+ nodeId: 1,
488
+ name: 'node1',
489
+ subNodes: [
490
+ { nodeId: 2, name: 'node2' }
491
+ ]
492
+ }
493
+ ]
494
+ const customMap = t.convertToMapTree(customTree, {
495
+ children: 'subNodes',
496
+ id: 'nodeId'
497
+ })
498
+ console.log(customMap.get(1)) // { nodeId: 1, name: 'node1' }
499
+ ```
500
+
501
+ ### convertToLevelArrayTree
502
+
503
+ Convert tree-structured data to a level array (two-dimensional array), grouped by depth. The outer array is indexed by depth, and the inner array contains all nodes at that depth.
504
+
505
+ ```javascript
506
+ // Convert tree structure to level array
507
+ const levelArray = t.convertToLevelArrayTree(treeData)
508
+ console.log(levelArray)
509
+ // [
510
+ // [{ id: 1, name: 'node1' }], // Level 0
511
+ // [{ id: 2, name: 'node2' }, { id: 3, name: 'node3' }], // Level 1
512
+ // [{ id: 4, name: 'node4' }, { id: 5, name: 'node5' }, { id: 6, name: 'node6' }] // Level 2
513
+ // ]
514
+
515
+ // Traverse each level
516
+ levelArray.forEach((level, depth) => {
517
+ console.log(`Depth ${depth}:`, level)
518
+ })
519
+
520
+ // Note: Returned nodes do not contain children field
521
+ levelArray[0][0].children // undefined
522
+
523
+ // Support custom field names
524
+ const customTree = [
525
+ {
526
+ nodeId: 1,
527
+ name: 'node1',
528
+ subNodes: [
529
+ { nodeId: 2, name: 'node2' }
530
+ ]
531
+ }
532
+ ]
533
+ const customLevelArray = t.convertToLevelArrayTree(customTree, {
534
+ children: 'subNodes',
535
+ id: 'nodeId'
536
+ })
537
+ console.log(customLevelArray) // Array grouped by level
538
+ ```
539
+
540
+ ### convertToObjectTree
541
+
542
+ Convert single-root tree-structured data to an object. If the tree has only one root node, returns that node object; otherwise returns `null`.
543
+
544
+ ```javascript
545
+ // Convert single-root tree to object
546
+ const singleRootTree = [
547
+ {
548
+ id: 1,
549
+ name: 'node1',
550
+ value: 100,
551
+ children: [
552
+ { id: 2, name: 'node2' }
553
+ ]
554
+ }
555
+ ]
556
+ const rootNode = t.convertToObjectTree(singleRootTree)
557
+ console.log(rootNode)
558
+ // {
559
+ // id: 1,
560
+ // name: 'node1',
561
+ // value: 100,
562
+ // children: [{ id: 2, name: 'node2' }]
563
+ // }
564
+
565
+ // Multiple root nodes return null
566
+ const multiRootTree = [
567
+ { id: 1, name: 'node1' },
568
+ { id: 2, name: 'node2' }
569
+ ]
570
+ const result = t.convertToObjectTree(multiRootTree)
571
+ console.log(result) // null
572
+
573
+ // Empty tree returns null
574
+ const emptyTree = []
575
+ const emptyResult = t.convertToObjectTree(emptyTree)
576
+ console.log(emptyResult) // null
577
+ ```
578
+
579
+ ### convertBackTree
580
+
581
+ Convert various data structures to tree-structured data. Supports array, Map, Record (object) and other formats. Each element in the array needs to contain `id` and `parentId` fields.
582
+
583
+ ```javascript
584
+ // Convert flat array to tree structure
585
+ const array = [
586
+ { id: 1, name: 'node1', parentId: null },
587
+ { id: 2, name: 'node2', parentId: 1 },
588
+ { id: 3, name: 'node3', parentId: 1 },
589
+ { id: 4, name: 'node4', parentId: 2 },
590
+ { id: 5, name: 'node5', parentId: 2 },
591
+ { id: 6, name: 'node6', parentId: 3 }
592
+ ]
593
+ const tree = t.convertBackTree(array)
594
+ console.log(tree)
595
+ // [
596
+ // {
597
+ // id: 1,
598
+ // name: 'node1',
599
+ // children: [
600
+ // {
601
+ // id: 2,
602
+ // name: 'node2',
603
+ // children: [
604
+ // { id: 4, name: 'node4', children: [] },
605
+ // { id: 5, name: 'node5', children: [] }
606
+ // ]
607
+ // },
608
+ // {
609
+ // id: 3,
610
+ // name: 'node3',
611
+ // children: [
612
+ // { id: 6, name: 'node6', children: [] }
613
+ // ]
614
+ // }
615
+ // ]
616
+ // }
617
+ // ]
618
+
619
+ // Custom root node parentId value
620
+ const arrayWithZero = [
621
+ { id: 1, name: 'node1', parentId: 0 },
622
+ { id: 2, name: 'node2', parentId: 1 }
623
+ ]
624
+ const treeWithZero = t.convertBackTree(arrayWithZero, { rootParentId: 0 })
625
+ console.log(treeWithZero) // Correctly converted
626
+
627
+ // Custom parentId field name
628
+ const arrayWithPid = [
629
+ { id: 1, name: 'node1', pid: null },
630
+ { id: 2, name: 'node2', pid: 1 }
631
+ ]
632
+ const treeWithPid = t.convertBackTree(arrayWithPid, { parentIdField: 'pid' })
633
+ console.log(treeWithPid) // Correctly converted
634
+
635
+ // Support custom field names
636
+ const customArray = [
637
+ { nodeId: 1, name: 'node1', parentId: null },
638
+ { nodeId: 2, name: 'node2', parentId: 1 }
639
+ ]
640
+ const customTree = t.convertBackTree(customArray, {
641
+ fieldNames: { id: 'nodeId', children: 'subNodes' }
642
+ })
643
+ console.log(customTree)
644
+ // [
645
+ // {
646
+ // nodeId: 1,
647
+ // name: 'node1',
648
+ // subNodes: [
649
+ // { nodeId: 2, name: 'node2', subNodes: [] }
650
+ // ]
651
+ // }
652
+ // ]
653
+
654
+ // Handle multiple root nodes
655
+ const multiRootArray = [
656
+ { id: 1, name: 'root1', parentId: null },
657
+ { id: 2, name: 'root2', parentId: null },
658
+ { id: 3, name: 'child1', parentId: 1 }
659
+ ]
660
+ const multiRootTree = t.convertBackTree(multiRootArray)
661
+ console.log(multiRootTree) // Contains two root nodes
662
+ ```
663
+
664
+ **Parameter Description:**
665
+ - `data` - Supports multiple data formats:
666
+ - Array: Flat array, each element needs to contain `id` and `parentId` fields
667
+ - Map: key is node ID, value is node object
668
+ - Record (object): key is node ID, value is node object
669
+ - Single object: Single tree node object
670
+ - `options.rootParentId` - The parentId value for root nodes, defaults to `null`
671
+ - `options.parentIdField` - Parent node ID field name, defaults to `'parentId'`
672
+ - `options.fieldNames` - Custom field name configuration, supports custom `id` and `children` field names
673
+
674
+ **Notes:**
675
+ - If a node's `parentId` cannot find a corresponding parent node, that node will be treated as a root node
676
+ - Nodes without `id` will be skipped
677
+ - Nodes with `parentId` as `null`, `undefined`, or equal to `rootParentId` will be treated as root nodes
678
+ - When converting Map and Record formats, the key will be set as the node's `id`
679
+
680
+ **Example: Support Map and Record formats**
681
+
682
+ ```javascript
683
+ // Map format
684
+ const map = new Map([
685
+ [1, { name: 'node1', parentId: null }],
686
+ [2, { name: 'node2', parentId: 1 }]
687
+ ])
688
+ const treeFromMap = t.convertBackTree(map)
689
+ console.log(treeFromMap) // Correctly converted to tree structure
690
+
691
+ // Record format
692
+ const record = {
693
+ 1: { name: 'node1', parentId: null },
694
+ 2: { name: 'node2', parentId: 1 }
695
+ }
696
+ const treeFromRecord = t.convertBackTree(record)
697
+ console.log(treeFromRecord) // Correctly converted to tree structure
698
+ ```
699
+
700
+ ---
701
+
702
+ ## Query Methods
703
+
704
+ ### getParentTree
705
+
706
+ Get the parent node of the specified node. Returns null if the node is a root node or not found.
707
+
708
+ ```javascript
709
+ // Get the parent node of the node with ID 2
710
+ const parentNode = t.getParentTree(treeData, 2)
711
+ console.log(parentNode) // Returns the parent node object { id: 1, name: 'node1', ... }
712
+
713
+ // Root nodes have no parent, returns null
714
+ const rootParentNode = t.getParentTree(treeData, 1)
715
+ console.log(rootParentNode) // null
716
+
717
+ // Node not found returns null
718
+ const parentNotFound = t.getParentTree(treeData, 999)
719
+ console.log(parentNotFound) // null
720
+ ```
721
+
722
+ ### getChildrenTree
723
+
724
+ Get all direct child nodes of the specified node. Returns an empty array if the node is not found or has no children.
725
+
726
+ ```javascript
727
+ // Get all child nodes of the node with ID 1
728
+ const children = t.getChildrenTree(treeData, 1)
729
+ console.log(children) // Returns child node array [{ id: 2, ... }, { id: 3, ... }]
730
+
731
+ // Node has no children, returns empty array
732
+ const emptyChildren = t.getChildrenTree(treeData, 4)
733
+ console.log(emptyChildren) // []
734
+
735
+ // Node not found returns empty array
736
+ const notFound = t.getChildrenTree(treeData, 999)
737
+ console.log(notFound) // []
738
+
739
+ // Support custom field names
740
+ const customTree = [
741
+ {
742
+ nodeId: 1,
743
+ name: 'root',
744
+ subNodes: [
745
+ { nodeId: 2, name: 'child1' },
746
+ { nodeId: 3, name: 'child2' },
747
+ ],
748
+ },
749
+ ];
750
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
751
+ const customChildren = t.getChildrenTree(customTree, 1, fieldNames)
752
+ console.log(customChildren) // Returns child node array
753
+ ```
754
+
755
+ ### getSiblingsTree
756
+
757
+ Get all sibling nodes of the specified node (including itself). Returns an empty array if the node is not found. Sibling nodes of root nodes are other root nodes.
758
+
759
+ ```javascript
760
+ // Get all sibling nodes (including itself) of the node with ID 2
761
+ const siblings = t.getSiblingsTree(treeData, 2)
762
+ console.log(siblings) // Returns sibling node array [{ id: 2, ... }, { id: 3, ... }]
763
+
764
+ // Sibling nodes of root nodes are other root nodes
765
+ const multiRoot = [
766
+ { id: 1, children: [{ id: 2 }] },
767
+ { id: 3, children: [{ id: 4 }] },
768
+ ];
769
+ const rootSiblings = t.getSiblingsTree(multiRoot, 1)
770
+ console.log(rootSiblings) // Returns all root nodes [{ id: 1, ... }, { id: 3, ... }]
771
+
772
+ // Node not found returns empty array
773
+ const notFound = t.getSiblingsTree(treeData, 999)
774
+ console.log(notFound) // []
775
+
776
+ // Support custom field names
777
+ const customTree = [
778
+ {
779
+ nodeId: 1,
780
+ name: 'root',
781
+ subNodes: [
782
+ { nodeId: 2, name: 'child1' },
783
+ { nodeId: 3, name: 'child2' },
784
+ { nodeId: 4, name: 'child3' },
785
+ ],
786
+ },
787
+ ];
788
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
789
+ const customSiblings = t.getSiblingsTree(customTree, 2, fieldNames)
790
+ console.log(customSiblings) // Returns sibling node array (including itself)
791
+ ```
792
+
793
+ ### getNodeDepthMap
794
+
795
+ Returns a dictionary where keys represent node ids and values represent which layer the node is in the data. Depth starts from 1, root nodes have depth 1.
796
+
797
+ ```javascript
798
+ // Get depth map of all nodes
799
+ const nodeDepthMap = t.getNodeDepthMap(treeData)
800
+ console.log(nodeDepthMap) // { 1: 1, 2: 2, 3: 2, 4: 3, 5: 3, 6: 3 }
801
+
802
+ // Get depth of a specific node
803
+ const node2Depth = nodeDepthMap[2]
804
+ console.log(node2Depth) // 2
805
+
806
+ // Empty tree returns empty object
807
+ const emptyDepthMap = t.getNodeDepthMap([])
808
+ console.log(emptyDepthMap) // {}
809
+ ```
810
+
811
+ ### getNodeDepth
812
+
813
+ Get the depth of the specified node. Depth starts from 1, root nodes have depth 1.
814
+
815
+ ```javascript
816
+ // Get depth of root node
817
+ const rootDepth = t.getNodeDepth(treeData, 1)
818
+ console.log(rootDepth) // 1
819
+
820
+ // Get depth of child node
821
+ const childDepth = t.getNodeDepth(treeData, 2)
822
+ console.log(childDepth) // 2
823
+
824
+ // Get depth of deep node
825
+ const deepDepth = t.getNodeDepth(treeData, 4)
826
+ console.log(deepDepth) // 3
827
+
828
+ // Node not found returns null
829
+ const notFound = t.getNodeDepth(treeData, 999)
830
+ console.log(notFound) // null
831
+
832
+ // Support custom field names
833
+ const customTree = [
834
+ {
835
+ nodeId: 1,
836
+ name: 'root',
837
+ subNodes: [
838
+ { nodeId: 2, name: 'child' },
839
+ ],
840
+ },
841
+ ];
842
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
843
+ const depth = t.getNodeDepth(customTree, 2, fieldNames)
844
+ console.log(depth) // 2
845
+ ```
846
+
847
+ **Difference from getNodeDepthMap:**
848
+ - `getNodeDepthMap` - Batch get depth of all nodes (calculates all nodes at once)
849
+ - `getNodeDepth` - Only get depth of a single node (only calculates the target node, more efficient)
850
+
851
+ ---
852
+
853
+ ## Validation Methods
854
+
855
+ ### isLeafNode
856
+
857
+ Check if a node is a leaf node (has no children). Lightweight method that only checks the node itself, doesn't traverse the tree.
858
+
859
+ ```javascript
860
+ // Nodes without children field are leaf nodes
861
+ const leafNode1 = { id: 1, name: 'node1' };
862
+ console.log(t.isLeafNode(leafNode1)) // true
863
+
864
+ // Nodes with empty children array are leaf nodes
865
+ const leafNode2 = { id: 2, name: 'node2', children: [] };
866
+ console.log(t.isLeafNode(leafNode2)) // true
867
+
868
+ // Nodes with children are not leaf nodes
869
+ const parentNode = {
870
+ id: 3,
871
+ name: 'node3',
872
+ children: [{ id: 4, name: 'node4' }],
873
+ };
874
+ console.log(t.isLeafNode(parentNode)) // false
875
+
876
+ // Use in filterTree (filter out all leaf nodes)
877
+ const leafNodes = t.filterTree(treeData, (node) => t.isLeafNode(node))
878
+ console.log(leafNodes) // Returns all leaf nodes
879
+
880
+ // Use in forEachTree
881
+ t.forEachTree(treeData, (node) => {
882
+ if (t.isLeafNode(node)) {
883
+ console.log('Leaf node:', node.name)
884
+ }
885
+ })
886
+
887
+ // Support custom field names
888
+ const customNode = {
889
+ nodeId: 1,
890
+ name: 'node1',
891
+ subNodes: [],
892
+ };
893
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
894
+ console.log(t.isLeafNode(customNode, fieldNames)) // true
895
+ ```
896
+
897
+ **Difference from existing methods:**
898
+ - `isLeafNode` - Only checks a single node, lightweight (O(1)), suitable for use during traversal
899
+ - `getChildrenTree` - Gets child node array, requires passing tree and nodeId, needs to find the node (O(n))
900
+
901
+ ### isRootNode
902
+
903
+ Check if a node is a root node (has no parent). Root nodes are top-level nodes in the tree-structured data array.
904
+
905
+ ```javascript
906
+ // Check root node
907
+ const treeData = [
908
+ {
909
+ id: 1,
910
+ name: 'root1',
911
+ children: [{ id: 2, name: 'child1' }],
912
+ },
913
+ ];
914
+ console.log(t.isRootNode(treeData, 1)) // true
915
+ console.log(t.isRootNode(treeData, 2)) // false
916
+
917
+ // Multiple root nodes case
918
+ const multiRoot = [
919
+ { id: 1, name: 'root1' },
920
+ { id: 2, name: 'root2' },
921
+ { id: 3, name: 'root3' },
922
+ ];
923
+ console.log(t.isRootNode(multiRoot, 1)) // true
924
+ console.log(t.isRootNode(multiRoot, 2)) // true
925
+ console.log(t.isRootNode(multiRoot, 3)) // true
926
+
927
+ // Use during traversal
928
+ t.forEachTree(treeData, (node) => {
929
+ if (t.isRootNode(treeData, node.id)) {
930
+ console.log('Root node:', node.name)
931
+ }
932
+ })
933
+
934
+ // Support custom field names
935
+ const customTree = [
936
+ {
937
+ nodeId: 1,
938
+ name: 'root1',
939
+ subNodes: [{ nodeId: 2, name: 'child1' }],
940
+ },
941
+ ];
942
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
943
+ console.log(t.isRootNode(customTree, 1, fieldNames)) // true
944
+ console.log(t.isRootNode(customTree, 2, fieldNames)) // false
945
+
946
+ // Returns false when node doesn't exist
947
+ console.log(t.isRootNode(treeData, 999)) // false
948
+ ```
949
+
950
+ **Difference from existing methods:**
951
+ - `isRootNode` - Semantic method that directly returns boolean value
952
+ - `getParentTree` - Returns parent node object, need to check if it's null
953
+ - `getNodeDepth` - Returns depth, need to check if it equals 1
954
+
955
+ ### isEmptyTreeData
956
+
957
+ Check if tree-structured data (array) is empty. Empty arrays, null, and undefined are all considered empty. This function supports the fieldNames parameter to maintain API consistency, but the parameter has no effect (because it only checks if the array is empty, doesn't access children or id fields).
958
+
959
+ ```javascript
960
+ // Check if tree-structured data is empty
961
+ const isEmptyTree = t.isEmptyTreeData(treeData)
962
+ console.log(isEmptyTree) // false (has data)
963
+
964
+ // Empty array returns true
965
+ const isEmptyArray = t.isEmptyTreeData([])
966
+ console.log(isEmptyArray) // true
967
+
968
+ // null or undefined returns true
969
+ const isNullTree = t.isEmptyTreeData(null)
970
+ console.log(isNullTree) // true
971
+
972
+ // Support fieldNames parameter (maintains API consistency, but has no effect)
973
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
974
+ const isEmptyWithFieldNames = t.isEmptyTreeData(treeData, fieldNames)
975
+ console.log(isEmptyWithFieldNames) // false (same result as not passing fieldNames)
976
+ ```
977
+
978
+ ### isEmptySingleTreeData
979
+
980
+ Check if a single tree-structured data is empty. If the data is not valid single tree-structured data, has no children field, or children is an empty array, it is considered empty. If there are child nodes (children array is not empty), even if the child nodes themselves are empty, the tree is not empty.
981
+
982
+ ```javascript
983
+ // No children field, considered empty
984
+ const tree1 = { id: 1, name: 'node1' };
985
+ const isEmpty1 = t.isEmptySingleTreeData(tree1)
986
+ console.log(isEmpty1) // true
987
+
988
+ // children is empty array, considered empty
989
+ const tree2 = {
990
+ id: 1,
991
+ name: 'node1',
992
+ children: [],
993
+ };
994
+ const isEmpty2 = t.isEmptySingleTreeData(tree2)
995
+ console.log(isEmpty2) // true
996
+
997
+ // Has child nodes, not empty
998
+ const tree3 = {
999
+ id: 1,
1000
+ name: 'node1',
1001
+ children: [
1002
+ { id: 2, name: 'node2' },
1003
+ ],
1004
+ };
1005
+ const isEmpty3 = t.isEmptySingleTreeData(tree3)
1006
+ console.log(isEmpty3) // false
1007
+
1008
+ // Has child nodes, even if child nodes themselves are empty, tree is not empty
1009
+ const tree4 = {
1010
+ id: 1,
1011
+ name: 'node1',
1012
+ children: [
1013
+ { id: 2, name: 'node2', children: [] },
1014
+ { id: 3, name: 'node3' }, // no children field
1015
+ ],
1016
+ };
1017
+ const isEmpty4 = t.isEmptySingleTreeData(tree4)
1018
+ console.log(isEmpty4) // false (because there are child nodes, even if they are empty)
1019
+
1020
+ // Support custom field names
1021
+ const customTree = {
1022
+ nodeId: 1,
1023
+ name: 'node1',
1024
+ subNodes: [],
1025
+ };
1026
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
1027
+ const isEmptyCustom = t.isEmptySingleTreeData(customTree, fieldNames)
1028
+ console.log(isEmptyCustom) // true
1029
+ ```
1030
+
1031
+ ### isTreeData
1032
+
1033
+ Check if data is tree-structured data (array). Tree-structured data must be an array, and each element in the array must be valid single tree-structured data.
1034
+
1035
+ ```javascript
1036
+ // Valid tree-structured data (forest)
1037
+ const forest = [
1038
+ {
1039
+ id: 1,
1040
+ name: 'node1',
1041
+ children: [{ id: 2, name: 'node2' }],
1042
+ },
1043
+ {
1044
+ id: 3,
1045
+ name: 'node3',
1046
+ children: [{ id: 4, name: 'node4' }],
1047
+ },
1048
+ ];
1049
+ console.log(t.isTreeData(forest)) // true
1050
+
1051
+ // Empty array is also valid tree-structured data (empty forest)
1052
+ console.log(t.isTreeData([])) // true
1053
+
1054
+ // Single object is not tree-structured data (should use isSingleTreeData)
1055
+ console.log(t.isTreeData({ id: 1 })) // false
1056
+
1057
+ // Array contains non-tree-structured elements, returns false
1058
+ const invalidForest = [
1059
+ { id: 1, children: [{ id: 2 }] },
1060
+ 'not a tree', // invalid element
1061
+ ];
1062
+ console.log(t.isTreeData(invalidForest)) // false
1063
+
1064
+ // null or undefined are not valid tree-structured data
1065
+ console.log(t.isTreeData(null)) // false
1066
+ console.log(t.isTreeData(undefined)) // false
1067
+
1068
+ // Support custom field names
1069
+ const customForest = [
1070
+ {
1071
+ nodeId: 1,
1072
+ name: 'node1',
1073
+ subNodes: [{ nodeId: 2, name: 'node2' }],
1074
+ },
1075
+ ];
1076
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
1077
+ console.log(t.isTreeData(customForest, fieldNames)) // true
1078
+ ```
1079
+
1080
+ ### isSingleTreeData
1081
+
1082
+ Check if data is single tree-structured data (single object). Tree-structured data must be an object (cannot be an array, null, undefined, or primitive type). If a children field exists, it must be an array type, and all child nodes will be recursively checked.
1083
+
1084
+ ```javascript
1085
+ // Valid single tree-structured data
1086
+ const tree = {
1087
+ id: 1,
1088
+ name: 'node1',
1089
+ children: [
1090
+ { id: 2, name: 'node2' },
1091
+ { id: 3, name: 'node3' },
1092
+ ],
1093
+ };
1094
+ const isValid = t.isSingleTreeData(tree)
1095
+ console.log(isValid) // true
1096
+
1097
+ // No children field is also valid (only root node)
1098
+ const singleNode = { id: 1, name: 'node1' }
1099
+ console.log(t.isSingleTreeData(singleNode)) // true
1100
+
1101
+ // Array is not single tree-structured data
1102
+ console.log(t.isSingleTreeData([])) // false
1103
+
1104
+ // null or undefined are not valid tree-structured data
1105
+ console.log(t.isSingleTreeData(null)) // false
1106
+ console.log(t.isSingleTreeData(undefined)) // false
1107
+
1108
+ // children cannot be null
1109
+ const invalidTree = { id: 1, children: null }
1110
+ console.log(t.isSingleTreeData(invalidTree)) // false
1111
+
1112
+ // Support custom field names
1113
+ const customTree = {
1114
+ nodeId: 1,
1115
+ name: 'node1',
1116
+ subNodes: [{ nodeId: 2, name: 'node2' }],
1117
+ };
1118
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
1119
+ console.log(t.isSingleTreeData(customTree, fieldNames)) // true
1120
+ ```
1121
+
1122
+ ### isValidTreeNode
1123
+
1124
+ Check if a single node is a valid tree node structure (lightweight, doesn't recursively check child nodes). Only checks the structure of the node itself, doesn't check child nodes.
1125
+
1126
+ ```javascript
1127
+ // Valid tree node (has children array)
1128
+ const node1 = {
1129
+ id: 1,
1130
+ name: 'node1',
1131
+ children: [{ id: 2 }],
1132
+ };
1133
+ console.log(t.isValidTreeNode(node1)) // true
1134
+
1135
+ // Valid tree node (no children field)
1136
+ const node2 = { id: 1, name: 'node1' };
1137
+ console.log(t.isValidTreeNode(node2)) // true
1138
+
1139
+ // Invalid tree node (children is not an array)
1140
+ const invalidNode = {
1141
+ id: 1,
1142
+ children: 'not an array',
1143
+ };
1144
+ console.log(t.isValidTreeNode(invalidNode)) // false
1145
+
1146
+ // Support custom field names
1147
+ const customNode = {
1148
+ nodeId: 1,
1149
+ subNodes: [{ nodeId: 2 }],
1150
+ };
1151
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
1152
+ console.log(t.isValidTreeNode(customNode, fieldNames)) // true
1153
+ ```
1154
+
1155
+ **Difference from isSingleTreeData:**
1156
+ - `isValidTreeNode` - Only checks the basic structure of a single node, doesn't recursively check child nodes (lightweight)
1157
+ - `isSingleTreeData` - Recursively checks the entire tree structure, ensuring all child nodes are valid tree structures
1158
+
1159
+ ### isTreeNodeWithCircularCheck
1160
+
1161
+ Check if a node is a valid tree node structure and detect circular references. Uses WeakSet to track visited nodes, returns false if circular references are found.
1162
+
1163
+ ```javascript
1164
+ // Valid tree node (no circular references)
1165
+ const validNode = {
1166
+ id: 1,
1167
+ children: [
1168
+ { id: 2, children: [{ id: 3 }] },
1169
+ ],
1170
+ };
1171
+ console.log(t.isTreeNodeWithCircularCheck(validNode)) // true
1172
+
1173
+ // Detect circular references
1174
+ const node1 = { id: 1, children: [] };
1175
+ const node2 = { id: 2, children: [] };
1176
+ node1.children.push(node2);
1177
+ node2.children.push(node1); // circular reference
1178
+ console.log(t.isTreeNodeWithCircularCheck(node1)) // false
1179
+
1180
+ // Detect self-reference
1181
+ const selfRefNode = { id: 1, children: [] };
1182
+ selfRefNode.children.push(selfRefNode); // self-reference
1183
+ console.log(t.isTreeNodeWithCircularCheck(selfRefNode)) // false
1184
+
1185
+ // Support custom field names
1186
+ const customNode = {
1187
+ nodeId: 1,
1188
+ subNodes: [{ nodeId: 2 }],
1189
+ };
1190
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
1191
+ console.log(t.isTreeNodeWithCircularCheck(customNode, fieldNames)) // true
1192
+ ```
1193
+
1194
+ **Use Cases:**
1195
+ - Check for circular references when receiving user input or external data
1196
+ - Data validation to prevent infinite recursion
1197
+ - Check if data structure is correct during debugging
1198
+
1199
+ ### isSafeTreeDepth
1200
+
1201
+ Check if the depth of tree-structured data is safe (prevent recursion stack overflow). Returns false if the tree depth exceeds `maxDepth`.
1202
+
1203
+ ```javascript
1204
+ // Safe depth tree
1205
+ const safeTree = [
1206
+ {
1207
+ id: 1,
1208
+ children: [
1209
+ { id: 2, children: [{ id: 3 }] },
1210
+ ],
1211
+ },
1212
+ ];
1213
+ console.log(t.isSafeTreeDepth(safeTree, 10)) // true (depth is 3, less than 10)
1214
+
1215
+ // Depth exceeds maximum depth
1216
+ const deepTree = [
1217
+ {
1218
+ id: 1,
1219
+ children: [
1220
+ { id: 2, children: [{ id: 3 }] },
1221
+ ],
1222
+ },
1223
+ ];
1224
+ console.log(t.isSafeTreeDepth(deepTree, 2)) // false (depth is 3, exceeds 2)
1225
+
1226
+ // Empty tree is always safe
1227
+ console.log(t.isSafeTreeDepth([], 10)) // true
1228
+
1229
+ // Single layer tree
1230
+ const singleLayer = [{ id: 1 }, { id: 2 }];
1231
+ console.log(t.isSafeTreeDepth(singleLayer, 1)) // true
1232
+
1233
+ // Support custom field names
1234
+ const customTree = [
1235
+ {
1236
+ nodeId: 1,
1237
+ subNodes: [
1238
+ { nodeId: 2, subNodes: [{ nodeId: 3 }] },
1239
+ ],
1240
+ },
1241
+ ];
1242
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
1243
+ console.log(t.isSafeTreeDepth(customTree, 3, fieldNames)) // true
1244
+ console.log(t.isSafeTreeDepth(customTree, 2, fieldNames)) // false
1245
+ ```
1246
+
1247
+ **Use Cases:**
1248
+ - Check if depth is safe before processing large trees
1249
+ - Prevent recursion call stack overflow
1250
+ - Performance optimization, avoid processing trees that are too deep
1251
+
1252
+ ## Custom Field Names
1253
+
1254
+ All methods support custom property names for children and id through the last parameter, passing in a configuration object:
1255
+
1256
+ ```javascript
1257
+ // Use default field names
1258
+ const foundNode1 = t.findTree(treeData, (node) => node.id === 2)
1259
+
1260
+ // Use custom field names
1261
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
1262
+ const foundNode2 = t.findTree(customTreeData, (node) => node.nodeId === 2, fieldNames);
1263
+ ```
1264
+
1265
+ **Note:** All 30 functions support the `fieldNames` parameter to maintain API consistency. Even if the parameter has no effect in certain functions (such as `isEmptyTreeData`), it can still be passed to maintain consistent code style.
1266
+
1267
+ ## Testing
1268
+
1269
+ ### Run Tests
1270
+
1271
+ ```bash
1272
+ # Run all tests (automatically build then test source + bundled files, 656 test cases)
1273
+ npm test
1274
+
1275
+ # Run all tests (once, don't watch for file changes)
1276
+ npm test -- --run
1277
+
1278
+ # Test source code only (328 test cases)
1279
+ npm run test:src
1280
+
1281
+ # Test bundled files only (328 test cases, requires npm run build first)
1282
+ npm run test:dist
1283
+
1284
+ # Run tests and generate coverage report
1285
+ npm run test:coverage
1286
+ ```
1287
+
1288
+ ## Development
1289
+
1290
+ ```bash
1291
+ # Install dependencies
1292
+ npm install
1293
+
1294
+ # Run tests
1295
+ npm test
1296
+
1297
+ # Build project (delete dist directory first, then rebuild)
1298
+ npm run build
1299
+ ```
1300
+
1301
+ <div align="center">
1302
+
1303
+ If this project helps you, please give it a ⭐️
1304
+
1305
+ Made with by [knott11]
1306
+
1307
+ </div>