File system walker with Readable stream interface for recursively traversing directories and their contents
npx @tessl/cli install tessl/npm-klaw@4.1.0Klaw is a Node.js file system walker that provides a Readable stream interface for recursively traversing directories and their contents. It enables developers to walk through file systems asynchronously, emitting objects containing file paths and fs.Stats for each discovered item (files, directories, symlinks).
npm install klawconst klaw = require('klaw');For ES modules:
import klaw from 'klaw';const klaw = require('klaw');
// Simple traversal collecting all paths
const items = [];
klaw('/some/directory')
.on('data', item => {
items.push(item.path);
console.log(item.path, item.stats.isDirectory() ? 'DIR' : 'FILE');
})
.on('end', () => console.log('Done!', items.length, 'items'));
// Using readable stream pattern
klaw('/some/directory')
.on('readable', function () {
let item;
while ((item = this.read())) {
// Process each item
console.log(item.path);
}
});
// Modern async iteration
for await (const item of klaw('/some/directory')) {
console.log(item.path, item.stats.size);
}Klaw is built around a simple, focused architecture:
Core functionality for recursively traversing directory structures and emitting file system items.
/**
* Creates a file system walker that recursively traverses directories
* @param {string|URL} directory - Root directory to walk (string path or file URL)
* @param {WalkerOptions} [options] - Configuration options for traversal behavior
* @returns {Walker} Readable stream in object mode emitting file system items
*/
function klaw(directory, options);
interface WalkerOptions {
/** Method for processing directory contents: 'shift' (breadth-first) or 'pop' (depth-first) */
queueMethod?: 'shift' | 'pop';
/** Function to sort directory contents before processing */
pathSorter?: (a: string, b: string) => number;
/** Function to filter which paths to include in traversal */
filter?: (path: string) => boolean;
/** Maximum recursion depth (-1 for unlimited) */
depthLimit?: number;
/** Whether to follow symlinks (false) or treat as items (true) */
preserveSymlinks?: boolean;
/** Custom file system implementation (defaults to Node.js fs) */
fs?: FileSystemInterface;
/** Additional Readable stream options (objectMode is always true) */
[key: string]: any;
}
interface FileSystemInterface {
stat: (path: string, callback: (err: Error | null, stats: fs.Stats) => void) => void;
lstat: (path: string, callback: (err: Error | null, stats: fs.Stats) => void) => void;
readdir: (path: string, callback: (err: Error | null, files: string[]) => void) => void;
}
interface WalkerItem {
/** Full path to the file or directory */
path: string;
/** Node.js fs.Stats object containing file system metadata */
stats: fs.Stats;
}Stream Events:
data: Emitted for each file system item with WalkerItem objectend: Emitted when traversal is completeerror: Emitted on file system errors, receives (error, item) parametersreadable: Standard Readable stream event for pull-based consumptionUsage Examples:
const klaw = require('klaw');
const path = require('path');
// Basic traversal with data events
klaw('/home/user/documents')
.on('data', item => {
if (item.stats.isFile()) {
console.log('File:', item.path, 'Size:', item.stats.size);
}
})
.on('error', (err, item) => {
console.error('Error processing:', item.path, err.message);
})
.on('end', () => console.log('Traversal complete'));
// Filtering hidden files and directories
const filterFunc = item => {
const basename = path.basename(item);
return basename === '.' || basename[0] !== '.';
};
klaw('/some/directory', { filter: filterFunc })
.on('data', item => {
// Only non-hidden items reach here
console.log(item.path);
});
// Depth-limited traversal
klaw('/deep/directory', { depthLimit: 2 })
.on('data', item => {
console.log(item.path);
});
// Custom sorting (alphabetical)
klaw('/directory', {
pathSorter: (a, b) => a.localeCompare(b)
})
.on('data', item => {
console.log(item.path);
});
// File URL support
const { pathToFileURL } = require('url');
klaw(pathToFileURL('/some/directory'))
.on('data', item => {
console.log(item.path);
});Since klaw returns a standard Node.js Readable stream, it integrates seamlessly with stream processing libraries.
Example with through2 for advanced filtering:
const klaw = require('klaw');
const through2 = require('through2');
const path = require('path');
// Filter to only include .js files
const jsFilesOnly = through2.obj(function (item, enc, next) {
if (!item.stats.isDirectory() && path.extname(item.path) === '.js') {
this.push(item);
}
next();
});
klaw('/project/src')
.pipe(jsFilesOnly)
.on('data', item => {
console.log('JavaScript file:', item.path);
});
// Aggregate file sizes by extension
let totalSizes = {};
const aggregateByExtension = through2.obj(function (item, enc, next) {
if (item.stats.isFile()) {
const ext = path.extname(item.path) || 'no-extension';
totalSizes[ext] = (totalSizes[ext] || 0) + item.stats.size;
}
this.push(item);
next();
});
klaw('/project')
.pipe(aggregateByExtension)
.on('end', () => {
console.log('File sizes by extension:', totalSizes);
});Klaw provides comprehensive error handling for file system operations.
// Error event signature
walker.on('error', (error: Error, item: WalkerItem) => void);Error Handling Examples:
const klaw = require('klaw');
klaw('/some/directory')
.on('data', item => {
console.log(item.path);
})
.on('error', (err, item) => {
// Handle individual file/directory errors
console.error(`Error accessing ${item.path}:`, err.message);
// Common error types:
// - ENOENT: File or directory doesn't exist
// - EACCES: Permission denied
// - ENOTDIR: Expected directory but found file
})
.on('end', () => {
console.log('Traversal completed despite errors');
});
// Propagating errors through streams
const through2 = require('through2');
const transform = through2.obj(function (item, enc, next) {
// Process items
this.push(item);
next();
});
klaw('/directory')
.on('error', err => transform.emit('error', err)) // Forward errors
.pipe(transform)
.on('error', err => {
console.error('Stream error:', err.message);
});Klaw supports advanced configuration options for customizing traversal behavior.
Queue Method Configuration:
// Breadth-first traversal (default)
klaw('/directory', { queueMethod: 'shift' })
.on('data', item => console.log(item.path));
// Depth-first traversal
klaw('/directory', { queueMethod: 'pop' })
.on('data', item => console.log(item.path));Symlink Handling:
// Default: follow symlinks and traverse target
klaw('/directory', { preserveSymlinks: false })
.on('data', item => {
// item.path shows target path for symlinks
console.log(item.path);
});
// Treat symlinks as items themselves
klaw('/directory', { preserveSymlinks: true })
.on('data', item => {
if (item.stats.isSymbolicLink()) {
console.log('Symlink:', item.path);
}
});Custom File System:
// Using mock-fs for testing
const mockFs = require('mock-fs');
klaw('/virtual/directory', { fs: mockFs })
.on('data', item => {
console.log('Virtual file:', item.path);
});/**
* Walker class extends Node.js Readable stream
* Internal class, not directly exported
*/
class Walker extends Readable {
constructor(directory: string | URL, options?: WalkerOptions);
/** Standard Readable stream methods */
read(size?: number): WalkerItem | null;
pipe<T extends NodeJS.WritableStream>(destination: T, options?: object): T;
/** Event emitter methods for stream events */
on(event: 'data', listener: (item: WalkerItem) => void): this;
on(event: 'end', listener: () => void): this;
on(event: 'error', listener: (error: Error, item: WalkerItem) => void): this;
on(event: 'readable', listener: () => void): this;
}