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 🙏

© 2026 – Pkg Stats / Ryan Hefner

pathpal

v1.0.0

Published

Zero-dependency filesystem path abstraction for Node.js

Readme

PathPal

Zero-dependency filesystem path abstraction for Node.js. Define your project's directory structure once, get type-safe path helpers everywhere.

Features

  • Zero dependencies - No external packages required
  • Type-safe - Full TypeScript support with generated helper types
  • Dynamic helpers - Auto-generated methods for each configured directory
  • File operations - Read, write, list, watch files with path-aware methods
  • Glob support - Built-in glob pattern matching without external tools
  • Path sanitization - Secure filename and path sanitization
  • Safe mode - Path traversal protection enabled by default
  • Caching - LRU cache for path computations
  • Temp directories - Built-in support for temporary test directories

Why PathPal?

Path handling in Node.js projects typically looks like this:

import { fileURLToPath } from 'node:url'
import { join, dirname } from 'node:path'

const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = join(__dirname, '..')

// Scattered throughout your codebase:
const configPath = join(ROOT, 'config', 'database.json')
const uploadPath = join(ROOT, 'storage', 'uploads', filename)
const logPath = join(ROOT, 'logs', `${date}.log`)

This approach has several problems:

  • Repetition - The same join(ROOT, 'config', ...) pattern repeated everywhere
  • No single source of truth - Directory structure is implicit and scattered across files
  • Refactoring is painful - Renaming config/ to settings/ means finding every occurrence
  • Easy to make mistakes - Typos in path strings aren't caught until runtime
  • ESM boilerplate - Every file needs the __dirname workaround for ES modules

PathPal solves this by centralizing your directory structure:

import { createPathPal } from 'pathpal'

const paths = createPathPal({
  root: import.meta.url,
  directories: {
    config: 'config',
    uploads: 'storage/uploads',
    logs: 'logs',
  },
})

// Use anywhere - type-safe and refactor-friendly:
paths.configPath('database.json')
paths.uploadsPath(filename)
paths.logsPath(`${date}.log`)

Change a directory location in one place, and it updates everywhere. TypeScript catches typos at compile time. No more __dirname boilerplate.

Installation

npm install pathpal

Quick Start

import { createPathPal } from 'pathpal'

const paths = createPathPal({
  root: import.meta.url,
  directories: {
    config: 'config',
    models: 'src/models',
    uploads: 'storage/uploads',
    logs: 'logs',
  },
})

// Generate paths
paths.configPath('database.json')     // /project/config/database.json
paths.modelsPath('User.ts')           // /project/src/models/User.ts
paths.uploadsPath('images', 'avatar.png')  // /project/storage/uploads/images/avatar.png

API

Creating an Instance

import { createPathPal } from 'pathpal'

const paths = createPathPal({
  // Root directory (string, URL, or file:// URL string)
  root: process.cwd(),

  // Directory mappings
  directories: {
    config: 'config',
    src: 'src',
    tests: '__tests__',
  },

  // Safe mode is enabled by default (prevents path traversal)
  // safe: true,

  // Enable path caching
  cache: true,
})

Core Methods

// Generate absolute paths
paths.makePath('src', 'index.ts')  // /project/src/index.ts

// Paths can include slashes - these are equivalent:
paths.makePath('src', 'components', 'Button.tsx')
paths.makePath('src/components', 'Button.tsx')
paths.makePath('src/components/Button.tsx')

// Generate file:// URLs
paths.makeURL('config', 'app.json')  // URL { href: 'file:///project/config/app.json' }

// Get relative path from root
paths.relativePath('/project/src/index.ts')  // 'src/index.ts'

// Check if path is within root
paths.isWithinRoot('/etc/passwd')  // false

// Get all configured directory keys
paths.getDirectories()  // ['config', 'src', 'tests']

Dynamic Directory Helpers

For each configured directory, PathPal generates a set of helper methods:

const paths = createPathPal({
  root: process.cwd(),
  directories: {
    config: 'config',
    uploads: 'storage/uploads',
  },
})

// Path generation
paths.configPath('database.json')

// File existence
await paths.configPathExists('database.json')
paths.configPathExistsSync('database.json')

// Read files
const data = await paths.configPathRead('database.json', 'utf-8')
const buffer = paths.configPathReadSync('settings.bin')

// Write files
await paths.configPathWrite('settings.json', JSON.stringify(config))
paths.configPathWriteSync('cache.json', data)

// Directory operations
await paths.uploadsPathMkdir('images', { recursive: true })
await paths.uploadsPathEnsure('documents')  // Create if not exists
await paths.uploadsPathRmdir('temp', { recursive: true })

// List files
const files = await paths.uploadsPathListFiles('images', { recursive: true })

// Delete recursively
await paths.uploadsPathDeleteRecursive('temp')

// Watch for changes
const watcher = paths.configPathWatch('settings.json', (event, filename) => {
  console.log(`File ${filename} ${event}`)
})

// Glob pattern matching
const jsFiles = await paths.srcPathGlob('**/*.js')

// Sanitize user input
const safeName = paths.uploadsPathSanitize(userFilename)

File Operations

// Read files
const content = await paths.readFile('config/app.json', 'utf-8')
const buffer = paths.readFileSync('data/binary.dat')

// Write files
await paths.writeFile('logs/app.log', 'Log entry')
paths.writeFileSync('cache/data.json', JSON.stringify(data))

// Directory operations
await paths.mkdir('uploads/images', { recursive: true })
await paths.ensureDir('logs')  // Create if not exists
await paths.rmdir('temp', { recursive: true })

// List files
const files = await paths.listFiles('src', {
  recursive: true,
  filter: (path) => path.endsWith('.ts'),
})

// Delete recursively
await paths.deleteRecursive('build')

// Check existence
const exists = await paths.exists('config/app.json')
const isFile = await paths.isFile('config/app.json')
const isDir = await paths.isDirectory('src')

// Watch files
const watcher = paths.watch('config', (event, filename) => {
  console.log(`${event}: ${filename}`)
})

Path Sanitization

Protect against malicious filenames and path traversal:

// Sanitize a single filename
paths.sanitizeFilename('../../../etc/passwd')  // '_.._.._etc_passwd'
paths.sanitizeFilename('file<>:"|?*.txt')      // 'file_______.txt'
paths.sanitizeFilename('CON.txt')              // 'CON_.txt' (Windows reserved)

// Sanitize with options
paths.sanitizeFilename(userInput, {
  replacement: '-',      // Character to replace invalid chars
  maxLength: 100,        // Max filename length
  allowSpaces: false,    // Replace spaces
  preserveExtension: true,
})

// Sanitize a full path (each segment)
paths.sanitizePath('uploads/../../../etc/passwd')  // 'uploads/etc/passwd'

Glob Pattern Matching

Built-in glob support without external dependencies:

// Simple wildcards
const jsFiles = await paths.glob('*.js')

// Recursive globstar
const allTs = await paths.glob('**/*.ts')

// Brace expansion
const configs = await paths.glob('*.{json,yaml,yml}')

// Character ranges
const logs = await paths.glob('log-[0-9].txt')

// With options
const files = await paths.glob('**/*.ts', {
  ignore: ['**/*.test.ts', '**/*.spec.ts'],
  absolute: false,
  dot: true,  // Include hidden files
  maxDepth: 3,
})

// Sync version
const syncFiles = paths.globSync('src/**/*.js')

Safe Mode

Safe mode is enabled by default to prevent path traversal attacks:

const paths = createPathPal({
  root: process.cwd(),
})

// These throw errors (safe mode is on by default):
paths.makePath('..', 'etc', 'passwd')  // Error: Path traversal detected
paths.makePath('/etc/passwd')          // Error: Absolute path detected
await paths.readFile('/etc/passwd')    // Error: Path outside root directory

// Disable safe mode if you need to allow path traversal (not recommended):
const unsafePaths = createPathPal({
  root: process.cwd(),
  safe: false,
})

Templates and Patterns

Dynamic path generation with templates:

const paths = createPathPal({
  root: process.cwd(),
  templates: {
    date: () => new Date().toISOString().split('T')[0],
    userId: (id: string) => id,
  },
  patterns: {
    daily: () => new Date().toISOString().split('T')[0],
    timestamp: () => Date.now().toString(),
  },
})

// Use patterns in paths
paths.makePath('logs', paths.pattern('daily'), 'app.log')
// /project/logs/2024-01-15/app.log

Caching

Enable LRU caching for frequently accessed paths:

const paths = createPathPal({
  root: process.cwd(),
  cache: {
    enabled: true,
    maxSize: 1000,  // Max entries
    ttl: 60000,     // 1 minute TTL
  },
})

// Get cache statistics
const stats = paths.getCacheStats()
// { hits: 150, misses: 50, size: 200, maxSize: 1000, hitRate: 0.75, evictions: 0 }

// Clear cache
paths.clearCache()

// Get memory usage
const memory = paths.getMemoryUsage()
// { helperCount: 10, cacheSize: 200, estimatedBytes: 12400 }

Benchmarks

Caching provides significant performance improvements for repeated path operations:

Path Generation (1,000,000 iterations):
┌─────────────────────┬──────────────┬─────────────┬─────────┐
│ Operation           │ No Cache     │ With Cache  │ Speedup │
├─────────────────────┼──────────────┼─────────────┼─────────┤
│ Simple path         │ 1167ms       │ 87ms        │ 13.4x   │
│ Nested path (3 seg) │ 1485ms       │ 149ms       │ 10.0x   │
│ makePath (3 seg)    │ 1276ms       │ 113ms       │ 11.3x   │
└─────────────────────┴──────────────┴─────────────┴─────────┘

Enable caching when:

  • Generating the same paths repeatedly in loops
  • Using patterns/templates that involve function calls
  • Building paths in hot code paths

Temporary Directories

Create isolated temporary instances for testing:

import { createTempPathPal } from 'pathpal'

const temp = await createTempPathPal({
  directories: {
    config: 'config',
    data: 'data',
  },
  fixtures: {
    'config/app.json': '{"debug": true}',
    'data/seed.sql': 'INSERT INTO ...',
  },
})

// Use like a normal PathPal instance
await temp.configPathWrite('new.json', '{}')

// Clean up when done
await temp.cleanup()

Batch Operations

Perform multiple operations efficiently:

const results = await paths.batch([
  { op: 'path', dir: 'config', paths: ['database.json'] },
  { op: 'exists', dir: 'config', paths: ['database.json'] },
  { op: 'read', dir: 'config', paths: ['app.json'], encoding: 'utf-8' },
])

Serialization

// Serialize to JSON
const json = paths.toJSON()
// { root: '/project', directories: { config: 'config', ... }, safe: true }

// Useful for logging, debugging, or recreating instances

TypeScript Support

PathPal provides full TypeScript support with generated types for all helper methods:

import { createPathPal, PathPal } from 'pathpal'

// Type inference works automatically
const paths = createPathPal({
  root: process.cwd(),
  directories: {
    config: 'config',
    models: 'src/models',
  },
})

// These methods are fully typed:
paths.configPath        // (...paths: string[]) => string
paths.configPathRead    // (path: string, encoding?: BufferEncoding) => Promise<Buffer | string>
paths.modelsPathExists  // (...paths: string[]) => Promise<boolean>

Requirements

  • Node.js >= 18.0.0

License

MIT