4.9 KiB
smartchok - Technical Hints
Native File Watching (v2.0.0+)
The module now uses native file watching APIs instead of chokidar, providing cross-runtime support for Node.js, Deno, and Bun.
Exported Class
The package exports the Smartwatch class (not Smartchok):
import { Smartwatch } from '@push.rocks/smartchok';
Architecture
ts/
├── smartwatch.classes.smartwatch.ts # Main Smartwatch class
├── smartwatch.plugins.ts # Dependencies (smartenv, picomatch, etc.)
├── watchers/
│ ├── index.ts # Factory with runtime detection
│ ├── interfaces.ts # IWatcher interface and types
│ ├── watcher.node.ts # Node.js/Bun implementation (fs.watch)
│ └── watcher.deno.ts # Deno implementation (Deno.watchFs)
└── utils/
└── write-stabilizer.ts # awaitWriteFinish polling implementation
Runtime Detection
Uses @push.rocks/smartenv v6.x for runtime detection:
- Node.js/Bun: Uses native
fs.watch()with{ recursive: true } - Deno: Uses
Deno.watchFs()async iterable
Dependencies
- picomatch: Glob pattern matching (zero deps, well-maintained)
- @push.rocks/smartenv: Runtime detection (Node.js, Deno, Bun)
- @push.rocks/smartrx: RxJS Subject/Observable management
- @push.rocks/smartpromise: Deferred promise utilities
- @push.rocks/lik: Stringmap for pattern storage
Why picomatch?
Native file watching APIs don't support glob patterns. Picomatch provides glob pattern matching with:
- Zero dependencies
- 164M+ weekly downloads
- Excellent security profile
- Full glob syntax support
Event Handling
Native events are normalized to a consistent interface:
| Node.js/Bun Event | Deno Event | Normalized Event |
|---|---|---|
rename (file exists) |
create |
add |
rename (file gone) |
remove |
unlink |
change |
modify |
change |
awaitWriteFinish Implementation
The WriteStabilizer class replaces chokidar's built-in write stabilization:
- Polls file size until stable (configurable threshold: 300ms default)
- Configurable poll interval (100ms default)
- Handles file deletion during write detection
Platform Requirements
- Node.js 20+: Required for native recursive watching on all platforms
- Deno: Works on all versions with
Deno.watchFs() - Bun: Uses Node.js compatibility layer
Robustness Features (v6.1.0+)
The Node.js watcher includes automatic recovery mechanisms based on learnings from chokidar and known fs.watch issues:
Auto-restart on failure:
- Watchers automatically restart when errors occur
- Exponential backoff (1s → 30s max)
- Maximum 3 retry attempts before giving up
Inode tracking (critical for long-running watchers):
fs.watch()watches the inode, not the path!- When directories are replaced (git checkout, atomic saves), the inode changes
- Health check detects inode changes and restarts the watcher
- This is the most common cause of "watcher stops working after some time"
Health check monitoring:
- 30-second periodic health checks
- Detects when watched paths disappear
- Detects inode changes (directory replacement)
- Detects ENOSPC errors (inotify limit exceeded)
ENOSPC detection (Linux inotify limit):
- Detects when
/proc/sys/fs/inotify/max_user_watchesis exceeded - Logs fix command:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
Error isolation:
- Subscriber errors don't crash the watcher
- All events emitted via
safeEmit()with try-catch
Verbose logging:
- All lifecycle events logged with
[smartwatch]prefix - Helps debug watcher issues in production
Example log output:
[smartwatch] Starting watcher for 1 base path(s)...
[smartwatch] Started watching: ./test/assets/
[smartwatch] Starting health check (every 30s)
[smartwatch] Watcher started with 1 active watcher(s)
[smartwatch] Health check: 1 watchers active
[smartwatch] Inode changed for ./src: 12345 -> 67890
[smartwatch] fs.watch watches inode, not path - restarting watcher
Known fs.watch Limitations
- Watches inode, not path - If a directory is replaced, watcher goes stale
- inotify limits on Linux - Default
max_user_watches(8192) may be too low - No events for some atomic writes - Some editors' save patterns may not trigger events
- Platform differences - Linux uses inotify, macOS uses FSEvents/kqueue
Testing
pnpm test
Tests verify:
- Creating Smartwatch instance
- Adding glob patterns
- Receiving 'add' events for new files
- Graceful shutdown
Dev Dependencies
- Using
@git.zone/tstestv3.x with tapbundle - Import from
@git.zone/tstest/tapbundle