tree-processor 0.8.1 → 0.8.2

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,1026 @@
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](https://img.shields.io/npm/dm/tree-processor?style=flat-square)
13
+ ![bundle size](https://img.shields.io/badge/bundle-6.6KB-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 **6-7 KB** (ESM: 6.39 KB, CJS: 6.64 KB, UMD: 6.68 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
+ - [Query Methods](#query-methods)
34
+ - [Validation Methods](#validation-methods)
35
+ - [Custom Field Names](#custom-field-names)
36
+ - [Testing](#testing)
37
+ - [Development](#development)
38
+
39
+ ## ✨ Features
40
+
41
+ - **Lightweight** - Each format bundle size is only 6-7 KB (ESM: 6.39 KB, CJS: 6.64 KB, UMD: 6.68 KB), minimal impact on project size
42
+ - **Tree-shaking Support** - Supports on-demand imports, only bundles the code you actually use, further reducing bundle size
43
+ - **Full TypeScript Support** - Provides complete type definitions and IntelliSense, improving development experience
44
+ - **Flexible Custom Field Names** - Supports custom children and id field names, adapting to various data structures
45
+ - **Zero Dependencies** - No external dependencies, ready to use out of the box, no need to worry about dependency conflicts
46
+ - **Comprehensive Test Coverage** - Contains 290 test cases with 99%+ test coverage (99.67% statement coverage, 99.32% branch coverage, 100% function coverage), covering basic functionality, edge cases, error handling, and complex scenarios
47
+ - **Rich API** - Provides 30+ 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, etc.), covering complete scenarios for traversal, search, modification, and validation
48
+
49
+ **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. The last parameter of each method can customize the property names for children and id.
50
+
51
+ ### 💡 Use Cases
52
+
53
+ - **Navigation Systems** - Multi-level menus, route configuration expansion, collapse, search, and filtering
54
+ - **File Systems** - File directory traversal, search, move, and delete
55
+ - **Permission Systems** - Organizational structure, role permission tree structure management and validation
56
+ - **Framework Development** - Component trees, route trees, and other tree structure construction and management
57
+ - **Data Management** - Category management, comment systems, tree selectors, and other data operations
58
+
59
+ ## 📦 Installation
60
+
61
+ ```bash
62
+ npm install tree-processor
63
+ # or
64
+ yarn add tree-processor
65
+ # or
66
+ pnpm add tree-processor
67
+ ```
68
+
69
+ ## 🚀 Quick Start
70
+
71
+ ```javascript
72
+ import { mapTree, findTree, filterTree } from 'tree-processor'
73
+
74
+ const treeData = [
75
+ {
76
+ id: 1,
77
+ name: 'node1',
78
+ children: [
79
+ { id: 2, name: 'node2' },
80
+ { id: 3, name: 'node3' },
81
+ ],
82
+ },
83
+ ]
84
+
85
+ // Get all node names
86
+ const names = mapTree(treeData, (node) => node.name)
87
+ console.log(names) // ['node1', 'node2', 'node3']
88
+
89
+ // Find a node
90
+ const node = findTree(treeData, (n) => n.id === 2)
91
+ console.log(node) // { id: 2, name: 'node2' }
92
+
93
+ // Filter nodes
94
+ const filtered = filterTree(treeData, (n) => n.id > 1)
95
+ console.log(filtered) // [{ id: 2, name: 'node2' }, { id: 3, name: 'node3' }]
96
+ ```
97
+
98
+ ## 📖 API Documentation
99
+
100
+ ### Import Methods
101
+
102
+ #### Default Import (Recommended for scenarios requiring multiple methods)
103
+
104
+ ```javascript
105
+ // ES Module
106
+ import t from 'tree-processor'
107
+
108
+ // CommonJS
109
+ const t = require('tree-processor')
110
+ ```
111
+
112
+ #### Named Import (Recommended for scenarios requiring only a few methods, supports tree-shaking)
113
+
114
+ ```javascript
115
+ // ES Module - Named import of individual methods
116
+ import { mapTree, filterTree, findTree } from 'tree-processor'
117
+
118
+ // ES Module - Import types
119
+ import type { TreeNode, TreeData, FieldNames } from 'tree-processor'
120
+
121
+ // CommonJS - Named import
122
+ const { mapTree, filterTree } = require('tree-processor')
123
+ ```
124
+
125
+ ### Sample Data
126
+
127
+ The following sample data will be used for demonstrations of all subsequent methods:
128
+
129
+ ```javascript
130
+ const treeData = [
131
+ {
132
+ id: 1,
133
+ name: 'node1',
134
+ children: [
135
+ {
136
+ id: 2,
137
+ name: 'node2',
138
+ children: [
139
+ { id: 4, name: 'node4' },
140
+ { id: 5, name: 'node5' },
141
+ ],
142
+ },
143
+ {
144
+ id: 3,
145
+ name: 'node3',
146
+ children: [{ id: 6, name: 'node6' }],
147
+ },
148
+ ],
149
+ },
150
+ ];
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Traversal Methods
156
+
157
+ ### mapTree
158
+
159
+ Traverse tree-structured data and execute a callback function for each node, returning a mapped array.
160
+
161
+ ```javascript
162
+ // Get all node names
163
+ const nodeNames = t.mapTree(treeData, (node) => node.name)
164
+ console.log(nodeNames) // ['node1', 'node2', 'node4', 'node5', 'node3', 'node6']
165
+
166
+ // Get all node IDs
167
+ const nodeIds = t.mapTree(treeData, (node) => node.id)
168
+ console.log(nodeIds) // [1, 2, 4, 5, 3, 6]
169
+
170
+ // Modify node data
171
+ const modifiedNodes = t.mapTree(treeData, (node) => ({
172
+ ...node,
173
+ label: node.name
174
+ }))
175
+ console.log(modifiedNodes) // Returns a new array containing the label field
176
+ ```
177
+
178
+ ### forEachTree
179
+
180
+ 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.
181
+
182
+ ```javascript
183
+ // Traverse all nodes and print
184
+ t.forEachTree(treeData, (node) => {
185
+ console.log(node)
186
+ })
187
+
188
+ // Modify node properties
189
+ t.forEachTree(treeData, (node) => {
190
+ node.visited = true
191
+ node.timestamp = Date.now()
192
+ })
193
+
194
+ // Count nodes
195
+ let nodeCount = 0
196
+ t.forEachTree(treeData, () => {
197
+ nodeCount++
198
+ })
199
+ console.log(nodeCount) // Total number of nodes
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Search Methods
205
+
206
+ ### filterTree
207
+
208
+ Filter tree-structured data and return nodes that meet the conditions.
209
+
210
+ ```javascript
211
+ // Filter nodes whose names contain 'node'
212
+ const filteredNodes = t.filterTree(treeData, (node) => {
213
+ return node.name.includes('node')
214
+ })
215
+ console.log(filteredNodes) // Returns an array of nodes that meet the conditions
216
+
217
+ // Filter nodes with ID greater than 2
218
+ const nodesWithLargeId = t.filterTree(treeData, (node) => node.id > 2)
219
+ console.log(nodesWithLargeId) // Returns an array of nodes with ID greater than 2
220
+
221
+ // Filter nodes without children (leaf nodes)
222
+ const leafNodes = t.filterTree(treeData, (node) => {
223
+ return !node.children || node.children.length === 0
224
+ })
225
+ console.log(leafNodes) // Returns all leaf nodes
226
+ ```
227
+
228
+ ### findTree
229
+
230
+ Find the first node in tree-structured data that meets the condition. Returns null if not found.
231
+
232
+ ```javascript
233
+ // Find node with ID 2
234
+ const foundNode = t.findTree(treeData, (node) => node.id === 2)
235
+ console.log(foundNode) // Returns the found node object, or null if not found
236
+
237
+ // Find node with name 'node3'
238
+ const node3 = t.findTree(treeData, (node) => node.name === 'node3')
239
+ console.log(node3) // { id: 3, name: 'node3', children: [...] }
240
+
241
+ // Find a non-existent node
242
+ const nodeNotFound = t.findTree(treeData, (node) => node.id === 999)
243
+ console.log(nodeNotFound) // null
244
+ ```
245
+
246
+ ### includesTree
247
+
248
+ Check if tree-structured data contains a node with the specified ID.
249
+
250
+ ```javascript
251
+ const nodeId = 2
252
+ const hasNode = t.includesTree(treeData, nodeId)
253
+
254
+ console.log(hasNode) // true means it contains the node, false means it doesn't
255
+ ```
256
+
257
+ ### someTree
258
+
259
+ Check if there exists a node in tree-structured data that meets the condition. Returns true if at least one node meets the condition.
260
+
261
+ ```javascript
262
+ // Check if there exists a node with name 'node2'
263
+ const hasNode2 = t.someTree(treeData, node => node.name === 'node2')
264
+ console.log(hasNode2) // true
265
+
266
+ // Check if there exists a node with ID greater than 10
267
+ const hasLargeId = t.someTree(treeData, node => node.id > 10)
268
+ console.log(hasLargeId) // false
269
+ ```
270
+
271
+ ### everyTree
272
+
273
+ Check if all nodes in tree-structured data meet the condition. Returns true only if all nodes meet the condition.
274
+
275
+ ```javascript
276
+ // Check if all node IDs are greater than 0
277
+ const allIdsPositive = t.everyTree(treeData, node => node.id > 0)
278
+ console.log(allIdsPositive) // true
279
+
280
+ // Check if all nodes have a name property
281
+ const allHaveName = t.everyTree(treeData, node => node.name)
282
+ console.log(allHaveName) // Returns true or false based on actual data
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Access Methods
288
+
289
+ ### atTree
290
+
291
+ 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.
292
+
293
+ ```javascript
294
+ // Get the first child node (index 0) of the node with ID 1
295
+ const firstChildNode = t.atTree(treeData, 1, 0)
296
+ console.log(firstChildNode) // Returns the first child node
297
+
298
+ // Get the last child node (negative index)
299
+ const lastChildNode = t.atTree(treeData, 1, -1)
300
+ console.log(lastChildNode) // Returns the last child node
301
+
302
+ // Index out of range returns null
303
+ const nodeNotFound = t.atTree(treeData, 1, 10)
304
+ console.log(nodeNotFound) // null
305
+ ```
306
+
307
+ ### indexOfTree
308
+
309
+ 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.
310
+
311
+ ```javascript
312
+ // Get the index path of the node with ID 4
313
+ const nodePath = t.indexOfTree(treeData, 4)
314
+ console.log(nodePath) // [0, 0, 0] means root node -> first child -> first child
315
+
316
+ // Node not found returns null
317
+ const pathNotFound = t.indexOfTree(treeData, 999)
318
+ console.log(pathNotFound) // null
319
+
320
+ // Use with atIndexOfTree
321
+ const indexPath = t.indexOfTree(treeData, 4)
322
+ const nodeByPath = t.atIndexOfTree(treeData, indexPath)
323
+ console.log(nodeByPath) // Gets the node with ID 4
324
+ ```
325
+
326
+ ### atIndexOfTree
327
+
328
+ Get a node by index path. Returns null if the path is invalid or out of range.
329
+
330
+ ```javascript
331
+ // Get node by index path
332
+ const nodeByIndexPath = t.atIndexOfTree(treeData, [0, 1, 0])
333
+ console.log(nodeByIndexPath) // Returns the node object at the corresponding path
334
+
335
+ // Use with indexOfTree
336
+ const targetPath = t.indexOfTree(treeData, 4)
337
+ const targetNode = t.atIndexOfTree(treeData, targetPath)
338
+ console.log(targetNode) // Gets the node with ID 4
339
+
340
+ // Invalid path returns null
341
+ const invalidPath = t.atIndexOfTree(treeData, [999])
342
+ console.log(invalidPath) // null
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Modification Methods
348
+
349
+ ### pushTree
350
+
351
+ Add a child node to the end under the specified node. Returns true if successful, false if the target node is not found.
352
+
353
+ ```javascript
354
+ // Add a new child node under the node with ID 1
355
+ const addSuccess = t.pushTree(treeData, 1, { id: 7, name: 'node7' })
356
+ console.log(addSuccess) // true
357
+ console.log(treeData) // New node has been added to the end of the children array
358
+
359
+ // Try to add under a non-existent node
360
+ const addFailed = t.pushTree(treeData, 999, { id: 8, name: 'node8' })
361
+ console.log(addFailed) // false, target node not found
362
+ ```
363
+
364
+ ### unshiftTree
365
+
366
+ Add a child node to the beginning under the specified node. Returns true if successful, false if the target node is not found.
367
+
368
+ ```javascript
369
+ // Add a new child node to the beginning under the node with ID 1
370
+ const unshiftSuccess = t.unshiftTree(treeData, 1, { id: 7, name: 'node7' })
371
+ console.log(unshiftSuccess) // true
372
+ console.log(treeData) // New node has been added to the beginning of the children array
373
+ ```
374
+
375
+ ### popTree
376
+
377
+ 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.
378
+
379
+ ```javascript
380
+ // Remove the last child node under the node with ID 1
381
+ const removedNode = t.popTree(treeData, 1)
382
+ console.log(removedNode) // Returns the removed node object, or false
383
+
384
+ // Try to remove from a non-existent node
385
+ const popFailed = t.popTree(treeData, 999)
386
+ console.log(popFailed) // false
387
+ ```
388
+
389
+ ### shiftTree
390
+
391
+ 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.
392
+
393
+ ```javascript
394
+ // Remove the first child node under the node with ID 1
395
+ const shiftedNode = t.shiftTree(treeData, 1)
396
+ console.log(shiftedNode) // Returns the removed node object, or false
397
+ ```
398
+
399
+ ### removeTree
400
+
401
+ Remove the node with the specified ID from tree-structured data, including root nodes and child nodes.
402
+
403
+ ```javascript
404
+ const nodeIdToRemove = 2
405
+ const removeSuccess = t.removeTree(treeData, nodeIdToRemove)
406
+
407
+ console.log(removeSuccess) // true means successful removal, false means node not found
408
+ console.log(treeData) // Tree structure after removal
409
+ ```
410
+
411
+ ### dedupTree
412
+
413
+ Tree-structured object array deduplication method that removes duplicate nodes based on the specified key. Keeps the first occurrence of the node.
414
+
415
+ ```javascript
416
+ // Deduplicate based on id field
417
+ const uniqueTreeData = t.dedupTree(treeData, 'id')
418
+ console.log(uniqueTreeData) // Returns deduplicated tree-structured data
419
+
420
+ // Deduplicate based on name field
421
+ const uniqueByNameTree = t.dedupTree(treeData, 'name')
422
+ console.log(uniqueByNameTree) // Returns data deduplicated by name
423
+ ```
424
+
425
+ ---
426
+
427
+ ## Query Methods
428
+
429
+ ### getParentTree
430
+
431
+ Get the parent node of the specified node. Returns null if the node is a root node or not found.
432
+
433
+ ```javascript
434
+ // Get the parent node of the node with ID 2
435
+ const parentNode = t.getParentTree(treeData, 2)
436
+ console.log(parentNode) // Returns the parent node object { id: 1, name: 'node1', ... }
437
+
438
+ // Root nodes have no parent, returns null
439
+ const rootParentNode = t.getParentTree(treeData, 1)
440
+ console.log(rootParentNode) // null
441
+
442
+ // Node not found returns null
443
+ const parentNotFound = t.getParentTree(treeData, 999)
444
+ console.log(parentNotFound) // null
445
+ ```
446
+
447
+ ### getChildrenTree
448
+
449
+ Get all direct child nodes of the specified node. Returns an empty array if the node is not found or has no children.
450
+
451
+ ```javascript
452
+ // Get all child nodes of the node with ID 1
453
+ const children = t.getChildrenTree(treeData, 1)
454
+ console.log(children) // Returns child node array [{ id: 2, ... }, { id: 3, ... }]
455
+
456
+ // Node has no children, returns empty array
457
+ const emptyChildren = t.getChildrenTree(treeData, 4)
458
+ console.log(emptyChildren) // []
459
+
460
+ // Node not found returns empty array
461
+ const notFound = t.getChildrenTree(treeData, 999)
462
+ console.log(notFound) // []
463
+
464
+ // Support custom field names
465
+ const customTree = [
466
+ {
467
+ nodeId: 1,
468
+ name: 'root',
469
+ subNodes: [
470
+ { nodeId: 2, name: 'child1' },
471
+ { nodeId: 3, name: 'child2' },
472
+ ],
473
+ },
474
+ ];
475
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
476
+ const customChildren = t.getChildrenTree(customTree, 1, fieldNames)
477
+ console.log(customChildren) // Returns child node array
478
+ ```
479
+
480
+ ### getSiblingsTree
481
+
482
+ 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.
483
+
484
+ ```javascript
485
+ // Get all sibling nodes (including itself) of the node with ID 2
486
+ const siblings = t.getSiblingsTree(treeData, 2)
487
+ console.log(siblings) // Returns sibling node array [{ id: 2, ... }, { id: 3, ... }]
488
+
489
+ // Sibling nodes of root nodes are other root nodes
490
+ const multiRoot = [
491
+ { id: 1, children: [{ id: 2 }] },
492
+ { id: 3, children: [{ id: 4 }] },
493
+ ];
494
+ const rootSiblings = t.getSiblingsTree(multiRoot, 1)
495
+ console.log(rootSiblings) // Returns all root nodes [{ id: 1, ... }, { id: 3, ... }]
496
+
497
+ // Node not found returns empty array
498
+ const notFound = t.getSiblingsTree(treeData, 999)
499
+ console.log(notFound) // []
500
+
501
+ // Support custom field names
502
+ const customTree = [
503
+ {
504
+ nodeId: 1,
505
+ name: 'root',
506
+ subNodes: [
507
+ { nodeId: 2, name: 'child1' },
508
+ { nodeId: 3, name: 'child2' },
509
+ { nodeId: 4, name: 'child3' },
510
+ ],
511
+ },
512
+ ];
513
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
514
+ const customSiblings = t.getSiblingsTree(customTree, 2, fieldNames)
515
+ console.log(customSiblings) // Returns sibling node array (including itself)
516
+ ```
517
+
518
+ ### getNodeDepthMap
519
+
520
+ 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.
521
+
522
+ ```javascript
523
+ // Get depth map of all nodes
524
+ const nodeDepthMap = t.getNodeDepthMap(treeData)
525
+ console.log(nodeDepthMap) // { 1: 1, 2: 2, 3: 2, 4: 3, 5: 3, 6: 3 }
526
+
527
+ // Get depth of a specific node
528
+ const node2Depth = nodeDepthMap[2]
529
+ console.log(node2Depth) // 2
530
+
531
+ // Empty tree returns empty object
532
+ const emptyDepthMap = t.getNodeDepthMap([])
533
+ console.log(emptyDepthMap) // {}
534
+ ```
535
+
536
+ ### getNodeDepth
537
+
538
+ Get the depth of the specified node. Depth starts from 1, root nodes have depth 1.
539
+
540
+ ```javascript
541
+ // Get depth of root node
542
+ const rootDepth = t.getNodeDepth(treeData, 1)
543
+ console.log(rootDepth) // 1
544
+
545
+ // Get depth of child node
546
+ const childDepth = t.getNodeDepth(treeData, 2)
547
+ console.log(childDepth) // 2
548
+
549
+ // Get depth of deep node
550
+ const deepDepth = t.getNodeDepth(treeData, 4)
551
+ console.log(deepDepth) // 3
552
+
553
+ // Node not found returns null
554
+ const notFound = t.getNodeDepth(treeData, 999)
555
+ console.log(notFound) // null
556
+
557
+ // Support custom field names
558
+ const customTree = [
559
+ {
560
+ nodeId: 1,
561
+ name: 'root',
562
+ subNodes: [
563
+ { nodeId: 2, name: 'child' },
564
+ ],
565
+ },
566
+ ];
567
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
568
+ const depth = t.getNodeDepth(customTree, 2, fieldNames)
569
+ console.log(depth) // 2
570
+ ```
571
+
572
+ **Difference from getNodeDepthMap:**
573
+ - `getNodeDepthMap` - Batch get depth of all nodes (calculates all nodes at once)
574
+ - `getNodeDepth` - Only get depth of a single node (only calculates the target node, more efficient)
575
+
576
+ ---
577
+
578
+ ## Validation Methods
579
+
580
+ ### isLeafNode
581
+
582
+ Check if a node is a leaf node (has no children). Lightweight method that only checks the node itself, doesn't traverse the tree.
583
+
584
+ ```javascript
585
+ // Nodes without children field are leaf nodes
586
+ const leafNode1 = { id: 1, name: 'node1' };
587
+ console.log(t.isLeafNode(leafNode1)) // true
588
+
589
+ // Nodes with empty children array are leaf nodes
590
+ const leafNode2 = { id: 2, name: 'node2', children: [] };
591
+ console.log(t.isLeafNode(leafNode2)) // true
592
+
593
+ // Nodes with children are not leaf nodes
594
+ const parentNode = {
595
+ id: 3,
596
+ name: 'node3',
597
+ children: [{ id: 4, name: 'node4' }],
598
+ };
599
+ console.log(t.isLeafNode(parentNode)) // false
600
+
601
+ // Use in filterTree (filter out all leaf nodes)
602
+ const leafNodes = t.filterTree(treeData, (node) => t.isLeafNode(node))
603
+ console.log(leafNodes) // Returns all leaf nodes
604
+
605
+ // Use in forEachTree
606
+ t.forEachTree(treeData, (node) => {
607
+ if (t.isLeafNode(node)) {
608
+ console.log('Leaf node:', node.name)
609
+ }
610
+ })
611
+
612
+ // Support custom field names
613
+ const customNode = {
614
+ nodeId: 1,
615
+ name: 'node1',
616
+ subNodes: [],
617
+ };
618
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
619
+ console.log(t.isLeafNode(customNode, fieldNames)) // true
620
+ ```
621
+
622
+ **Difference from existing methods:**
623
+ - `isLeafNode` - Only checks a single node, lightweight (O(1)), suitable for use during traversal
624
+ - `getChildrenTree` - Gets child node array, requires passing tree and nodeId, needs to find the node (O(n))
625
+
626
+ ### isRootNode
627
+
628
+ Check if a node is a root node (has no parent). Root nodes are top-level nodes in the tree-structured data array.
629
+
630
+ ```javascript
631
+ // Check root node
632
+ const treeData = [
633
+ {
634
+ id: 1,
635
+ name: 'root1',
636
+ children: [{ id: 2, name: 'child1' }],
637
+ },
638
+ ];
639
+ console.log(t.isRootNode(treeData, 1)) // true
640
+ console.log(t.isRootNode(treeData, 2)) // false
641
+
642
+ // Multiple root nodes case
643
+ const multiRoot = [
644
+ { id: 1, name: 'root1' },
645
+ { id: 2, name: 'root2' },
646
+ { id: 3, name: 'root3' },
647
+ ];
648
+ console.log(t.isRootNode(multiRoot, 1)) // true
649
+ console.log(t.isRootNode(multiRoot, 2)) // true
650
+ console.log(t.isRootNode(multiRoot, 3)) // true
651
+
652
+ // Use during traversal
653
+ t.forEachTree(treeData, (node) => {
654
+ if (t.isRootNode(treeData, node.id)) {
655
+ console.log('Root node:', node.name)
656
+ }
657
+ })
658
+
659
+ // Support custom field names
660
+ const customTree = [
661
+ {
662
+ nodeId: 1,
663
+ name: 'root1',
664
+ subNodes: [{ nodeId: 2, name: 'child1' }],
665
+ },
666
+ ];
667
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
668
+ console.log(t.isRootNode(customTree, 1, fieldNames)) // true
669
+ console.log(t.isRootNode(customTree, 2, fieldNames)) // false
670
+
671
+ // Returns false when node doesn't exist
672
+ console.log(t.isRootNode(treeData, 999)) // false
673
+ ```
674
+
675
+ **Difference from existing methods:**
676
+ - `isRootNode` - Semantic method that directly returns boolean value
677
+ - `getParentTree` - Returns parent node object, need to check if it's null
678
+ - `getNodeDepth` - Returns depth, need to check if it equals 1
679
+
680
+ ### isEmptyTreeData
681
+
682
+ 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).
683
+
684
+ ```javascript
685
+ // Check if tree-structured data is empty
686
+ const isEmptyTree = t.isEmptyTreeData(treeData)
687
+ console.log(isEmptyTree) // false (has data)
688
+
689
+ // Empty array returns true
690
+ const isEmptyArray = t.isEmptyTreeData([])
691
+ console.log(isEmptyArray) // true
692
+
693
+ // null or undefined returns true
694
+ const isNullTree = t.isEmptyTreeData(null)
695
+ console.log(isNullTree) // true
696
+
697
+ // Support fieldNames parameter (maintains API consistency, but has no effect)
698
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
699
+ const isEmptyWithFieldNames = t.isEmptyTreeData(treeData, fieldNames)
700
+ console.log(isEmptyWithFieldNames) // false (same result as not passing fieldNames)
701
+ ```
702
+
703
+ ### isEmptySingleTreeData
704
+
705
+ 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.
706
+
707
+ ```javascript
708
+ // No children field, considered empty
709
+ const tree1 = { id: 1, name: 'node1' };
710
+ const isEmpty1 = t.isEmptySingleTreeData(tree1)
711
+ console.log(isEmpty1) // true
712
+
713
+ // children is empty array, considered empty
714
+ const tree2 = {
715
+ id: 1,
716
+ name: 'node1',
717
+ children: [],
718
+ };
719
+ const isEmpty2 = t.isEmptySingleTreeData(tree2)
720
+ console.log(isEmpty2) // true
721
+
722
+ // Has child nodes, not empty
723
+ const tree3 = {
724
+ id: 1,
725
+ name: 'node1',
726
+ children: [
727
+ { id: 2, name: 'node2' },
728
+ ],
729
+ };
730
+ const isEmpty3 = t.isEmptySingleTreeData(tree3)
731
+ console.log(isEmpty3) // false
732
+
733
+ // Has child nodes, even if child nodes themselves are empty, tree is not empty
734
+ const tree4 = {
735
+ id: 1,
736
+ name: 'node1',
737
+ children: [
738
+ { id: 2, name: 'node2', children: [] },
739
+ { id: 3, name: 'node3' }, // no children field
740
+ ],
741
+ };
742
+ const isEmpty4 = t.isEmptySingleTreeData(tree4)
743
+ console.log(isEmpty4) // false (because there are child nodes, even if they are empty)
744
+
745
+ // Support custom field names
746
+ const customTree = {
747
+ nodeId: 1,
748
+ name: 'node1',
749
+ subNodes: [],
750
+ };
751
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
752
+ const isEmptyCustom = t.isEmptySingleTreeData(customTree, fieldNames)
753
+ console.log(isEmptyCustom) // true
754
+ ```
755
+
756
+ ### isTreeData
757
+
758
+ 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.
759
+
760
+ ```javascript
761
+ // Valid tree-structured data (forest)
762
+ const forest = [
763
+ {
764
+ id: 1,
765
+ name: 'node1',
766
+ children: [{ id: 2, name: 'node2' }],
767
+ },
768
+ {
769
+ id: 3,
770
+ name: 'node3',
771
+ children: [{ id: 4, name: 'node4' }],
772
+ },
773
+ ];
774
+ console.log(t.isTreeData(forest)) // true
775
+
776
+ // Empty array is also valid tree-structured data (empty forest)
777
+ console.log(t.isTreeData([])) // true
778
+
779
+ // Single object is not tree-structured data (should use isSingleTreeData)
780
+ console.log(t.isTreeData({ id: 1 })) // false
781
+
782
+ // Array contains non-tree-structured elements, returns false
783
+ const invalidForest = [
784
+ { id: 1, children: [{ id: 2 }] },
785
+ 'not a tree', // invalid element
786
+ ];
787
+ console.log(t.isTreeData(invalidForest)) // false
788
+
789
+ // null or undefined are not valid tree-structured data
790
+ console.log(t.isTreeData(null)) // false
791
+ console.log(t.isTreeData(undefined)) // false
792
+
793
+ // Support custom field names
794
+ const customForest = [
795
+ {
796
+ nodeId: 1,
797
+ name: 'node1',
798
+ subNodes: [{ nodeId: 2, name: 'node2' }],
799
+ },
800
+ ];
801
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
802
+ console.log(t.isTreeData(customForest, fieldNames)) // true
803
+ ```
804
+
805
+ ### isSingleTreeData
806
+
807
+ 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.
808
+
809
+ ```javascript
810
+ // Valid single tree-structured data
811
+ const tree = {
812
+ id: 1,
813
+ name: 'node1',
814
+ children: [
815
+ { id: 2, name: 'node2' },
816
+ { id: 3, name: 'node3' },
817
+ ],
818
+ };
819
+ const isValid = t.isSingleTreeData(tree)
820
+ console.log(isValid) // true
821
+
822
+ // No children field is also valid (only root node)
823
+ const singleNode = { id: 1, name: 'node1' }
824
+ console.log(t.isSingleTreeData(singleNode)) // true
825
+
826
+ // Array is not single tree-structured data
827
+ console.log(t.isSingleTreeData([])) // false
828
+
829
+ // null or undefined are not valid tree-structured data
830
+ console.log(t.isSingleTreeData(null)) // false
831
+ console.log(t.isSingleTreeData(undefined)) // false
832
+
833
+ // children cannot be null
834
+ const invalidTree = { id: 1, children: null }
835
+ console.log(t.isSingleTreeData(invalidTree)) // false
836
+
837
+ // Support custom field names
838
+ const customTree = {
839
+ nodeId: 1,
840
+ name: 'node1',
841
+ subNodes: [{ nodeId: 2, name: 'node2' }],
842
+ };
843
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
844
+ console.log(t.isSingleTreeData(customTree, fieldNames)) // true
845
+ ```
846
+
847
+ ### isValidTreeNode
848
+
849
+ 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.
850
+
851
+ ```javascript
852
+ // Valid tree node (has children array)
853
+ const node1 = {
854
+ id: 1,
855
+ name: 'node1',
856
+ children: [{ id: 2 }],
857
+ };
858
+ console.log(t.isValidTreeNode(node1)) // true
859
+
860
+ // Valid tree node (no children field)
861
+ const node2 = { id: 1, name: 'node1' };
862
+ console.log(t.isValidTreeNode(node2)) // true
863
+
864
+ // Invalid tree node (children is not an array)
865
+ const invalidNode = {
866
+ id: 1,
867
+ children: 'not an array',
868
+ };
869
+ console.log(t.isValidTreeNode(invalidNode)) // false
870
+
871
+ // Support custom field names
872
+ const customNode = {
873
+ nodeId: 1,
874
+ subNodes: [{ nodeId: 2 }],
875
+ };
876
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
877
+ console.log(t.isValidTreeNode(customNode, fieldNames)) // true
878
+ ```
879
+
880
+ **Difference from isSingleTreeData:**
881
+ - `isValidTreeNode` - Only checks the basic structure of a single node, doesn't recursively check child nodes (lightweight)
882
+ - `isSingleTreeData` - Recursively checks the entire tree structure, ensuring all child nodes are valid tree structures
883
+
884
+ ### isTreeNodeWithCircularCheck
885
+
886
+ 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.
887
+
888
+ ```javascript
889
+ // Valid tree node (no circular references)
890
+ const validNode = {
891
+ id: 1,
892
+ children: [
893
+ { id: 2, children: [{ id: 3 }] },
894
+ ],
895
+ };
896
+ console.log(t.isTreeNodeWithCircularCheck(validNode)) // true
897
+
898
+ // Detect circular references
899
+ const node1 = { id: 1, children: [] };
900
+ const node2 = { id: 2, children: [] };
901
+ node1.children.push(node2);
902
+ node2.children.push(node1); // circular reference
903
+ console.log(t.isTreeNodeWithCircularCheck(node1)) // false
904
+
905
+ // Detect self-reference
906
+ const selfRefNode = { id: 1, children: [] };
907
+ selfRefNode.children.push(selfRefNode); // self-reference
908
+ console.log(t.isTreeNodeWithCircularCheck(selfRefNode)) // false
909
+
910
+ // Support custom field names
911
+ const customNode = {
912
+ nodeId: 1,
913
+ subNodes: [{ nodeId: 2 }],
914
+ };
915
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
916
+ console.log(t.isTreeNodeWithCircularCheck(customNode, fieldNames)) // true
917
+ ```
918
+
919
+ **Use Cases:**
920
+ - Check for circular references when receiving user input or external data
921
+ - Data validation to prevent infinite recursion
922
+ - Check if data structure is correct during debugging
923
+
924
+ ### isSafeTreeDepth
925
+
926
+ Check if the depth of tree-structured data is safe (prevent recursion stack overflow). Returns false if the tree depth exceeds `maxDepth`.
927
+
928
+ ```javascript
929
+ // Safe depth tree
930
+ const safeTree = [
931
+ {
932
+ id: 1,
933
+ children: [
934
+ { id: 2, children: [{ id: 3 }] },
935
+ ],
936
+ },
937
+ ];
938
+ console.log(t.isSafeTreeDepth(safeTree, 10)) // true (depth is 3, less than 10)
939
+
940
+ // Depth exceeds maximum depth
941
+ const deepTree = [
942
+ {
943
+ id: 1,
944
+ children: [
945
+ { id: 2, children: [{ id: 3 }] },
946
+ ],
947
+ },
948
+ ];
949
+ console.log(t.isSafeTreeDepth(deepTree, 2)) // false (depth is 3, exceeds 2)
950
+
951
+ // Empty tree is always safe
952
+ console.log(t.isSafeTreeDepth([], 10)) // true
953
+
954
+ // Single layer tree
955
+ const singleLayer = [{ id: 1 }, { id: 2 }];
956
+ console.log(t.isSafeTreeDepth(singleLayer, 1)) // true
957
+
958
+ // Support custom field names
959
+ const customTree = [
960
+ {
961
+ nodeId: 1,
962
+ subNodes: [
963
+ { nodeId: 2, subNodes: [{ nodeId: 3 }] },
964
+ ],
965
+ },
966
+ ];
967
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
968
+ console.log(t.isSafeTreeDepth(customTree, 3, fieldNames)) // true
969
+ console.log(t.isSafeTreeDepth(customTree, 2, fieldNames)) // false
970
+ ```
971
+
972
+ **Use Cases:**
973
+ - Check if depth is safe before processing large trees
974
+ - Prevent recursion call stack overflow
975
+ - Performance optimization, avoid processing trees that are too deep
976
+
977
+ ## Custom Field Names
978
+
979
+ All methods support custom property names for children and id through the last parameter, passing in a configuration object:
980
+
981
+ ```javascript
982
+ // Use default field names
983
+ const foundNode1 = t.findTree(treeData, (node) => node.id === 2)
984
+
985
+ // Use custom field names
986
+ const fieldNames = { children: 'subNodes', id: 'nodeId' };
987
+ const foundNode2 = t.findTree(customTreeData, (node) => node.nodeId === 2, fieldNames);
988
+ ```
989
+
990
+ **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.
991
+
992
+ ## Testing
993
+
994
+ ### Run Tests
995
+
996
+ ```bash
997
+ # Run all tests
998
+ npm test
999
+
1000
+ # Run tests and generate coverage report
1001
+ npm run test:coverage
1002
+
1003
+ # Run tests (once, don't watch for file changes)
1004
+ npm test -- --run
1005
+ ```
1006
+
1007
+ ## Development
1008
+
1009
+ ```bash
1010
+ # Install dependencies
1011
+ npm install
1012
+
1013
+ # Run tests
1014
+ npm test
1015
+
1016
+ # Build project
1017
+ npm run build
1018
+ ```
1019
+
1020
+ <div align="center">
1021
+
1022
+ If this project helps you, please give it a ⭐️
1023
+
1024
+ Made with by [knott11]
1025
+
1026
+ </div>
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # tree-processor
2
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
+
3
9
  <div align="center">
4
10
 
5
11
  ![npm version](https://img.shields.io/npm/v/tree-processor?style=flat-square)
6
12
  ![npm downloads](https://img.shields.io/npm/dm/tree-processor?style=flat-square)
7
13
  ![bundle size](https://img.shields.io/badge/bundle-6.6KB-blue?style=flat-square)
8
- ![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue?style=flat-square)
9
14
  ![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)
10
- ![test coverage](https://img.shields.io/badge/coverage-272%20tests-brightgreen?style=flat-square)
15
+ ![coverage](https://img.shields.io/badge/coverage-99%25-brightgreen?style=flat-square)
11
16
 
12
17
  一个轻量级的树结构数据处理工具库,使用 TypeScript 编写,支持 tree-shaking,每个格式打包体积约 **6-7 KB**(ESM: 6.39 KB,CJS: 6.64 KB,UMD: 6.68 KB)。
13
18
 
@@ -23,17 +28,13 @@
23
28
  - [API 文档](#-api-文档)
24
29
  - [遍历方法](#遍历方法)
25
30
  - [查找方法](#查找方法)
31
+ - [访问方法](#访问方法)
26
32
  - [修改方法](#修改方法)
27
- - [判断方法](#判断方法)
28
- - [工具方法](#工具方法)
33
+ - [查询方法](#查询方法)
34
+ - [验证方法](#验证方法)
29
35
  - [自定义字段名](#自定义字段名)
30
36
  - [测试](#测试)
31
37
  - [开发](#开发)
32
- - [技术栈](#技术栈)
33
- - [贡献](#-贡献)
34
- - [更新日志](#-更新日志)
35
- - [许可证](#-许可证)
36
- - [相关链接](#-相关链接)
37
38
 
38
39
  ## ✨ 特性
39
40
 
@@ -42,7 +43,7 @@
42
43
  - **完整的 TypeScript 支持** - 提供完整的类型定义和智能提示,提升开发体验
43
44
  - **灵活的自定义字段名** - 支持自定义 children 和 id 字段名,适配各种数据结构
44
45
  - **零依赖** - 无任何外部依赖,开箱即用,无需担心依赖冲突
45
- - **完善的测试覆盖** - 包含 272 个测试用例,覆盖基础功能、边界情况、异常处理、复杂场景等
46
+ - **完善的测试覆盖** - 包含 290 个测试用例,测试覆盖率达到 99%+(语句覆盖率 99.67%,分支覆盖率 99.32%,函数覆盖率 100%),覆盖基础功能、边界情况、异常处理、复杂场景等
46
47
  - **丰富的 API** - 提供 30+ 个方法,包含类似数组的 API(map、filter、find、some、every、includes、at、indexOf 等),以及树结构特有的操作(获取父子节点、深度计算、数据验证等),涵盖遍历、查找、修改、判断等完整场景
47
48
 
48
49
  **已支持的方法:** 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。每个方法的最后一个参数可以自定义 children 和 id 的属性名。
@@ -123,16 +124,6 @@ import type { TreeNode, TreeData, FieldNames } from 'tree-processor'
123
124
  const { mapTree, filterTree } = require('tree-processor')
124
125
  ```
125
126
 
126
- **按需导入的优势:**
127
- - ✅ 支持 tree-shaking,只打包使用的代码,减小打包体积
128
- - ✅ 更好的代码提示和类型检查
129
- - ✅ 更清晰的依赖关系
130
-
131
- **关于类型导入:**
132
- - TypeScript 会自动从函数签名推断类型,**大多数情况下不需要显式引入类型**
133
- - 只有在需要显式声明变量类型时才需要引入类型(如 `const treeData: TreeData = [...]`)
134
- - 使用 `import type` 导入类型不会增加运行时体积(类型在编译时会被移除)
135
-
136
127
  ### 示例数据
137
128
 
138
129
  以下示例数据将用于后续所有方法的演示:
@@ -210,6 +201,10 @@ t.forEachTree(treeData, () => {
210
201
  console.log(nodeCount) // 节点总数
211
202
  ```
212
203
 
204
+ ---
205
+
206
+ ## 查找方法
207
+
213
208
  ### filterTree
214
209
 
215
210
  过滤树结构数据,返回满足条件的节点。
@@ -232,10 +227,6 @@ const leafNodes = t.filterTree(treeData, (node) => {
232
227
  console.log(leafNodes) // 返回所有叶子节点
233
228
  ```
234
229
 
235
- ---
236
-
237
- ## 查找方法
238
-
239
230
  ### findTree
240
231
 
241
232
  查找树结构数据中满足条件的第一个节点。如果未找到,返回 null。
@@ -265,6 +256,38 @@ const hasNode = t.includesTree(treeData, nodeId)
265
256
  console.log(hasNode) // true 表示包含该节点,false 表示不包含
266
257
  ```
267
258
 
259
+ ### someTree
260
+
261
+ 检查树结构数据中是否存在满足条件的节点。只要有一个节点满足条件就返回 true。
262
+
263
+ ```javascript
264
+ // 检查是否存在名称为 'node2' 的节点
265
+ const hasNode2 = t.someTree(treeData, node => node.name === 'node2')
266
+ console.log(hasNode2) // true
267
+
268
+ // 检查是否存在ID大于10的节点
269
+ const hasLargeId = t.someTree(treeData, node => node.id > 10)
270
+ console.log(hasLargeId) // false
271
+ ```
272
+
273
+ ### everyTree
274
+
275
+ 检查树结构数据中是否所有节点都满足条件。只有所有节点都满足条件才返回 true。
276
+
277
+ ```javascript
278
+ // 检查所有节点的ID是否都大于0
279
+ const allIdsPositive = t.everyTree(treeData, node => node.id > 0)
280
+ console.log(allIdsPositive) // true
281
+
282
+ // 检查所有节点是否都有 name 属性
283
+ const allHaveName = t.everyTree(treeData, node => node.name)
284
+ console.log(allHaveName) // 根据实际数据返回 true 或 false
285
+ ```
286
+
287
+ ---
288
+
289
+ ## 访问方法
290
+
268
291
  ### atTree
269
292
 
270
293
  根据父节点ID和子节点索引获取节点。支持负数索引,和数组的 at 方法一样。未找到返回 null。
@@ -403,35 +426,7 @@ console.log(uniqueByNameTree) // 返回根据 name 去重后的数据
403
426
 
404
427
  ---
405
428
 
406
- ## 判断方法
407
-
408
- ### someTree
409
-
410
- 检查树结构数据中是否存在满足条件的节点。只要有一个节点满足条件就返回 true。
411
-
412
- ```javascript
413
- // 检查是否存在名称为 'node2' 的节点
414
- const hasNode2 = t.someTree(treeData, node => node.name === 'node2')
415
- console.log(hasNode2) // true
416
-
417
- // 检查是否存在ID大于10的节点
418
- const hasLargeId = t.someTree(treeData, node => node.id > 10)
419
- console.log(hasLargeId) // false
420
- ```
421
-
422
- ### everyTree
423
-
424
- 检查树结构数据中是否所有节点都满足条件。只有所有节点都满足条件才返回 true。
425
-
426
- ```javascript
427
- // 检查所有节点的ID是否都大于0
428
- const allIdsPositive = t.everyTree(treeData, node => node.id > 0)
429
- console.log(allIdsPositive) // true
430
-
431
- // 检查所有节点是否都有 name 属性
432
- const allHaveName = t.everyTree(treeData, node => node.name)
433
- console.log(allHaveName) // 根据实际数据返回 true 或 false
434
- ```
429
+ ## 查询方法
435
430
 
436
431
  ### getParentTree
437
432
 
@@ -522,10 +517,6 @@ const customSiblings = t.getSiblingsTree(customTree, 2, fieldNames)
522
517
  console.log(customSiblings) // 返回兄弟节点数组(包括自己)
523
518
  ```
524
519
 
525
- ---
526
-
527
- ## 工具方法
528
-
529
520
  ### getNodeDepthMap
530
521
 
531
522
  返回一个字典,键代表节点的 id,值代表该节点在数据的第几层。深度从1开始,根节点深度为1。
@@ -584,6 +575,10 @@ console.log(depth) // 2
584
575
  - `getNodeDepthMap` - 批量获取所有节点的深度(一次性计算所有节点)
585
576
  - `getNodeDepth` - 只获取单个节点的深度(只计算目标节点,效率更高)
586
577
 
578
+ ---
579
+
580
+ ## 验证方法
581
+
587
582
  ### isLeafNode
588
583
 
589
584
  检查节点是否是叶子节点(没有子节点)。轻量级方法,只检查节点本身,不遍历树。
@@ -1024,43 +1019,10 @@ npm test
1024
1019
  npm run build
1025
1020
  ```
1026
1021
 
1027
- ## 技术栈
1028
-
1029
- - **Rollup** - 模块打包工具
1030
- - **Vitest** - 单元测试框架
1031
- - **Terser** - JavaScript 压缩工具
1032
- - **TypeScript** - 类型支持
1033
-
1034
- ## 🤝 贡献
1035
-
1036
- 欢迎贡献!如果你有任何想法或发现问题,请:
1037
-
1038
- 1. Fork 本仓库
1039
- 2. 创建你的特性分支 (`git checkout -b feature/AmazingFeature`)
1040
- 3. 提交你的更改 (`git commit -m 'Add some AmazingFeature'`)
1041
- 4. 推送到分支 (`git push origin feature/AmazingFeature`)
1042
- 5. 开启一个 Pull Request
1043
-
1044
- ## 📝 更新日志
1045
-
1046
- 查看 [CHANGELOG.md](./CHANGELOG.md) 了解详细的版本更新记录。
1047
-
1048
- ## 📄 许可证
1049
-
1050
- 本项目采用 [MIT](./LICENSE) 许可证。
1051
-
1052
- ## 🔗 相关链接
1053
-
1054
- - [GitHub 仓库](https://github.com/knott11/tree-processor)
1055
- - [npm 包](https://www.npmjs.com/package/tree-processor)
1056
- - [问题反馈](https://github.com/knott11/tree-processor/issues)
1057
-
1058
- ---
1059
-
1060
1022
  <div align="center">
1061
1023
 
1062
1024
  如果这个项目对你有帮助,请给它一个 ⭐️
1063
1025
 
1064
- Made with ❤️ by [knott11](https://github.com/knott11)
1026
+ Made with by [knott11]
1065
1027
 
1066
1028
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/stats.html CHANGED
@@ -4929,7 +4929,7 @@ var drawChart = (function (exports) {
4929
4929
  </script>
4930
4930
  <script>
4931
4931
  /*<!--*/
4932
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"tree-processor.cjs.js","children":[{"name":"src/index.ts","uid":"7eca4295-1"}]}],"isRoot":true},"nodeParts":{"7eca4295-1":{"renderedLength":17081,"gzipLength":2155,"brotliLength":1901,"metaUid":"7eca4295-0"}},"nodeMetas":{"7eca4295-0":{"id":"\\src\\index.ts","moduleParts":{"tree-processor.cjs.js":"7eca4295-1"},"imported":[],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.56.0"},"options":{"gzip":true,"brotli":true,"sourcemap":false}};
4932
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"tree-processor.umd.js","children":[{"name":"src/index.ts","uid":"975942c2-1"}]}],"isRoot":true},"nodeParts":{"975942c2-1":{"renderedLength":17081,"gzipLength":2155,"brotliLength":1901,"metaUid":"975942c2-0"}},"nodeMetas":{"975942c2-0":{"id":"\\src\\index.ts","moduleParts":{"tree-processor.umd.js":"975942c2-1"},"imported":[],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.57.1"},"options":{"gzip":true,"brotli":true,"sourcemap":false}};
4933
4933
 
4934
4934
  const run = () => {
4935
4935
  const width = window.innerWidth;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tree-processor",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "A lightweight TypeScript library for processing tree-structured data with comprehensive methods (map, filter, find, push, pop, remove, getParent, includes, etc.), supporting tree-shaking and custom field names",
5
5
  "main": "dist/tree-processor.cjs.js",
6
6
  "module": "dist/tree-processor.esm.js",
@@ -46,12 +46,6 @@
46
46
  "deduplicate",
47
47
  "menu",
48
48
  "navigation",
49
- "file-tree",
50
- "directory",
51
- "category",
52
- "comment",
53
- "permission",
54
- "tree-select",
55
49
  "tree-processor"
56
50
  ],
57
51
  "author": "",