npm package: @r2abreu/path-tree
Description: Build hierarchical trees from flat arrays of path-based objects. Zero dependencies, TypeScript-first, framework-agnostic.
Author: Arturo Abreu
Latest version: 2.0.3

# @r2abreu/path-tree

Build hierarchical trees from flat arrays of path-based objects. TypeScript-first, framework-agnostic.

## Why?

You have flat data with paths:

```javascript
[
  { path: '/docs/api/users' },
  { path: '/docs/guides/intro' },
  { path: '/blog/2024/post-1' }
]
```

You need a tree:

```javascript
[
  {
    item: { path: '/docs' },
    children: [
      {
        item: { path: '/docs/api' },
        children: [
          { item: { path: '/docs/api/users' }, children: [] }
        ]
      },
      {
        item: { path: '/docs/guides' },
        children: [
          { item: { path: '/docs/guides/intro' }, children: [] }
        ]
      }
    ]
  },
  {
    item: { path: '/blog' },
    children: [
      {
        item: { path: '/blog/2024' },
        children: [
          { item: { path: '/blog/2024/post-1' }, children: [] }
        ]
      }
    ]
  }
]
```

## Features

- ✅ **Zero dependencies** - Lightweight and fast
- ✅ **TypeScript-first** - Full type safety with generics
- ✅ **Framework-agnostic** - Works with any JavaScript framework
- ✅ **Automatic parent creation** - Missing intermediate paths are created automatically
- ✅ **Synthetic node marking** - Clearly identifies generated vs. original nodes
- ✅ **Vue Router adapter** - First-class support for Vue Router
- ✅ **Tree-shakeable** - Only bundle what you use

## Installation

```bash
npm install @r2abreu/path-tree
```

## Quick Start

### Generic Usage

```typescript
import { buildPathTree } from '@r2abreu/path-tree';

const items = [
  { path: '/a/b/c', data: 1 },
  { path: '/a/b/d', data: 2 },
  { path: '/x/y', data: 3 }
];

const tree = buildPathTree(items);
```

### Vue Router

```typescript
import { useRouter } from 'vue-router';
import { buildRouteTree } from '@r2abreu/path-tree/vue-router';

const router = useRouter();
const tree = buildRouteTree(router.getRoutes());
```

## Use Cases

### 1. Navigation Menus

```vue
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { buildRouteTree, isRealRoute } from '@r2abreu/path-tree/vue-router';

const router = useRouter();
const routeTree = buildRouteTree(router.getRoutes());
</script>

<template>
  <nav>
    <ul>
      <li v-for="node in routeTree" :key="node.item.path">
        <router-link v-if="isRealRoute(node)" :to="node.item.path">
          {{ node.item.path }}
        </router-link>
        <span v-else class="parent">{{ node.item.path }}</span>
      </li>
    </ul>
  </nav>
</template>
```

### 2. File System Trees

```typescript
import { buildPathTree } from '@r2abreu/path-tree';

const files = [
  { path: '/src/components/Button.vue', size: 1024 },
  { path: '/src/utils/helpers.ts', size: 512 },
  { path: '/tests/unit/Button.test.ts', size: 2048 }
];

const fileTree = buildPathTree(files);
```

### 3. API Endpoint Documentation

```typescript
import { buildPathTree } from '@r2abreu/path-tree';

const endpoints = [
  { path: '/api/v1/users', method: 'GET' },
  { path: '/api/v1/users/:id', method: 'GET' },
  { path: '/api/v1/posts', method: 'POST' }
];

const apiTree = buildPathTree(endpoints);
```

### 4. Breadcrumbs

```typescript
import { buildPathTree } from '@r2abreu/path-tree';

const pages = [
  { path: '/', title: 'Home' },
  { path: '/docs', title: 'Documentation' },
  { path: '/docs/api', title: 'API Reference' },
  { path: '/docs/api/users', title: 'Users API' }
];

const tree = buildPathTree(pages);
// Use tree to generate breadcrumb trail
```

## API

### Core

#### `buildPathTree<T>(items, options?)`

Build a hierarchical tree from a flat array of path-based items.

**Parameters:**
- `items: T[]` - Array of items with path property
- `options?: BuildPathTreeOptions<T>` - Optional configuration

**Returns:** `TreeNode<T>[]` - Array of root nodes

**Example:**
```typescript
const tree = buildPathTree(items, {
  getPath: (item) => item.url,           // Custom path extractor
  createNode: (path) => ({ ... }),       // Custom node factory
  enrichItem: (item) => ({ ...item }),   // Transform items
  getParentPath: (path) => dirname(path) // Custom parent resolver
});
```

#### `isRealNode<T>(node)`

Check if a node is from the original input (not synthetic).

**Example:**
```typescript
if (isRealNode(node)) {
  // This node was in the original input
}
```

#### `filterRealNodes<T>(tree)`

Filter tree to only include real nodes (removes synthetic parents).

**Example:**
```typescript
const realOnly = filterRealNodes(tree);
```

### Vue Router Adapter

#### `buildRouteTree(routes, options?)`

Build a hierarchical tree from Vue Router routes.

**Parameters:**
- `routes: RouteRecordNormalized[]` - Vue Router routes
- `options?: BuildPathTreeOptions` - Optional configuration

**Returns:** `RouteTreeNode[]` - Array of root route nodes

#### `useRouteTree(routes, options?)`

Vue composable for building route tree.

#### `isRealRoute(node)`

Check if a route node is real (not synthetic).

#### `filterRealRoutes(tree)`

Filter tree to only real routes.

## Synthetic Nodes

The library automatically creates parent nodes for missing intermediate paths:

```typescript
buildPathTree([{ path: '/a/b/c' }])
// Creates: /, /a, /a/b (all marked with synthetic: true)
```

**When synthetic nodes are useful:**
- ✅ File systems (directories always exist)
- ✅ Breadcrumbs (need complete path)
- ✅ Navigation structure (visual hierarchy)

**When to filter them out:**
- ❌ API endpoints (only real endpoints should be callable)
- ❌ Strict routing (only defined routes should be clickable)

**Identifying synthetic nodes:**

```typescript
import { isRealNode } from '@r2abreu/path-tree';

tree.forEach(node => {
  if (node.synthetic) {
    console.log('Synthetic:', node.item.path);
  }
  // or use the helper
  if (!isRealNode(node)) {
    console.log('Synthetic:', node.item.path);
  }
});
```

## TypeScript

Fully typed with generics:

```typescript
interface Page {
  path: string;
  title: string;
  meta?: Record<string, any>;
}

const tree: TreeNode<Page>[] = buildPathTree(pages);
```

### Types

```typescript
interface TreeNode<T> {
  item: T;
  children: TreeNode<T>[];
  synthetic?: boolean;
}

interface BuildPathTreeOptions<T> {
  getPath?: (item: T) => string;
  createNode?: (path: string) => TreeNode<T>;
  enrichItem?: (item: T) => T;
  getParentPath?: (path: string) => string;
}
```

## Advanced Examples

### Custom Path Property

```typescript
interface CustomItem {
  path: string;
  url: string;
  title: string;
}

const items: CustomItem[] = [
  { path: '/custom', url: '/custom/path', title: 'Custom' }
];

const tree = buildPathTree(items, {
  getPath: (item) => item.url  // Use 'url' instead of 'path'
});
```

### Enrich Items

```typescript
const tree = buildPathTree(routes, {
  enrichItem: (route) => ({
    ...route,
    displayName: route.meta?.title || route.path,
    icon: route.meta?.icon
  })
});
```

### Custom Parent Resolution

```typescript
import { dirname } from 'pathe';

const tree = buildPathTree(items, {
  getParentPath: (path) => {
    // Custom logic for determining parent
    return dirname(path);
  }
});
```

## Framework Examples

### Vue 3

```vue
<script setup lang="ts">
import { buildRouteTree, isRealRoute } from 'path-tree/vue-router';
import { useRouter } from 'vue-router';

const router = useRouter();
const tree = buildRouteTree(router.getRoutes());
</script>

<template>
  <nav>
    <TreeNode :nodes="tree" />
  </nav>
</template>
```

### React

```typescript
import { buildPathTree } from '@r2abreu/path-tree';

function Navigation({ routes }) {
  const tree = buildPathTree(routes);
  
  return (
    <nav>
      {tree.map(node => (
        <TreeNode key={node.item.path} node={node} />
      ))}
    </nav>
  );
}
```

### Svelte

```svelte
<script>
  import { buildPathTree } from '@r2abreu/path-tree';
  
  export let routes;
  const tree = buildPathTree(routes);
</script>

<nav>
  {#each tree as node}
    <TreeNode {node} />
  {/each}
</nav>
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT © [r2abreu](https://gitlab.com/r2abreu1)

## Links

- [GitLab Repository](https://gitlab.com/r2abreu1/path-tree)
- [Issue Tracker](https://gitlab.com/r2abreu1/path-tree/-/issues)
- [npm Package](https://www.npmjs.com/package/@r2abreu/path-tree)

---
**Glory to God**