or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-watchr

Better file system watching for Node.js with normalized APIs and accurate detailed events

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/watchr@6.11.x

To install, run

npx @tessl/cli install tessl/npm-watchr@6.11.0

index.mddocs/

Watchr

Watchr provides a normalized API for file system watching across different Node.js versions. It supports nested/recursive file and directory watching with accurate detailed events for file/directory creations, updates, and deletions. The library offers two main concepts: Watcher (wraps native file system watching with reliability and deep watching support) and Stalker (manages multiple watchers for the same path efficiently).

Package Information

  • Package Name: watchr
  • Package Type: npm
  • Language: JavaScript (with Flow type annotations)
  • Installation: npm install watchr

Core Imports

const { open, create, Stalker, Watcher } = require('watchr');

For destructuring specific components:

const { open } = require('watchr');  // Simple usage
const { create } = require('watchr'); // Advanced usage
const { Stalker, Watcher } = require('watchr'); // Direct class access

Basic Usage

Simple file watching with the convenience function:

const watchr = require('watchr');

// Define change listener
function listener(changeType, fullPath, currentStat, previousStat) {
    switch (changeType) {
        case 'update':
            console.log('File updated:', fullPath);
            break;
        case 'create':
            console.log('File created:', fullPath);
            break;
        case 'delete':
            console.log('File deleted:', fullPath);
            break;
    }
}

// Start watching with error handling
function next(err) {
    if (err) return console.log('Watch failed:', err);
    console.log('Watch successful');
}

// Watch the current directory
const stalker = watchr.open(process.cwd(), listener, next);

// Stop watching when done
stalker.close();

Advanced usage with configuration:

const { create } = require('watchr');

const stalker = create('/path/to/watch');

// Configure watching options
stalker.setConfig({
    stat: null,
    interval: 5007,
    persistent: true,
    catchupDelay: 2000,
    preferredMethods: ['watch', 'watchFile'],
    followLinks: true,
    ignorePaths: false,
    ignoreHiddenFiles: false,
    ignoreCommonPatterns: true,
    ignoreCustomPatterns: null
});

// Set up event listeners
stalker.on('change', listener);
stalker.on('log', console.log);
stalker.once('close', (reason) => {
    console.log('Watcher closed:', reason);
});

// Start watching
stalker.watch(next);

Architecture

Watchr uses a two-layer architecture:

  • Stalker Layer: Multiple Stalkers can watch the same path, providing a many-to-one relationship for event listeners
  • Watcher Layer: Only one Watcher exists per path, handling the actual file system watching
  • Event Bubbling: Events from child watchers bubble up to parent watchers
  • Method Fallback: Prefers fs.watch, automatically falls back to fs.watchFile if needed
  • Cross-platform Compatibility: Handles differences between Node.js versions and operating systems

Capabilities

Simple File Watching

Quick setup for basic file watching needs using the convenience function.

/**
 * Alias for creating a new Stalker with basic configuration and immediate watching
 * @param {string} path - The path to watch
 * @param {function} changeListener - The change listener for the Watcher
 * @param {function} next - The completion callback for Watcher#watch
 * @returns {Stalker} stalker instance
 */
function open(path, changeListener, next);

Advanced File Watching

Create stalkers with full configuration control for complex watching scenarios.

/**
 * Alias for creating a new Stalker instance
 * @param {...any} args - Arguments passed to Stalker constructor
 * @returns {Stalker} stalker instance
 */
function create(...args);

Stalker Management

The Stalker class manages multiple watchers for the same path, ensuring efficient resource usage.

/**
 * A watcher of the watchers. Events listened to on the stalker are proxied to the attached watcher.
 * When the watcher is closed, the stalker's listeners are removed.
 * When all stalkers for a watcher are removed, the watcher will close.
 */
class Stalker extends EventEmitter {
    /**
     * Create a new Stalker for the given path
     * @param {string} path - The path to watch
     */
    constructor(path);
    
    /**
     * Close the stalker, and if it is the last stalker for the path, close the watcher too
     * @param {string} [reason] - Optional reason to provide for closure
     * @returns {Stalker} this instance for chaining
     */
    close(reason);
    
    /**
     * Configure the underlying watcher with various options
     * @param {...any} args - Arguments passed to watcher's setConfig method
     * @returns {Stalker} this instance for chaining
     */
    setConfig(...args);
    
    /**
     * Start watching the path and its children
     * @param {...any} args - Arguments passed to watcher's watch method
     * @returns {Stalker} this instance for chaining
     */
    watch(...args);
}

Low-Level Watching

Direct access to the Watcher class for specialized use cases.

/**
 * Watches a path and if it's a directory, its children too. 
 * Emits change events for updates, deletions, and creations.
 */
class Watcher extends EventEmitter {
    /**
     * Create a new Watcher for the given path
     * @param {string} path - The path to watch
     */
    constructor(path);
    
    /**
     * Configure the Watcher with various options
     * @param {WatcherOpts} opts - Configuration options
     * @returns {Watcher} this instance for chaining
     */
    setConfig(opts);
    
    /**
     * Setup watching for the path and its children
     * @param {ResetOpts} [opts] - Watch options
     * @param {function} next - Completion callback with signature (error) => void
     * @returns {Watcher} this instance for chaining
     */
    watch(opts, next);
    watch(next);
    
    /**
     * Close the watching abilities of this watcher and its children
     * @param {string} [reason='unknown'] - Reason for closure
     * @returns {Watcher} this instance for chaining
     */
    close(reason);
    
    /**
     * Get the stat for the path of the watcher
     * @param {ResetOpts} opts - Options
     * @param {function} next - Callback with signature (error, stat) => void
     * @returns {Watcher} this instance for chaining
     */
    getStat(opts, next);
    
    /**
     * Emit a log event with the given arguments
     * @param {...any} args - Arguments for logging
     * @returns {Watcher} this instance for chaining
     */
    log(...args);
}

Events

Both Stalker and Watcher classes extend EventEmitter and emit the following events:

Change Event

/**
 * Emitted when a file or directory change is detected
 * @param {string} changeType - 'update', 'create', or 'delete'
 * @param {string} fullPath - Full path to the changed file/directory
 * @param {Stats|null} currentStat - Current Stats object (null for deletions)
 * @param {Stats|null} previousStat - Previous Stats object (null for creations)
 */
stalker.on('change', (changeType, fullPath, currentStat, previousStat) => {
    // Handle the change
});

Close Event

/**
 * Emitted when the watcher is closed
 * @param {string} reason - String describing why the watcher was closed
 */
stalker.on('close', (reason) => {
    // Handle closure
});

Log Event

/**
 * Emitted for debugging information
 * @param {string} logLevel - Log level string
 * @param {...any} args - Additional logging arguments
 */
stalker.on('log', (logLevel, ...args) => {
    // Handle log message
});

Error Event

/**
 * Emitted when an error occurs
 * @param {Error} error - Error object
 */
stalker.on('error', (error) => {
    // Handle error
});

Configuration Types

/**
 * Configuration options for Watcher
 */
interface WatcherOpts {
    /** Pre-existing stat object for the path */
    stat?: Stats;
    /** Polling interval for watchFile method (default: 5007) */
    interval?: number;
    /** Whether watching should keep the process alive (default: true) */
    persistent?: boolean;
    /** Delay after change events for accurate detection (default: 2000) */
    catchupDelay?: number;
    /** Order of watch methods to attempt (default: ['watch', 'watchFile']) */
    preferredMethods?: Array<'watch' | 'watchFile'>;
    /** Whether to follow symlinks (default: true) */
    followLinks?: boolean;
    /** Array of paths to ignore or false to ignore none (default: false) */
    ignorePaths?: Array<string> | false;
    /** Whether to ignore files/dirs starting with '.' (default: false) */
    ignoreHiddenFiles?: boolean;
    /** Whether to ignore common patterns like .git, .svn (default: true) */
    ignoreCommonPatterns?: boolean;
    /** Custom regex for ignoring paths */
    ignoreCustomPatterns?: RegExp;
}

/**
 * Options for watch operations
 */
interface ResetOpts {
    /** Whether to close existing watchers and setup new ones (default: false) */
    reset?: boolean;
}

Error Handling

Watchr provides comprehensive error handling through events and callbacks:

const stalker = create('/path/to/watch');

// Handle errors through events
stalker.on('error', (error) => {
    console.error('Watch error:', error.message);
    // Implement error recovery logic
});

// Handle errors in watch callback
stalker.watch((error) => {
    if (error) {
        console.error('Failed to start watching:', error.message);
        return;
    }
    console.log('Watching started successfully');
});

Common error scenarios:

  • Permission errors: Insufficient permissions to watch the path
  • Path not found: The specified path doesn't exist
  • Method failures: Both fs.watch and fs.watchFile fail
  • System limits: Too many open file watchers (platform-dependent)

Usage Examples

Watching with custom ignore patterns:

const stalker = create('/project/directory');

stalker.setConfig({
    ignoreHiddenFiles: true,
    ignoreCommonPatterns: true,
    ignoreCustomPatterns: /\.(log|tmp)$/,
    ignorePaths: ['/project/directory/node_modules']
});

stalker.on('change', (changeType, fullPath) => {
    console.log(`${changeType}: ${fullPath}`);
});

stalker.watch((err) => {
    if (err) throw err;
    console.log('Watching project directory...');
});

Watching with specific method preference:

const stalker = create('/slow/network/path');

// Prefer polling for network paths
stalker.setConfig({
    preferredMethods: ['watchFile'],
    interval: 2000,  // Check every 2 seconds
    persistent: false  // Don't keep process alive
});

stalker.watch((err) => {
    if (err) throw err;
    console.log('Polling network path...');
});

Multiple watchers on same path:

// Multiple stalkers can watch the same path efficiently
const stalker1 = create('/shared/path');
const stalker2 = create('/shared/path');

stalker1.on('change', (type, path) => {
    console.log('Stalker 1 detected:', type, path);
});

stalker2.on('change', (type, path) => {
    console.log('Stalker 2 detected:', type, path);
});

// Only one underlying watcher is created
stalker1.watch(() => console.log('Stalker 1 ready'));
stalker2.watch(() => console.log('Stalker 2 ready'));