Private testing started on 6 November 2026. Got an invite code? Sign up here

Auvyra

Database Adapters

How Auvyra stays independent of its database.

Business logic talks to repository interfaces, never to a database driver.

Layout

packages/database/src/
  interfaces/    DatabaseAdapter and the 12 repository contracts
  schemas/       Zod schemas and the types inferred from them
  repositories/  Shared helpers and the contract test suite
  migrations/    The migration runner
  adapters/      mongodb, postgresql, and sql for MySQL, MariaDB and SQLite

Repositories

Users, sessions, stations, news, articles, pages, presenters, shows, applications, settings, activity logs, integrations, media assets, listener samples, track likes, the song log and station records.

Station records hold everything listeners and staff create together: polls and votes, shoutouts, comments, competitions and entries, events, subscribers, newsletters and the email queue, alerts, listener accounts and their favorites. They share one store with a kind, a status and a JSON payload, so a new feature of this sort needs no migration on any engine. Each feature validates its payload in its own file under apps/web/lib/services/.

Each adapter also has a transfer, which reads and writes a station's rows as plain data. It is what lets a station move between engines, because rows keep their IDs and a row that clashes on an ID or a unique key is skipped. See Moving a Station.

Shared rules

  • IDs are application-generated UUIDs
  • Validation happens in the adapter with shared Zod schemas, so bad data and query operators are rejected the same way everywhere
  • Pagination is { page, perPage }, with perPage capped at 100
  • Errors are NotFoundError, ConflictError and DatabaseConnectionError, which never contain credentials
  • Methods reject rather than throwing synchronously

Indexes, transactions and migrations belong to each adapter.

Differences that adapters hide

ConcernMongoDBPostgreSQLMySQL and MariaDBSQLite
ClientOfficial driverDrizzle ORM on postgresmysql2Built into Node.js
Nested dataDocumentsjsonb columnsJSON stored as textJSON stored as text
DatesNativetimestamptzDATETIME(3) in UTCMilliseconds as an integer
Expired sessionsTTL indexCleared at each sign-inCleared at each sign-inCleared at each sign-in
Null orderingNulls sort last when descendingRequested with NULLS LASTNulls sort last when descendingNulls sort last when descending
Text searchEscaped regular expressionEscaped ILIKEEscaped LIKE on lowercased textEscaped LIKE on lowercased text
Migration safetyIdempotent operationsAdvisory lock in a transactionGET_LOCKOne writer at a time

No engine uses foreign keys or cascades, so behavior stays identical. Rules such as "a presenter with shows cannot be deleted" live in the services.

Contract tests

packages/database/src/repositories/contract-suite.ts defines behavior every adapter must satisfy, including CRUD, unique constraints, pagination, filtering, query-operator rejection, concurrent owner claims and connection failures. An engine is only advertised as supported once it passes.