A modern, type-safe Swift client for Kurrent (formerly EventStoreDB)
為 Kurrent / EventStoreDB 而設計的現代化 Swift 客戶端
Built for Server-Side Swift and Event Sourcing — 為伺服器端 Swift 與 Event Sourcing 打造
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,原本程式碼可繼續運作,遷移節奏自己決定。
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 — 不合法的操作根本通不過編譯。
Every operation flows through a typed target. Tombstoning $all? Reading from a write-only target? Won't compile.
每個操作都透過型別化的 target 進入。對 $all 下 tombstone?編譯就過不去。
Options are mutated in an inout closure. No more options structs, no more parameter explosion.
用 inout closure 設定 options,告別參數爆炸與龐大的 options struct。
KurrentDBClient is an actor. The whole package compiles under -strict-concurrency=complete with zero @unchecked Sendable.
KurrentDBClient 是 actor。整個 package 在 -strict-concurrency=complete 下零 @unchecked Sendable 通過編譯。
Every operation throws KurrentError via typed throws — failure cases are exhaustive at compile time.
所有操作以 typed throws 拋出 KurrentError,錯誤處理在編譯期就被窮舉。
KurrentDB → GRPCEncapsulates → Generated. gRPC patterns live in their own module — reusable beyond KurrentDB.
KurrentDB → GRPCEncapsulates → Generated 三層拆分,gRPC 抽象可被其他客戶端重用。
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 保留。今天升級依舊跑得動,遷移節奏自己掌握。
// 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)
}
// 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
}
Designed for Swift from the ground up — not a wrapper around another language's client.
從 Swift 角度設計,不是其他語言客戶端的轉接層。
Full async/await throughout, with Swift 6 strict-concurrency compliance and zero @unchecked Sendable.
全面 async/await,符合 Swift 6 strict concurrency,零 @unchecked Sendable。
Target-based API rules out illegal operations (e.g. tombstoning $all) before they reach the wire.
Target-based API 讓不合法的操作(例如對 $all 下 tombstone)在編譯期就被擋下。
Every operation throws KurrentError via typed throws — failure cases are exhaustive at compile time.
所有操作以 typed throws 拋出 KurrentError,錯誤處理在編譯期就被窮舉。
First-class support for multi-node TLS clusters with gossip discovery and NodePreference routing.
原生支援多節點 TLS 叢集,含 gossip discovery 與 NodePreference 路由。
Comprehensive DocC guides on Swift Package Index — getting started, appending events, projections, subscriptions, and more.
Swift Package Index 上有完整 DocC 指南:入門、寫入事件、Projections、訂閱等。
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)
// 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)")
}
}
// 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)")
}
The target-based design scopes every operation through a typed target — the compiler keeps you on rails.
所有操作都透過型別化的 target 進入,編譯器幫你守住合法的呼叫範圍。
client.streams(specified: "orders")
.append(events: ...)
.read { ... }
.subscribe()
.delete()
.tombstone()
client.allStreams.read { ... }
client.allStreams.subscribe()
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()
client.persistentSubscriptions(
stream: "orders", group: "workers"
).create {
$0.revision = .start
$0.settings.maxRetryCount = 5
}
// subscribe → ack / nack with park/retry
client.users.create(...)
client.user("jane").enable()
client.operations(of: .scavenge)
.startScavenge(...)
let members = try await client.readCluster()
| Server Version | Status | Notes |
|---|---|---|
| KurrentDB 26.1 | ✅ | Full feature support · 全功能支援 |
| KurrentDB 26.0 | ✅ | Full feature support · 全功能支援 |
| KurrentDB 25.1 | ✅ | Full feature support · 全功能支援 |
| EventStoreDB 24.x | ✅ | Core 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 對所有支援版本進行完整整合測試。