# Persistence The [Fluent](https://github.com/hummingbird-project/hummingbird-fluent)-based data layer the platform's services build on: runtime selection between a PostgreSQL backend and an ephemeral in-memory SQLite one, single-place migration registration, and a database readiness probe. ## Overview | Role | Types | | --- | --- | | Backend selection | `Driver` (`postgres` or `inMemory`), `Configuration` (the PostgreSQL connection parameters), `TLS` (the connection's TLS posture) | | Service | `Service`, which builds the `Fluent` service configured for the chosen driver | | Migrations | `PrepareDB`, the single registrar declaring every migration, in order | | Readiness | `Probe`, which reports whether the default database answers a `SELECT 1` within a deadline | | Accounts | `AccountRepository`, which finds an account by email or creates it, returning a `User` snapshot, and keeps each account's `Passkey`s — one is only ever registered together with a new account; `CreateUser` seeds the prototype's demo accounts | | Collecting | `CollectingRepository`, which keeps an account's `Want`s with the listings meeting each, its collection of owned issues, the `Series` catalogue, and the upcoming `Event`s with the shops trading at each | | Trade | `TradeRepository`, which reads and writes `Conversation`, `Offer`, and `Sale` snapshots and enforces who may do what, throwing a `TradeError` otherwise; `Sale` carries the five-step handshake's rules as pure functions | | Catalogue | `CatalogueRepository`, which opens a shop from a `ShopDraft`, publishes a seller's `ListingDraft` under a slug of its own, and returns `Shop` and `Listing` snapshots narrowed and ordered by a `ListingFilter`; `SeedCatalogue` fills it with the prototype's demo data | ## Design rules - **The package reads no configuration.** The executable maps its `database.*` keys onto a `Driver` and hands it over; connection values arrive as plain data. The Website service's `ConfigReader+Properties` has the mapping. - **One default database.** `Service` registers the selected backend as the *default* database, so repositories resolve it with a plain `fluent.db()` and stay agnostic of which driver is in use. - **Migrations are declared once, and append-only.** `PrepareDB` registers every migration in the order it must run; alter the schema by adding a migration, never by editing one that has already run. Registering does not apply them: the in-memory backend is migrated at launch, while a shared PostgreSQL database is migrated out of band (the executable's migrate-and-exit mode), so booting instances never race. - **Models never cross a concurrency boundary.** FluentKit models are mutable reference types, so they stay internal to the package and repositories return `Sendable` value-type snapshots (e.g. `Listing`) instead. - **Registration order is not creation order.** `AddTradeColumns`, `AddListingDeclarations`, and `AddShopRegistration` are registered ahead of `SeedCatalogue`, whose models write their columns; Fluent tracks migrations by name, so a database already past the seed still gains them. - **Prototype shortcuts.** `CatalogueRepository` filters in memory, the catalogue fields (series, issue, publisher) are folded into the listing, and the demo data ships as a migration. All three go once real data arrives. - **Readiness never throws, and never hangs.** `Probe` runs a schema-independent `SELECT 1`, maps every failure to `false`, and races the query against a deadline (2 seconds by default) — a hanging database yields a prompt "not ready" instead of a stalled endpoint. - **A single connection for the in-memory store.** The SQLite backend is capped at one connection per event loop, so every query reaches the same in-memory database instead of each pooled connection getting a private one. - **Method structs.** `Service`, `PrepareDB`, and `Probe` hold their lifetime-fixed configuration in `init` and take only per-call inputs in `callAsFunction`. > **Note:** the driver itself enforces both TLS postures — `prefer` upgrades only when the server advertises TLS and continues in plaintext otherwise, `require` refuses a server that offers none. Tests pin both against a fake plaintext-only server. ## Layout Sources are split by visibility, then by kind, one type per file: ``` Sources/ ├── Public/ │ ├── Enumerations/ Driver, TLS, ShopError, TradeError │ ├── Methods/ Service, PrepareDB, Probe │ ├── Models/ Shop, ShopDraft, Listing, ListingDraft, ListingFilter, User, Passkey, Want, Series, Event, Conversation, Offer, Sale │ ├── Repositories/ CatalogueRepository, AccountRepository, CollectingRepository, TradeRepository │ └── Types/ Configuration └── Internal/ ├── Migrations/ CreateShop, CreateListing, AddTradeColumns, AddListingDeclarations, AddShopRegistration, SeedCatalogue, CreateUser, CreateTrade, CreatePasskey, CreateCollecting, CreateCommunity ├── Models/ ShopRecord, ListingRecord, UserRecord, PasskeyRecord, WantRecord, SeriesRecord, EventRecord, EventShopRecord, OwnedIssueRecord, ConversationRecord, MessageRecord, OfferRecord, SaleRecord └── Seeds/ Seed Tests/ ├── Cases/ the test suites, mirroring the Sources/ layout └── Utils/ the NotSQL* fakes backing the probe's non-SQL-database case, the plaintext-only and silent fake PostgreSQL servers, and the suite Tag constants ``` ## Testing The suite runs against the in-memory backend by default, so `swift test` needs no database. The PostgreSQL integration test is skipped unless `POSTGRES_TEST_HOST` points at one (with optional `POSTGRES_TEST_PORT`, `POSTGRES_TEST_NAME`, `POSTGRES_TEST_USERNAME`, and `POSTGRES_TEST_PASSWORD`); it reverts its migrations afterwards, leaving a shared database as it was found: ```sh swift test # in-memory only POSTGRES_TEST_HOST=127.0.0.1 swift test # against the local PostgreSQL (make db-mount) ``` Outside the application's service group, a built `Fluent` service must be shut down explicitly — even on failure — or its connection pool asserts on `deinit`; the suites' `do`/`catch` around `fluent.shutdown()` is the shape to follow. Every suite carries a tag for the kind of API it exercises — `.enumeration` or `.method`, declared in `Tests/Utils/Extensions/Tag+Constants.swift` — so test plans and summaries can slice a run by kind. A new suite adopts the tag matching its subject, or adds one when none fits. ## Requirements - Swift 6.4 toolchain (`swift-tools-version:6.4`). - macOS 15, matching the sibling packages (the services deploy to Linux containers; the packages carry no UI platforms). - Package dependencies: `hummingbird-fluent`, `fluent-postgres-driver`, `fluent-sqlite-driver`, `sql-kit`, `postgres-nio`, and `swift-nio-ssl`; the test target additionally depends on `swift-nio` for the TLS fallback tests' fake server.