7.5 KiB
App Database
Source: src/lib/server/db/ (db.ts, migrations.ts, migrations/, queries/)
The app database (profilarr.db) is a SQLite file that stores all application
state: Arr instances, settings, job queue, notifications, sessions, and PCD ops.
It uses a singleton DatabaseManager, migration-based schema management, and
raw SQL query modules with parameter binding. Kysely is not
used here -- it's reserved for the PCD cache (see pcd.md).
Table of Contents
Initialization
The database initializes early in the server startup sequence
(src/hooks.server.ts):
config.init()-- ensure data directories existdb.initialize()-- open SQLite connection, set pragmasrunMigrations()-- apply pending schema changes- Load log settings, initialize PCD manager, start job queue
Four pragmas are set on every connection:
| Pragma | Value | Purpose |
|---|---|---|
foreign_keys |
ON |
Enforce referential integrity |
journal_mode |
WAL |
Write-Ahead Logging for concurrency |
synchronous |
NORMAL |
Balanced performance/safety |
busy_timeout |
5000 |
Wait up to 5s for a write lock instead of SQLITE_BUSY |
The manager includes HMR recovery for development: isHealthy() runs
SELECT 1 to verify the connection is alive. If the check fails during
initialization (common after Vite hot reloads), the manager closes and
reinitializes the connection.
Transaction support is available via transaction<T>(fn), an async wrapper
that auto-commits on success and auto-rollbacks on error. Raw
beginTransaction(), commit(), and rollback() methods are also exposed.
The helper issues BEGIN IMMEDIATE rather than the default BEGIN DEFERRED,
so write transactions acquire the write lock at BEGIN time. Two concurrent
writers serialize cleanly via busy_timeout instead of one failing
immediately with SQLITE_BUSY from a deferred read→write upgrade conflict
(SQLite explicitly bypasses the busy handler in that case to avoid a
permanent deadlock).
Schema
src/lib/server/db/schema.sql is a reference snapshot of the current
schema after all migrations have been applied. Its header reads:
DO NOT execute this file directly -- use migrations instead.
The file is useful for understanding the final table structure, reviewing database design, and onboarding new contributors. It is not used at runtime. Migrations are the source of truth for schema changes.
Migrations
Source: src/lib/server/db/migrations/ (sequentially numbered NNN_*.ts)
Runner: src/lib/server/db/migrations.ts
Each migration exports an object matching the Migration interface:
| Field | Required | Purpose |
|---|---|---|
version |
yes | Sequential number (001, 002, ...) |
name |
yes | Human-readable description |
up |
yes | SQL to apply the migration |
down |
no | SQL to roll back (omit if not rollbackable) |
afterUp |
no | Callback for data migrations after schema DDL |
The runner applies pending migrations in version order. Each up block runs
inside a transaction. The afterUp callback runs outside the transaction,
which is useful for data migrations that need the new schema committed first.
A migrations table tracks applied versions with timestamps. The runner
supports up (apply pending), down (roll back to target version), and
fresh (reset and reapply all).
Adding a Migration
- Review
migrations/_template.tsas your starting point. - Number sequentially after the highest existing migration.
- Write the
upSQL. Providedownif the migration is rollbackable. - Add the import and entry in
migrations.tsinsideloadMigrations(). - Test both fresh (
db.fresh()) and incremental application. - Update
schema.sqlto reflect the new state.
Rule: Never modify an already-applied migration. If a migration has a bug, create a new migration to fix it.
Query Layer
Source: src/lib/server/db/queries/
Each module covers one database table and exports a queries object with typed methods.
All use raw SQL with ? parameter binding and typed input/output interfaces.
| Module | Domain |
|---|---|
arrInstances |
Arr instance CRUD |
arrSync |
Sync config and status per instance |
arrCleanupSettings |
Stale config cleanup settings |
arrDriftSettings |
Drift detection scheduling settings |
arrDriftStatus |
Latest drift detection result per Arr instance |
arrRenameSettings |
Rename job settings per instance |
authSettings |
Session duration, API key |
sessions |
Session CRUD and cleanup |
jobQueue |
Job queue CRUD, claim, reschedule |
jobRunHistory |
Job execution history |
pcdOps |
PCD operation CRUD and filtering |
pcdOpHistory |
Per-compile op application results |
notificationServices |
Notification service config |
logSettings |
Log level and output settings |
renameRuns |
Rename job run history |
upgradeRuns |
Upgrade job run history |
parsedReleaseCache |
Parser result cache |
patternMatchCache |
Regex match result cache |
githubCache |
GitHub API response cache |
tmdbSettings |
TMDB API key storage |
aiSettings |
AI service settings |
announcements |
Announcements cache + read state |
versionsSnapshot |
Cached bulletin versions manifest |
databaseAnnouncements |
Per-PCD announcement cache (scoped per database, ON DELETE CASCADE) |
Query methods follow a consistent pattern: typed input interfaces for
mutations, generic return types for reads, and the singleton db instance
for all operations.
Arr instance duplicate detection is based on normalized type + url, not API
key. This allows cloned Radarr/Sonarr instances to reuse an API key while still
blocking accidental duplicate targets.