Commit System¶
The commit system provides transactional persistence with history tracking.
When to use: Use CommitDatabase for versioned persistence with history, concurrent
streams, and sync. Every change creates a commit, enabling undo/redo and concurrent editing.
CommitDatabase¶
CommitDatabase provides versioned data storage:
History is preserved as a DAG of commits
You can read from any point in history
Important
The Dual-Layer Contract
CommitDatabase guarantees structural integrity (deterministic merge, DAG consistency) but NOT semantic integrity. When concurrent streams converge, mutations are applied automatically without checking business rules—mutations on non-existent documents or unresolved paths are silently ignored.
Your application must validate data when consuming state. The validation cost scales with your data model complexity and business rules, not with Viper.
See The Dual-Layer Contract for details.
Opening a CommitDatabase¶
# Open existing database
>>> db = CommitDatabase.open("model.cdb")
# Create new database (with embedded definitions)
# Use: python3 tools/dsm_util.py create_commit_database model.dsm model.cdb
Reading State¶
Get a read-only state from any commit:
# State at last commit
>>> state = db.state(db.last_commit_id())
# State at first commit
>>> state = db.state(db.first_commit_id())
# Initial state (before any commits)
>>> state = db.initial_state()
AttachmentGetting Interface¶
Read attachments via attachment_getting():
>>> getting = state.attachment_getting()
# Get a document
>>> doc = getting.get(attachment, key)
>>> doc
Optional({...})
# Get all keys
>>> keys = getting.keys(attachment)
Mutations¶
Create a mutable state and apply changes:
>>> mutable_state = CommitMutableState(db.state(db.last_commit_id()))
>>> mutating = mutable_state.attachment_mutating()
# Set a document
>>> mutating.set(attachment, key, document)
# Update a field (path-based)
>>> mutating.update(attachment, key, path, new_value)
Note: CommitDatabase tracks history via mutations from CommitMutableState.
Committing¶
Commit mutations to the database:
>>> commit_id = db.commit_mutations("Commit message", mutable_state)
>>> commit_id
'87b2b1d275f0ac3b2389b18f58d7abe0214c2493'
Complete Example¶
>>> from dsviper import *
# Open database and inject definitions
>>> db = CommitDatabase.open("model.cdb")
>>> db.definitions().inject()
# Create a new key and document
>>> key = TUTO_A_USER_LOGIN.create_key()
>>> login = TUTO_A_USER_LOGIN.create_document()
>>> login.nickname = "alice"
>>> login.password = "secret"
# Commit the document
>>> mutable = CommitMutableState(db.state(db.last_commit_id()))
>>> mutable.attachment_mutating().set(TUTO_A_USER_LOGIN, key, login)
>>> db.commit_mutations("Add Alice", mutable)
# Read it back
>>> state = db.state(db.last_commit_id())
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
Optional({nickname='alice', password='secret'})
Path-Based Mutators¶
Instead of replacing entire documents with set(), path-based mutators use
Paths to target specific locations. This enables path-based merging when multiple
users edit concurrently.
Mutator |
Target |
Operation |
|---|---|---|
|
Field |
Replace value at path |
|
Set |
Add elements |
|
Set |
Remove elements |
|
Map |
Add key-value pairs |
|
Map |
Remove keys |
|
Map |
Update existing key |
|
XArray |
Insert at position |
|
XArray |
Update at position |
|
XArray |
Remove at position |
Field Update¶
>>> mutating.update(
...
TUTO_A_USER_LOGIN, key,
...
TUTO_P_LOGIN_NICKNAME, "alice_updated"
... )
Set Operations¶
# Add elements to a set field
>>> mutating.union_in_set(
...
GRAPH_A_TOPOLOGY, graph_key,
...
GRAPH_P_TOPOLOGY_VERTEX_KEYS, {new_vertex_key}
... )
# Remove elements from a set field
>>> mutating.subtract_in_set(
...
GRAPH_A_TOPOLOGY, graph_key,
...
GRAPH_P_TOPOLOGY_VERTEX_KEYS, {deleted_vertex_key}
... )
Map Operations¶
# Add entries to a map field
>>> mutating.union_in_map(
...
attachment, key, path_to_map,
...
{"new_key": "new_value"}
... )
Why Paths Matter¶
When two users edit different fields simultaneously:
User A: update(attachment, key, path_to_name, "Alice")
User B: update(attachment, key, path_to_email, "bob@example.com")
After merge: Both updates apply (disjoint paths)
With set(), one user’s changes would overwrite the other’s.
Commit History¶
Inspect commit metadata:
>>> header = db.commit_header(db.last_commit_id())
>>> header.label()
'Update nickname'
>>> header.parent_commit_id()
'87b2b1d275f0ac3b2389b18f58d7abe0214c2493'
Navigate history:
# Compare states at different commits
>>> state1 = db.state(db.first_commit_id())
>>> state2 = db.state(db.last_commit_id())
Embedded Definitions¶
CommitDatabase stores its definitions:
>>> defs = db.definitions()
>>> defs.types()
[Tuto::User, Tuto::Login, Tuto::Identity]
>>> defs.inject()
# Now TUTO_A_USER_LOGIN etc. are available
What’s Next¶
Blobs - Binary data storage
Serialization - JSON and binary encoding