Files
kjol/tools/tsgo/internal/fswatch/README.md
2026-07-09 16:50:43 -04:00

2.7 KiB

fswatch

A filesystem watcher for Go. Pure Go, no cgo.

A Go port of the C++ @parcel/watcher, with substantial modifications. See CHANGES.md for the list of differences and bugfixes.

GOOS Watcher
linux fanotify (default, kernel ≥ 5.13), inotify
darwin FSEvents (default), kqueue
windows ReadDirectoryChangesW
freebsd, openbsd, netbsd, dragonfly kqueue

Usage

package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"

	"github.com/microsoft/typescript-go/internal/fswatch"
)

func main() {
	dir, _ := os.Getwd()

	sub, err := fswatch.Default().WatchDirectory(dir, func(events []fswatch.Event, err error) {
		if err != nil {
			log.Println("watch error:", err)
			return
		}
		for _, e := range events {
			fmt.Printf("%s %s\n", e.Kind, e.Path)
		}
	})
	if err != nil {
		log.Fatal(err)
	}
	defer sub.Close()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	<-c
}

Picking a watcher

Default() picks the best watcher for the current OS. To use a specific one:

sub, err := fswatch.Inotify().WatchDirectory(dir, callback, fswatch.WithRecursive())

All watchers exist on every platform. Use Available() to check support at runtime, or just call WatchDirectory; it returns ErrUnavailable if the watcher isn't supported.

Error handling

Errors are delivered through the callback. Use errors.Is to distinguish them:

  • ErrOverflow: some events were lost (kernel queue overflow). The watch is still active; rescan the directory to catch up.
  • ErrWatchTerminated: the watch is dead (e.g. directory deleted). No further events will arrive. Call Close to clean up.
if errors.Is(err, fswatch.ErrOverflow) {
    rescanDir(dir)
    return
}
if errors.Is(err, fswatch.ErrWatchTerminated) {
    log.Println("watch terminated:", err)
    sub.Close()
    return
}

Behavior notes

  • Events arriving in quick succession are batched before delivery.

  • Event order within a batch is not guaranteed.

  • The callback runs on a library goroutine, not the caller's. Each watch's callback is serialized (never concurrent with itself).

  • Paths in events are absolute. Resolve symlinks before subscribing; backends report canonical paths:

    realDir, err := filepath.EvalSymlinks(dir)