How Meetily's SQLite Database Stores Meeting Data, Transcripts, and Summaries Locally
Meetily persists all meeting‑related information in a local SQLite database managed by the DatabaseManager in its Tauri backend, using migrations for schema version control and WAL mode for reliable writes.
Meetily stores every aspect of your meetings—metadata, transcripts, speaker information, and AI-generated summaries—in a local SQLite database, ensuring complete data privacy and offline access. This architecture centers on the DatabaseManager struct in frontend/src-tauri/src/database/manager.rs, which handles database lifecycle, connection pooling, migrations, and write-ahead logging. Below is a complete breakdown of how Meetily implements this local-first storage system.
Database Location and Initialization
Meetily places the SQLite database file in the user's application-data directory:
- macOS:
~/Library/Application Support/Meetily/meeting_minutes.sqlite - Windows/Linux: Equivalent platform-specific directories
The DatabaseManager::new_from_app_handle method constructs this path, checks for legacy .db files to migrate, creates the database if missing, and immediately applies all pending migrations. This guarantees that the schema always matches the current application version.
// Database initialization sequence (from manager.rs)
let db = DatabaseManager::new_from_app_handle(&app_handle).await?;
Connection Management and Transactions
The DatabaseManager creates a pooled SqlitePool via SqlitePool::connect, which all queries share. For atomic operations, the manager provides with_transaction, an async helper that automatically commits on success or rolls back on failure.
// Example transaction usage pattern
db.with_transaction(|txn| async move {
// Multiple operations that succeed or fail together
sqlx::query!("INSERT INTO ...").execute(txn).await?;
sqlx::query!("UPDATE ...").execute(txn).await?;
Ok(())
}).await?;
Write-Ahead Logging (WAL) and Durability
Meetily uses SQLite's WAL mode for better concurrency and crash recovery. The DatabaseManager implements specific safeguards:
- Startup check: Detects and removes corrupted
.walor.shmfiles, then re-opens the database - Shutdown cleanup: Calls
PRAGMA wal_checkpoint(TRUNCATE)to flush pending writes and delete WAL files
These measures prevent data corruption and reclaim disk space predictably.
Database Schema and Migrations
All schema changes are version-controlled in frontend/src-tauri/migrations/ and applied via sqlx::migrate!("./migrations"). The migration history reveals how Meetily's data model evolved:
Core Tables (Initial Schema)
20250916100000_initial_schema.sql establishes the foundation:
meetings—id,title,start_ts,end_tstranscripts—id,meeting_id,speaker_id,text,tsspeakers—id,name
Extended Tables (Subsequent Migrations)
| Migration File | Purpose |
|---|---|
20251101000000_add_summary_backup.sql |
Creates summaries table for generated meeting summaries |
20251223000000_add_meeting_notes.sql |
Adds meeting_notes table for free-form notes linked to meetings |
20251229000000_add_gemini_api_key.sql |
Stores Gemini API key in settings table |
20250920155811_add_openrouter_api_key.sql |
Adds OpenRouter API key to settings |
20251006000000_add_audio_sync_fields.sql |
Extends meetings with audio_offset and sync metadata |
20251010153942_add_ollama_endpoint.sql |
Ollama endpoint configuration in settings |
20251105120000_add_pro_license_custom_openai.sql |
Licensing data for custom OpenAI endpoints |
20251110000001_add_speaker_field.sql |
Enhanced speaker tracking in transcripts |
Storing Meeting Data: Complete Examples
Inserting Meeting Metadata
When a user starts recording, Meetily creates a meeting record:
sqlx::query!(
"INSERT INTO meetings (title, start_ts) VALUES (?, ?)",
"Q4 Planning Session",
chrono::Utc::now().timestamp()
)
.execute(db.pool())
.await?;
Saving Transcript Fragments
As Whisper produces transcription segments, they stream into the database:
sqlx::query!(
"INSERT INTO transcripts (meeting_id, speaker_id, text, ts) VALUES (?, ?, ?, ?)",
meeting_id, // Foreign key to meetings.id
speaker_id, // Foreign key to speakers.id (or NULL)
"Let's review the quarterly metrics.",
chrono::Utc::now().timestamp()
)
.execute(db.pool())
.await?;
Retrieving and Displaying Data
The frontend fetches transcripts through Tauri commands using ordered queries:
let rows = sqlx::query!(
"SELECT text, ts, speaker_id FROM transcripts
WHERE meeting_id = ? ORDER BY ts ASC",
meeting_id
)
.fetch_all(db.pool())
.await?;
Storing AI-Generated Summaries
After processing, summaries persist to a dedicated table:
sqlx::query!(
"INSERT INTO summaries (meeting_id, content, generated_at) VALUES (?, ?, ?)",
meeting_id,
"Key decisions: 1) Expand hiring... 2) Delay product launch...",
chrono::Utc::now().timestamp()
)
.execute(db.pool())
.await?;
Data Flow from Recording to Storage
- Audio capture produces separate microphone and system audio streams
- Voice Activity Detection (VAD) filters non-speech segments before sending to Whisper
- Transcription returns text fragments that the backend inserts into
transcripts - Post-processing generates summaries stored in
summariesand notes inmeeting_notes - Querying happens via Tauri commands that execute SQL against the pooled connection
Key Implementation Files
| File Path | Responsibility |
|---|---|
frontend/src-tauri/src/database/manager.rs |
DatabaseManager struct: initialization, migrations, pool management, WAL cleanup, transaction helper |
frontend/src-tauri/migrations/20250916100000_initial_schema.sql |
Core tables: meetings, transcripts, speakers |
frontend/src-tauri/migrations/20251101000000_add_summary_backup.sql |
summaries table for meeting summaries |
frontend/src-tauri/migrations/20251223000000_add_meeting_notes.sql |
meeting_notes table |
frontend/src-tauri/migrations/*.sql |
All schema evolution tracked as versioned migrations |
Summary
- Local-first architecture: All data stays on-device in a single SQLite file
- Versioned schema: Migrations in
frontend/src-tauri/migrations/ensure reliable upgrades - Reliable writes: WAL mode with corruption detection and explicit checkpointing
- Flexible storage: Separate tables for meetings, transcripts, speakers, summaries, and notes
- Atomic operations:
with_transactionhelper prevents partial updates
Frequently Asked Questions
Where does Meetily store my meeting data?
Meetily stores everything in a single SQLite file at ~/Library/Application Support/Meetily/meeting_minutes.sqlite on macOS (with equivalent paths on Windows and Linux). This file contains all meetings, transcripts, speakers, summaries, and settings—no cloud required.
How does Meetily prevent data loss if the app crashes?
The DatabaseManager uses SQLite's WAL (Write-Ahead Logging) mode. On startup, it checks for and removes corrupted WAL/SHM files. On shutdown, it runs PRAGMA wal_checkpoint(TRUNCATE) to ensure all data is fully written to the main database file. Transactions also guarantee that multi-step operations complete entirely or not at all.
Can I access my Meetily data from other applications?
Yes—the SQLite file is standard and queryable with any SQLite client. The schema follows migration files in frontend/src-tauri/migrations/, with core tables including meetings, transcripts, speakers, summaries, and meeting_notes. Note that schema changes may occur with app updates.
What happens to my data when Meetily updates?
The DatabaseManager automatically runs sqlx::migrate!("./migrations") on startup, applying any new migration files in version order. This ensures your existing data is preserved while the schema evolves to support new features.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →