Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

babashka.filewatcher

Watch files and directories from babashka, with the same event types on macOS, Linux, and Windows. Modeled after chokidar, built on babashka.ffi: FSEvents on macOS, inotify on Linux, ReadDirectoryChangesW on Windows, and polling everywhere.

Status

Experimental. Needs a babashka with babashka.ffi: a dev build from 2026-08-29 or later, or version 1.13.220 when released.

Install

The library is a git dependency. In bb.edn or deps.edn:

{:deps {io.github.babashka/filewatcher
        {:git/url "https://github.com/babashka/filewatcher"
         :git/sha "<a commit sha>"}}}

On the JVM

The same dependency works on the JVM. babashka.ffi comes along as a git dependency and uses the Java FFM API, so:

  • JDK 22 or newer.
  • Start the JVM with --enable-native-access=ALL-UNNAMED, or set the Enable-Native-Access manifest attribute in an uberjar. Without the flag, modern JDKs warn, and a future JDK release refuses the calls.

For example, in deps.edn:

{:deps {io.github.babashka/filewatcher
        {:git/url "https://github.com/babashka/filewatcher"
         :git/sha "<a commit sha>"}}
 :aliases {:run {:jvm-opts ["--enable-native-access=ALL-UNNAMED"]}}}
clojure -M:run -e "(require '[babashka.filewatcher :as fw]) (fw/watch \".\" prn)"

Nothing else is needed on any platform: FSEvents, inotify, and ReadDirectoryChangesW are part of the operating system.

Usage

(require '[babashka.filewatcher :as fw])

(def watcher
  (fw/watch "src" (fn [event] (prn event))))
;; {:type :add, :path "src/app.clj"}
;; {:type :add-dir, :path "src/app"}
;; {:type :ready}

(spit "src/app.clj" "(ns app)")
;; {:type :change, :path "src/app.clj"}

(fw/close watcher)

watch takes one path or a collection of paths, each a file or a directory, and returns a watcher for close. The function receives one event map at a time, in order, on the watcher's thread. The watcher keeps the process alive until close, on babashka and on the JVM, so a script that only watches needs no other way to block.

Events

Every backend reports the same event types for the same changes, because the events come from a comparison of the file system with a tree the watcher keeps. The operating system only says where to look.

:type when
:add a file appeared
:change a file's size or modification time changed
:unlink a file disappeared
:add-dir a directory appeared
:unlink-dir a directory disappeared, after the :unlink of what it held
:ready the first scan is complete
:error something failed; the exception is under :error

:path uses the watched path as you gave it. It includes the path below it. For example, watch "src" to get "src/app.clj".

The watcher collects changes to one path for :delay-ms milliseconds (default 50). It then reports one result for the path. A file moved onto another file reports one :change for the target. A directory removed with its contents reports :unlink for each file, deepest first, then :unlink-dir.

Options

(fw/watch "src" handler
          {:ignored [#"node_modules" "**.log"]
           :ignore-initial true
           :depth 2
           :await-write-finish true})
option meaning default
:ignored a predicate over the path, a regex, a glob, or a collection of these; the watcher does not enter an ignored directory none
:ignore-initial no :add and :add-dir for what exists at the start false
:depth the number of subdirectory levels to enter; 0 watches the entries of the path itself unlimited
:follow-symlinks stat through symbolic links, so a link is watched as the file it points at false
:await-write-finish true, or {:stability-threshold ms :poll-interval ms}: hold :add and :change until the size and modification time stay the same for the threshold (default 2000 ms, checked every 100) false
:atomic hide editor temporary files, such as names that end in ~ or .swp, and report a file replaced through a rename as one :change true, false with polling
:use-polling compare the tree every :interval ms instead of listening to the operating system; for network and container file systems false
:interval the polling interval in ms 100
:delay-ms how long to collect changes to a path before reporting them 50
:persistent keep the process alive until close true

watched returns what the watcher knows. It maps each watched directory to the names in that directory.

The options and their defaults are chokidar's, with one exception: :follow-symlinks is false here, where chokidar follows symbolic links. A watcher that follows links can walk into a tree you did not point it at. Beholder and the JDK's own WatchService do not follow them either. Set :follow-symlinks true for chokidar's behavior.

Tests

The suite is the contract: one set of file operations, one expected stream of events, run on every backend.

bb test:bb         # the backend of this operating system
bb test:polling    # the polling backend, the reference
bb test:jvm        # the same suite on the JVM, with babashka.ffi from git

License

MIT, see LICENSE.

About

Filewatcher using FFI, modeled after Chokidar

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages