Sign in

    chokidar

    Cross-platform file watching library for MoonBit native target. Port of chokidar v5 with kqueue (macOS/FreeBSD) and inotify (Linux) backends.

    file-watcher
    fsevents
    inotify
    kqueue
    native
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    5 months ago
    Downloads
    47

    #i5ting/chokidar

    Minimal and efficient cross-platform file watching library for MoonBit (native target).

    A faithful port of chokidar v5 to MoonBit, with the same API shape and identical option names (in MoonBit snake_case convention). Runs on macOS (kqueue), Linux (inotify), and FreeBSD with no external dependencies.

    #Getting started

    moon add i5ting/chokidar

    fn main {
    let watcher = @chokidar.watch(@chokidar.One("."))
    watcher
    .on(@chokidar.Add, event => println("added: " + event.path))
    .on(@chokidar.Change, event => println("changed: " + event.path))
    .on(@chokidar.Unlink, event => println("removed: " + event.path))
    .on(@chokidar.Ready, _ => println("ready"))
    |> ignore

    // Drive the watcher in your application loop
    while true {
    watcher.process() catch { _ => () }
    }
    }

    #How it works

    Unlike the Node.js original, the MoonBit port is poll-driven: no background threads are spawned. You call watcher.process() in your own loop to drain OS events and run diff scans. Between calls the watcher is completely idle.

    Native OS notifications are used for low latency (kqueue on macOS/FreeBSD, inotify on Linux). Polling mode (use_polling: true) falls back to periodic directory snapshots.

    #API

    #watch

    pub fn watch(
    paths : WatchPaths,
    options? : Options,
    ) -> Watcher raise ChokidarError

    WatchPaths is either One(String) or Many(Array[String]).

    Returns a Watcher. Raises ChokidarError if an initial scan fails.

    #Watcher

    MethodDescription
    add(paths) -> WatcherAdd more paths to watch
    unwatch(paths) -> WatcherStop watching paths
    on(event, callback) -> WatcherSubscribe to an event
    off(event, callback) -> WatcherUnsubscribe a specific callback
    remove_all_listeners() -> WatcherRemove all event callbacks
    get_watched() -> Map[String, Array[String]]Return currently watched paths
    close() -> UnitStop all watchers and clear state
    process() -> Unit raise ChokidarErrorDrain OS events and emit file-change events

    #Events

    EventNameEmitted when
    AddA file is added
    AddDirA directory is added
    ChangeA file changes
    UnlinkA file is removed
    UnlinkDirA directory is removed
    ReadyInitial scan complete (emitted once)
    RawRaw OS event received
    ErrorA scan error occurs
    AllAny of Add/AddDir/Change/Unlink/UnlinkDir

    Each callback receives an Event:

    pub struct Event {
    name : EventName
    path : String
    stats : FileStats? // size, mode, modified_nanos
    raw : RawEvent? // underlying OS event (Raw only)
    error : ChokidarError? // (Error only)
    }

    FileStats is attached when always_stat: true or on Change events:

    pub struct FileStats {
    size : Int64
    mode : Int
    modified_nanos : Int64
    }

    FileKind classifies the entry type. Special files (sockets, FIFOs, device nodes) use the Other variant and generate Add/Unlink events:

    pub enum FileKind {
    File
    Directory
    Symlink
    Other // sockets, pipes, device nodes
    }

    #Options

    let watcher = @chokidar.watch(
    @chokidar.One("src"),
    options={
    ..@chokidar.Options::default(),
    ignored: [
    // exact path
    @chokidar.MatchPath("src/generated.mbt"),
    // recursive subtree
    @chokidar.MatchRecursivePath("src/vendor"),
    // regex
    @chokidar.MatchRegex(@regexp.compile("\\.log$") |> Result::unwrap),
    // function
    @chokidar.MatchFunction((path, _stats) => path.has_suffix(".tmp")),
    ],
    ignore_initial: true,
    follow_symlinks: true,
    cwd: Some("/project/root"),
    use_polling: false,
    interval: 100,
    binary_interval: 300,
    always_stat: false,
    depth: 2147483647, // Int::max_value — unlimited
    ignore_permission_errors: false,
    atomic_option: @chokidar.AtomicDefault,
    await_write_finish: @chokidar.AwaitWriteFinishOff,
    }
    )

    #Path filtering

    • ignored — Array of Matcher values; a path is ignored if any matcher matches.
      • MatchPath(s) — exact path equality
      • MatchRecursivePath(s) — s or any path underneath s
      • MatchRegex(re) — matches via @regexp.Regexp
      • MatchFunction((path, stats?) -> Bool) — custom predicate
    • ignore_initial (default false) — suppress add/addDir events during the initial scan.
    • follow_symlinks (default true) — traverse symlinked directories.
    • cwd — base directory; emitted paths will be relative to it.

    #Performance

    • use_polling (default false) — force directory polling instead of native OS events. Required for network filesystems. Also overridable via CHOKIDAR_USEPOLLING=true|1|false|0.
    • interval (default 100 ms) — polling interval. Also overridable via CHOKIDAR_INTERVAL=<ms>.
    • binary_interval (default 300 ms) — polling interval for binary files (option accepted; uniform interval used for all files).
    • always_stat (default false) — always attach FileStats to Add, AddDir, and Change events.
    • depth (default unlimited) — maximum directory depth to scan.

    #Correctness

    • ignore_permission_errors (default false) — silently skip unreadable files instead of raising.
    • atomic_option:
      • AtomicDefault — detect editor atomic-write patterns (vim swapfiles, ~ backups, Sublime .tmp); suppress the intermediate Unlink and convert Unlink+Add pairs to Change.
      • AtomicWindow(ms) — explicit window in milliseconds.
      • AtomicOff — disable atomic handling.
    • await_write_finish:
      • AwaitWriteFinishOff (default) — emit immediately.
      • AwaitWriteFinishOn({ stability_threshold: Int, poll_interval: Int }) — hold Add/Change until file size is stable for stability_threshold ms; re-check every poll_interval ms.
      • AwaitWriteFinish::default_on() — { stability_threshold: 2000, poll_interval: 100 }.

    #Differences from chokidar-main (Node.js v5)

    Topicchokidar-main (JS)i5ting/chokidar (MoonBit)
    Event modelAsync callbacks via EventEmitterSynchronous; call watcher.process() in a loop
    close()Returns Promise<void>Synchronous Unit
    on / off return typeFSWatcher (this)Watcher (same behavior)
    Event namesstring literals: "add", "addDir" …EventName enum variants; all_event_names() lists all nine
    Matcher typesstring, RegExp, function, { path, recursive? }Matcher enum: MatchPath, MatchRecursivePath, MatchRegex, MatchFunction
    ignored + cwdMatchPath patterns are joined with cwd so absolute patterns workMatchPath uses literal string equality; combine cwd into the pattern manually
    Non-existent watch pathsSilently deferred until path appearsRaises ChokidarError on first scan
    binaryIntervalDifferent poll interval for binary file extensionsOption accepted; uniform interval used
    Special filessocket/FIFO/device nodes counted separatelyCovered by FileKind::Other; emit Add/Unlink like regular files

    #Name mapping (JS → MoonBit)

    JS nameMoonBit nameNote
    FSWatcherWatcher
    watch()watch()Same
    FSWatcher.add()Watcher::add()Same
    FSWatcher.on()Watcher::on()Same
    FSWatcher.off()Watcher::off()Same
    FSWatcher.unwatch()Watcher::unwatch()Same
    FSWatcher.close()Watcher::close()Sync in MoonBit
    FSWatcher.getWatched()Watcher::get_watched()snake_case
    FSWatcher.removeAllListeners()Watcher::remove_all_listeners()snake_case
    ignoreInitialignore_initialsnake_case
    followSymlinksfollow_symlinkssnake_case
    usePollinguse_pollingsnake_case
    binaryIntervalbinary_intervalsnake_case
    alwaysStatalways_statsnake_case
    ignorePermissionErrorsignore_permission_errorssnake_case
    awaitWriteFinishawait_write_finishsnake_case
    atomicatomic_optionrenamed (avoids keyword conflict)

    #Examples

    #Watch a directory, ignore node_modules

    let watcher = @chokidar.watch(
    @chokidar.One("src"),
    options={
    ..@chokidar.Options::default(),
    ignore_initial: true,
    ignored: [@chokidar.MatchRecursivePath("src/node_modules")],
    },
    )
    watcher.on(@chokidar.All, event => {
    println(event.name.to_string() + " " + event.path)
    }) |> ignore

    #Watch multiple paths

    let watcher = @chokidar.watch(
    @chokidar.Many(["src", "test"]),
    options={
    ..@chokidar.Options::default(),
    ignore_initial: true,
    },
    )

    #Await write finish for large files

    let watcher = @chokidar.watch(
    @chokidar.One("uploads"),
    options={
    ..@chokidar.Options::default(),
    await_write_finish: @chokidar.AwaitWriteFinish::default_on(),
    },
    )

    #Force polling (e.g. for network drives)

    let watcher = @chokidar.watch(
    @chokidar.One("/mnt/share"),
    options={
    ..@chokidar.Options::default(),
    use_polling: true,
    interval: 500,
    },
    )

    #Attach stats to every change event

    watcher.on(@chokidar.Change, event => {
    match event.stats {
    Some(s) => println("size: " + s.size.to_string())
    None => ()
    }
    }) |> ignore

    #Unsubscribe a listener

    let cb : (@chokidar.Event) -> Unit = event => println(event.path)
    watcher.on(@chokidar.Add, cb) |> ignore
    // later...
    watcher.off(@chokidar.Add, cb) |> ignore

    #Stop watching

    watcher.close()

    #Environment variable overrides

    VariableEffect
    CHOKIDAR_USEPOLLING=true | 1Force use_polling: true
    CHOKIDAR_USEPOLLING=false | 0Force use_polling: false
    CHOKIDAR_INTERVAL=<ms>Override interval

    #License

    Apache-2.0

    ChokidarError

    pub(all) suberror ChokidarError {
    ChokidarError(String)
    } derive(Eq,
    Debug
    )

    AtomicOption

    pub(all) enum AtomicOption {
    AtomicDefault
    AtomicOff
    AtomicWindow(Int)
    } derive(Eq,
    Debug
    )

    AwaitWriteFinish

    pub(all) enum AwaitWriteFinish {
    AwaitWriteFinishOff
    AwaitWriteFinishOn(AwaitWriteFinishOptions)
    } derive(Eq,
    Debug
    )

    AwaitWriteFinish::default_on

    AwaitWriteFinishOptions

    pub(all) struct AwaitWriteFinishOptions {
    stability_threshold : Int
    poll_interval : Int
    } derive(Eq,
    Debug
    )

    Event

    pub(all) struct Event {
    name : EventName
    path : String
    stats : FileStats?
    raw : RawEvent?
    error : ChokidarError?
    } derive(Eq,
    Debug
    )

    EventName

    pub(all) enum EventName {
    Add
    Change
    AddDir
    Unlink
    UnlinkDir
    Ready
    Raw
    Error
    All
    } derive(Eq,
    Debug
    )

    impl Show for EventName

    FileKind

    pub(all) enum FileKind {
    File
    Directory
    Symlink
    Other
    } derive(Eq,
    Debug
    )

    FileStats

    pub(all) struct FileStats {
    size : Int64
    mode : Int
    modified_nanos : Int64
    } derive(Eq,
    Debug
    )

    Matcher

    pub(all) enum Matcher {
    MatchPath(String)
    MatchRecursivePath(String)
    MatchFunction((String, FileStats?) -> Bool)
    MatchRegex(
    Regexp
    )
    }

    Options

    pub(all) struct Options {
    persistent : Bool
    ignored : Array[Matcher]
    ignore_initial : Bool
    follow_symlinks : Bool
    cwd : String?
    use_polling : Bool
    interval : Int
    binary_interval : Int
    always_stat : Bool
    depth : Int
    ignore_permission_errors : Bool
    atomic_option : AtomicOption
    await_write_finish : AwaitWriteFinish
    }

    Options::default

    fn Options::default() -> Options

    RawEvent

    pub(all) struct RawEvent {
    backend_event_name : String
    watched_path : String
    event_path : String?
    platform : String
    error_message : String?
    } derive(Eq,
    Debug
    )

    WatchPaths

    pub(all) enum WatchPaths {
    One(String)
    Many(Array[String])
    } derive(Eq,
    Debug
    )

    Watcher

    pub struct Watcher {
    // private fields
    }

    Watcher::add

    fn Watcher::add(self : Watcher, paths : WatchPaths) -> Watcher raise ChokidarError

    Watcher::close

    fn Watcher::close(self : Watcher) -> Unit

    Watcher::get_watched

    fn Watcher::get_watched(self : Watcher) -> Map[String, Array[String]]

    Watcher::off

    fn Watcher::off(self : Watcher, event : EventName, callback : (Event) -> Unit) -> Watcher

    Watcher::on

    fn Watcher::on(self : Watcher, event : EventName, callback : (Event) -> Unit) -> Watcher

    Watcher::process

    fn Watcher::process(self : Watcher) -> Unit raise ChokidarError

    Watcher::remove_all_listeners

    fn Watcher::remove_all_listeners(self : Watcher) -> Watcher

    Watcher::unwatch

    fn Watcher::unwatch(self : Watcher, paths : WatchPaths) -> Watcher

    all_event_names

    fn all_event_names() -> Array[EventName]

    version

    fn version() -> String

    watch

    fn watch(paths : WatchPaths, options? : Options) -> Watcher raise ChokidarError