Better file system watching for Node.js with normalized APIs and accurate detailed events
npx @tessl/cli install tessl/npm-watchr@6.11.00
# Watchr
1
2
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).
3
4
## Package Information
5
6
- **Package Name**: watchr
7
- **Package Type**: npm
8
- **Language**: JavaScript (with Flow type annotations)
9
- **Installation**: `npm install watchr`
10
11
## Core Imports
12
13
```javascript
14
const { open, create, Stalker, Watcher } = require('watchr');
15
```
16
17
For destructuring specific components:
18
19
```javascript
20
const { open } = require('watchr'); // Simple usage
21
const { create } = require('watchr'); // Advanced usage
22
const { Stalker, Watcher } = require('watchr'); // Direct class access
23
```
24
25
## Basic Usage
26
27
Simple file watching with the convenience function:
28
29
```javascript
30
const watchr = require('watchr');
31
32
// Define change listener
33
function listener(changeType, fullPath, currentStat, previousStat) {
34
switch (changeType) {
35
case 'update':
36
console.log('File updated:', fullPath);
37
break;
38
case 'create':
39
console.log('File created:', fullPath);
40
break;
41
case 'delete':
42
console.log('File deleted:', fullPath);
43
break;
44
}
45
}
46
47
// Start watching with error handling
48
function next(err) {
49
if (err) return console.log('Watch failed:', err);
50
console.log('Watch successful');
51
}
52
53
// Watch the current directory
54
const stalker = watchr.open(process.cwd(), listener, next);
55
56
// Stop watching when done
57
stalker.close();
58
```
59
60
Advanced usage with configuration:
61
62
```javascript
63
const { create } = require('watchr');
64
65
const stalker = create('/path/to/watch');
66
67
// Configure watching options
68
stalker.setConfig({
69
stat: null,
70
interval: 5007,
71
persistent: true,
72
catchupDelay: 2000,
73
preferredMethods: ['watch', 'watchFile'],
74
followLinks: true,
75
ignorePaths: false,
76
ignoreHiddenFiles: false,
77
ignoreCommonPatterns: true,
78
ignoreCustomPatterns: null
79
});
80
81
// Set up event listeners
82
stalker.on('change', listener);
83
stalker.on('log', console.log);
84
stalker.once('close', (reason) => {
85
console.log('Watcher closed:', reason);
86
});
87
88
// Start watching
89
stalker.watch(next);
90
```
91
92
## Architecture
93
94
Watchr uses a two-layer architecture:
95
96
- **Stalker Layer**: Multiple Stalkers can watch the same path, providing a many-to-one relationship for event listeners
97
- **Watcher Layer**: Only one Watcher exists per path, handling the actual file system watching
98
- **Event Bubbling**: Events from child watchers bubble up to parent watchers
99
- **Method Fallback**: Prefers `fs.watch`, automatically falls back to `fs.watchFile` if needed
100
- **Cross-platform Compatibility**: Handles differences between Node.js versions and operating systems
101
102
## Capabilities
103
104
### Simple File Watching
105
106
Quick setup for basic file watching needs using the convenience function.
107
108
```javascript { .api }
109
/**
110
* Alias for creating a new Stalker with basic configuration and immediate watching
111
* @param {string} path - The path to watch
112
* @param {function} changeListener - The change listener for the Watcher
113
* @param {function} next - The completion callback for Watcher#watch
114
* @returns {Stalker} stalker instance
115
*/
116
function open(path, changeListener, next);
117
```
118
119
### Advanced File Watching
120
121
Create stalkers with full configuration control for complex watching scenarios.
122
123
```javascript { .api }
124
/**
125
* Alias for creating a new Stalker instance
126
* @param {...any} args - Arguments passed to Stalker constructor
127
* @returns {Stalker} stalker instance
128
*/
129
function create(...args);
130
```
131
132
### Stalker Management
133
134
The Stalker class manages multiple watchers for the same path, ensuring efficient resource usage.
135
136
```javascript { .api }
137
/**
138
* A watcher of the watchers. Events listened to on the stalker are proxied to the attached watcher.
139
* When the watcher is closed, the stalker's listeners are removed.
140
* When all stalkers for a watcher are removed, the watcher will close.
141
*/
142
class Stalker extends EventEmitter {
143
/**
144
* Create a new Stalker for the given path
145
* @param {string} path - The path to watch
146
*/
147
constructor(path);
148
149
/**
150
* Close the stalker, and if it is the last stalker for the path, close the watcher too
151
* @param {string} [reason] - Optional reason to provide for closure
152
* @returns {Stalker} this instance for chaining
153
*/
154
close(reason);
155
156
/**
157
* Configure the underlying watcher with various options
158
* @param {...any} args - Arguments passed to watcher's setConfig method
159
* @returns {Stalker} this instance for chaining
160
*/
161
setConfig(...args);
162
163
/**
164
* Start watching the path and its children
165
* @param {...any} args - Arguments passed to watcher's watch method
166
* @returns {Stalker} this instance for chaining
167
*/
168
watch(...args);
169
}
170
```
171
172
### Low-Level Watching
173
174
Direct access to the Watcher class for specialized use cases.
175
176
```javascript { .api }
177
/**
178
* Watches a path and if it's a directory, its children too.
179
* Emits change events for updates, deletions, and creations.
180
*/
181
class Watcher extends EventEmitter {
182
/**
183
* Create a new Watcher for the given path
184
* @param {string} path - The path to watch
185
*/
186
constructor(path);
187
188
/**
189
* Configure the Watcher with various options
190
* @param {WatcherOpts} opts - Configuration options
191
* @returns {Watcher} this instance for chaining
192
*/
193
setConfig(opts);
194
195
/**
196
* Setup watching for the path and its children
197
* @param {ResetOpts} [opts] - Watch options
198
* @param {function} next - Completion callback with signature (error) => void
199
* @returns {Watcher} this instance for chaining
200
*/
201
watch(opts, next);
202
watch(next);
203
204
/**
205
* Close the watching abilities of this watcher and its children
206
* @param {string} [reason='unknown'] - Reason for closure
207
* @returns {Watcher} this instance for chaining
208
*/
209
close(reason);
210
211
/**
212
* Get the stat for the path of the watcher
213
* @param {ResetOpts} opts - Options
214
* @param {function} next - Callback with signature (error, stat) => void
215
* @returns {Watcher} this instance for chaining
216
*/
217
getStat(opts, next);
218
219
/**
220
* Emit a log event with the given arguments
221
* @param {...any} args - Arguments for logging
222
* @returns {Watcher} this instance for chaining
223
*/
224
log(...args);
225
}
226
```
227
228
## Events
229
230
Both Stalker and Watcher classes extend EventEmitter and emit the following events:
231
232
### Change Event
233
234
```javascript { .api }
235
/**
236
* Emitted when a file or directory change is detected
237
* @param {string} changeType - 'update', 'create', or 'delete'
238
* @param {string} fullPath - Full path to the changed file/directory
239
* @param {Stats|null} currentStat - Current Stats object (null for deletions)
240
* @param {Stats|null} previousStat - Previous Stats object (null for creations)
241
*/
242
stalker.on('change', (changeType, fullPath, currentStat, previousStat) => {
243
// Handle the change
244
});
245
```
246
247
### Close Event
248
249
```javascript { .api }
250
/**
251
* Emitted when the watcher is closed
252
* @param {string} reason - String describing why the watcher was closed
253
*/
254
stalker.on('close', (reason) => {
255
// Handle closure
256
});
257
```
258
259
### Log Event
260
261
```javascript { .api }
262
/**
263
* Emitted for debugging information
264
* @param {string} logLevel - Log level string
265
* @param {...any} args - Additional logging arguments
266
*/
267
stalker.on('log', (logLevel, ...args) => {
268
// Handle log message
269
});
270
```
271
272
### Error Event
273
274
```javascript { .api }
275
/**
276
* Emitted when an error occurs
277
* @param {Error} error - Error object
278
*/
279
stalker.on('error', (error) => {
280
// Handle error
281
});
282
```
283
284
## Configuration Types
285
286
```javascript { .api }
287
/**
288
* Configuration options for Watcher
289
*/
290
interface WatcherOpts {
291
/** Pre-existing stat object for the path */
292
stat?: Stats;
293
/** Polling interval for watchFile method (default: 5007) */
294
interval?: number;
295
/** Whether watching should keep the process alive (default: true) */
296
persistent?: boolean;
297
/** Delay after change events for accurate detection (default: 2000) */
298
catchupDelay?: number;
299
/** Order of watch methods to attempt (default: ['watch', 'watchFile']) */
300
preferredMethods?: Array<'watch' | 'watchFile'>;
301
/** Whether to follow symlinks (default: true) */
302
followLinks?: boolean;
303
/** Array of paths to ignore or false to ignore none (default: false) */
304
ignorePaths?: Array<string> | false;
305
/** Whether to ignore files/dirs starting with '.' (default: false) */
306
ignoreHiddenFiles?: boolean;
307
/** Whether to ignore common patterns like .git, .svn (default: true) */
308
ignoreCommonPatterns?: boolean;
309
/** Custom regex for ignoring paths */
310
ignoreCustomPatterns?: RegExp;
311
}
312
313
/**
314
* Options for watch operations
315
*/
316
interface ResetOpts {
317
/** Whether to close existing watchers and setup new ones (default: false) */
318
reset?: boolean;
319
}
320
```
321
322
## Error Handling
323
324
Watchr provides comprehensive error handling through events and callbacks:
325
326
```javascript
327
const stalker = create('/path/to/watch');
328
329
// Handle errors through events
330
stalker.on('error', (error) => {
331
console.error('Watch error:', error.message);
332
// Implement error recovery logic
333
});
334
335
// Handle errors in watch callback
336
stalker.watch((error) => {
337
if (error) {
338
console.error('Failed to start watching:', error.message);
339
return;
340
}
341
console.log('Watching started successfully');
342
});
343
```
344
345
Common error scenarios:
346
- **Permission errors**: Insufficient permissions to watch the path
347
- **Path not found**: The specified path doesn't exist
348
- **Method failures**: Both `fs.watch` and `fs.watchFile` fail
349
- **System limits**: Too many open file watchers (platform-dependent)
350
351
## Usage Examples
352
353
**Watching with custom ignore patterns:**
354
355
```javascript
356
const stalker = create('/project/directory');
357
358
stalker.setConfig({
359
ignoreHiddenFiles: true,
360
ignoreCommonPatterns: true,
361
ignoreCustomPatterns: /\.(log|tmp)$/,
362
ignorePaths: ['/project/directory/node_modules']
363
});
364
365
stalker.on('change', (changeType, fullPath) => {
366
console.log(`${changeType}: ${fullPath}`);
367
});
368
369
stalker.watch((err) => {
370
if (err) throw err;
371
console.log('Watching project directory...');
372
});
373
```
374
375
**Watching with specific method preference:**
376
377
```javascript
378
const stalker = create('/slow/network/path');
379
380
// Prefer polling for network paths
381
stalker.setConfig({
382
preferredMethods: ['watchFile'],
383
interval: 2000, // Check every 2 seconds
384
persistent: false // Don't keep process alive
385
});
386
387
stalker.watch((err) => {
388
if (err) throw err;
389
console.log('Polling network path...');
390
});
391
```
392
393
**Multiple watchers on same path:**
394
395
```javascript
396
// Multiple stalkers can watch the same path efficiently
397
const stalker1 = create('/shared/path');
398
const stalker2 = create('/shared/path');
399
400
stalker1.on('change', (type, path) => {
401
console.log('Stalker 1 detected:', type, path);
402
});
403
404
stalker2.on('change', (type, path) => {
405
console.log('Stalker 2 detected:', type, path);
406
});
407
408
// Only one underlying watcher is created
409
stalker1.watch(() => console.log('Stalker 1 ready'));
410
stalker2.watch(() => console.log('Stalker 2 ready'));
411
```