boxlang-file-watchers
Use this skill when implementing BoxLang filesystem watchers: watcherNew/watcherStart/watcherStop lifecycle BIFs, WatcherInstance APIs, event payload handling, recursive directory monitoring, debounce/throttle/atomicWrites tuning, errorThreshold auto-stop behavior, and listener patterns (closure, struct, class, class name string).
BoxLang File Watchers
Overview
BoxLang includes a runtime watcher service for real-time filesystem automation.
Watchers are ideal for:
- hot reload and development workflows
- build pipelines and asset regeneration
- file drop ingestion and ETL triggers
- reacting to create/modify/delete events
Application.bx Integration
You can register app-scoped watchers directly in Application.bx with this.watchers.
For full descriptor behavior (app discovery, multi-app isolation, lifecycle), see
application-descriptor.
class {
this.name = "MyApp"
watcherListener = new app.listeners.HotReloadListener()
this.watchers = {
sourceWatcher : {
paths : [ expandPath( "./src" ) ],
listener : watcherListener,
recursive : true,
debounce : 250,
atomicWrites : true,
errorThreshold : 10
}
}
}
Supported watcher definition keys commonly include paths, listener, recursive, debounce, throttle, atomicWrites, and errorThreshold.
Event Model
Event kinds:
createdmodifieddeletedoverflow
Listener payload struct keys:
kindpath(absolute path; blank for overflow)relativePath(relative to watcher root; blank for overflow)watchRoot(blank for overflow)timestamp(ISO-8601)
Creating a Watcher
watcherNew() registers a watcher but does not automatically start it unless you chain .start() on the returned instance.
watcher = watcherNew(
name = "sourceWatcher",
paths = [ "./src" ],
listener = ( event ) => {
println( "[#event.kind#] #event.relativePath#" )
},
recursive = true,
debounce = 250,
throttle = 0,
atomicWrites = true,
errorThreshold = 10,
force = false
).start()
Accepted paths forms:
- string path
- array of string paths
Listener Forms
1) Closure / Lambda
listener = ( event ) => {
if ( event.kind == "modified" ) {
recompileAsset( event.path )
}
}
2) Struct Listener
listener = {
onCreate: ( event ) => onCreated( event ),
onModify: ( event ) => onModified( event ),
onDelete: ( event ) => onDeleted( event ),
onOverflow: ( event ) => fullResync(),
onEvent: ( event ) => writeLog( text: "Watcher event: #event.kind#", log: "async" )
}
3) Class Name String or Class Instance
watcherNew(
name = "prodWatcher",
paths = [ "/srv/import" ],
listener = "app.listeners.ImportWatcher"
)
watcherNew(
name = "prodWatcher",
paths = [ "/srv/import" ],
listener = new ImportWatcher()
)
For class listeners, implement onEvent( required struct event ) as the baseline and optionally:
onCreate( event )onModify( event )onDelete( event )onOverflow( event )onError( error )
Watcher BIFs
watcherNew( name?, paths, listener, recursive=true, debounce=0, throttle=0, atomicWrites=true, delay=0, errorThreshold=10, force=false )watcherStart( name )watcherStop( name, force=false )watcherRestart( name )watcherGet( name )watcherGetAll()watcherList()watcherExists( name )watcherShutdown( name )watcherStopAll( force=false )watcherShutdownAll( force=false )
Lifecycle distinctions:
stop= stop execution but keep registrationshutdown= stop + unregister (destructive)
WatcherInstance API
watcherNew() and watcherGet() return WatcherInstance:
start()stop( force=false )restart()isRunning()/isStopped()getState()/getStateAsString()getName()getWatchPaths()getListener()getStats()
getStats() includes:
namestate(CREATED,RUNNING,STOPPED)pathsrecursivedebouncethrottleatomicWriteserrorThresholdconsecutiveErrors
Tuning and Safety
debounce: suppress rapid duplicate events on the same path.throttle: limit event frequency per path.atomicWrites: reduce noisy editor temp-write sequences.errorThreshold: auto-stop watcher after consecutive listener failures.forcestop: faster stop via interruption and watch service close; may drop in-flight events.
Production Pattern
watcher = watcherNew(
name = "inbound-files",
paths = [ "/data/inbound" ],
listener = new InboundFileListener(),
recursive = true,
debounce = 300,
atomicWrites = true,
errorThreshold = 5
).start()
if ( !watcher.isRunning() ) {
watcher.restart()
}
Best Practices
- Keep listener logic fast; offload expensive work to executors/futures.
- Use class listeners for reusable, testable production handlers.
- Always log errors in
onErrorand trackconsecutiveErrors. - Use absolute paths in production to avoid cwd ambiguity.
- Use
watcherStopAll()in graceful shutdown flows.
Troubleshooting
- No events firing: confirm path exists and is a directory.
- Too many duplicate events: increase
debounce, enableatomicWrites, and considerthrottle. - Watcher disappears: check if
watcherShutdown()orwatcherShutdownAll()was invoked. - Unexpected stop: inspect listener exceptions; watcher may have hit
errorThreshold.