Skip to content

Database Schema

Auto-generated from services/api-hono/src/db/schema.ts. Do not edit manually. Run vp run -F @libris/docs generate:db -- --db to regenerate.

PostgreSQL with Drizzle ORM. Migrations auto-apply on startup via runMigrations() in services/api-hono/src/bootstrap.ts (the drizzle-orm/postgres-js migrator in src/db/migrate.ts); they run before the database pool is created and are skipped when NODE_ENV=test.

Enums

book_status

Values: inbox, review, organized

reading_status

Values: unread, reading, finished, paused

Tables

TableDescription
api_keysAPI key records — the identity table (hashed key, label, is_admin, timestamps)
booksMain book records with status, metadata, and the full-text search vector
book_filesPhysical file storage per book (format, checksums, content hashes, paths)
book_metadata_candidatesMetadata candidates per book and source (file, hardcover) shown during review
reading_progressCurrent KoReader/KoSync reading position per document and device
reading_progress_historyAppend-only history of reading-progress snapshots
reading_aggregatePer-(user, book) reading lifecycle: effective status plus started/finished/paused dates and any manual override
service_credentialsPer-user credentials for OPDS, KoSync, and Hardcover (bcrypt or sealed token)
app_settingsGlobal key/value application settings (e.g. Hardcover metadata/sync toggles)
upload_registryChecksum → uploader (api_key) ownership mapping consumed by the book-detected worker
hardcover_sync_logPer-(user, book) Hardcover sync tracking and last status

api_keys

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
key_prefixtextNo
key_hashtextNo
labeltextNo
is_adminbooleanNofalse
created_attimestamptzNonow()
last_used_attimestamptzYes

Indexes: api_keys_key_prefix_idx

books

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
statusenum(book_status)Noinbox
titletextYes
authortextYes
isbn_10textYes
isbn_13textYes
publishertextYes
published_yearintegerYes
languagetextYes
descriptiontextYes
cover_urltextYes
cover_pathtextYes
page_countintegerYes
seriestextYes
series_indexrealYes
genrestext[]No[]
tagstext[]No[]
hardcover_book_idintegerYes
hardcover_edition_idintegerYes
created_byuuidYesFK → apiKeys.id (ON DELETE set null)
possible_duplicate_ofuuidYes
approved_attimestamptzYes
created_attimestamptzNonow()
updated_attimestamptzNonow()
search_vectortsvectorYes

Indexes: books_status_created_at_idx, books_isbn13_idx, books_search_vector_idx (GIN), books_title_trgm_idx (GIN), books_author_trgm_idx (GIN), books_series_idx, books_possible_duplicate_of_idx, books_created_by_idx, books_hardcover_book_id_idx, books_series_series_index_uniq (unique)

book_files

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
book_iduuidNoFK → books.id (ON DELETE cascade)
formattextNo
original_nametextNo
storage_pathtextYes
inbox_pathtextYes
file_sizebigintNo0
checksumtextYes
content_hashtextYes
original_content_hashtextYes
created_attimestamptzNonow()
updated_attimestamptzNonow()

Indexes: book_files_book_id_idx, book_files_checksum_idx, book_files_content_hash_idx, book_files_original_content_hash_idx

book_metadata_candidates

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
book_iduuidNoFK → books.id (ON DELETE cascade)
sourcetextNo
raw_responsejsonbYes
normalizedjsonbNo{}
confidencenumeric(5,4)No0
selected_fieldstext[]No[]

Indexes: book_metadata_candidates_book_id_idx

reading_progress

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
book_iduuidYesFK → books.id (ON DELETE set null)
api_key_iduuidNoFK → apiKeys.id (ON DELETE cascade)
documenttextNo
devicetextNo
device_idtextYes
progresstextNo
percentagenumeric(5,4)No0
timestampbigintNo0
raw_payloadjsonbYes
updated_attimestamptzNonow()

Indexes: reading_progress_device_idx, reading_progress_book_id_idx, reading_progress_api_key_id_idx

reading_progress_history

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
book_iduuidYesFK → books.id (ON DELETE set null)
api_key_iduuidYesFK → apiKeys.id (ON DELETE set null)
documenttextNo
devicetextNo
progresstextNo
percentagenumeric(5,4)No0
timestampbigintNo0
created_attimestamptzNonow()

Indexes: reading_progress_history_document_created_at_idx, reading_progress_history_created_at_idx, reading_progress_history_book_id_idx, reading_progress_history_api_key_id_idx

reading_aggregate

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
api_key_iduuidNoFK → apiKeys.id (ON DELETE cascade)
book_iduuidYesFK → books.id (ON DELETE set null)
started_attimestamptzYes
finished_attimestamptzYes
manual_statusenum(reading_status)Yes
manual_started_attimestamptzYes
manual_finished_attimestamptzYes
manual_paused_attimestamptzYes
manual_set_attimestamptzYes
external_statusenum(reading_status)Yes
external_status_synced_attimestamptzYes
updated_attimestamptzNonow()

Indexes: reading_aggregate_book_id_idx, reading_aggregate_api_key_id_idx

service_credentials

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
servicetextNo
api_key_iduuidNoFK → apiKeys.id (ON DELETE cascade)
usernametextNo
password_hashtextNo
created_attimestamptzNonow()
updated_attimestamptzNonow()

Indexes: service_credentials_api_key_id_idx, service_credentials_service_username_uniq (unique)

app_settings

ColumnTypeNullableDefaultNotes
keytextNoPK
valuejsonbNo
updated_attimestamptzNonow()

upload_registry

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
checksumtextNo
api_key_iduuidNoFK → apiKeys.id (ON DELETE cascade)
filenametextNo
created_attimestamptzNonow()

hardcover_sync_log

ColumnTypeNullableDefaultNotes
iduuidNorandom()PK
book_iduuidNoFK → books.id (ON DELETE cascade)
api_key_iduuidNoFK → apiKeys.id (ON DELETE cascade)
hardcover_user_book_idintegerYes
hardcover_read_idintegerYes
last_statustextYes
last_progressnumeric(5,4)Yes
last_ratingnumeric(3,1)Yes
last_synced_attimestamptzNo
created_attimestamptzNonow()
updated_attimestamptzNonow()

Indexes: hardcover_sync_log_status_idx, hardcover_sync_log_last_synced_at_idx, hardcover_sync_log_api_key_id_idx

Notes

  • books.language holds a canonical lowercase ISO 639-1 code (e.g. en, fr); arbitrary input is normalized by services/api-hono/src/lib/languages.ts.
  • book_files.format, book_metadata_candidates.source, service_credentials.service, and hardcover_sync_log.last_status are free-text columns (not Postgres enums) even though they hold format/source/status-like values.
  • books.search_vector is excluded from all API responses — the API selects the bookColumns projection, which omits it.
  • reading_progress, reading_progress_history, and reading_aggregate use ON DELETE SET NULL on book_id so reading history survives a book deletion, while their api_key_id cascades (or is set null for history).
  • The trigram indexes (*_trgm_idx) require the pg_trgm extension; the full-text *_search_vector_idx is a GIN index over the generated tsvector.

Relationships

mermaid
erDiagram
    apiKeys ||--o{ books : "has many"
    books ||--o{ book_files : "has many"
    books ||--o{ book_metadata_candidates : "has many"
    books ||--o{ reading_progress : "has many"
    apiKeys ||--o{ reading_progress : "has many"
    books ||--o{ reading_progress_history : "has many"
    apiKeys ||--o{ reading_progress_history : "has many"
    apiKeys ||--o{ reading_aggregate : "has many"
    books ||--o{ reading_aggregate : "has many"
    apiKeys ||--o{ service_credentials : "has many"
    apiKeys ||--o{ upload_registry : "has many"
    books ||--o{ hardcover_sync_log : "has many"
    apiKeys ||--o{ hardcover_sync_log : "has many"