tree-processor 0.9.7 → 0.11.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 CHANGED
@@ -9,12 +9,12 @@
9
9
  <div align="center">
10
10
 
11
11
  ![version](https://img.shields.io/npm/v/tree-processor?style=flat-square&label=version)
12
- ![npm downloads (2 months)](https://img.shields.io/badge/downloads-1.3K%2F2mo-brightgreen?style=flat-square)
13
- ![bundle size](https://img.shields.io/badge/bundle-8.4KB-blue?style=flat-square)
12
+ ![npm downloads (2 months)](https://img.shields.io/badge/downloads-1.7K%2F2mo-brightgreen?style=flat-square)
13
+ ![bundle size](https://img.shields.io/badge/bundle-15KB-blue?style=flat-square)
14
14
  ![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)
15
15
  ![coverage](https://img.shields.io/badge/coverage-99%25-brightgreen?style=flat-square)
16
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).
17
+ A lightweight tree-structured data processing utility library written in TypeScript, providing 50+ APIs including traversal, search, modification, conversion, query, analysis, and validation.
18
18
 
19
19
 
20
20
  </div>
@@ -26,28 +26,26 @@ A lightweight tree-structured data processing utility library written in TypeScr
26
26
  - [Installation](#-installation)
27
27
  - [Quick Start](#-quick-start)
28
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)
29
+ - [Traversal Operation Methods](#traversal-operation-methods)
30
+ - [Conditional Search Methods](#conditional-search-methods)
31
+ - [Index Access Methods](#index-access-methods)
32
+ - [Node Operation Methods](#node-operation-methods)
33
+ - [Format Conversion Methods](#format-conversion-methods)
34
+ - [Clone and Copy Methods](#clone-and-copy-methods)
35
+ - [Relationship Query Methods](#relationship-query-methods)
36
+ - [Data Validation Methods](#data-validation-methods)
37
+ - [Aggregation Analysis Methods](#aggregation-analysis-methods)
36
38
  - [Custom Field Names](#custom-field-names)
37
39
  - [Testing](#testing)
38
40
  - [Development](#development)
39
41
 
40
42
  ## ✨ Features
41
43
 
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.
44
+ - **Multiple Format Support** - Provides ESM, CJS, UMD formats, bundle size only 14.9-15.2 KB, supports Tree-shaking, on-demand imports
45
+ - **Zero Dependencies** - No external dependencies, ready to use
46
+ - **High Performance** - Average execution time < 0.03ms on medium-sized trees (~120 nodes)
47
+ - **Complete Functionality** - 50+ APIs covering traversal, search, modification, conversion, query, analysis, and validation
48
+ - **Comprehensive Testing** - 447 test cases with 99%+ coverage
51
49
 
52
50
  ### 💡 Use Cases
53
51
 
@@ -153,7 +151,9 @@ const treeData = [
153
151
 
154
152
  ---
155
153
 
156
- ## Traversal Methods
154
+ ## Traversal Operation Methods
155
+
156
+ Methods for traversing tree-structured data and performing operations on each node.
157
157
 
158
158
  ### mapTree
159
159
 
@@ -202,7 +202,9 @@ console.log(nodeCount) // Total number of nodes
202
202
 
203
203
  ---
204
204
 
205
- ## Search Methods
205
+ ## Conditional Search Methods
206
+
207
+ Methods for finding nodes by conditions or predicate functions.
206
208
 
207
209
  ### filterTree
208
210
 
@@ -285,7 +287,9 @@ console.log(allHaveName) // Returns true or false based on actual data
285
287
 
286
288
  ---
287
289
 
288
- ## Access Methods
290
+ ## Index Access Methods
291
+
292
+ Methods for accessing nodes by position index or index path.
289
293
 
290
294
  ### atTree
291
295
 
@@ -345,7 +349,9 @@ console.log(invalidPath) // null
345
349
 
346
350
  ---
347
351
 
348
- ## Modification Methods
352
+ ## Node Operation Methods
353
+
354
+ Methods for adding, removing, and modifying nodes in tree structures.
349
355
 
350
356
  ### pushTree
351
357
 
@@ -375,26 +381,30 @@ console.log(treeData) // New node has been added to the beginning of the childre
375
381
 
376
382
  ### popTree
377
383
 
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.
384
+ Remove the last child node under the specified node. Returns the removed node, or null if the node doesn't exist or has no children.
379
385
 
380
386
  ```javascript
381
387
  // Remove the last child node under the node with ID 1
382
388
  const removedNode = t.popTree(treeData, 1)
383
- console.log(removedNode) // Returns the removed node object, or false
389
+ console.log(removedNode) // Returns the removed node object, or null
384
390
 
385
391
  // Try to remove from a non-existent node
386
392
  const popFailed = t.popTree(treeData, 999)
387
- console.log(popFailed) // false
393
+ console.log(popFailed) // null
388
394
  ```
389
395
 
390
396
  ### shiftTree
391
397
 
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.
398
+ Remove the first child node under the specified node. Returns the removed node, or null if the node doesn't exist or has no children.
393
399
 
394
400
  ```javascript
395
401
  // Remove the first child node under the node with ID 1
396
402
  const shiftedNode = t.shiftTree(treeData, 1)
397
- console.log(shiftedNode) // Returns the removed node object, or false
403
+ console.log(shiftedNode) // Returns the removed node object, or null
404
+
405
+ // Try to remove from a non-existent node
406
+ const shiftFailed = t.shiftTree(treeData, 999)
407
+ console.log(shiftFailed) // null
398
408
  ```
399
409
 
400
410
  ### removeTree
@@ -409,23 +419,138 @@ console.log(removeSuccess) // true means successful removal, false means node no
409
419
  console.log(treeData) // Tree structure after removal
410
420
  ```
411
421
 
422
+ ### concatTree
423
+
424
+ Concatenate multiple tree-structured data arrays, returns a new concatenated tree (deep cloned).
425
+
426
+ ```javascript
427
+ const tree1 = [
428
+ { id: 1, name: 'node1' },
429
+ { id: 2, name: 'node2' }
430
+ ]
431
+ const tree2 = [
432
+ { id: 3, name: 'node3' }
433
+ ]
434
+
435
+ // Concatenate multiple trees
436
+ const result = t.concatTree(tree1, tree2)
437
+ console.log(result) // [{ id: 1, name: 'node1' }, { id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
438
+ ```
439
+
440
+ **Parameters:**
441
+ - `...trees`: Multiple tree-structured data arrays (variadic arguments)
442
+
443
+ **Notes:**
444
+ - All trees are deep cloned, won't modify the original trees
445
+ - Supports concatenating any number of tree structures
446
+
447
+ ### sortTree
448
+
449
+ Sort tree-structured data, recursively sorts all levels.
450
+
451
+ ```javascript
452
+ const tree = [
453
+ { id: 3, name: 'node3' },
454
+ { id: 1, name: 'node1' },
455
+ { id: 2, name: 'node2' }
456
+ ]
457
+
458
+ // Sort by id
459
+ const sorted = t.sortTree(tree, (a, b) => a.id - b.id)
460
+ console.log(sorted)
461
+ // [{ id: 1, name: 'node1' }, { id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
462
+ ```
463
+
464
+ **Parameters:**
465
+ - `tree`: Tree-structured data
466
+ - `compareFn`: Comparison function, same as `Array.sort`'s `compareFn` (optional)
467
+ - `fieldNames`: Custom field name configuration (optional)
468
+
469
+ **Notes:**
470
+ - Recursively sorts nodes at all levels
471
+ - Returns a new sorted tree (deep cloned), won't modify the original tree
472
+ - Uses default sorting if `compareFn` is not provided
473
+
474
+ ### sliceTree
475
+
476
+ Slice the root nodes of tree-structured data (similar to array's `slice`).
477
+
478
+ ```javascript
479
+ const tree = [
480
+ { id: 1, name: 'node1' },
481
+ { id: 2, name: 'node2' },
482
+ { id: 3, name: 'node3' }
483
+ ]
484
+
485
+ // Slice: get nodes from index 1 to 3
486
+ const sliced = t.sliceTree(tree, 1, 3)
487
+ console.log(sliced) // [{ id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
488
+
489
+ // Supports negative indices
490
+ const lastTwo = t.sliceTree(tree, -2)
491
+ console.log(lastTwo) // [{ id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
492
+ ```
493
+
494
+ **Parameters:**
495
+ - `tree`: Tree-structured data
496
+ - `start`: Start index (inclusive), optional
497
+ - `end`: End index (exclusive), optional
498
+ - `fieldNames`: Custom field name configuration (optional)
499
+
500
+ **Notes:**
501
+ - Only slices root nodes, doesn't recursively process children
502
+ - Returns a new sliced tree (deep cloned), won't modify the original tree
503
+ - Supports negative indices (calculated from the end)
504
+ - Child node structures are completely preserved
505
+
412
506
  ### dedupTree
413
507
 
414
- Tree-structured object array deduplication method that removes duplicate nodes based on the specified key. Keeps the first occurrence of the node.
508
+ Tree-structured object array deduplication method that removes duplicate nodes based on the specified key. Keeps the first occurrence of the node. Supports single field, multiple fields combined deduplication, and custom functions.
509
+
510
+ **Parameters:**
511
+ - `tree`: Tree-structured data
512
+ - `key`: Key(s) for deduplication, supports three types:
513
+ - `string`: Single field deduplication (e.g., `'id'`)
514
+ - `string[]`: Multiple fields combined deduplication (e.g., `['id', 'type']`)
515
+ - `(node: TreeNode) => any`: Custom function that returns the value for deduplication
516
+ - `fieldNames`: Custom field name configuration (optional)
415
517
 
416
518
  ```javascript
417
- // Deduplicate based on id field
519
+ // Method 1: Single field deduplication (original usage)
418
520
  const uniqueTreeData = t.dedupTree(treeData, 'id')
419
521
  console.log(uniqueTreeData) // Returns deduplicated tree-structured data
420
522
 
421
- // Deduplicate based on name field
422
- const uniqueByNameTree = t.dedupTree(treeData, 'name')
423
- console.log(uniqueByNameTree) // Returns data deduplicated by name
523
+ // Method 2: Multiple fields combined deduplication (new feature)
524
+ const tree = [
525
+ {
526
+ id: 1,
527
+ children: [
528
+ { id: 2, type: 'A', name: 'node1' },
529
+ { id: 2, type: 'B', name: 'node2' }, // Kept (same id but different type)
530
+ { id: 2, type: 'A', name: 'node3' }, // Deduplicated (both id and type are same)
531
+ ]
532
+ }
533
+ ]
534
+ const uniqueByMultiFields = t.dedupTree(tree, ['id', 'type'])
535
+ // Result: Keeps node1 and node2, node3 is deduplicated
536
+
537
+ // Method 3: Custom function deduplication
538
+ const uniqueByCustom = t.dedupTree(treeData, (node) => node.code)
539
+ // Or more complex logic
540
+ const uniqueByComplex = t.dedupTree(treeData, (node) => `${node.id}-${node.type}`)
424
541
  ```
425
542
 
543
+ **Notes:**
544
+ - If the dedupKey value is `undefined` or `null`, the node will not be deduplicated (all will be kept)
545
+ - Multiple fields combined deduplication uses the combination of field values to determine duplicates
546
+ - Recursively processes all levels of child nodes
547
+ - **Performance Optimization**: Multiple fields combined deduplication has been optimized, using efficient delimiter concatenation instead of JSON.stringify for better performance
548
+
426
549
  ---
427
550
 
428
- ## Conversion Methods
551
+ ## Format Conversion Methods
552
+
553
+ Methods for converting between different data formats (array, Map, object, etc.).
429
554
 
430
555
  ### convertToArrayTree
431
556
 
@@ -699,7 +824,169 @@ console.log(treeFromRecord) // Correctly converted to tree structure
699
824
 
700
825
  ---
701
826
 
702
- ## Query Methods
827
+ ## Clone and Copy Methods
828
+
829
+ Methods for copying tree-structured data (deep copy, shallow copy, subtree copy, etc.).
830
+
831
+ ### cloneTree
832
+
833
+ Deep clone tree-structured data, returns a completely independent copy without modifying the original tree.
834
+
835
+ ```javascript
836
+ const original = [
837
+ { id: 1, name: 'node1', children: [{ id: 2, name: 'node2' }] }
838
+ ]
839
+
840
+ // Deep clone
841
+ const cloned = t.cloneTree(original)
842
+
843
+ // Modifying the cloned tree won't affect the original
844
+ cloned[0].name = 'modified'
845
+ console.log(original[0].name) // 'node1'
846
+ console.log(cloned[0].name) // 'modified'
847
+ ```
848
+
849
+ **Parameters:**
850
+ - `tree`: Tree-structured data
851
+ - `fieldNames`: Custom field name configuration (optional)
852
+
853
+ **Notes:**
854
+ - Recursively deep clones all levels of nodes and children
855
+ - Returns a tree completely independent from the original, modifications won't affect each other
856
+ - Supports custom field name configuration
857
+
858
+ ### shallowCloneTree
859
+
860
+ Shallow clone tree-structured data (only copies the first level, children share references). Better performance than deep clone, suitable for scenarios where only the top-level structure needs to be copied.
861
+
862
+ ```javascript
863
+ const original = [
864
+ { id: 1, name: 'node1', children: [{ id: 2, name: 'node2' }] }
865
+ ]
866
+
867
+ // Shallow clone
868
+ const cloned = t.shallowCloneTree(original)
869
+
870
+ // Modifying the first level won't affect the original
871
+ cloned[0].name = 'modified'
872
+ console.log(original[0].name) // 'node1'
873
+
874
+ // But children share references, modifying children will affect the original
875
+ cloned[0].children[0].name = 'changed'
876
+ console.log(original[0].children[0].name) // 'changed'
877
+ ```
878
+
879
+ **Parameters:**
880
+ - `tree`: Tree-structured data
881
+ - `fieldNames`: Custom field name configuration (optional)
882
+
883
+ **Notes:**
884
+ - Only copies the first level of nodes, children maintain shared references
885
+ - Better performance than deep clone, suitable for scenarios where only top-level independence is needed
886
+ - Modifying children will affect the original tree
887
+
888
+ ### cloneSubtree
889
+
890
+ Clone a subtree starting from the specified node. Returns a deep copy containing the target node and all its children. Supports finding nodes by any field.
891
+
892
+ ```javascript
893
+ const tree = [
894
+ {
895
+ id: 1,
896
+ name: 'root',
897
+ children: [
898
+ { id: 2, name: 'sub1', children: [{ id: 4, name: 'sub1-1' }] },
899
+ { id: 3, name: 'sub2' }
900
+ ]
901
+ }
902
+ ]
903
+
904
+ // Find by id field
905
+ const subtree1 = t.cloneSubtree(tree, { id: 2 })
906
+ console.log(subtree1)
907
+ // [{ id: 2, name: 'sub1', children: [{ id: 4, name: 'sub1-1' }] }]
908
+
909
+ // Find by name field
910
+ const subtree2 = t.cloneSubtree(tree, { name: 'sub1' })
911
+ console.log(subtree2)
912
+ // [{ id: 2, name: 'sub1', children: [{ id: 4, name: 'sub1-1' }] }]
913
+
914
+ // Find by other field (e.g., code)
915
+ const treeWithCode = [
916
+ {
917
+ id: 1,
918
+ code: 'A001',
919
+ children: [
920
+ { id: 2, code: 'B001', children: [{ id: 4, code: 'C001' }] }
921
+ ]
922
+ }
923
+ ]
924
+ const subtree3 = t.cloneSubtree(treeWithCode, { code: 'B001' })
925
+ console.log(subtree3)
926
+ // [{ id: 2, code: 'B001', children: [{ id: 4, code: 'C001' }] }]
927
+
928
+ // Support custom children field name
929
+ const customTree = [
930
+ { nodeId: 1, subNodes: [{ nodeId: 2 }] }
931
+ ]
932
+ const subtree4 = t.cloneSubtree(customTree, { nodeId: 2 }, { children: 'subNodes', id: 'nodeId' })
933
+ console.log(subtree4)
934
+ // [{ nodeId: 2 }]
935
+
936
+ // Modifying the cloned subtree won't affect the original
937
+ subtree1[0].name = 'modified'
938
+ console.log(tree[0].children[0].name) // 'sub1'
939
+ ```
940
+
941
+ **Parameters:**
942
+ - `tree`: Tree-structured data
943
+ - `target`: Target node object, e.g., `{ id: 1 }` or `{ name: 'sub1' }` or `{ code: 'B001' }`, object must contain only one field
944
+ - `fieldNames`: Custom field name configuration (optional, used to customize `children` field name, search field is determined by the key name in the `target` object)
945
+
946
+ **Notes:**
947
+ - Returns a subtree containing the target node (deep cloned)
948
+ - Returns an empty array if the target node is not found
949
+ - Recursively deep clones all child nodes
950
+ - Must pass an object, search field is determined by the object's key name (e.g., `{ id: 1 }` searches by `id` field, `{ name: 'xxx' }` searches by `name` field)
951
+ - `fieldNames` parameter is used to customize `children` field name, defining `id` has no effect
952
+
953
+ ### cloneWithTransform
954
+
955
+ Clone tree-structured data and apply a transform function to each node. Suitable for modifying node data while cloning.
956
+
957
+ ```javascript
958
+ const tree = [
959
+ { id: 1, name: 'node1', children: [{ id: 2, name: 'node2' }] }
960
+ ]
961
+
962
+ // Clone and add label field
963
+ const cloned = t.cloneWithTransform(tree, (node) => ({
964
+ ...node,
965
+ label: node.name,
966
+ processed: true
967
+ }))
968
+
969
+ console.log(cloned[0].label) // 'node1'
970
+ console.log(cloned[0].processed) // true
971
+ console.log(cloned[0].children[0].label) // 'node2'
972
+ console.log(tree[0].label) // undefined (original tree not modified)
973
+ ```
974
+
975
+ **Parameters:**
976
+ - `tree`: Tree-structured data
977
+ - `transform`: Transform function that receives a node and returns the transformed node
978
+ - `fieldNames`: Custom field name configuration (optional)
979
+
980
+ **Notes:**
981
+ - Recursively transforms all levels of nodes
982
+ - Returns the transformed tree (deep cloned), won't modify the original tree
983
+ - The transform function should return a new node object
984
+
985
+ ---
986
+
987
+ ## Relationship Query Methods
988
+
989
+ Methods for getting relationship information between nodes (parent-child relationships, sibling relationships, depth, etc.).
703
990
 
704
991
  ### getParentTree
705
992
 
@@ -850,7 +1137,9 @@ console.log(depth) // 2
850
1137
 
851
1138
  ---
852
1139
 
853
- ## Validation Methods
1140
+ ## Data Validation Methods
1141
+
1142
+ Methods for validating the validity of tree-structured data and node types.
854
1143
 
855
1144
  ### isLeafNode
856
1145
 
@@ -902,6 +1191,8 @@ console.log(t.isLeafNode(customNode, fieldNames)) // true
902
1191
 
903
1192
  Check if a node is a root node (has no parent). Root nodes are top-level nodes in the tree-structured data array.
904
1193
 
1194
+ **Performance Optimization**: Optimized to use a single traversal, avoiding duplicate tree traversals.
1195
+
905
1196
  ```javascript
906
1197
  // Check root node
907
1198
  const treeData = [
@@ -1249,6 +1540,435 @@ console.log(t.isSafeTreeDepth(customTree, 2, fieldNames)) // false
1249
1540
  - Prevent recursion call stack overflow
1250
1541
  - Performance optimization, avoid processing trees that are too deep
1251
1542
 
1543
+ ---
1544
+
1545
+ ## Aggregation Analysis Methods
1546
+
1547
+ Methods for aggregating, statistics, and analyzing tree-structured data.
1548
+
1549
+ ### reduceTree
1550
+
1551
+ Reduce tree-structured data, traverses all nodes and accumulates results.
1552
+
1553
+ ```javascript
1554
+ const tree = [
1555
+ { id: 1, value: 10 },
1556
+ { id: 2, value: 20, children: [{ id: 3, value: 30 }] }
1557
+ ]
1558
+
1559
+ // Calculate the sum of all node values
1560
+ const sum = t.reduceTree(tree, (acc, node) => acc + (node.value || 0), 0)
1561
+ console.log(sum) // 60
1562
+
1563
+ // Collect all node IDs
1564
+ const ids = t.reduceTree(tree, (ids, node) => {
1565
+ ids.push(node.id)
1566
+ return ids
1567
+ }, [])
1568
+ console.log(ids) // [1, 2, 3]
1569
+ ```
1570
+
1571
+ **Parameters:**
1572
+ - `tree`: Tree-structured data
1573
+ - `reducer`: Reduction function that receives accumulator and current node, returns new accumulator
1574
+ - `initialValue`: Initial value
1575
+ - `fieldNames`: Custom field name configuration (optional)
1576
+
1577
+ **Notes:**
1578
+ - Traverses all nodes in depth-first order
1579
+ - Can be used to implement various aggregation operations
1580
+
1581
+ ---
1582
+
1583
+ ### aggregateTree
1584
+
1585
+ Aggregate tree-structured data by groups, supports multiple aggregation operations (sum, average, max, min, count).
1586
+
1587
+ ```javascript
1588
+ const tree = [
1589
+ { id: 1, category: 'A', value: 10, score: 80 },
1590
+ { id: 2, category: 'A', value: 20, score: 90 },
1591
+ { id: 3, category: 'B', value: 30, score: 70, children: [{ id: 4, category: 'B', value: 40, score: 85 }] }
1592
+ ]
1593
+
1594
+ // Aggregate by category
1595
+ const result = t.aggregateTree(tree, {
1596
+ groupBy: node => node.category,
1597
+ aggregations: {
1598
+ totalValue: { operation: 'sum', field: 'value' },
1599
+ avgScore: { operation: 'avg', field: 'score' },
1600
+ maxValue: { operation: 'max', field: 'value' },
1601
+ count: { operation: 'count' }
1602
+ }
1603
+ })
1604
+
1605
+ console.log(result)
1606
+ // {
1607
+ // 'A': { totalValue: 30, avgScore: 85, maxValue: 20, count: 2 },
1608
+ // 'B': { totalValue: 70, avgScore: 77.5, maxValue: 40, count: 2 }
1609
+ // }
1610
+ ```
1611
+
1612
+ **Parameters:**
1613
+ - `tree`: Tree-structured data
1614
+ - `options`: Aggregation options
1615
+ - `groupBy`: Grouping function that receives a node and returns a group key
1616
+ - `aggregations`: Aggregation configuration object, key is result field name, value is aggregation config
1617
+ - `operation`: Aggregation operation type ('sum' | 'avg' | 'max' | 'min' | 'count')
1618
+ - `field`: Field name to aggregate (not needed for count operation)
1619
+ - `fieldNames`: Custom field name configuration (optional)
1620
+
1621
+ **Notes:**
1622
+ - Supports multiple aggregation operations simultaneously
1623
+ - Recursively processes all levels of nodes
1624
+ - Count operation counts node quantity, doesn't need field parameter
1625
+
1626
+ ---
1627
+
1628
+ ### groupTree
1629
+
1630
+ Group tree-structured data by field, returns node arrays grouped by field value.
1631
+
1632
+ ```javascript
1633
+ const tree = [
1634
+ { id: 1, category: 'A' },
1635
+ { id: 2, category: 'A' },
1636
+ { id: 3, category: 'B', children: [{ id: 4, category: 'B' }] }
1637
+ ]
1638
+
1639
+ // Group by category field
1640
+ const grouped = t.groupTree(tree, 'category')
1641
+ console.log(grouped)
1642
+ // {
1643
+ // 'A': [{ id: 1, category: 'A' }, { id: 2, category: 'A' }],
1644
+ // 'B': [{ id: 3, category: 'B' }, { id: 4, category: 'B' }]
1645
+ // }
1646
+ ```
1647
+
1648
+ **Parameters:**
1649
+ - `tree`: Tree-structured data
1650
+ - `field`: Grouping field name
1651
+ - `fieldNames`: Custom field name configuration (optional)
1652
+
1653
+ **Notes:**
1654
+ - Returns references to original nodes, not deep copies
1655
+ - Recursively processes all levels of nodes
1656
+
1657
+ ---
1658
+
1659
+ ### groupByTree
1660
+
1661
+ Group tree-structured data by condition, uses custom function to determine group key.
1662
+
1663
+ ```javascript
1664
+ const tree = [
1665
+ { id: 1, value: 10 },
1666
+ { id: 2, value: 20 },
1667
+ { id: 3, value: 10, children: [{ id: 4, value: 30 }] }
1668
+ ]
1669
+
1670
+ // Group by whether value is >= 20
1671
+ const grouped = t.groupByTree(tree, node => node.value >= 20 ? 'high' : 'low')
1672
+ console.log(grouped)
1673
+ // {
1674
+ // 'low': [{ id: 1, value: 10 }, { id: 3, value: 10 }],
1675
+ // 'high': [{ id: 2, value: 20 }, { id: 4, value: 30 }]
1676
+ // }
1677
+ ```
1678
+
1679
+ **Parameters:**
1680
+ - `tree`: Tree-structured data
1681
+ - `groupFn`: Grouping function that receives a node and returns a group key
1682
+ - `fieldNames`: Custom field name configuration (optional)
1683
+
1684
+ **Notes:**
1685
+ - Group keys are converted to strings
1686
+ - Returns references to original nodes, not deep copies
1687
+
1688
+ ---
1689
+
1690
+ ### sumTree
1691
+
1692
+ Calculate the sum of a field in tree-structured data.
1693
+
1694
+ ```javascript
1695
+ const tree = [
1696
+ { id: 1, value: 10 },
1697
+ { id: 2, value: 20, children: [{ id: 3, value: 30 }] }
1698
+ ]
1699
+
1700
+ // Calculate sum of value field
1701
+ const total = t.sumTree(tree, 'value')
1702
+ console.log(total) // 60
1703
+ ```
1704
+
1705
+ **Parameters:**
1706
+ - `tree`: Tree-structured data
1707
+ - `field`: Field name
1708
+ - `fieldNames`: Custom field name configuration (optional)
1709
+
1710
+ **Notes:**
1711
+ - Missing or null/undefined values are treated as 0
1712
+ - Recursively processes all levels of nodes
1713
+
1714
+ ---
1715
+
1716
+ ### avgTree
1717
+
1718
+ Calculate the average value of a field in tree-structured data.
1719
+
1720
+ ```javascript
1721
+ const tree = [
1722
+ { id: 1, value: 10 },
1723
+ { id: 2, value: 20 },
1724
+ { id: 3, value: 30 }
1725
+ ]
1726
+
1727
+ // Calculate average of value field
1728
+ const average = t.avgTree(tree, 'value')
1729
+ console.log(average) // 20
1730
+ ```
1731
+
1732
+ **Parameters:**
1733
+ - `tree`: Tree-structured data
1734
+ - `field`: Field name
1735
+ - `fieldNames`: Custom field name configuration (optional)
1736
+
1737
+ **Notes:**
1738
+ - Ignores null and undefined values
1739
+ - Returns 0 if all values are null/undefined
1740
+
1741
+ ---
1742
+
1743
+ ### maxTree
1744
+
1745
+ Get the maximum value of a field in tree-structured data.
1746
+
1747
+ ```javascript
1748
+ const tree = [
1749
+ { id: 1, value: 10 },
1750
+ { id: 2, value: 30 },
1751
+ { id: 3, value: 20 }
1752
+ ]
1753
+
1754
+ // Get maximum value of value field
1755
+ const max = t.maxTree(tree, 'value')
1756
+ console.log(max) // 30
1757
+ ```
1758
+
1759
+ **Parameters:**
1760
+ - `tree`: Tree-structured data
1761
+ - `field`: Field name
1762
+ - `fieldNames`: Custom field name configuration (optional)
1763
+
1764
+ **Notes:**
1765
+ - Only processes numeric values
1766
+ - Returns null if tree is empty or has no valid values
1767
+
1768
+ ---
1769
+
1770
+ ### minTree
1771
+
1772
+ Get the minimum value of a field in tree-structured data.
1773
+
1774
+ ```javascript
1775
+ const tree = [
1776
+ { id: 1, value: 30 },
1777
+ { id: 2, value: 10 },
1778
+ { id: 3, value: 20 }
1779
+ ]
1780
+
1781
+ // Get minimum value of value field
1782
+ const min = t.minTree(tree, 'value')
1783
+ console.log(min) // 10
1784
+ ```
1785
+
1786
+ **Parameters:**
1787
+ - `tree`: Tree-structured data
1788
+ - `field`: Field name
1789
+ - `fieldNames`: Custom field name configuration (optional)
1790
+
1791
+ **Notes:**
1792
+ - Only processes numeric values
1793
+ - Returns null if tree is empty or has no valid values
1794
+
1795
+ ---
1796
+
1797
+ ### countTree
1798
+
1799
+ Count the number of nodes in tree-structured data that meet a condition.
1800
+
1801
+ ```javascript
1802
+ const tree = [
1803
+ { id: 1, value: 10 },
1804
+ { id: 2, value: 20 },
1805
+ { id: 3, value: 10, children: [{ id: 4, value: 30 }] }
1806
+ ]
1807
+
1808
+ // Count all nodes
1809
+ const total = t.countTree(tree)
1810
+ console.log(total) // 4
1811
+
1812
+ // Count nodes that meet condition
1813
+ const count = t.countTree(tree, node => node.value === 10)
1814
+ console.log(count) // 2
1815
+ ```
1816
+
1817
+ **Parameters:**
1818
+ - `tree`: Tree-structured data
1819
+ - `conditionFn`: Condition function (optional), if not provided counts all nodes
1820
+ - `fieldNames`: Custom field name configuration (optional)
1821
+
1822
+ **Notes:**
1823
+ - Counts all nodes if condition function is not provided
1824
+ - Recursively processes all levels of nodes
1825
+
1826
+ ---
1827
+
1828
+ ### getTreeStats
1829
+
1830
+ Get comprehensive statistics of tree-structured data.
1831
+
1832
+ ```javascript
1833
+ const tree = [
1834
+ { id: 1, children: [{ id: 2 }, { id: 3, children: [{ id: 4 }] }] }
1835
+ ]
1836
+
1837
+ // Get statistics
1838
+ const stats = t.getTreeStats(tree)
1839
+ console.log(stats)
1840
+ // {
1841
+ // totalNodes: 4, // Total number of nodes
1842
+ // leafNodes: 2, // Number of leaf nodes
1843
+ // maxDepth: 3, // Maximum depth
1844
+ // minDepth: 1, // Minimum depth
1845
+ // avgDepth: 2, // Average depth
1846
+ // levels: 3 // Number of levels (equals maxDepth)
1847
+ // }
1848
+ ```
1849
+
1850
+ **Parameters:**
1851
+ - `tree`: Tree-structured data
1852
+ - `fieldNames`: Custom field name configuration (optional)
1853
+
1854
+ **Notes:**
1855
+ - Returns complete statistics object
1856
+ - Empty tree returns statistics with all values as 0
1857
+
1858
+ ---
1859
+
1860
+ ### analyzeTree
1861
+
1862
+ Comprehensively analyze tree-structured data, providing detailed statistics, distribution, balance analysis, and more.
1863
+
1864
+ ```javascript
1865
+ const tree = [
1866
+ { id: 1, children: [{ id: 2 }, { id: 3, children: [{ id: 4 }] }] }
1867
+ ]
1868
+
1869
+ // Comprehensive tree structure analysis
1870
+ const analysis = t.analyzeTree(tree)
1871
+ console.log(analysis)
1872
+ // {
1873
+ // // Basic Statistics
1874
+ // totalNodes: 4, // Total number of nodes
1875
+ // leafNodes: 2, // Number of leaf nodes
1876
+ // internalNodes: 2, // Number of internal nodes
1877
+ // maxDepth: 3, // Maximum depth
1878
+ // minDepth: 1, // Minimum depth
1879
+ // avgDepth: 2, // Average depth
1880
+ // levels: 3, // Number of levels
1881
+ //
1882
+ // // Level Analysis
1883
+ // byLevel: { 0: 1, 1: 2, 2: 1 }, // Node count by level
1884
+ // maxWidth: 2, // Maximum width (max nodes in a single level)
1885
+ // avgWidth: 1.33, // Average width
1886
+ // widthByLevel: { 0: 1, 1: 2, 2: 1 }, // Width per level
1887
+ //
1888
+ // // Branching Factor Analysis
1889
+ // avgBranchingFactor: 1.5, // Average branching factor (avg children per node)
1890
+ // maxBranchingFactor: 2, // Maximum branching factor
1891
+ // minBranchingFactor: 1, // Minimum branching factor
1892
+ // branchingFactorDistribution: { 1: 1, 2: 1 }, // Branching factor distribution
1893
+ //
1894
+ // // Depth Distribution
1895
+ // depthDistribution: { 1: 1, 2: 2, 3: 1 }, // Node count by depth
1896
+ //
1897
+ // // Balance Analysis
1898
+ // depthVariance: 0.5, // Depth variance (smaller = more balanced)
1899
+ // isBalanced: true, // Whether tree is balanced
1900
+ // balanceRatio: 0.33, // Balance ratio (minDepth/maxDepth)
1901
+ //
1902
+ // // Path Analysis
1903
+ // avgPathLength: 2.25, // Average path length
1904
+ // maxPathLength: 3, // Maximum path length
1905
+ // minPathLength: 1, // Minimum path length
1906
+ //
1907
+ // // Leaf Node Analysis
1908
+ // leafNodeRatio: 0.5, // Leaf node ratio
1909
+ // leafNodesByLevel: { 2: 1, 3: 1 } // Leaf nodes per level
1910
+ // }
1911
+ ```
1912
+
1913
+ **Parameters:**
1914
+ - `tree`: Tree-structured data
1915
+ - `options`: Analysis options (optional), can specify which statistics to calculate, defaults to calculating all statistics
1916
+ - `includeBasic`: Whether to include basic statistics (totalNodes, leafNodes, internalNodes, maxDepth, minDepth, avgDepth, levels), default `true`
1917
+ - `includeLevelAnalysis`: Whether to include level analysis (byLevel, maxWidth, avgWidth, widthByLevel), default `true`
1918
+ - `includeBranchingFactor`: Whether to include branching factor analysis (avgBranchingFactor, maxBranchingFactor, minBranchingFactor, branchingFactorDistribution), default `true`
1919
+ - `includeDepthDistribution`: Whether to include depth distribution (depthDistribution), default `true`
1920
+ - `includeBalanceAnalysis`: Whether to include balance analysis (depthVariance, isBalanced, balanceRatio), default `true`
1921
+ - `includePathAnalysis`: Whether to include path analysis (avgPathLength, maxPathLength, minPathLength), default `true`
1922
+ - `includeLeafAnalysis`: Whether to include leaf node analysis (leafNodeRatio, leafNodesByLevel), default `true`
1923
+ - `fieldNames`: Custom field name configuration (optional)
1924
+
1925
+ ```javascript
1926
+ // Calculate only basic statistics and branching factor (performance optimization)
1927
+ const quickAnalysis = t.analyzeTree(tree, {
1928
+ includeBasic: true,
1929
+ includeBranchingFactor: true,
1930
+ includeLevelAnalysis: false,
1931
+ includeDepthDistribution: false,
1932
+ includeBalanceAnalysis: false,
1933
+ includePathAnalysis: false,
1934
+ includeLeafAnalysis: false,
1935
+ })
1936
+ console.log(quickAnalysis.totalNodes) // 4
1937
+ console.log(quickAnalysis.maxBranchingFactor) // 2
1938
+ console.log(quickAnalysis.byLevel) // {} (not calculated)
1939
+
1940
+ // Calculate only balance analysis
1941
+ const balanceAnalysis = t.analyzeTree(tree, {
1942
+ includeBasic: true,
1943
+ includeBalanceAnalysis: true,
1944
+ includeLevelAnalysis: false,
1945
+ includeBranchingFactor: false,
1946
+ includeDepthDistribution: false,
1947
+ includePathAnalysis: false,
1948
+ includeLeafAnalysis: false,
1949
+ })
1950
+ console.log(balanceAnalysis.isBalanced) // true/false
1951
+ console.log(balanceAnalysis.depthVariance) // 0.5
1952
+ ```
1953
+
1954
+ **Analysis information includes:**
1955
+
1956
+ 1. **Basic Statistics**: Total nodes, leaf nodes, internal nodes, depth information, etc.
1957
+ 2. **Level Analysis**: Nodes per level, maximum width, average width, etc.
1958
+ 3. **Branching Factor Analysis**: Average/max/min branching factor, branching factor distribution, etc.
1959
+ 4. **Depth Distribution**: Number of nodes at each depth
1960
+ 5. **Balance Analysis**: Depth variance, whether balanced, balance ratio, etc.
1961
+ 6. **Path Analysis**: Average/max/min path length
1962
+ 7. **Leaf Node Analysis**: Leaf node ratio, leaf nodes per level
1963
+
1964
+ **Notes:**
1965
+ - Provides comprehensive tree structure analysis, suitable for performance optimization, structure evaluation, etc.
1966
+ - `isBalanced` is determined based on depth variance and depth range. A tree is considered balanced if depth variance < 2 and depth range ≤ 2
1967
+ - `balanceRatio` closer to 1 indicates a more balanced tree
1968
+ - **Performance Optimization**: Use the `options` parameter to calculate only the needed statistics, which can significantly improve performance for large tree structures
1969
+
1970
+ ---
1971
+
1252
1972
  ## Custom Field Names
1253
1973
 
1254
1974
  All methods support custom property names for children and id through the last parameter, passing in a configuration object:
@@ -1269,16 +1989,16 @@ const foundNode2 = t.findTree(customTreeData, (node) => node.nodeId === 2, field
1269
1989
  ### Run Tests
1270
1990
 
1271
1991
  ```bash
1272
- # Run all tests (automatically build then test source + bundled files, 656 test cases)
1992
+ # Run all tests (automatically build then test source + bundled files, 712 test cases)
1273
1993
  npm test
1274
1994
 
1275
1995
  # Run all tests (once, don't watch for file changes)
1276
1996
  npm test -- --run
1277
1997
 
1278
- # Test source code only (328 test cases)
1998
+ # Test source code only (447 test cases)
1279
1999
  npm run test:src
1280
2000
 
1281
- # Test bundled files only (328 test cases, requires npm run build first)
2001
+ # Test bundled files only (447 test cases, requires npm run build first)
1282
2002
  npm run test:dist
1283
2003
 
1284
2004
  # Run tests and generate coverage report