CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-watchr

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

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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'));
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/watchr@6.11.x
Publish Source
CLI
Badge
tessl/npm-watchr badge