The composition-root runtime for Swift — a set-once root whose residence is a replaceable seam, a boundary table each shell declares for itself, an engine-free lifecycle contract, and the feature-module grammar.
- A shell declares itself, and the rest follows — conform to
Application.Runtime.Protocoland you have said which execution boundaries you open, how each obtains the composition root, how process resources are constructed, and what running and shutting down mean.start()andenter(_:operation:)are derived from that, not reimplemented per shell. - The root's residence is a seam —
Application.Composition.Protocolnames the mechanism;Application.Composition.Localis the one shipped here. A shell whose boundaries need different residence conforms its own type rather than working around this one. - Set-once, and resolvable from tasks that inherit nothing — the root lives in a process-global cell, so a detached background task resolves it. A boundary that re-applies additionally rebinds it, along with the dependency values captured at registration.
- The boundary table does something — routing a boundary through
enter(_:operation:)consults the shell's own table, so a table is behaviour rather than documentation. It is total by construction, so no boundary can go unstated. - Engine-free throughout — no Vapor, no SwiftUI, no platform SDK, and no Foundation. A server and a client differ in their shells and their composition roots, not in their application.
- Typed errors end to end —
Application.Runtime.Errorkeeps the shell's own failure domain and the composition contract's failures apart instead of erasing both toany Error.
A shell states four things. Everything else is derived:
import Application
import Environment
struct Root: Sendable {
var greeting: String
}
enum Server: Application.Runtime.`Protocol` {
typealias Failure = Never
// Every boundary accounted for. A request runs inside the scope that already
// carries the root; a queued job starts detached and must re-establish it.
static var boundaries: Application.Boundary.Table {
var table = Application.Boundary.Table.inherited
table[.job] = .reapplied
table[.task] = .reapplied
return table
}
// Boot phase one and two: construct process resources, compose the root.
// The environment is passed in, so a test boots this against a snapshot it owns.
static func boot(_ environment: Environment.Snapshot) async throws(Never) -> Root {
Root(greeting: environment.string("GREETING") ?? "hello")
}
static func run(_ root: Root) async throws(Never) { /* serve */ }
static func shutdown(_ root: Root) async throws(Never) { /* release */ }
}
// Boot, register, run, shut down — in that order, with no window in which a
// boundary could resolve a root that boot has not finished registering.
try await Server.start()
// At a boundary, the table decides whether the root is inherited or re-applied.
let response = Server.enter(.job) {
// ... resolves the same root either way
}No versions are tagged yet; pin to main:
dependencies: [
.package(url: "https://github.com/swift-foundations/swift-application.git", branch: "main")
].target(
name: "YourTarget",
dependencies: [
.product(name: "Application", package: "swift-application")
]
)Requires Swift 6.3.3. Platform minimums: macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26.
One library product over a single source module, re-exporting the L1 algebra.
| Product | When to import |
|---|---|
Application |
Writing a shell, a feature module, or anything resolving the composition root. Re-exports Application Primitives. |
Key types, all in the Application namespace:
| Type | Purpose |
|---|---|
Application.Composition |
Namespace for where the composition root lives. |
Application.Composition.Protocol |
The residence seam: register once, resolve anywhere, re-apply per boundary. |
Application.Composition.Local |
The residence shipped here: a process-global set-once cell rebound through task locals. |
Application.Composition.Error |
alreadyRegistered, notRegistered, mismatch. |
Application.Runtime.Protocol |
The lifecycle and execution-context contract a shell realizes. |
Application.Runtime.Error |
The shell's own failure, or the composition contract's — kept apart. |
Application.Feature.Protocol |
A feature's interface: client-as-witness, routes, views, typed errors. |
Application.Feature.Implementation |
live or remote, over one interface and one router. |
Application.Composition is deliberately not Dependency.Scope under another name.
A dependency scope is re-enterable and task-local by design: nested scopes shadow outer ones, and nothing survives the closure that established it — a detached task resolves every key to its liveValue. A composition root is the opposite on both counts. It is registered exactly once, a second registration is a defect rather than an override, and it has to resolve from tasks that inherit nothing.
The root also cannot simply be a dependency key: Dependency.Key and Witness.Key both require a static liveValue, and a composition root has no value until boot constructs one.
So this package owns only what the dependency packages do not — set-once registration and process-wide residence — and composes them for everything else. Application.Composition.Local captures the ambient dependency values at registration and reinstalls them through withDependencies at every re-applied boundary, leaving scoped keyed values owned by swift-dependencies.
- swift-application-primitives — the L1 algebra this package realizes: the set-once root, the boundary table, the two-phase boot shape.
Discussion thread will be created at first public release.
Apache 2.0. See LICENSE.