# Tutorial

This tutorial walks through a complete example: creating a data model, setting
up a database, and performing operations with CommitDatabase.

> **Prerequisites**: This tutorial assumes you have read [DSM](dsm.md) and
> understand the basics of DSM syntax and the assemble → parse → introspect
> workflow.

## The User/Login Model

We'll create a simple model with Users who have Login credentials and Identity
information.

### Step 1: Define the Data Model

Create a file `model.dsm`:

```dsm
namespace Tuto {f529bc42-0618-4f54-a3fb-d55f95c5ad03} {

concept User;

struct Login {
    string nickname;
    string password;
};

struct Identity {
    string firstname;
    string lastname;
};

attachment<User, Login> login;
attachment<User, Identity> identity;

};
```

### Step 2: Validate the Model

Check the DSM syntax:

```bash
python3 tools/dsm_util.py check model.dsm
```

### Step 3: Create a Database

Create a Commit database that embeds the definitions:

```bash
python3 tools/dsm_util.py create_commit_database model.dsm model.cdb
```

### Step 4: Open and Explore

Open the database in Python and list the types and attachments it carries:

```{doctest}
>>> sorted(str(t) for t in db.definitions().types())
['Tuto::Account', 'Tuto::Identity', 'Tuto::Login', 'Tuto::Status', 'Tuto::Texture', 'Tuto::Thumbnail', 'Tuto::User']

>>> sorted(str(a).split()[-1] for a in db.definitions().attachments())
['Tuto::account', 'Tuto::avatar', 'Tuto::identity', 'Tuto::login', 'Tuto::portrait']
```

### Step 5: Inject Constants

`db.definitions().inject()` makes types accessible as constants in the
caller's namespace (already done in this tutorial's setup):

```{doctest}
>>> TUTO_A_USER_LOGIN
attachment<User, Login> Tuto::login

>>> TUTO_S_LOGIN
Tuto::Login
```

**Naming convention**: Constants follow the pattern `{NAMESPACE}_{KIND}_{NAME}`:

| Kind        | Prefix | Example                 | Description      |
|-------------|--------|-------------------------|------------------|
| Attachment  | `_A_`  | `TUTO_A_USER_LOGIN`     | Attachment type  |
| Structure   | `_S_`  | `TUTO_S_LOGIN`          | Structure type   |
| Enumeration | `_E_`  | `TUTO_E_STATUS`         | Enumeration type |
| Concept     | `_C_`  | `TUTO_C_USER`           | Concept type     |
| Path        | `_P_`  | `TUTO_P_LOGIN_NICKNAME` | Path to field    |

### Step 6: Create a Key and Document

Create a new User key. `instance_id()` returns the underlying UUID
(randomly generated, so we just check the type here):

```{doctest}
>>> key = TUTO_A_USER_LOGIN.create_key()
>>> isinstance(key.instance_id(), ValueUUId)
True
```

Create a Login document:

```{doctest}
>>> login = TUTO_A_USER_LOGIN.create_document()
>>> login
{nickname='', password=''}

>>> login.nickname = "zoop"
>>> login.password = "robust"
>>> login
{nickname='zoop', password='robust'}
```

### Step 7: Commit to Database

Create a mutable state, associate the document with the key, and commit:

```{doctest}
>>> mutable_state = CommitMutableState(db.initial_state())

>>> mutable_state.attachment_mutating().set(TUTO_A_USER_LOGIN, key, login)

>>> commit_id = db.commit_mutations("First Commit", mutable_state)
>>> isinstance(commit_id, ValueCommitId) and len(str(commit_id)) == 40
True
```

```{important}
**The Dual-Layer Contract**

CommitDatabase guarantees structural integrity but NOT semantic integrity.
When concurrent streams converge, mutations on non-existent documents or unresolved paths are silently ignored.
Your application must validate business rules when consuming state.

See [The Dual-Layer Contract](commit_contract.md).
```

### Step 8: Read from Database

Read the document back:

```{doctest}
>>> state = db.state(commit_id)
>>> result = state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
>>> result
Optional({nickname='zoop', password='robust'})

>>> result.unwrap()
{nickname='zoop', password='robust'}
```

### Step 9: Update a Field

Update using a path-based setter. Capture the new commit id returned by
`commit_mutations` — the database's mutating APIs don't auto-advance an
implicit "current commit" pointer, so chaining commits requires the
explicit id:

```{doctest}
>>> mutable_state = CommitMutableState(db.state(commit_id))
>>> mutable_state.attachment_mutating().update(TUTO_A_USER_LOGIN, key, TUTO_P_LOGIN_NICKNAME, "zoopy")
>>> updated_id = db.commit_mutations("Update Nickname", mutable_state)
```

### Step 10: View History

Read from different commits:

```{doctest}
>>> state = db.state(updated_id)
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
Optional({nickname='zoopy', password='robust'})

>>> state = db.state(commit_id)
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
Optional({nickname='zoop', password='robust'})

>>> state = db.initial_state()
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
nil
```

### Step 11: Inspect Commit Headers

```{doctest}
>>> header = db.commit_header(updated_id)
>>> header.label()
'Update Nickname'
>>> header.parent_commit_id() == commit_id
True
```

## Two Approaches

Viper supports two ways to work with your data model:

### Dynamic API (Above)

Use Viper's runtime metadata directly:

- `DSMBuilder.assemble(path).parse()` → work with types at runtime
- Flexible, interpretive, Python-friendly
- No code generation needed

### Static API (Generated)

Generate infrastructure code from your DSM:

```bash
python3 tools/dsm_util.py create_python_package model.dsm
```

This creates a Python package with:

- Type-safe classes for concepts and structures
- Generated accessors for attachments
- IDE autocompletion support

```pycon
>>> import model.attachments as ma

# Use generated types
>>> key = ma.Tuto_UserKey.create()
>>> login = ma.Tuto_Login()
>>> login.nickname = "user"

# Use generated accessors
>>> state = CommitMutableState(db.state(db.last_commit_id()))
>>> ma.tuto_user_login_set(state.attachment_mutating(), key, login)
```

```{note}
The Static API snippet above requires running Kibo first to generate the
`model.attachments` package. It is illustrative — see the
[Kibo](../tools/kibo.md) chapter for the full code-generation workflow.
```

Both approaches use the same underlying Viper runtime.

## What's Next

- [Database](database.md) - Database and CommitDatabase in detail
- [Blobs](blobs.md) - Working with binary data
- [Types and Values](types_values.md) - Deep dive into the type system
