CommitStore

This page applies in every mode; nothing on it depends on the Dual-Layer Contract.

A CommitDatabase is the persistence layer — an immutable DAG of commits on disk. It exposes mutations, but every read and write goes through explicit commit ids: there is no “current commit”, no undo stack, no notifications. Useful as a backing store; awkward as the core of an interactive application.

CommitStore is the in-memory wrapper that turns a CommitDatabase into the runtime surface a Commit Application actually uses. It:

  • reconstructs and owns a current state (the commit a UI is looking at) — via CommitStateBuilder,

  • dispatches typed mutations as commits,

  • maintains an undo / redo stack,

  • notifies observers when any of the above changes,

  • navigates the DAG (multi-head, forward) and applies the default head reduction — via CommitDatabaseHelper.

This page introduces the four surfaces and points at the references for each. For full method signatures, see the CommitStore API reference.


The four surfaces

1. Lifecycle

A store starts empty. It is bound to a database (and optionally a specific state) and released the same way:

>>> from dsviper import CommitStore, CommitDatabase
>>> store = CommitStore()
>>> store.use(CommitDatabase.open("model.cdb"))
>>> store.has_database()
True
>>> store.close()

use() is the high-level entry; set_database() / set_state() exist for finer control (cdbe-style generic editors that decide “open this database at this commit”). Once a database is attached, state() returns the current CommitState and mutable_state() hands out a fresh CommitMutableState to build a commit on top.

2. Dispatch

All mutations flow through dispatch. The store builds a mutable state, runs the callable, persists the resulting commit, advances the head, and notifies observers — atomically:

>>> store.dispatch(
...     "Add Alice",
...     lambda m: m.set(Tuto.attachments.User.login, key, login),
... )

Convenience wrappers for the most common shapes:

Method

What it dispatches

dispatch_set

A single set(attachment, key, value).

dispatch_update

A path-based update(...).

dispatch_diff

The diff of an updated document vs current.

dispatch_enable_commit

Append an Enable / Disable commit that masks a target.

The single dispatch surface is what gives the Commit Application Model its Redux-like shape: every state change is an explicit, labelled action against the store, never a sideways write to the database.

3. Undo / redo and DAG navigation

The store maintains an in-memory undo stack on top of the underlying mutation DAG. Calls do not rewrite history — they move the current state along existing commits. The stack is per-session: it does not survive closing the store or restarting the application. The commits it moved through stay in the DAG and remain reachable by id — what is lost is the sequence, not the history.

>>> store.can_undo()
True
>>> store.undo()
>>> store.redo()
>>> store.use_commit(some_commit_id)        # jump anywhere
>>> store.forward()                         # onto the head; with several, walk down from here
>>> store.reduce_heads()                    # collapse multi-head to one

For the conceptual model of divergence and multi-head exploration, see Modes of Use. For the structural DAG guarantees the store leans on, see What Commit Provides.

4. The notification protocol

The store communicates with the UI through CommitStoreNotifying — a small, framework-agnostic Python protocol. Each platform adapts it to its own notification mechanism (Qt Signals, NSNotificationCenter, …). The store never imports a UI framework.

Notification

When

notify_database_did_open

A database has been attached.

notify_database_did_close

The store has been closed.

notify_state_did_change

The current state moved (commit, undo, navigation).

notify_definitions_did_change

Definitions extended at runtime.

notify_dispatch_error

A dispatched callable raised.

notify_database_will_reset / notify_database_did_reset

Reset bracket.

notify_stop_live

Live mode requested to stop.

A notifier is installed once:

>>> store.set_notifier(my_notifier)   # any CommitStoreNotifying impl

The Qt adapter — DSCommitStoreNotifier, which turns each notification into a Qt Signal — lives in dsviper-components. See The Notifier Bridge for the bridge code and the Qt integration recipe.


End-to-end

from dsviper import CommitStore, CommitDatabase

store = CommitStore()
store.use(CommitDatabase.open("model.cdb"))
store.set_notifier(my_notifier)  # observable

store.dispatch("Add Alice",
               lambda m: m.set(Tuto.attachments.User.login, key, login))

assert store.can_undo()
store.undo()
store.redo()

store.close()

A real application wraps this in an Application Context that also holds domain state and the dispatch wiring its UI calls into.


See also