# How the Zakirullin Files Database Interface Works: In-Memory Session Storage Explained

> Explore the Zakirullin Files database interface. Discover how its in-memory session storage uses sync Map and temporary persistence for efficient Telegram bot state management.

- Repository: [Artem Zakirullin/files.md](https://github.com/zakirullin/files.md)
- Tags: internals
- Published: 2026-05-21

---

**The Zakirullin Files database interface implements a lightweight, per-user session store built on Go's `sync.Map` that keeps Telegram bot state entirely in memory, persisting only the last keyboard message ID to a temporary file.**

The [`zakirullin/files.md`](https://github.com/zakirullin/files.md/blob/main/zakirullin/files.md) project avoids external database dependencies by managing transient bot data through a simple yet thread-safe interface defined in [`server/db/db.go`](https://github.com/zakirullin/files.md/blob/main/server/db/db.go). This design prioritizes speed and simplicity, storing everything from pending command expectations to photo message IDs in memory structures keyed by Telegram user ID.

## Core Architecture and Design Philosophy

The database interface centers on the `DB` struct, a minimal holder for the current user's identifier that provides methods for all session state operations. Unlike traditional databases requiring connection pools or migration scripts, this implementation creates a new `DB` instance per user session via `db.NewDB(userID int64) *DB` (`server/db/db.go:27-30`).

All transient data lives in `sync.Map` structures keyed by the Telegram user ID combined with type-specific suffixes (e.g., `"12345:inputExpectations"`). This approach guarantees safe concurrent access across goroutines without explicit locking mechanisms. Only one value—the last keyboard message ID—survives process restarts, serialized to the OS temporary directory at `<tmp>/<uid>.msgid`.

## Database Operations and Method Reference

### Keyboard Message Persistence

The interface provides file-backed storage for the last interactive keyboard message, allowing the bot to edit or delete previous UI elements. The `LastKeyboardMsgID() (int, bool)` method (`server/db/db.go:31-44`) attempts to read from the temporary file, returning `false` if missing or malformed. To update this value, `SetLastKeyboardMsgID(ID int)` writes the ID to disk (`server/db/db.go:46-48`), while `DelLastKeyboardMsgID()` removes the file entirely (`server/db/db.go:50-52`).

### Input Expectations and Command Flow

To handle multi-step conversational commands, the database stores pending input expectations using `SetInputExpectation(cmd tg.Cmd)` (`server/db/db.go:64-66`). When the next user message arrives, the bot retrieves the pending command via `InputExpectation() *tg.Cmd` (`server/db/db.go:54-62`), which returns `nil` if no expectation exists. After processing, `DelInputExpectation()` clears the pending state (`server/db/db.go:68-70`).

### Message-to-Resource Mapping

The bot maintains a bidirectional mapping between Telegram message IDs and file identifiers using `SetHashOrPathByMsgID(msgID int, value string)` (`server/db/db.go:82-85`). This enables callback handlers to resolve which file or hash a user clicked. The lookup method `HashOrPathByMsgID(msgID int) (string, bool)` (`server/db/db.go:72-80`) distinguishes paths (starting with `/`) from content hashes by examining the stored string prefix.

### Command History Tracking

For "repeat last command" functionality, the interface stores the most recent command name and arguments. `SetRecentCommand(cmd string)` (`server/db/db.go:96-98`) and `SetRecentCommandParams(params []string)` (`server/db/db.go:109-111`) persist these values, retrievable via `RecentCommand() (string, bool)` (`server/db/db.go:87-94`) and `RecentCommandParams() ([]string, bool)` (`server/db/db.go:100-107`).

### Image Deduplication

To prevent re-uploading identical photos, the database tracks sent image message IDs in a per-user slice. `AddImgMsgID(msgID int)` appends new IDs (`server/db/db.go:13-21`), while `ImgMsgID() ([]int, bool)` retrieves the complete list (`server/db/db.go:23-31`). The `DelImgMsgID()` method clears this history when needed (`server/db/db.go:33-37`).

## Practical Implementation Example

The following pattern demonstrates typical usage within the bot's request handler, showing initialization, state management, and cleanup:

```go
// Initialize per-user database handle (typically in worker.go or server.go)
db := db.NewDB(update.Message.From.ID)

// Persist keyboard message for later editing
db.SetLastKeyboardMsgID(sentKeyboardMsgID)

// Set expectation for a two-step command (e.g., move file)
db.SetInputExpectation(tg.Cmd{
    Name: "mv",
    Args: []string{"/source/file.md"},
})

// Later, when processing the follow-up message
if exp := db.InputExpectation(); exp != nil {
    exp.Args = append(exp.Args, update.Message.Text)
    executeCommand(exp)
    db.DelInputExpectation()
}

// Map a rendered message to its underlying resource
db.SetHashOrPathByMsgID(renderedMsgID, "QmXyz123...")
if resource, ok := db.HashOrPathByMsgID(clickedMsgID); ok {
    if strings.HasPrefix(resource, "/") {
        // Handle as file path
    } else {
        // Handle as IPFS hash
    }
}

// Track command history for repeat functionality
db.SetRecentCommand("search")
db.SetRecentCommandParams([]string{"keyword", "limit:10"})

// Manage photo deduplication
db.AddImgMsgID(photoMsgID)
if existingIDs, ok := db.ImgMsgID(); ok {
    for _, id := range existingIDs {
        // Check for duplicates before uploading
    }
}

```

## Key Source Files and Integration Points

| File | Responsibility |
|------|---------------|
| [`server/db/db.go`](https://github.com/zakirullin/files.md/blob/main/server/db/db.go) | Core implementation of all database methods and `sync.Map` key generation |
| [`server/worker.go`](https://github.com/zakirullin/files.md/blob/main/server/worker.go) | Instantiates `DB` per incoming Telegram update and injects it into bot handlers |
| [`cmd/server/server.go`](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go) | Entry point that demonstrates database initialization patterns |
| [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go) | Consumes database methods for command routing and keyboard management |

## Summary

- The **Zakirullin Files database interface** provides a zero-dependency, in-memory session store using Go's `sync.Map` for thread safety.
- All data is **scoped to individual Telegram user IDs**, ensuring isolation between concurrent sessions.
- **Transient state** (input expectations, command history, image IDs, message mappings) lives only for the process lifetime.
- **Keyboard message IDs** persist to temporary files, enabling the bot to clean up or edit previous interface elements after restarts.
- The interface supports **conversational command flows** through input expectations and maintains **message-to-resource mappings** for interactive file browsing.

## Frequently Asked Questions

### What makes the Zakirullin Files database interface different from traditional databases?

Unlike PostgreSQL or MongoDB, this interface requires no external server, connection strings, or schema migrations. It stores everything in process memory using `sync.Map` structures, making it ideal for ephemeral bot state that doesn't require durability across deployments.

### How does the interface handle concurrent users?

Each user receives an isolated namespace within the shared `sync.Map` instances through composite keys like `"<userID>:inputExpectations"`. The `DB` struct itself is instantiated per request with the specific `UserID`, ensuring that `db.NewDB(12345)` cannot access data belonging to `db.NewDB(67890)`.

### Which data survives a bot restart?

Only the last keyboard message ID persists to disk, written to a temporary file at `<os.TempDir()>/<userID>.msgid` via `SetLastKeyboardMsgID`. All other state—including pending commands, message mappings, and command history—is intentionally ephemeral and resets when the process restarts.

### How are message IDs mapped to file paths or hashes?

The bot calls `SetHashOrPathByMsgID(msgID, value)` when rendering a file block, storing either an absolute path (starting with `/`) or a content hash. Callback handlers later use `HashOrPathByMsgID(msgID)` to resolve which resource the user interacted with, distinguishing file paths from hashes by checking for the leading slash character.