# 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`): ```typescript 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](https://github.com/paulmillr/chokidar) and known [fs.watch issues](https://github.com/nodejs/node/issues/47058): **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_watches` is 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 1. **Watches inode, not path** - If a directory is replaced, watcher goes stale 2. **inotify limits on Linux** - Default `max_user_watches` (8192) may be too low 3. **No events for some atomic writes** - Some editors' save patterns may not trigger events 4. **Platform differences** - Linux uses inotify, macOS uses FSEvents/kqueue ### Testing ```bash pnpm test ``` Tests verify: - Creating Smartwatch instance - Adding glob patterns - Receiving 'add' events for new files - Graceful shutdown ## Dev Dependencies - Using `@git.zone/tstest` v3.x with tapbundle - Import from `@git.zone/tstest/tapbundle`