How Meetily Structures Meetings, Transcripts, and Summaries with SQLite: Database Architecture Explained

Meetily stores meetings, transcripts, and AI-generated summaries in a local SQLite database using type-safe Rust models and a centralized DatabaseManager that handles migrations, WAL recovery, and atomic transactions without external services.

Meetily is an open-source AI meeting assistant that prioritizes data privacy through a local-first architecture. Understanding how Meetily's database module structures meetings, transcripts, and summaries with SQLite reveals a lightweight yet robust persistence layer built on Rust's sqlx crate. The implementation resides entirely within the Tauri backend at frontend/src-tauri/src/database, providing relational data management through five normalized tables and corresponding Rust structs.

SQLite Schema Design for Meeting Data

The database file meeting_minutes.sqlite lives in the user's Tauri application data directory. The initial migration script at frontend/src-tauri/migrations/20250916100000_initial_schema.sql (lines 1‑73) establishes a relational schema optimized for meeting workflows:

Table Purpose Key Relationships
meetings Represents a single meeting session id (Primary Key)
transcripts Stores raw transcription text and optional AI outputs meeting_id (Foreign Key → meetings.id)
summary_processes Tracks async AI summarization jobs meeting_id (Primary Key + Foreign Key)
transcript_chunks Holds chunked transcript data for processing meeting_id (Foreign Key)
settings Persists user-level LLM and STT configuration Standalone configuration table

The transcripts table includes optional columns for summary, action_items, and key_points, allowing the schema to accommodate both raw transcription data and processed AI outputs. The summary_processes table stores processing status and JSON-encoded results, enabling the UI to track summarization progress in real-time.

Rust Data Models with sqlx

Type-safe database access is implemented in frontend/src-tauri/src/database/models.rs, where structs derive sqlx::FromRow to map directly to SQLite rows:

  • MeetingModel (lines 5‑12) mirrors the meetings table with fields for id, title, created_at, updated_at, and optional folder_path.
  • Transcript (lines 24‑38) maps to the transcripts table and adds audio-synchronization fields including audio_start_time, audio_end_time, and duration.
  • SummaryProcess (lines 40‑55) reflects the summary_processes table, storing status, timestamps, and a JSON result field for AI outputs.
  • TranscriptChunk (lines 58‑67) represents chunked data storage with transcript_text, model, chunk_size, and overlap parameters.

These models enable compile-time checked SQL queries through sqlx macros, eliminating runtime schema mismatches between Rust code and the SQLite database.

Database Manager Implementation

All database interactions flow through the DatabaseManager defined in frontend/src-tauri/src/database/manager.rs. This singleton manages connection pooling, error recovery, and schema migrations.

Connection Initialization and Migrations

The manager initializes the SQLite pool via DatabaseManager::new_from_app_handle(&app_handle), which automatically resolves the Tauri data directory. If the database file does not exist, it creates the file and executes the embedded migration bundle (sqlx::migrate!("./migrations")) to establish the schema (lines 19‑36).

WAL Mode Recovery

To prevent corruption issues common in SQLite write-ahead logging mode, the manager implements startup guards that detect invalid WAL/SHM files. It removes corrupted auxiliary files and retries the connection until successful (lines 72‑108).

Legacy Migration Support

For users upgrading from Meetily's archived Python backend, the manager detects legacy .db files, copies them to the new Tauri data directory location, and opens them using standard SQLite handling (lines 54‑63).

Transactional Operations

The with_transaction method provides atomic execution for multi-statement operations, ensuring data consistency when inserting related records across the meetings, transcripts, and summary_processes tables (lines 64‑81).

Practical Data Flow Examples

The following Rust examples demonstrate how Meetily's database module structures meetings, transcripts, and summaries in practice.

Creating a Meeting Record

// Initialize the database manager from Tauri app handle
let db = DatabaseManager::new_from_app_handle(&app_handle).await?;

// Construct a new meeting model
let meeting = MeetingModel {
    id: uuid::Uuid::new_v4().to_string(),
    title: "Team Stand-up".into(),
    created_at: DateTimeUtc::from(Utc::now().naive_utc()),
    updated_at: DateTimeUtc::from(Utc::now().naive_utc()),
    folder_path: None,
};

// Insert into the meetings table
sqlx::query_as!(
    MeetingModel,
    "INSERT INTO meetings (id, title, created_at, updated_at) VALUES (?1, ?2, ?3, ?4)",
    meeting.id,
    meeting.title,
    meeting.created_at.0,
    meeting.updated_at.0
)
.execute(db.pool())
.await?;

Storing Transcript Data

// Prepare transcript with audio synchronization metadata
let transcript = Transcript {
    id: uuid::Uuid::new_v4().to_string(),
    meeting_id: meeting.id.clone(),
    transcript: raw_text,
    timestamp: Utc::now().to_rfc3339(),
    summary: None,
    action_items: None,
    key_points: None,
    audio_start_time: Some(start_sec),
    audio_end_time: Some(end_sec),
    duration: Some(end_sec - start_sec),
};

// Persist to the transcripts table linked by meeting_id
sqlx::query_as!(
    Transcript,
    "INSERT INTO transcripts (id, meeting_id, transcript, timestamp, audio_start_time, audio_end_time, duration) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
    transcript.id,
    transcript.meeting_id,
    transcript.transcript,
    transcript.timestamp,
    transcript.audio_start_time,
    transcript.audio_end_time,
    transcript.duration
)
.execute(db.pool())
.await?;

Updating AI Summary Status

// Update the summary_processes table when LLM processing completes
sqlx::query!(
    "UPDATE summary_processes SET status = ?1, result = ?2, updated_at = ?3 WHERE meeting_id = ?4",
    "completed",
    json_result,
    Utc::now().to_rfc3339(),
    meeting.id
)
.execute(db.pool())
.await?;

Summary

  • Meetily uses a local SQLite database (meeting_minutes.sqlite) stored in the Tauri data directory to ensure complete data privacy without external database dependencies.
  • The schema separates concerns into five normalized tables: meetings, transcripts, summary_processes, transcript_chunks, and settings tables.
  • Rust models in models.rs provide type-safe mapping through sqlx::FromRow, supporting fields for audio synchronization and JSON-stored AI results.
  • The DatabaseManager in manager.rs handles connection pooling, automatic migrations, WAL file recovery, and legacy database upgrades from the Python backend.
  • All operations use parameterized queries through sqlx macros, preventing SQL injection while enabling compile-time schema validation.

Frequently Asked Questions

Where does Meetily store the SQLite database file?

The database is stored as meeting_minutes.sqlite in the user's Tauri application data directory, resolved automatically by app_handle.path_resolver().app_data_dir(). This ensures the data remains local to the device and is not synced to external servers.

How does Meetily handle database schema migrations?

Meetily uses sqlx's embedded migration system defined in frontend/src-tauri/migrations/20250916100000_initial_schema.sql. On startup, the DatabaseManager runs sqlx::migrate!("./migrations") to apply any pending schema changes automatically, versioning the database alongside the application code.

What happens if the SQLite WAL file becomes corrupted?

The DatabaseManager includes startup guards that detect corrupted WAL (Write-Ahead Log) and SHM files. It removes these corrupted auxiliary files and re-establishes the database connection, allowing SQLite to regenerate fresh transaction logs without data loss in the main database file.

Can Meetily import data from the previous Python backend version?

Yes, the DatabaseManager detects legacy .db files from the archived Python backend during initialization. It copies the existing data to the new Tauri data directory location (meeting_minutes.sqlite) before opening the connection, facilitating seamless migration for existing users.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →