PhoenixKitKnowledgeBase.Collab.Store (PhoenixKitKnowledgeBase v0.1.0)

Copy Markdown View Source

Where a live document goes: through Notes, onto disk, and into the index.

Leaf.Collab.Store.File ships with Leaf and describes exactly this arrangement — the file is the document, its hash is checked before writing, and a note edited from outside is answered with a conflict rather than overwritten. It is not used here for one reason: it writes the file and stops there.

A note is not only its bytes. Its title, front matter, tags, outgoing links and search vector are all derived from those bytes, and writing the file without re-deriving them leaves Postgres describing a note that no longer exists — invariant 1 says the index is disposable, not that it may be wrong. Notes.update_content/4 already writes to disk and indexes what landed, and already carries the stale guard, so this store is a thin adapter onto it.

The two callbacks

save/2 is a no-op. It is called on every change and is meant for a store cheap enough to write to constantly; a file plus a reindex is the expensive copy, which is what flush/2 is for. If losing seconds of a session ever matters more than it does now, save/2 is where a row in a table would go.

flush/2 runs when the writing pauses, at a bounded interval while it continues, and once when the room stops.

Conflict

{:error, :conflict} means the file moved underneath the session — someone edited the note outside the editor, or a rescan re-stamped it. The room stops flushing and keeps the document, which is the right answer: the work in the file is somebody's, and this session's copy is not more entitled to exist. Resolving it needs a person.

The note row is re-read on every flush rather than held, because the previous flush changed it: Notes.update_content/4 compares the file's hash against the row's, so a stale row would report a conflict against this session's own last write.