swift-kurrentdb icon

swift-kurrentdb

A modern, type-safe Swift client for Kurrent (formerly EventStoreDB)

為 Kurrent / EventStoreDB 而設計的現代化 Swift 客戶端

Built for Server-Side Swift and Event Sourcing — 為伺服器端 Swift 與 Event Sourcing 打造

Swift versions Platforms License Build status

Install 安裝

Add to your Package.swift:

Package.swift 加入:

dependencies: [
    .package(url: "https://github.com/gradyzhuo/swift-kurrentdb.git", from: "2.0.3")
]

🎉 2.0 is here — the target-based API is the recommended way to use swift-kurrentdb. Already on 1.x? Use KurrentDB_V1 to keep your existing code while you migrate.

🎉 2.0 已上線 目前推薦使用 target-based API。仍在 1.x?請改用 KurrentDB_V1,原本程式碼可繼續運作,遷移節奏自己決定。

Major release · v2.0.0

What's new in 2.0 2.0 帶來了什麼

Version 2.0 is a ground-up redesign of the public API. The flat client.appendToStream(...) surface gives way to typed targets the compiler can verify — illegal operations literally don't compile.

2.0 是 API 的全面重新設計。原本 client.appendToStream(...) 的扁平呼叫, 換成編譯器能驗證的型別化 target — 不合法的操作根本通不過編譯。

🎯 Target-based API

Every operation flows through a typed target. Tombstoning $all? Reading from a write-only target? Won't compile.

每個操作都透過型別化的 target 進入。對 $all 下 tombstone?編譯就過不去。

⚡️ Trailing-closure builders

Options are mutated in an inout closure. No more options structs, no more parameter explosion.

inout closure 設定 options,告別參數爆炸與龐大的 options struct。

🛡️ Actor + Swift 6 strict concurrency

KurrentDBClient is an actor. The whole package compiles under -strict-concurrency=complete with zero @unchecked Sendable.

KurrentDBClient 是 actor。整個 package 在 -strict-concurrency=complete 下零 @unchecked Sendable 通過編譯。

📐 Typed throws

Every operation throws KurrentError via typed throws — failure cases are exhaustive at compile time.

所有操作以 typed throws 拋出 KurrentError,錯誤處理在編譯期就被窮舉。

🧩 Three-layer module split

KurrentDBGRPCEncapsulatesGenerated. gRPC patterns live in their own module — reusable beyond KurrentDB.

KurrentDBGRPCEncapsulatesGenerated 三層拆分,gRPC 抽象可被其他客戶端重用。

🛟 1.x preserved as KurrentDB_V1

The 1.x flat-method API ships in the same package under a separate library. Migrate at your own pace — your code keeps working today.

1.x 平面式 API 在同一個 package 內以獨立 library 保留。今天升級依舊跑得動,遷移節奏自己掌握。

See the difference 前後對照

1.x · Flat methods

// Append
try await client.appendToStream(
    "orders",
    events: [event]
) {
    $0.revision(expected: .streamExists)
}

// Read
let responses = try await client.readStream("orders") {
    $0.startFrom(revision: .start).limit(50)
}

// Persistent subscription
try await client.createPersistentSubscription(
    stream: "orders",
    groupName: "workers"
) {
    $0.startFrom(revision: .start)
      .maxRetryCount(5)
}

2.x · Target-based

// Append
try await client.streams(specified: "orders")
    .append(events: [event]) {
        $0.expectedRevision = .streamExists
    }

// Read
let responses = try await client.streams(specified: "orders").read {
    $0.revision = .start
    $0.limit = 50
}

// Persistent subscription
try await client.persistentSubscriptions(
    stream: "orders", group: "workers"
).create {
    $0.revision = .start
    $0.settings.maxRetryCount = 5
}
Read the Migration Guide → Full 1.x → 2.x walkthrough with every breaking change documented.
完整 1.x → 2.x 遷移指南,每一個破壞性變更都有對照。

Why swift-kurrentdb 為什麼選擇

Native Swift

Designed for Swift from the ground up — not a wrapper around another language's client.

從 Swift 角度設計,不是其他語言客戶端的轉接層。

Modern Concurrency

Full async/await throughout, with Swift 6 strict-concurrency compliance and zero @unchecked Sendable.

全面 async/await,符合 Swift 6 strict concurrency,零 @unchecked Sendable

Compile-Time Safety

Target-based API rules out illegal operations (e.g. tombstoning $all) before they reach the wire.

Target-based API 讓不合法的操作(例如對 $all 下 tombstone)在編譯期就被擋下。

Typed Errors

Every operation throws KurrentError via typed throws — failure cases are exhaustive at compile time.

所有操作以 typed throws 拋出 KurrentError,錯誤處理在編譯期就被窮舉。

Cluster-Ready

First-class support for multi-node TLS clusters with gossip discovery and NodePreference routing.

原生支援多節點 TLS 叢集,含 gossip discovery 與 NodePreference 路由。

Well-Documented

Comprehensive DocC guides on Swift Package Index — getting started, appending events, projections, subscriptions, and more.

Swift Package Index 上有完整 DocC 指南:入門、寫入事件、Projections、訂閱等。

Quick Start 快速上手

Connect 連線

import KurrentDB

// Local development — single node
let settings = ClientSettings.localhost()
    .authenticated(.credentials(username: "admin", password: "changeit"))

// Production — remote cluster (TLS enabled by default)
let production = ClientSettings.remote(
    "node1.example.com:2113",
    "node2.example.com:2113",
    "node3.example.com:2113"
).authenticated(.credentials(username: "admin", password: "changeit"))

let client = KurrentDBClient(settings: settings)

Append & Read Events 寫入與讀取事件

// Create an event
let event = EventData(
    eventType: "OrderPlaced",
    model: ["orderId": "order-123", "total": 99.99]
)

// Append to stream
try await client.streams(specified: "orders").append(events: [event]) {
    $0.expectedRevision = .any
}

// Read events
let responses = try await client.streams(specified: "orders").read {
    $0.revision = .start
    $0.limit = 10
}

for try await response in responses {
    if let event = try response.event {
        print("Event: \(event.record.eventType)")
    }
}

Subscribe 訂閱

// Catch-up subscription
let subscription = try await client.streams(specified: "orders").subscribe()

for try await event in subscription.events {
    print("Live event: \(event.record.eventType)")
}

API at a glance API 一覽

The target-based design scopes every operation through a typed target — the compiler keeps you on rails.

所有操作都透過型別化的 target 進入,編譯器幫你守住合法的呼叫範圍。

Streams

client.streams(specified: "orders")
  .append(events: ...)
  .read { ... }
  .subscribe()
  .delete()
  .tombstone()

client.allStreams.read { ... }
client.allStreams.subscribe()

Projections

client.projections(of: .continuous(name: "x"))
  .create(query: js)

client.projections(name: "x")
  .enable() / .disable()
  .state(of: T.self)
  .result(of: T.self)

client.projections(of: .anyMode).list()

Persistent Subscriptions

client.persistentSubscriptions(
    stream: "orders", group: "workers"
).create {
    $0.revision = .start
    $0.settings.maxRetryCount = 5
}

// subscribe → ack / nack with park/retry

Users · Operations · Gossip

client.users.create(...)
client.user("jane").enable()

client.operations(of: .scavenge)
  .startScavenge(...)

let members = try await client.readCluster()

Server Compatibility 伺服器相容性

Server VersionStatusNotes
KurrentDB 26.1Full feature support · 全功能支援
KurrentDB 26.0Full feature support · 全功能支援
KurrentDB 25.1Full feature support · 全功能支援
EventStoreDB 24.xCore features · v2 batch append 尚未提供

Tested in CI across Swift 6.0 / 6.1 / 6.2 / 6.3 against every supported server version.

CI 在 Swift 6.0 / 6.1 / 6.2 / 6.3 對所有支援版本進行完整整合測試。