podcast-feed-maker-vapor

0.3.1

Vapor middleware for serving podcast RSS feeds with caching, streaming, Podping, and Fluent integration. Built on PodcastFeedMaker.
atelier-socle/podcast-feed-maker-vapor

What's New

0.3.1 — Toolchain & CI modernization

2026-05-14T15:07:14Z

What's Changed

Maintenance release — no public API changes. Brings the toolchain, CI, and dependencies up to date.

CI & toolchain

  • Build/test CI now runs as a matrix across Swift 6.2 (Xcode 26.2) and Swift 6.3 (Xcode 26.4).
  • All GitHub Actions bumped to their minimum Node 24 major: checkout v5, codecov-action v6, upload-pages-artifact v5, deploy-pages v5, action-gh-release v3.
  • DocC deployment workflow moved to Xcode 26.4.
  • The release workflow now builds and tests on Xcode 26.4 before publishing, so a broken tag never reaches a release.

Dependencies

  • podcast-feed-maker bumped to 0.3.2.
  • async-kit pinned to 1.22.0+ — earlier versions fail to build on Xcode 26.2/26.4 due to a missing-import issue with the MemberImportVisibility upcoming feature.
  • swift-log pin relaxed to 1.10.1+ (the previous < 1.10.0 exclusion only needed to skip one broken release).

Internal

  • Library version reported by /health bumped to 0.3.1.

Full Changelog: 0.3.0...0.3.1

podcast-feed-maker-vapor

CI codecov Swift 6.2 Platforms Documentation

podcast-feed-maker-vapor

Vapor middleware library for serving dynamic podcast RSS feeds. Built on PodcastFeedMaker — the RSS generation, parsing, and validation engine — PodcastFeedVapor provides HTTP caching (ETag, Last-Modified, 304), CORS with preflight, chunked XML streaming, query-based pagination, Podping webhook notifications, batch feed auditing, Fluent protocol mapping, and optional Redis cache and queue worker targets. Modular by design: import only what you need.

Part of the Atelier Socle ecosystem.


Features

  • Middleware — X-Generator header, ETag/304 HTTP caching, CORS with preflight
  • Route Builderapp.podcastFeed("feed.xml") DSL for feed routes
  • Streaming — Chunked XML streaming via StreamingFeedGenerator for large catalogs
  • Pagination — Query params parsing (?limit=N&offset=N) with safe clamping
  • Podping — Feed update notifications via webhook and real-time WebSocket
  • Batch Audit — Parallel feed quality scoring with grades and recommendations
  • Fluent Mapping — Pure Swift protocols for model-to-feed conversion
  • Redis Cache — Optional FeedCacheStore protocol + Redis implementation
  • Queue Workers — Optional background feed regeneration via Vapor Queues
  • Metrics — Feed request counters, latency timers, and response size recording via swift-metrics
  • Strict concurrency — All types Sendable, Swift 6.2 strict concurrency

Installation

Requirements

  • Swift 6.2+ with strict concurrency
  • Vapor 4.121+
  • PodcastFeedMaker 0.3.2+
  • Platforms: macOS 14+ · Linux (Ubuntu 22.04+)

Swift Package Manager

Add the dependency to your Package.swift:

dependencies: [
    .package(url: "https://github.com/atelier-socle/podcast-feed-maker-vapor.git", from: "0.3.1")
]

Four products are available — import only what you need:

// Core (required) — middleware, routes, encoding, Fluent mapping
.product(name: "PodcastFeedVapor", package: "podcast-feed-maker-vapor")

// Optional — Redis-backed feed cache
.product(name: "PodcastFeedVaporRedis", package: "podcast-feed-maker-vapor")

// Optional — Background feed regeneration jobs
.product(name: "PodcastFeedVaporQueues", package: "podcast-feed-maker-vapor")

// Optional — Feed serving metrics (Prometheus, StatsD, etc.)
.product(name: "PodcastFeedVaporMetrics", package: "podcast-feed-maker-vapor")

Quick Start

import PodcastFeedVapor

func configure(_ app: Application) throws {
    app.feedConfiguration = FeedConfiguration(
        ttl: .hours(1),
        prettyPrint: false,
        generatorHeader: "MyApp/1.0"
    )
    app.healthCheck()
    app.middleware.use(CORSFeedMiddleware())
    app.middleware.use(PodcastFeedMiddleware())
    app.grouped(FeedCacheMiddleware()).podcastFeed("feed.xml") { req in
        try await loadFeed(on: req.db)
    }
}

Key Concepts

Middleware Stack

Three middlewares add production headers to feed responses. Stack them in order: CORS (outermost), generator header, cache (innermost):

app.middleware.use(CORSFeedMiddleware())
app.middleware.use(PodcastFeedMiddleware())
app.grouped(FeedCacheMiddleware(ttl: .hours(2)))
    .podcastFeed("feed.xml") { _ in feed }

Feed Routes

Register feed routes with the podcastFeed() DSL. Supports static paths and dynamic parameters:

app.podcastFeed("feed.xml") { _ in feed }
app.podcastFeed("shows", ":showId", "feed.xml") { req in
    let showId = req.parameters.get("showId") ?? "unknown"
    return try await loadFeed(for: showId, on: req.db)
}

HTTP Caching

FeedCacheMiddleware adds ETag (SHA256), Last-Modified, and Cache-Control headers. Returns 304 Not Modified when the client's cached version is still valid:

app.grouped(FeedCacheMiddleware(ttl: .minutes(15)))
    .podcastFeed("feed.xml") { _ in feed }

Fluent Mapping

Three protocols map your types to feeds — no Fluent dependency required:

extension Show: FeedMappable {
    func toPodcastFeed() -> PodcastFeed { ... }
}
extension Episode: ItemMappable {
    func toItem() -> Item { ... }
}
let items = episodes.toItems()  // Array extension

Podping

Webhook notifications via PodpingNotifier and real-time WebSocket via PodpingWebSocketManager:

// Webhook (fire-and-forget)
let notifier = PodpingNotifier(client: req.client, authToken: "token")
try await notifier.notify(feedURL: "https://example.com/feed.xml")

// WebSocket (real-time, persistent)
app.podpingWebSocket("podping")
await app.podpingWebSocketManager.broadcast(
    feedURL: "https://example.com/feed.xml"
)

Batch Audit

Score feed quality in parallel:

app.batchAudit("feeds", "audit")
// GET /feeds/audit?urls=https://a.com/feed.xml,https://b.com/feed.xml

Metrics

Optional target for feed serving observability. Uses Apple's swift-metrics API — plug in any backend:

import PodcastFeedVaporMetrics

// Add metrics before other feed middleware
app.middleware.use(FeedMetricsMiddleware())
app.middleware.use(PodcastFeedMiddleware())

Emits: pfv_feed_requests_total (counter), pfv_feed_request_duration_seconds (timer), pfv_feed_response_size_bytes (recorder), pfv_feed_active_streams (gauge).

Redis and Queues

Optional targets for production scale — no Redis/Queues dependency in core:

// Redis cache
import PodcastFeedVaporRedis
let cache = app.redisFeedCache(keyPrefix: "myapp:feed:", defaultTTL: 600)
try await cache.set(identifier: "show-123", xml: feedXML, ttl: 300)

// Queue workers
import PodcastFeedVaporQueues
app.registerFeedRegenerationJob(handler: MyFeedRegenerator())
try await req.queue.dispatch(
    FeedRegenerationJob.self,
    FeedRegenerationPayload(feedIdentifier: "show-123", reason: "episode_added")
)

Architecture

Sources/
    PodcastFeedVapor/              # Core (Vapor + PodcastFeedMaker + Fluent)
        PodcastFeedMiddleware.swift
        FeedCacheMiddleware.swift
        CORSFeedMiddleware.swift
        FeedRouteBuilder.swift
        FeedResponseEncoder.swift
        FeedConfiguration.swift
        FeedPagination.swift
        StreamingFeedResponse.swift
        FluentFeedMapping.swift
        PodpingNotifier.swift
        PodpingMessage.swift
        PodpingWebSocketManager.swift
        PodpingWebSocketRoute.swift
        BatchAuditEndpoint.swift
        HealthCheck.swift
        Extensions/
            Request+Feed.swift
            Response+XML.swift
    PodcastFeedVaporRedis/         # Optional Redis cache
        RedisFeedCache.swift
    PodcastFeedVaporQueues/        # Optional queue workers
        FeedRegenerationJob.swift
    PodcastFeedVaporMetrics/       # Optional metrics middleware
        FeedMetricsMiddleware.swift
        FeedMetricsConfiguration.swift
        FeedActiveStreamsGauge.swift

Sample Server

A companion test server demonstrates all features with real HTTP endpoints. Run the setup script to scaffold it as a sibling directory:

./scripts/setup-sample-server.sh
cd ../sample-vapor-server
swift run

Or specify a custom path:

./scripts/setup-sample-server.sh /path/to/my-test-server

Then test with curl:

curl http://localhost:8080/health
curl http://localhost:8080/feed.xml
curl -sI http://localhost:8080/feed.xml | grep etag
curl "http://localhost:8080/feeds/audit?urls=https://feeds.simplecast.com/54nAGcIl"

See scripts/setup-sample-server.sh for the full list of endpoints and test scenarios.


Roadmap

  • Streaming cache — Stream-through caching for very large feeds
  • Metrics — Prometheus/StatsD middleware for feed serving metrics
  • WebSocket Podping — Real-time podping via WebSocket instead of webhook

Specification References


Documentation

Full API documentation is available as a DocC catalog hosted on GitHub Pages and bundled with the package. Open the project in Xcode and select Product > Build Documentation to browse it locally.

Guides:

Guide Content
Getting Started Installation and first feed route
Middleware Guide Caching, CORS, and generator header
Feed Serving Guide Route builder DSL, streaming, and pagination
Fluent Integration Protocol-based model mapping
Advanced Features Podping, batch audit, and health check
Redis and Queues Optional production targets
Metrics Feed serving metrics via swift-metrics

Contributing

See CONTRIBUTING.md for guidelines.


License

This project is licensed under the Apache License 2.0.

Copyright 2026 Atelier Socle SAS. See NOTICE for details.

Description

  • Swift Tools 6.2.0
View More Packages from this Author

Dependencies

Last updated: Mon Jul 13 2026 13:46:46 GMT-0900 (Hawaii-Aleutian Daylight Time)