Skip to content

swift-application

Development Status

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.


Key Features

  • A shell declares itself, and the rest follows — conform to Application.Runtime.Protocol and 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() and enter(_:operation:) are derived from that, not reimplemented per shell.
  • The root's residence is a seamApplication.Composition.Protocol names the mechanism; Application.Composition.Local is 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 endApplication.Runtime.Error keeps the shell's own failure domain and the composition contract's failures apart instead of erasing both to any Error.

Quick Start

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
}

Installation

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.

Architecture

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.

Composition, not a renamed dependency scope

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.

Related packages


Community

Discussion thread will be created at first public release.

License

Apache 2.0. See LICENSE.

About

The composition-root runtime for Swift: a set-once root with a replaceable resolution mechanism, a per-shell boundary table, and an engine-free lifecycle contract.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages