Skip to content

thoven87/strand

Repository files navigation

Strand

Strand

Postgres-native durable workflow engine for Swift 6.3.

No separate coordination service. No Redis. No Cassandra. Just Swift workers and Postgres.

struct OrderWorkflow: Workflow {
    typealias Input  = OrderInput
    typealias Output = ShipResult

    mutating func run(
        context: WorkflowContext<Self>,
        input: OrderInput
    ) async throws -> ShipResult {
        let charge = try await context.runActivity(
            ChargeCardActivity.self,
            input: .init(amount: input.amount)
        )
        return try await context.runActivity(
            ShipOrderActivity.self,
            input: .init(paymentID: charge.paymentID)
        )
    }
}

If a worker crashes mid-workflow the next worker that picks it up resumes from the last checkpoint. No work is duplicated, no state is lost.

Strand dashboard

Documentation

Quick start

1. Start Postgres

# docker-compose.yml
services:
  db:
    image: postgres:18-alpine
    environment:
      POSTGRES_USER: strand
      POSTGRES_PASSWORD: strand
      POSTGRES_DB: strand_dev
    ports:
      - "5499:5432"
docker compose up -d
psql "postgresql://strand:strand@localhost:5499/strand_dev" -f strand.sql

2. Add to Package.swift

.package(url: "https://github.com/thoven87/strand", from: "0.1.0"),

3. Run

import Strand
import ServiceLifecycle

let postgres = PostgresClient(configuration: .init(
    host: "localhost", port: 5499,
    username: "strand", password: "strand",
    database: "strand_dev", tls: .disable
))
let client = StrandClient(postgres: postgres, queue: "default")
let worker = StrandWorker(
    postgres: postgres,
    options: WorkerOptions(queue: "default"),
    workflows: [OrderWorkflow.self],
    activities: [ChargeCardActivity(), ShipOrderActivity()]
)
let group = ServiceGroup(configuration: .init(
    services: [.init(service: postgres), .init(service: worker)],
    gracefulShutdownSignals: [.sigterm, .sigint]
))

// Start a workflow — returns a handle you can poll for the result:
let handle = try await client.startWorkflow(
    OrderWorkflow.self,
    input: OrderInput(amount: 99_00, orderID: "ord-1")
)

try await group.run()

Examples

See Examples/ for complete runnable examples:

Example What it shows
Greeting Minimal activity + workflow
MultipleActivities Sequential and parallel activities
Schedule Cron and interval scheduling
ChildWorkflows Fan-out orchestration

Requirements

  • Swift 6.3+
  • PostgreSQL 15+

Dependencies

Package Version
PostgresNIO 1.32.2
Hummingbird 2.22.0
swift-service-lifecycle 2.11.0
swift-log 1.12.0
swift-metrics 2.10.1
swift-distributed-tracing 1.4.1
swift-collections 1.0.0+
swift-nio 2.77+

License

Apache 2.0

About

Postgres-native durable workflow engine for Swift 6.3.**

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors