Watcher
Watch a directory tree and get one event shape whatever the kernel underneath reports.
Each platform describes a change differently. Linux inotify sends "rename" and "change" and can send "change" before the write has finished. macOS FSEvents coalesces creates, deletes and moves into one "rename". Windows sends both "rename" and "change" for a single create. The watcher settles a burst, stats the path afterwards to see what actually happened, and calls onChange or onDelete once.
It follows no symbolic link when it stats, so a link planted inside the tree cannot walk the watcher out of it.
It is for rebuilds, reloads and cache busts. fs.watch is best-effort on every kernel and drops events under load, so nothing that has to know about every change should be built on it.
b.watcher.create(opts) #
{
root: string, // directory to watch; required
mode: string, // "fs", "poll" or "auto"; default "fs"
ignore: string[], // path patterns to skip
ignoreCaseFold: boolean, // default false — match ignore patterns case-insensitively
debounceMs: number, // burst settle time; default 100
maxPending: number, // events held while settling; default 10000
pollIntervalMs: number, // poll backend interval; default 1000
pollMaxFiles: number, // poll backend entry ceiling; default 50000
onChange: function, // ({ type, relativePath, fullPath, size, mtime })
onDelete: function, // ({ type, relativePath, fullPath })
onError: function, // (err)
audit: boolean, // default true
}
Start watching root and answer the handle to stop it. The handle carries stop(), root as it was resolved, and mode, which is the backend that ended up running.
onChange receives { type, relativePath, fullPath, size, mtime } and onDelete receives the first three, where type is "file" or "dir". onError receives anything the backend reports, including the one-time notice that a kernel too old for a recursive watch has fallen back to watching the top directory alone.
mode picks the backend. "fs" uses fs.watch. "poll" walks the tree on an interval, for a filesystem where watching does not work, such as a network mount; it reads at most pollMaxFiles entries so a large tree cannot turn a poll into a sweep of everything. "auto" reads the mount table and picks poll for FUSE, 9p, virtiofs, NFS, SMB and vboxsf, and for a bind mount inside a container. Leaving it out picks per platform.
A watcher keeps the process alive under either backend until close(), so a program whose only work is watching does not exit. The poll backend used to leave the process free to exit while fs.watch held it open, which made lifetime under "auto" depend on the filesystem. The poll walk yields to the event loop between directories and the next walk is timed from the end of the last one, so a tree that takes longer than pollIntervalMs to read does not starve the rest of the process.
debounceMs is how long a burst is allowed to settle before one event goes out. maxPending bounds the events held while settling, so a tree being rewritten wholesale cannot grow the queue without limit.
var b = require("@blamejs/core");
var w = b.watcher.create({
root: "./src",
ignore: ["node_modules", ".git"],
onChange: function (ev) { rebuild(ev.relativePath); },
onDelete: function (ev) { forget(ev.relativePath); },
});
w.stop();