Swift SDK¶
The Swift package is named HeddleActor. It provides Codable wire models,
subject helpers, shallow schema validation, and a generic worker base.
Add the package¶
For a local checkout:
For a Git dependency:
Use the HeddleActor product. The package: identifier depends on
which dependency form you used: the repo slug heddle-sdk for the Git
URL form above, or the directory name swift for a local-path form
(.package(path: "../../swift"), as used in
examples/swift/echo-worker/Package.swift).
// Git-URL consumption (matches the dependency block above):
.product(name: "HeddleActor", package: "heddle-sdk")
// Local-path consumption (for in-repo development):
// .product(name: "HeddleActor", package: "swift")
Define payload and output types¶
import HeddleActor
struct EchoPayload: Codable, Sendable {
var text: String
}
struct EchoOutput: Codable, Sendable {
var text: String
var length: Int
}
Payload types decode from TaskMessage.payload. Output types must encode to a
JSON object; arrays and primitive outputs fail the worker contract because
TaskResult.output is object-shaped.
Implement a worker¶
final class EchoWorker: HeddleWorker<EchoPayload, EchoOutput>, @unchecked Sendable {
init() {
super.init(workerType: "echo", tier: "local")
}
override func process(
payload: EchoPayload,
metadata: [String: JSONValue]
) async throws -> WorkerOutput<EchoOutput> {
WorkerOutput(
output: EchoOutput(
text: payload.text.uppercased(),
length: payload.text.count
),
modelUsed: "swift-example"
)
}
}
Run with a transport¶
The core SDK defines the transport boundary:
public protocol HeddleTransport: Sendable {
func publish(subject: String, payload: Data) async throws
func subscribe(
subject: String,
queueGroup: String?
) async throws -> AsyncThrowingStream<HeddleMessage, Error>
}
The core package includes an in-memory implementation for local examples and tests:
A broker adapter can implement the same protocol without changing worker code:
Use the shipped NATS adapter package for Heddle runtime interop:
import HeddleActorNATS
let transport = NatsTransport(url: URL(string: "nats://localhost:4222")!)
try await transport.connect()
try await EchoWorker().run(transport: transport)
The checked-in example uses InMemoryTransport so it can run without NATS
while still exercising the transport loop:
Swift concurrency notes¶
- Payload and output types should be
Sendable. HeddleWorkeris an open class and marked@unchecked Sendablebecause subclasses define their own state. Keep subclasses stateless between tasks.- Override
reset()to clear temporary resources after each task. - Override
malformedMessage(_:)to log malformed input without crashing the subscription loop. InMemoryTransportis process-local. Use a shared broker transport for a native worker that needs to talk to a running Heddle or Workshop process.HeddleActorNATSdepends on the officialnats-io/nats.swiftpackage and stays separate from the core Swift package. The real NATS binding currently builds on macOS, matching the official client's published platform support.