npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

mini-tree

v1.2.0

Published

A tiny universal tree data structure that just works!

Downloads

403

Readme

mini-tree

mini-tree is a tiny, flexible, zero-dependency Tree data structure designed to be used for any purpose that requires data either ordered in a hierarchical structure or are in relation to each other. With an easy to use API, it's designed for those problems suited for managing hierarchical data without asking the question "What kind of tree fits the best?".

    pnpm i mini-tree

Why should I use a tree data structure?

Even if you don't know whether your data in question is in any kind of relation to each other, a tree data structure is designed to reduce the number of checks and branches by organizing your data. It's designed to efficiently search the data you need based on previously searched data, optimizing a linear or even binary search. As an example we use the JS linter eslint. eslint is configured to lint our src folder using eslint src and for that, it tries to find this folder. Luckily, a modern OS structures its folders in a tree data structure, so instead of the 10.000+ folders a modern OS comes with, eslint only needs to check the current working directory it's been called from and only has to check 5 or 6 folders as these are the direct child folders of the parent current working directory. And due to every folder being ordered hierarchically, eslint can easily detect what files belong to the src folder instead of blindly linting every file it finds. That is the power of a tree data structure.

TL;DR: It's to vastly optimize linear searching when searching something or to access scoped data relating to a common parent.

Usage

mini-tree relies on callbacks to achieve it's universal-purpose design. And those callbacks are structured differently:

  • TreeComparator<T> compares a TreeNode<T> with a specific value to usually check for further traversal to its child nodes. In other cases, namely if a value equals a node, it will either remove it (in tree.remove) or cancel a process (in tree.add).
  • Traverser<T> takes only a node to either iterate through it (in tree.traverseAsync) or validate it for further traversal to its child nodes.
  • AsyncTraverser<T> is an asynchronous version of Traverser only used in tree.traverseAsync

To reduce the bloat of using the mini-tree Tree, you pass a default comparator and on equal value callback into the constructor. This is used by default in every method of Tree, except node and toJSON. These callbacks can have different meanings depending on how you want to use the tree.

  1. If the passed callbacks are used to search values in has, remove, nodeByValue and branch, define extra insertion callbacks into add and addAll.
  2. If the passed callbacks are used to insert values, add callbacks to the search functions.

import Tree, { type TreeNode, type TreeComparator, type Traverser } from 'mini-tree'


// Comparator that traverses down the hierarchy if the target value is larger than a node value
const comp: TreeComparator<number> = (node: TreeNode<number>, value: number) => value > node.value
// Edge case comparator to check if a tree already contains the value or a new node has to be added
const eq: TreeComparator<number> = (node: TreeNode<number>, value: number) => value === node.value

// Create a new tree with comp and eq as its default comparators and a root node value of 0.
// The root value dictates the generic type of the tree. This should match the generic type of comp and eq
const tree = new Tree(0, comp, eq)

// Add values
tree.add(5)
tree.add(3)
tree.add(7)

// Add same values (won't do anything)
tree.add(5)

// Add values with custom comparators
tree.add(9, (n, v) => n.value > v, (n, v) => n.value === 5)

// Start from a specific sub node
tree.add(13, comp, eq, tree.node(3))

// Remove node with a specific value
tree.remove(5)

// Traverse through all nodes using BFS
for (const node of tree) {
    console.log(node.value)
}

// Find nodes
if (tree.has(7)) // Do cool stuff

Usage with complex data

Normally, you won't just use trees for mere numbers, but rather to hierarchically order metadata of specific items, like menus with sub menus, folders with files or categories with objects. For that, it's much easier to search for nodes using primitive values as keys.

interface PathMetaData {
  path: string
  isDir: boolean
  fileCount: number
}
// ...

// Define a tree where the key (the value to compare to) is the path.
const comp: TreeComparator<PathMetaData, string> = (n, v) => v.startsWith(n.value.path)
const eq: TreeComparator<PathMetaData, string> = (n, v) => v === n.value.path

const tree = new Tree<PathMetaData, string>({ path: '/', isDir: true, fileCount: 7 }, comp, eq)

// Since we are adding complex data, the key to search for, i.e. the path string, doesn't fit when trying to
// order new elements to the tree. So we need to define custom comparators for simply adding new data.
tree.addAll([
  { path: '/package.json', dir: false, fileCount: 0 },
  { path: '/a', dir: true, fileCount: 1 },
  { path: '/a/package.json', dir: false, fileCount: 0 },
  { path: '/a/b', dir: true, fileCount: 0 }
], (n, v) => v.path.startsWith(n.value.path), (n, v) => v.path === n.value.path)

// Find values
tree.has('/')
tree.has('/a/b')

// Get the branch with the value of /a/b
// This returns the actual complex data as an array
tree.branch('/a/b')

tree.remove('/package.json')

// Find the node with the value '/a' and start searching from there.
tree.has('/a/b', comp, eq, tree.nodeByValue('/a'))

Persistence

Persisting tree data can be a very useful thing as well. While it is very hard to persist the relations between nodes, you still want to persist node modifications. This can be done like so:

// Define your tree. If you want to persist it, it's recommended to reuse the comparator globally
const comp: TreeComparator<number, number> = (n, v) => v > n.value
const tree = new Tree(0, comp)
// ... adding values
// Persist your tree with toJSON. This returns a string of an array of your node values.
const jsonString = tree.toJSON()

const parsed = JSON.parse(jsonString)
const newTree = new Tree(parsed[0], comp)
// Even with the root value still present, mini-tree is robust enough to filter out the duplicate value
newTree.addAll(parsed)

© Torathion 2025