diff --git a/Packages/Infrastructure/README.md b/Packages/Infrastructure/README.md index dfc8bd7..9297df6 100644 --- a/Packages/Infrastructure/README.md +++ b/Packages/Infrastructure/README.md @@ -2,7 +2,6 @@ The shared [Hummingbird](https://github.com/hummingbird-project/hummingbird) toolkit the **Loud** services build on: declarative routing, hardened HTTP middlewares, pre-rendered localized HTML responses, and the page and asset scaffolding. ## Overview -The package provides, grouped by role: | Role | Types | | --- | --- | | Routing | `RouterController`, `RouteCollectionBuilder`, the `addController` extension on `RouterMethods` | @@ -17,9 +16,9 @@ The package provides, grouped by role: ## Design rules The package holds only what every service can reuse; anything a service owns is injected, never referenced: -- **No site-specific content.** No page markup, no asset catalog, no `Bundle.module` lookups. A type that needs a service's content takes it as a parameter: the `bundle:` whose String Catalog names the supported languages (`LocalizationMiddleware`, `LocalizedHTMLCollectionResponse`, `NotFoundMiddleware`), the `document:` closure that builds a page for a locale, and the `metadata` requirement through which a `Page` conformer supplies its icon links and theme colors — as are the `summary`, `canonicalURL`, `socialCard`, `structuredData`, and `analytics` values its other head tags render, each omitted unless the page provides it. A `SocialCard` and a `StructuredData` node take their URLs fully formed and absolute, as does an `Analytics` its script URL; composing them from an origin and a versioned asset path stays with the page providing them. -- **Types own their format; `Page` renders generically.** Each optional head concern derives its own render-ready representation — the `SocialCard.tags` meta tags, the `StructuredData.payload` JSON-LD, the `Analytics.attributes` script attributes — and the `Page` scaffolding applies it without knowing the vocabulary (Open Graph, schema.org, or the Umami tracker convention). The page ``scripts`` and the analytics tracker render as `defer`red head tags, with a `preconnect` hint for the tracker's cross-origin host. -- **Services fill the gaps once, via extensions.** A service restores its convenient call sites with retroactive extensions — the Website's `Page+Defaults`, `LocalizationMiddleware+Defaults`, and `NotFoundMiddleware+Defaults` are the pattern to follow. The open schema.org vocabularies extend the same way: the package declares only the `Property.Name` and `Node.Kind` constants every service shares, and a service adds the ones its own node shapes need. +- **No site-specific content.** No page markup, no asset catalog, no `Bundle.module` lookups. A type that needs a service's content takes it as a parameter: the `bundle:` whose String Catalog names the supported languages (`LocalizationMiddleware`, `LocalizedHTMLCollectionResponse`, `NotFoundMiddleware`), the `document:` closure that builds a page for a locale, and a `Page` conformer's `metadata` — along with the `summary`, `canonicalURL`, `socialCard`, `structuredData`, and `analytics` its other head tags render, each omitted unless the page provides it. URLs arrive fully formed and absolute: composing them from an origin and a versioned asset path stays with the page. +- **Types own their format; `Page` renders generically.** Each optional head concern derives its own render-ready representation — `SocialCard.tags`, `StructuredData.payload`, `Analytics.attributes` — and the `Page` scaffolding applies it without knowing the vocabulary (Open Graph, schema.org, or the Umami convention). Page ``scripts`` and the tracker render as `defer`red head tags, with a `preconnect` hint for the tracker's cross-origin host. +- **Services fill the gaps once, via extensions.** A service restores its convenient call sites with retroactive extensions — the Website's `Page+Defaults`, `LocalizationMiddleware+Defaults`, and `NotFoundMiddleware+Defaults` are the pattern. The open schema.org vocabularies extend the same way: the package declares only the `Property.Name` and `Node.Kind` constants every service shares, and a service adds what its own node shapes need. - **Method structs.** Single-operation types such as `FingerprintAssets` hold their lifetime-fixed configuration in `init` and take only per-call inputs in `callAsFunction`. ## Layout @@ -34,7 +33,7 @@ Sources/ │ ├── Middlewares/ the five HTTP middlewares │ ├── Protocols/ Asset, LocalizedRequestContext, Page, RouterController │ ├── Responses/ CachedHTMLResponse, LocalizedHTMLCollectionResponse -│ └── Types/ Analytics, SocialCard, and StructuredData, the latter two with their nested types in SocialCard/ and StructuredData/ +│ └── Types/ Analytics, SocialCard, StructuredData (the latter two nesting their own types in SocialCard/ and StructuredData/) └── Internal/ ├── Extensions/ implementation details (the String separators) └── Types/ implementation details (FNV1aHash) @@ -45,8 +44,8 @@ Tests/ ``` ## Testing -Every suite carries a tag naming the kind of API it exercises — `.asset`, `.extension`, `.middleware`, `.protocol`, or `.type`, 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). +Every suite carries a tag for the kind of API it exercises — `.asset`, `.extension`, `.middleware`, `.protocol`, or `.type`, 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.3 toolchain (`swift-tools-version:6.3`). -- macOS 15, matching the sibling `Localization`, `Persistence`, and `Utility` packages (the services deploy to Linux containers; the packages carry no UI platforms). +- macOS 15, matching the sibling packages (the services deploy to Linux containers; the packages carry no UI platforms). diff --git a/Packages/Localization/README.md b/Packages/Localization/README.md index e0a1c40..db9263a 100644 --- a/Packages/Localization/README.md +++ b/Packages/Localization/README.md @@ -2,7 +2,6 @@ The server-side localization toolkit the **Loud** services build on: locale-explicit String Catalog lookups, `Accept-Language` negotiation, and the catalog-derived language list — with no dependencies beyond Foundation. ## Overview -The package provides, grouped by role: | Role | Types | | --- | --- | | Lookup | `Localize`, a bundle-bound localizer that resolves a catalog key for an explicit locale | @@ -11,10 +10,10 @@ The package provides, grouped by role: | Diagnostics | `CatalogState`, the outcome of reading the catalog (`loaded`, `missing`, or `undecodable`) | ## Design rules -- **The String Catalog is the single source of truth.** Supported languages, the default language, and every string come from the bundle's `Localizable.xcstrings`. Adding a language is a translation-only change — once a locale exists in the catalog, `LanguageList` and `Negotiate` pick it up with no code change. +- **The String Catalog is the single source of truth.** Supported languages, the default language, and every string come from the bundle's `Localizable.xcstrings`, so adding a language is a translation-only change — `LanguageList` and `Negotiate` pick up a new locale with no code change. - **The locale is always explicit.** A server has no single "current" locale, so every lookup names the locale to resolve in; nothing reads process-wide locale state. -- **Raw `.xcstrings` parsing, for Linux parity.** The catalog is decoded from its JSON rather than through Foundation's compiled-catalog APIs, which are unavailable or non-functional on Linux. Consumers must `.copy` the catalog resource verbatim (not `.process` it) so it ships as raw JSON on every platform. Only simple `stringUnit` values are decoded; plural and device variations are not represented. -- **Resolution never fails.** A missing entry falls back to the source-language string, then to the key itself; a missing or undecodable catalog degrades the language list to the default. Check `catalogState` once at startup and warn when it is not `.loaded`, before visitors ever see raw keys. +- **Raw `.xcstrings` parsing, for Linux parity.** The catalog is decoded from its JSON rather than through Foundation's compiled-catalog APIs, which are unavailable or non-functional on Linux. Consumers must `.copy` the resource verbatim (never `.process` it) so it ships as raw JSON everywhere. Only simple `stringUnit` values are decoded — plural and device variations are not. +- **Resolution never fails.** A missing entry falls back to the source-language string, then to the key itself; a missing or undecodable catalog degrades the language list to the default. Check `catalogState` once at startup and warn when it is not `.loaded`, before visitors see raw keys. - **Method structs.** `Localize` and `Negotiate` hold their lifetime-fixed configuration (the bundle) in `init` and take only per-call inputs in `callAsFunction`. - **One decoded catalog per bundle.** Catalogs are immutable at runtime, so every `Localize`, `Negotiate`, and `LanguageList` bound to the same bundle and table shares one cached `StringCatalog`. @@ -36,9 +35,9 @@ Tests/ ``` ## Testing -Every suite carries a tag naming the kind of API it exercises — `.method` or `.type`, 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). +Every suite carries a tag for the kind of API it exercises — `.method` or `.type`, 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.3 toolchain (`swift-tools-version:6.3`). -- macOS 15, matching the sibling `Infrastructure` and `Persistence` packages (the services deploy to Linux containers; the packages carry no UI platforms). +- macOS 15, matching the sibling packages (the services deploy to Linux containers; the packages carry no UI platforms). - No package dependencies — Foundation and Synchronization only. diff --git a/Packages/Persistence/README.md b/Packages/Persistence/README.md index 278c173..c881d74 100644 --- a/Packages/Persistence/README.md +++ b/Packages/Persistence/README.md @@ -2,7 +2,6 @@ The [Fluent](https://github.com/hummingbird-project/hummingbird-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) | @@ -12,15 +11,15 @@ The package provides, grouped by role: | 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 a `Driver` and hands it over; connection values arrive as plain data. See the Website service's `ConfigReader+Properties` for the mapping. +- **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` is 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 `Sendable` value-type snapshots (e.g. `Example`) before returning, and the models themselves stay internal to the package. +- **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 migrates on startup, 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. `Example`) instead. - **Readiness never throws.** `Probe` runs a `SELECT 1` — the cheapest statement both backends understand, independent of any schema — and maps every failure to `false`, 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. +- **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:** both TLS postures are enforced by the driver itself: `prefer` upgrades the connection only when the server advertises TLS and continues in plaintext otherwise, while `require` refuses the connection when the server offers none. Both behaviors are pinned by tests against a fake server that offers no TLS. +> **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: @@ -41,20 +40,17 @@ Tests/ ``` ## 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: +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 -# in-memory only -swift test -# or -# with the local PostgreSQL up (make db-mount): -POSTGRES_TEST_HOST=127.0.0.1 swift test +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` pattern around `fluent.shutdown()` is the shape to follow. +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 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). +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.3 toolchain (`swift-tools-version:6.3`). -- macOS 15, matching the sibling `Infrastructure` and `Localization` packages (the services deploy to Linux containers; the packages carry no UI platforms). +- 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. diff --git a/Packages/Utility/README.md b/Packages/Utility/README.md index 168ee32..5f62ff0 100644 --- a/Packages/Utility/README.md +++ b/Packages/Utility/README.md @@ -2,7 +2,6 @@ The general-purpose helpers the **Loud** services share: small, single-purpose methods with no dependencies beyond Foundation and no ties to any web framework. ## Overview -The package provides, grouped by role: | Role | Types | | --- | --- | | Validation | `NormalizeEmail`, which reduces a submitted email address to its canonical form | @@ -23,9 +22,9 @@ Tests/ ``` ## Testing -Every suite carries a tag naming the kind of API it exercises — `.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). +Every suite carries a tag for the kind of API it exercises — `.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.3 toolchain (`swift-tools-version:6.3`). -- macOS 15, matching the sibling `Infrastructure`, `Localization`, and `Persistence` packages (the services deploy to Linux containers; the packages carry no UI platforms). +- macOS 15, matching the sibling packages (the services deploy to Linux containers; the packages carry no UI platforms). - No package dependencies — Foundation only.