You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ShellWatch currently only supports SQLite via better-sqlite3 and drizzle-orm/better-sqlite3. Add support for additional database backends (e.g. PostgreSQL) for deployments where SQLite is not ideal — multi-instance setups, larger scale, or environments where a centralized database is preferred.
Current state
src/db/connection.ts hardcodes the better-sqlite3 driver and rejects non-SQLite connection strings
Drizzle ORM supports multiple dialects (PostgreSQL via drizzle-orm/node-postgres, MySQL via drizzle-orm/mysql2), but migrations are dialect-specific
Would need per-dialect migration directories or a generation step
Connection string parsing in connection.ts should route to the appropriate driver based on scheme (postgres://, mysql://, sqlite:)
SQLite should remain the default for simplicity — no external dependencies for single-instance deployments
Docker image would need additional native dependencies (e.g. pg or mysql2) or a separate image variant
Concurrency audit (read-then-write TOCTOU)
The repository layer was written against better-sqlite3's single-process synchronous model, where SELECT ... ; if (...) INSERT/UPDATE is effectively atomic because the event loop cannot yield between statements and only one writer process exists. Once we add a second backend (Postgres) or run multiple writer processes against the same SQLite file, that assumption breaks: two requests can interleave their SELECT and INSERT, producing stale-read bugs and silent overwrites.
Before/while landing this ticket, audit every read-then-decide-then-write pattern in src/db/repositories/ and convert each to one of:
a single statement with a conditional ON CONFLICT DO UPDATE ... WHERE ... clause (preferred — driver-agnostic, atomic at the statement level), or
an explicit db.transaction(...) wrapping the read and write.
Known instance (do not regress when porting):
DrizzlePushSubscriptionRepository.upsert (src/db/repositories/push-subscription-repo.ts) — does a SELECT to enforce that an endpoint cannot be claimed by a second account, then an INSERT with onConflictDoUpdate. Safe under better-sqlite3, racy under multi-writer Postgres / multi-process SQLite. Flagged in PR security(MEDIUM): SSRF via user-controlled push subscription endpoint #131 review. Fix at port time: collapse to a single INSERT ... ON CONFLICT (endpoint) DO UPDATE SET ... WHERE accountId = :accountId RETURNING * and treat zero-row return as the cross-account-rejection case.
Summary
ShellWatch currently only supports SQLite via
better-sqlite3anddrizzle-orm/better-sqlite3. Add support for additional database backends (e.g. PostgreSQL) for deployments where SQLite is not ideal — multi-instance setups, larger scale, or environments where a centralized database is preferred.Current state
src/db/connection.tshardcodes thebetter-sqlite3driver and rejects non-SQLite connection stringsdrizzle.config.tsusesdialect: "sqlite"drizzle/are SQLite-specific SQLrunMigrations()usesdrizzle-orm/better-sqlite3/migratorConsiderations
drizzle-orm/node-postgres, MySQL viadrizzle-orm/mysql2), but migrations are dialect-specificconnection.tsshould route to the appropriate driver based on scheme (postgres://,mysql://,sqlite:)pgormysql2) or a separate image variantConcurrency audit (read-then-write TOCTOU)
The repository layer was written against
better-sqlite3's single-process synchronous model, whereSELECT ... ; if (...) INSERT/UPDATEis effectively atomic because the event loop cannot yield between statements and only one writer process exists. Once we add a second backend (Postgres) or run multiple writer processes against the same SQLite file, that assumption breaks: two requests can interleave their SELECT and INSERT, producing stale-read bugs and silent overwrites.Before/while landing this ticket, audit every
read-then-decide-then-writepattern insrc/db/repositories/and convert each to one of:ON CONFLICT DO UPDATE ... WHERE ...clause (preferred — driver-agnostic, atomic at the statement level), ordb.transaction(...)wrapping the read and write.Known instance (do not regress when porting):
DrizzlePushSubscriptionRepository.upsert(src/db/repositories/push-subscription-repo.ts) — does a SELECT to enforce that anendpointcannot be claimed by a second account, then an INSERT withonConflictDoUpdate. Safe underbetter-sqlite3, racy under multi-writer Postgres / multi-process SQLite. Flagged in PR security(MEDIUM): SSRF via user-controlled push subscription endpoint #131 review. Fix at port time: collapse to a singleINSERT ... ON CONFLICT (endpoint) DO UPDATE SET ... WHERE accountId = :accountId RETURNING *and treat zero-row return as the cross-account-rejection case.Related