Persistence
The Fluent-based data layer the Loud 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
The package provides, grouped by role:
| 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 |
| Scaffolding (internal) | ExampleRecord, CreateExampleRecord, and ExampleRepository — the model → migration → repository pattern, to be replaced by the first real domain model |
Design rules
- The package reads no configuration. The executable maps its
database.*keys onto aDriverand hands it over; connection values arrive as plain data. See the Website service'sConfigReader+Propertiesfor the mapping. - One default database.
Serviceregisters the selected backend as the default database, so repositories resolve it with a plainfluent.db()and stay agnostic of which driver is in use. - Migrations are declared once, and append-only.
PrepareDBis the single place migrations are registered, in the order they must run; alter the schema by adding a new migration, never by editing one that has already run. Registering does not apply them — the in-memory backend is migrated on startup, while a shared PostgreSQL database is migrated out of band (the executable's migrate-and-exit mode), so multiple booting instances never race. - Models never cross a concurrency boundary. FluentKit models are mutable reference types; repositories map them to
Sendablevalue-type snapshots (e.g.Example) before returning, and the models themselves stay internal to the package. - Readiness never throws.
Proberuns aSELECT 1— the cheapest statement both backends understand, independent of any schema — and maps every failure tofalse, so callers translate it straight into a readiness response. - 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, rather than each pooled connection getting its own private one.
- Method structs.
Service,PrepareDB, andProbehold their lifetime-fixed configuration ininitand take only per-call inputs incallAsFunction.
Note: both TLS postures are enforced by the driver itself:
preferupgrades the connection only when the server advertises TLS and continues in plaintext otherwise, whilerequirerefuses the connection when the server offers none. Both behaviors are pinned by tests against a fake server that offers no TLS.
Layout
Sources are split by visibility, then by kind, one type per file:
Sources/
├── Public/
│ ├── Enumerations/ Driver, TLS
│ ├── Methods/ Service, PrepareDB, Probe
│ └── Types/ Configuration
└── Internal/
├── Migrations/ CreateExampleRecord
├── Models/ ExampleRecord
└── Repositories/ ExampleRepository (returning the Example snapshot)
Tests/
├── Cases/ the test suites, mirroring the Sources/ layout
└── Utils/ the NotSQL* fakes backing the probe's non-SQL-database case, the
plaintext-only fake PostgreSQL server, 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 a database is pointed at via POSTGRES_TEST_HOST (with optional POSTGRES_TEST_PORT, POSTGRES_TEST_NAME, POSTGRES_TEST_USERNAME, and POSTGRES_TEST_PASSWORD); it reverts its migrations afterwards, so the shared database is left as it was found:
# in-memory only
swift test
# or
# with the local PostgreSQL up (make db-mount):
POSTGRES_TEST_HOST=127.0.0.1 swift test
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 pattern around fluent.shutdown() is the shape to follow.
Every suite carries a tag naming the kind of API it exercises — .enumeration or .method, declared in Tests/Utils/Extensions/Tag+Constants.swift — so test plans and result summaries can slice the run by kind. A new suite must adopt the tag matching its subject (or add a tag there if none fits).
Requirements
- Swift 6.3 toolchain (
swift-tools-version:6.3). - macOS 15, matching the sibling
InfrastructureandLocalizationpackages (the services deploy to Linux containers; the packages carry no UI platforms). - Package dependencies:
hummingbird-fluent,fluent-postgres-driver,fluent-sqlite-driver, andsql-kit; the test target additionally depends onpostgres-nio,swift-nio, andswift-nio-sslfor the TLS fallback tests.