Files
javier 65b62681eb Project updates from Template (#1)
This PR contains the latest updates from the generic Website template, which have been added while working on #loud-amsterdam.

Reviewed-on: #1
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
2026-09-04 13:40:35 +00:00
..
2026-09-04 13:40:35 +00:00
2026-08-19 23:19:08 +02:00
2026-09-04 13:40:35 +00:00

Infrastructure

The shared Hummingbird toolkit the platform's services build on: declarative routing, hardened HTTP middlewares, pre-rendered localized responses, and page and asset scaffolding.

Overview

Role Types
Routing RouterController, RouteCollectionBuilder, the addController extension on RouterMethods
Middlewares SecurityHeadersMiddleware, HTTPSRedirectMiddleware, TrailingSlashRedirectMiddleware, VaryMiddleware, RateLimitMiddleware, LocalizationMiddleware (negotiating from a lang query parameter, then a leading path segment, then Accept-Language), NotFoundMiddleware
Pages and assets Page, Asset, AssetExtension, FingerprintAssets
Link previews SocialCard, its locale and the alternateLocales of its other language editions, its Image and Style, and the Tag meta tags it derives
Structured data StructuredData, the Node, Property, and Value types of its schema.org graph, the open Name and Kind vocabularies, and the site-wide initializer building the Organization/WebSite pair
Analytics Analytics, the Events a page reports (with tagging() to apply one to any attribute-bearing HTML or SVG tag), the tracker script Attributes it derives, the optional session recorder script paired with it, and the origin its preconnect hint targets
Responses CachedHTMLResponse, and LocalizedHTMLCollectionResponse rendering one of them per catalog language
Contexts LocalizedRequestContext
Constants The HTTPField.Name header names, Int.RateLimit limits, and String.Security header values the middlewares default to

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. What a type needs, it takes as a parameter: the bundle: whose String Catalog names the supported languages, the document: closure that builds a page for a locale, a Page conformer's metadata and its optional head concerns (summary, canonicalURL, socialCard, structuredData, analytics). URLs arrive absolute and fully formed — composing them stays with the page. The same holds for request semantics: only the caller knows whether it negotiates the language or pins it by route, so variesOnAcceptLanguage: declares whether the responses carry Vary: Accept-Language.
  • Types own their format; Page renders generically. Each head concern derives its own render-ready form — SocialCard.tags, StructuredData.payload, Analytics.attributes — which Page applies without knowing the vocabulary. A page's scripts and the tracker render as deferred head tags, the tracker preceded by a preconnect to its cross-origin host.
  • Nodes are joined by @id, not repetition. A node another page must point at gets its identifier from a helper rather than a hand-spelled fragment — organizationID(forSiteURL:) names the node the site-wide initializer builds, and that initializer's founder takes such an identifier back. A service adds the helper for any node it owns, so neither side can drift.
  • Services fill the gaps once, via extensions. A service restores its convenient call sites retroactively — the Website's *+Defaults are the pattern. The open schema.org vocabularies work the same way: the package declares the shared Property.Name and Node.Kind constants, a service adds its own.
  • Method structs. Single-operation types such as FingerprintAssets take lifetime-fixed configuration in init and per-call inputs in callAsFunction.

Layout

Sources are split by visibility, then by kind, one type per file:

Sources/
├── Public/                public API
│   ├── Builders/          RouteCollectionBuilder
│   ├── Enumerations/      AssetExtension
│   ├── Extensions/        addController and Analytics.Event tagging, plus the default header names, rate limits, and header values
│   ├── Methods/           FingerprintAssets
│   ├── Middlewares/       the seven HTTP middlewares
│   ├── Protocols/         Asset, LocalizedRequestContext, Page, RouterController
│   ├── Responses/         CachedHTMLResponse, LocalizedHTMLCollectionResponse
│   └── Types/             Analytics, SocialCard, StructuredData — each nesting its own types in a folder of that name
└── Internal/
    ├── Extensions/        implementation details (the String separators)
    └── Types/             implementation details (FNV1aHash)
Tests/
├── Cases/                 the test suites, mirroring the Sources/ layout
├── Catalogs/              the String Catalog fixture, copied verbatim so it loads on Linux
└── Utils/                 stubs (StubAsset, StubPage, …) and the suite Tag constants

Testing

Every suite carries a tag for the kind of API it exercises — .asset, .extension, .middleware, .protocol, .type — declared in Tests/Utils/Extensions/Tag+Constants.swift, so a run can be sliced by kind. A new suite takes 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 packages. The services deploy to Linux containers; the packages declare no UI platforms.