Skip to content

App-State Versioning

Graft versions application state, not just database bytes.

For a SQLite-backed app, durable state often includes database files, settings, attachments, generated assets, imports, and other files the database points at. Graft records those paths together so a commit describes one state the app can open.

The key difference from Git is not that Git cannot store SQLite files. It can. The difference is that Git’s first-class versioning experience is built around plain text, while Graft makes SQLite databases first-class versioned app-state objects. A database path can have snapshot identity, row-level diffs, row-aware merge, and structured conflict output.

app-state commit
data.sqlite
search.sqlite
settings.json
attachments/report.pdf

SQLite rows frequently reference files outside the database:

data.sqlite
attachments/3f/report.pdf
attachments/8a/avatar.png
assets/icon.png

If a rollback restores data.sqlite without restoring the matching attachments, the app can open broken references. If a branch updates an attachment without the row that points at it, the app can show the wrong state.

Graft’s invariant is:

A commit records one coherent application state.

Graft uses Git-like repository concepts for app state:

  • worktree: app data files on disk
  • index: staged database snapshots and file artifacts
  • commit: an immutable app-state version
  • refs: branches, tags, and remote-tracking refs
  • remotes: shared storage for repository history and payloads

SQLite still owns transaction atomicity. A committed SQLite transaction is not a Graft commit until the path is staged and committed at the repository layer.

In a source-code repository, users usually decide which files enter version control. In a SQLite-backed app, the state boundary is often more like a product contract: the developer knows which databases, resource directories, and internal files form a recoverable app state.

Embedded apps can encode that contract in repository config:

  • track.default_roots: developer-defined default app-state roots.
  • track.user_roots: user-owned space content explicitly opted into versioning.
  • files.external_paths: file-content storage policy, not tracking scope.

This lets app-private state enter status and commits by default while ordinary user files in the same space do not pollute the app-state view. See Configuration for the detailed keys.

Graft distinguishes content kind from storage strategy.

KindMeaning
sqlite_databaseA database path tracked as a SQLite snapshot.
text_fileA UTF-8 app file.
binary_fileA binary app file.
StorageMeaning
sqlite_snapshotSnapshot descriptor backed by Graft page/log storage.
inlineSmall text bytes stored directly in the object database.
externalPayload bytes stored by content hash outside normal commit objects.

This split lets apps render “what is this?” and “where are the bytes?” as separate concerns.

The CLI is useful for local development and scripts:

Terminal window
graft status --json
graft add --all
graft commit -m "save checkpoint"

Applications can invoke the CLI with --json to consume the same repository model without scraping terminal text. They continue to open normal SQLite worktree files through their existing database library. The optional Graft VFS is a separate live page-storage mode, not a second repository command API.