# unmake

`unmake` is a Python library for recording destructive operations in **sessions** and orchestrating undos later.

It is intentionally split into:

- **Storage backend** (you choose persistence strategy, e.g., SQLite)
- **Operation payload** (you define schema and reconstruction)
- **Undo orchestration** (library provides session + operation ordering)

## Install

```bash
uv sync
```

## Quickstart

```python
from pathlib import Path

from unmake import HistoryManager, OperationRegistry, SQLiteStorage

manager = HistoryManager(SQLiteStorage("history.db"))

# Record a run
with manager.start_session(script="dangerous_script.py", args=["--force"]) as session:
    Path("a.txt").rename("b.txt")
    session.record_operation("fs.move", {"src": "a.txt", "dst": "b.txt"})

# Register reconstruction logic for undo
class MoveUndo:
    def __init__(self, payload: dict):
        self.src = payload["src"]
        self.dst = payload["dst"]

    def undo(self) -> None:
        Path(self.dst).rename(self.src)

registry = OperationRegistry()
registry.register("fs.move", MoveUndo)

# Undo all operations in reverse order
session_id = manager.list_sessions(limit=1)[0].session_id
manager.undo_session(session_id, registry)
```

## Development workflow

This project is configured for `uv`, `make`, and PEP 621 metadata.

```bash
make install
make test
make lint
make build
```

### Publish

```bash
# Configure token in env first
# export UV_PUBLISH_TOKEN=...
make publish
```

### Bump version

```bash
make bump-patch
make bump-minor
make bump-major
```

## Thread Safety

### InMemoryStorage
- Not thread-safe by default
- Use external locking for concurrent access:

```python
import threading

from unmake import HistoryManager, InMemoryStorage

storage = InMemoryStorage()
manager = HistoryManager(storage)
lock = threading.Lock()

with lock:
    with manager.start_session(script="safe.py") as session:
        session.record_operation("demo.noop", {"value": 1})
```

### SQLiteStorage
- Thread-safe for standard usage with one connection per method call
- SQLite handles concurrent access with internal locking
- Performance may degrade under very high contention
- Prefer one `HistoryManager` per thread

### Best Practices
- Use `SQLiteStorage` for multi-threaded applications
- Use `InMemoryStorage` only with external locking when shared across threads
- Avoid sharing a mutable `HistoryManager` + `InMemoryStorage` pair across threads

## CLI

Install project dependencies and run:

```bash
unmake --db history.db list-sessions
unmake --db history.db show-session <session-id>
unmake --db history.db undo <session-id>
```
