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.1.0")
]
🎉 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,原本程式碼可繼續運作,遷移節奏自己決定。
2.1 builds on the 2.0 target-based API with new write and read capabilities, per-call authentication, and a hardened persistent-subscription connection lifecycle.
2.1 在 2.0 的 target-based API 上,新增寫入與讀取能力、逐請求認證,並強化了持久訂閱的連線生命週期。
Atomically append across multiple streams with cross-stream consistency checks. Requires server support (KurrentDB 25.1+).
跨多個 stream 的原子寫入,帶前置一致性檢查(Dynamic Consistency Boundary)。需伺服器支援(KurrentDB 25.1+)。
High-throughput pipelined multi-stream append over v1 BatchAppend — non-atomic, per-item results, for bulk writes.
以 v1 BatchAppend 一次 pipeline 多筆的高吞吐寫入 — 非原子、逐筆回報,適合大量寫入。
$all filteringFilter $all reads on the server by stream name or event type, with prefix or regex matching.
在伺服器端過濾 $all 讀取,可依 stream 名稱或 event type,支援 prefix 與 regex。
Override authentication per operation via .authenticated(_:), and authenticate with Bearer tokens — for multi-tenant and per-request authorization.
以 .authenticated(_:) 逐操作覆寫認證身分,並支援 Bearer token — 適用多租戶與逐請求授權。
SubscriptionFilter is renamed to StreamFilter (the old name is deprecated), now shared across subscriptions and filtered reads.
SubscriptionFilter 更名為 StreamFilter(舊名標記 deprecated),並在訂閱與過濾讀取間共用。
Connection tasks are now owned and torn down deterministically, connection failures are observable, and node-level drops surface as subscriptionDropped with resume position.
連線 task 現在有明確擁有者與確定性收尾,連線故障可被觀察,節點層級中斷會以 subscriptionDropped 回報並附續傳位置。
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 對所有支援版本進行完整整合測試。