What It Does
Files on a mounted backend change outside your workspace all the time: a teammate uploads to Nextcloud, a pipeline rewrites an S3 object, or a cron job deletes a report.watch turns those changes into an async event stream.
Scopes
The root’s shape defines the depth, using GNU shell glob semantics.* never
crosses /, and matching happens at delivery time, so newly created files can
match.
The Event
FileMetadata carries fingerprint, size, and modified. Producers fill
only what their signal honestly knows. A listing walk usually supplies all
three; a webhook payload may supply none.
Events are level-triggered: an event says what is dirty, not every
intermediate edit. Read current content through the workspace after receiving
one. Mirage invalidates the changed path and every cached ancestor listing up
to its mount root before the event reaches a subscriber.
unknown is the overflow signal. If a burst exceeds the queue cap, pending
events collapse into one unknown event per watch root, meaning “re-inventory
this subtree.” Precision degrades, but dirtiness is not lost.
Push Mode
Map a provider webhook or queue message to aFileEvent, then inject it with
ws.notify:
Pull Mode
Backends that implementdeltaHook() answer one question: what changed under
this root since the last checkpoint? Nextcloud ships this capability today. A
baseline pull (checkpoint === null) establishes state and emits no changes.
Queues
Eachwatch() owns its own queue. notify fans an event out to every matching
queue, so overlapping watches consume independently and a slow consumer only
overflows its own queue.
The default RAMWatchQueue keeps one pending change per path. Create followed
by update stays create; create followed by delete cancels; delete followed by
create becomes update. Overflow policy is collapse (default), drop_oldest,
or error.
Attach a custom runtime before the first watch() or notify() call:
WatchQueue (push, pop, pending, clear, and
close) can replace the RAM queue. This is the extension point for a durable
Redis, SQS, or database-backed queue.
Call await ws.detachWatchRuntime() to close subscriber queues and end active
watch loops cleanly without closing the workspace. The next watch() or
notify() lazily creates a fresh default runtime.
Scope Notes
- Watch roots are fixed at subscription time. Start a new
watch()to change the scope. - One watch may span mounts, including nested mounts. Each event is invalidated on the longest-prefix mount that owns its path.
- Delivery works on every backend. Nextcloud is currently the first backend
with a shipped
deltaHook(); see the Watch Matrix. - Events are in-memory and at-most-once per subscriber unless you provide a durable queue implementation.