CommitStore

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:

  • owns a current state (the commit a UI is looking at),

  • dispatches typed mutations as commits,

  • maintains an undo / redo stack,

  • notifies observers when any of the above changes,

  • exposes DAG navigation (multi-head, merge, forward).

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_A_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.

>>> store.can_undo()
True
>>> store.undo()
>>> store.redo()
>>> store.use_commit(some_commit_id)        # jump anywhere
>>> store.forward()                         # follow to the most plausible head
>>> store.reduce_heads()                    # collapse multi-head to one

For the conceptual model of divergence and multi-head exploration, see Modes of Use. For the underlying DAG guarantees the store leans on, see the Dual-Layer Contract.

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_A_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