Browse docs

Data Model

Database schema, tables, relationships, and access patterns

Alex uses SQLite as its primary data store. Database access is mediated by the Rust watcher-rs bridge, and the schema supports both local-folder and S3-backed books.

Entity relationship diagram

Rendering diagram...

Tables

users

Stores user accounts and roles.

ColumnTypeDescription
idTEXT (UUID)Primary key (admin user has fixed ID "1" for desktop auth compatibility)
emailTEXTUnique login identifier
password_hashTEXTBcrypt hash
display_nameTEXTDisplayed in UI
roleTEXTadmin or user
created_atINTEGERUnix timestamp
updated_atINTEGERUnix timestamp

The default admin account (admin@localhost / admin123) is seeded using INSERT...ON CONFLICT DO UPDATE so it is idempotent and safe to re-run on every app launch.

books

Stores metadata for every ingested book.

ColumnTypeDescription
idTEXT (UUID)Primary key
titleTEXTExtracted from metadata or filename
authorTEXTNullable author
descriptionTEXTNullable summary/description
file_typeTEXTpdf or epub
file_pathTEXTLocal absolute path (source='local') or S3 object key (source='s3')
file_sizeINTEGERSize in bytes
file_hashTEXTSHA-256 hash (unique)
cover_pathTEXTCover path, nullable
page_countINTEGERPDF page count, nullable for EPUB
added_atINTEGERUnix timestamp
updated_atINTEGERUnix timestamp
sourceTEXTStorage source: local or s3
s3_bucketTEXTBucket name for S3 books, nullable for local
s3_etagTEXTLast-seen object ETag for S3 diffing

Notes:

  • source='local' rows are ingested from LIBRARY_PATH.
  • source='s3' rows are ingested from configured S3 poll mode.
  • Duplicate content is prevented via unique file_hash.

reading_progress

Per-user reading position for each book.

ColumnTypeDescription
idTEXT (UUID)Primary key
user_idTEXTFK -> users.id
book_idTEXTFK -> books.id
current_pageINTEGERPDF page position
total_pagesINTEGERPDF total pages
epub_locationTEXTEPUB CFI location
percent_completeREAL0.0 to 100.0
statusTEXTnot_started, reading, completed
last_read_atINTEGERUnix timestamp

collections

User-owned groups of books.

ColumnTypeDescription
idTEXT (UUID)Primary key
user_idTEXTFK -> users.id
nameTEXTCollection name
descriptionTEXTOptional description
share_tokenTEXTPublic token, nullable
shared_atINTEGERWhen sharing enabled
created_atINTEGERUnix timestamp

collection_books

Many-to-many join table between collections and books.

ColumnTypeDescription
collection_idTEXTFK -> collections.id (composite PK)
book_idTEXTFK -> books.id (composite PK)
added_atINTEGERUnix timestamp

settings

App-level key-value settings.

ColumnTypeDescription
keyTEXTPrimary key
valueTEXTSetting value
updated_atINTEGERUnix timestamp

Currently, library_version is used to notify clients of ingestion changes.

Relationships

One-to-many

  • users -> reading_progress
  • users -> collections
  • books -> reading_progress

Many-to-many

  • books <-> collections via collection_books

Cascade behavior

  • Deleting a book removes associated reading_progress and collection_books records.
  • Deleting a user removes their progress and collections.
  • Deleting a collection removes collection_books links but not the books.

Indexes

Key indexes include:

  • users.email (unique)
  • books.file_path (unique)
  • books.file_hash (unique)
  • books.title, books.author
  • reading_progress(user_id, book_id)
  • reading_progress.status
  • collections.share_token (unique)
  • collection_books(collection_id), collection_books(book_id)

Access and schema management

Database operations flow through the Rust bridge:

  • watcher-rs db query-all
  • watcher-rs db query-one
  • watcher-rs db execute

Schema is managed via SQL migration files applied by scripts/db-push.js:

  • 0000_wide_expediter.sql — Initial schema (users, books, reading_progress, collections, collection_books, settings)
  • 0001_s3_source_columns.sql — Adds source, s3_bucket, s3_etag columns to the books table

Schema management commands:

# Apply schema migrations
pnpm db:push

# Seed default admin user (idempotent upsert)
pnpm db:seed

# Reset database (destructive)
rm -f data/library.db data/library.db-shm data/library.db-wal && pnpm db:push && pnpm db:seed