CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-sane

Fast, small, and reliable file system watcher with multiple monitoring strategies.

Pending
Overview
Eval results
Files

watcher-modes.mddocs/

Watcher Modes

Different watcher implementations for various use cases and environments, all providing a consistent EventEmitter-based API with the same event types and method signatures.

Capabilities

NodeWatcher (Default Mode)

Uses Node.js built-in fs.watch for efficient native file system watching.

/**
 * Default fs.watch-based watcher
 * @param {string} dir - Directory to watch
 * @param {Object} options - Watcher options
 */
class NodeWatcher extends EventEmitter {
  constructor(dir, options);
  
  /**
   * Stop watching and clean up resources
   * @param {Function} callback - Optional callback called when closed
   */
  close(callback);
}

Best for:

  • General use cases on Linux and macOS
  • Applications requiring low CPU usage and fast response times
  • When native file system events are available and reliable

Limitations:

  • May have issues on some network file systems
  • Windows compatibility varies
  • Can hit file descriptor limits with many directories

Usage Example:

const { NodeWatcher } = require('sane');

const watcher = new NodeWatcher('/path/to/watch', {
  glob: ['**/*.js', '**/*.json'],
  ignored: ['node_modules/**']
});

watcher.on('ready', () => console.log('NodeWatcher ready'));
watcher.on('change', (file) => console.log('Changed:', file));

PollWatcher (Polling Mode)

Uses fs.watchFile polling for maximum compatibility across all environments.

/**
 * Polling-based watcher using fs.watchFile
 * @param {string} dir - Directory to watch
 * @param {Object} options - Watcher options including interval
 */
class PollWatcher extends EventEmitter {
  constructor(dir, options);
  
  /**
   * Stop watching and clean up resources
   * @param {Function} callback - Optional callback called when closed
   */
  close(callback);
}

Additional Options:

interface PollWatcherOptions {
  /** Polling interval in milliseconds (default: 100) */
  interval?: number;
}

Best for:

  • Network file systems (NFS, CIFS, etc.)
  • Virtual environments like Vagrant
  • When native file system events are unreliable
  • Cross-platform consistency requirements

Limitations:

  • Higher CPU usage due to constant polling
  • Slower response time (dependent on polling interval)
  • May miss very rapid file changes

Usage Example:

const { PollWatcher } = require('sane');

const watcher = new PollWatcher('/path/to/watch', {
  interval: 1000, // Poll every second
  glob: ['**/*.css', '**/*.scss']
});

watcher.on('ready', () => console.log('PollWatcher ready'));
watcher.on('add', (file) => console.log('Added:', file));

WatchmanWatcher (Watchman Mode)

Uses Facebook's Watchman for highly efficient watching of large directory trees.

/**
 * Facebook Watchman-based watcher
 * @param {string} dir - Directory to watch
 * @param {Object} options - Watcher options including watchmanPath
 */
class WatchmanWatcher extends EventEmitter {
  constructor(dir, options);
  
  /**
   * Stop watching and clean up resources
   * @param {Function} callback - Optional callback called when closed
   */
  close(callback);
  
  /**
   * Create Watchman subscription options
   * @returns {Object} Watchman subscription configuration
   */
  createOptions();
  
  /**
   * Handle error events from Watchman
   * @param {Error} error - Error from Watchman service
   */
  handleErrorEvent(error);
  
  /**
   * Handle change events from Watchman
   * @param {Object} resp - Watchman response object
   */
  handleChangeEvent(resp);
}

Additional Options:

interface WatchmanOptions {
  /** Custom path to Watchman binary */
  watchmanPath?: string;
}

Best for:

  • Very large codebases (thousands of files)
  • Development environments where Watchman is already installed
  • Applications requiring maximum performance and reliability
  • Projects that hit fs.watch file descriptor limits

Limitations:

  • Requires separate Watchman installation
  • Additional system dependency
  • More complex setup process

Usage Example:

const { WatchmanWatcher } = require('sane');

const watcher = new WatchmanWatcher('/large/project', {
  watchmanPath: '/usr/local/bin/watchman',
  glob: ['src/**/*.js', 'test/**/*.js']
});

watcher.on('ready', () => console.log('Watchman ready'));
watcher.on('all', (type, file) => console.log(type, ':', file));

WatchexecWatcher (Watchexec Mode)

Uses the external watchexec tool for cross-platform file watching.

/**
 * Watchexec-based watcher using external watchexec process
 * @param {string} dir - Directory to watch
 * @param {Object} options - Watcher options
 */
class WatchexecWatcher extends EventEmitter {
  constructor(dir, options);
  
  /**
   * Stop watching and clean up resources
   * @param {Function} callback - Optional callback called when closed
   */
  close(callback);
  
  /**
   * Emit filesystem events to the watcher
   * @param {string} type - Event type ('change', 'add', 'delete')
   * @param {string} file - File path relative to watched directory
   * @param {fs.Stats|null} stat - File statistics (null for delete events)
   */
  emitEvent(type, file, stat);
}

/**
 * Static message handler for processing watchexec output
 * @static
 */
WatchexecWatcher._messageHandler;

Best for:

  • Cross-platform consistency
  • When other watchers have platform-specific issues
  • Integration with existing watchexec workflows

Limitations:

  • Requires separate watchexec installation
  • Additional external process overhead
  • Less mature integration compared to other modes

Usage Example:

const { WatchexecWatcher } = require('sane');

const watcher = new WatchexecWatcher('/path/to/watch', {
  ignored: ['*.tmp', 'build/**']
});

watcher.on('ready', () => console.log('Watchexec ready'));
watcher.on('delete', (file) => console.log('Deleted:', file));

Common Event Interface

All watcher classes emit the same events with identical signatures:

interface WatcherEvents {
  /** Emitted when watcher is ready to detect changes */
  'ready': () => void;
  
  /** Emitted when a file is modified */
  'change': (filepath: string, root: string, stat: fs.Stats) => void;
  
  /** Emitted when a file or directory is added */
  'add': (filepath: string, root: string, stat: fs.Stats) => void;
  
  /** Emitted when a file or directory is deleted (stat parameter is null) */
  'delete': (filepath: string, root: string, stat: null) => void;
  
  /** Emitted for all change types */
  'all': (eventType: 'change'|'add'|'delete', filepath: string, root: string, stat?: fs.Stats) => void;
  
  /** Emitted when errors occur */
  'error': (error: Error) => void;
}

Mode Selection Guidelines

Choose the appropriate watcher mode based on your environment and requirements:

  1. NodeWatcher (Default): Use for most general applications on modern systems
  2. PollWatcher: Use in virtual environments, network filesystems, or when native events are unreliable
  3. WatchmanWatcher: Use for large projects or when maximum performance is required
  4. WatchexecWatcher: Use when other modes have platform-specific issues

Error Handling

All watchers handle common filesystem errors gracefully:

  • ENOENT errors (file not found) are generally ignored
  • EPERM errors on Windows are handled appropriately
  • Connection errors (Watchman) are propagated as error events
  • Process spawn errors (Watchexec) are propagated as error events
watcher.on('error', (error) => {
  if (error.code === 'ENOENT') {
    // File was deleted, usually safe to ignore
  } else {
    console.error('Watcher error:', error);
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-sane

docs

cli.md

core-factory.md

index.md

watcher-modes.md

tile.json