swift-kurrentdb logo

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.6.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,原本的程式碼可以繼續運作,遷移節奏由你決定。

What's new in版本更新

Latest · v2.6.0最新 · v2.6.0

Typed errors for deleted streams and conflicts, and KurrentDB 26.2刪除的 stream 與版本衝突改成型別化錯誤,並支援 KurrentDB 26.2

2.6 turns server errors that used to surface as raw gRPC errors into typed KurrentErrors, adds KurrentDB 26.2 to the tested servers, and documents the source and license of every bundled .proto file. The public API is source compatible with 2.5; read the upgrade notes if you match errors from delete, tombstone or a buffered read().

2.6 把原本以原始 gRPC 錯誤冒出來的伺服器錯誤,改成型別化的 KurrentError; 測試的伺服器加入 KurrentDB 26.2;並記錄每個內附 .proto 檔的來源與授權。 公開 API 與 2.5 原始碼相容;如果你有比對 delete、tombstone 或一般 read() 丟出的錯誤,請先看下面的升級注意事項。

Typed errors型別化錯誤

A read() the server rejects now throws the same KurrentError as read().lazy: resourceDeleted for a tombstoned stream, accessDenied without permission. delete and tombstone report a revision conflict as wrongExpectedVersion(expected:current:), the same case append throws, with current set to .noStream when the stream does not exist.

被伺服器拒絕的 read(),現在會丟出跟 read().lazy 一樣的 KurrentError:stream 被 tombstone 時是 resourceDeleted,沒有權限時是 accessDenied。delete 和 tombstone 遇到版本衝突時,改丟 wrongExpectedVersion(expected:current:),跟 append 同一個 case;stream 不存在時 current 是 .noStream。

do {
    try await client.streams(specified: "orders").delete {
        $0.expectedRevision = .at(3)
    }
} catch KurrentError.wrongExpectedVersion(_, let current) {
    // current is .noStream or .revision(n)
}

KurrentDB 26.2KurrentDB 26.2

CI now tests KurrentDB 26.2 across Swift 6.0–6.4, on a single TLS node: 26.2 needs a Kurrent license to run a multi-node cluster. X.509 user certificates are tested on every run against an unlicensed 26.2 node. 26.2 rejects creating transient projections as deprecated.

CI 現在在 Swift 6.0–6.4 上測試 KurrentDB 26.2,使用單一 TLS 節點,因為 26.2 的多節點 cluster 需要 Kurrent license。X.509 使用者憑證每次都會對一個沒有 license 的 26.2 節點測試。26.2 會以 deprecated 為由拒絕建立 transient projection。

More changes其他變更

Upgrade notes升級注意事項

v2.5.0

Reads that stream, and inputs that can't hurt you邊讀邊用的讀取,以及傷不了你的輸入

2.5 lets a read hand you events as you iterate instead of loading the whole result first, puts a bound on subscription memory, and closes every finding of a security review — connection strings, credentials, CA certificates and server responses can no longer crash the process, leak a password, or quietly weaken TLS. The public API is source compatible with 2.4, but some inputs 2.4 accepted now throw: read the upgrade notes below.

2.5 讓讀取可以邊迭代邊交出事件,不必先把整批結果載入;訂閱的記憶體也有了上限。 同時處理完一次安全盤點的所有項目 —— 連線字串、帳密、CA 憑證和伺服器回應,都不會再讓程序崩潰、 洩漏密碼,或悄悄降低 TLS 的保護。公開 API 與 2.4 原始碼相容,但有些 2.4 接受的輸入現在會報錯: 升級前請先看下面的升級注意事項。

Lazy reads and bounded subscriptionsLazy 讀取與有上限的訂閱

read().lazy pulls events from the server as your loop consumes them, and the call ends as soon as the loop does — on completion, break, a thrown error or cancellation. Plain read() keeps the 2.4 behaviour (the whole result first). Subscriptions now wait for your code instead of piling events up in memory.

read().lazy 會隨著你的迴圈消費,才向伺服器拉下一筆事件;迴圈一結束(跑完、break、丟錯或取消),這次呼叫就跟著結束。一般的 read() 維持 2.4 的行為(先拿完整批結果)。訂閱現在會等你的程式消費,不會把事件一直堆在記憶體裡。

for try await response in client.streams(specified: "orders").read().lazy {
    // events arrive as you iterate; memory does not grow with the stream
}

Strict connection strings嚴格的連線字串

Connection strings are parsed according to RFC 3986. A character in your password can no longer turn off TLS or point the client at another host; unknown, duplicate or invalid parameters throw instead of being ignored; and error messages name the parameter, never its value. Percent-encode a password that contains @ / ? # % — or pass it with .authenticated(.credentials(username:password:)) and skip the connection string.

連線字串改成依照 RFC 3986 解析。密碼裡的字元再也不能關掉 TLS,或讓 client 連到別的 host;未知、重複或格式錯誤的參數會直接報錯,不會被默默忽略;錯誤訊息只寫參數名稱,不會帶出值。密碼含 @ / ? # % 時請先 percent-encode,或改用 .authenticated(.credentials(username:password:)),完全不經過連線字串。

More fixes其他修正

Upgrade notes升級注意事項

v2.4.3

Connection reuse — faster and more solid連線復用 —— 更快,也更穩

Up to 2.3, every single call — each append, each read, each delete — dialled a brand-new connection to the server: a TCP handshake, a TLS handshake, the request, then hang up. 2.4 keeps the line open. Same API, nothing to change in your code; it just gets faster and holds up better.

到 2.3 為止,每一次呼叫(每個 append、每個 read、每個 delete)都會跟伺服器重新「撥一通電話」: TCP 握手、TLS 握手、講一句話,然後掛掉。2.4 開始,線路會一直保持接通。API 完全一樣, 你的程式碼一行都不用改,就是變快、也變穩了。

Think of it like phone calls. Before, it was like hanging up and redialling after every sentence. Now there's one open line everyone takes turns on — HTTP/2 was built to carry many requests over one connection at once. Callers who talk for hours (subscriptions) get their own line, so they never tie up the shared one.

打個比方:以前就像每講一句話就掛電話、再重撥一次。現在是開一條一直接通的專線,大家在同一條線上輪流講 —— HTTP/2 本來就是設計來在一條連線上同時跑很多請求的。至於訂閱這種一講就講好幾個小時的,另外給它自己的線,不會把公用線佔住。

Connections before and after 2.4 Before 2.4, every call from the app to the KurrentDB node opens its own connection with its own handshake. In 2.4, short calls share one connection with a single handshake, while subscriptions and reads each have their own dedicated connection. Before 2.4 2.4 之前 Your app 你的 App KurrentDB node 節點 append append read delete append subscribe = TCP + TLS handshake = TCP + TLS 握手 a new connection for every call 每個呼叫都開新連線、握手一次 With 2.4 2.4 之後 Your app 你的 App KurrentDB node 節點 shared connection 共用連線 append · delete · metadata · admin subscription 訂閱 subscription 訂閱 read 讀取 short calls share one line; streams get their own 短呼叫共用一條線;stream 各走自己的線
Which line a call takes depends on what comes back: one response → the shared line; a stream of responses → its own line.
走哪條線,看回來的是什麼:只回一個結果的,走共用線;會一直回傳資料的(stream),走自己的線。

⚡️ Faster⚡️ 更快在哪

  • Far fewer handshakes. A TLS handshake takes several network round trips before a single byte of your request moves. A page that does 200 appends used to pay for 200 of them; now it pays once.
  • 握手大幅減少。TLS 握手要在網路上來回好幾趟,請求本身才開始送。一個頁面打 200 次 append,以前就是 200 次握手;現在只有第一次要握,後面直接走開好的線。
  • Concurrency shares too. Fire 100 appends at once and they ride the same connection side by side — no burst of 100 new sockets.
  • 並行也共用。同時丟出 100 個 append,它們會一起擠在同一條連線上跑,不會瞬間開出 100 個 socket。
  • Cluster discovery reuses it. Gossip and server-feature checks go over the same shared connections instead of opening their own.
  • 叢集探索也共用。gossip 與伺服器功能檢查也走同一批共用連線,不再各開各的。
  • Verified, not assumed. Our integration tests check that after warm-up, 200 back-to-back appends open zero new connections.
  • 有測試撐腰。整合測試會實際驗證:暖機之後連續 200 次 append,一條新連線都沒開。

🛡️ More solid🛡️ 更穩在哪

  • Subscriptions never clog the line. Each one has its own connection, so a pile of long-running subscriptions can't make your appends wait in line. Tested with 150 subscriptions open at once while appends still go through.
  • 訂閱不會把線塞住。每個訂閱都有自己的連線,就算開了一堆長時間的訂閱,你的 append 也不用排隊。實測同時開 150 個訂閱,append 照樣順利完成。
  • Cancel means stop. Cancelling a subscription used to leave the network read running in the background. Now it really stops.
  • 取消就是真的停。以前取消訂閱後,底層的網路讀取還會在背景偷偷跑;現在是真的停下來。
  • Blips heal themselves. If the network hiccups or the server restarts, the connection reconnects on its own. One slow call timing out doesn't tear down the line everyone else is using.
  • 網路抖一下會自己好。網路短暫中斷或伺服器重啟,連線會自己重連。某一次呼叫逾時,也不會把大家正在用的那條線砍掉。
  • Dead connections get noticed. Your keepAliveInterval / keepAliveTimeout settings are now actually applied, so a connection that silently died while calls are in flight is detected instead of hanging.
  • 斷掉的連線會被發現。設定裡的 keepAliveInterval / keepAliveTimeout 現在真的會生效,有呼叫在跑時,連線如果默默斷掉會被偵測出來,而不是一直卡著。
  • shutdown() means it. It closes every connection, ends active subscriptions, and any later call fails fast with connectionClosed.
  • shutdown() 說到做到。它會關掉所有連線、結束進行中的訂閱,之後的呼叫會立刻以 connectionClosed 失敗。

The life of a shared connection一條共用連線的一生

  1. Opened建立on the first call to that node第一次打到這個 node 時
  2. In use使用中any number of calls share it多少個呼叫都一起用
  3. Idle閒置no calls in flight目前沒有呼叫在跑
  4. Retired回收after 1 hour idle閒置超過 1 小時
  5. Reopened重新建立on the next call, automatically下次呼叫時自動重開

A connection is never retired while a call is using it — the client keeps count.

只要還有呼叫在用,連線就不會被回收 —— client 會記著目前有幾個人在用。

What you need to do你需要做什麼

Almost nothing. Create one client for your whole app and share it — creating a new client per request means connections can't be shared.

幾乎什麼都不用做。整個 app 建一個 client、到處共用就好 —— 如果每個請求都 new 一個 client,連線就沒辦法共用了。

// One client for the whole app
let client = KurrentDBClient(settings: settings)

// When the app is shutting down
try client.shutdown()

2.4.1: dropped subscriptions hang up2.4.1:訂閱丟掉就掛線

A persistent subscription's connection now closes as soon as nothing references the subscription or its events stream — even if you never iterated it. Before 2.4.1 a retain cycle kept those connections open.

只要沒有人再持有持久訂閱或它的 events stream,它的連線就會關閉 —— 就算從來沒迭代過也一樣。2.4.1 之前,一個 retain cycle 會讓這些連線一直開著。

2.4.2: retries that actually wait, and X.509 that actually sends2.4.2:重試真的會等,X.509 真的會送

2.4.3: reads share the connection2.4.3:讀取共用連線

v2.3.0

Test isolation and endpoint resolution測試隔離與 endpoint 解析

2.3 adds a pool for running tests against several independent KurrentDB instances, and fixes how a selected node's address is resolved.

2.3 新增了一個 pool,讓測試可以同時跑在多個彼此獨立的 KurrentDB instance 上,並修正了選中 node 的位址解析方式。

🧪 KurrentDBPool

A new library for tests: borrow a client from a pool of independent KurrentDB instances with withBorrowedClient(_:), configured through the KURRENTDB_POOL_URLS environment variable (a JSON array of connection strings).

給測試用的新 library:用 withBorrowedClient(_:) 從一組彼此獨立的 KurrentDB instance 裡借一個 client 來用,透過環境變數 KURRENTDB_POOL_URLS(連線字串的 JSON 陣列)設定。

🧭 Explicit endpoint resolution🧭 明確的 endpoint 解析

ClientSettings.endpointResolutionPreference states whether to use the gossip-reported address (.gossipReported, the default for seeds and DNS) or the one you configured (.userConfigured, the default for standalone). This stops endpoints collapsing onto the same address when several local containers share 127.0.0.1.

ClientSettings.endpointResolutionPreference 讓你明確指定要用 gossip 回報的位址(.gossipReported,seeds 與 DNS 的預設)還是你自己設定的位址(.userConfigured,standalone 的預設)。多個本機 container 共用 127.0.0.1 時,endpoint 不會再全部被壓到同一個位址。

⏱️ 2.3.1 · Persistent subscription timeouts⏱️ 2.3.1 · 持久訂閱的逾時預設值

Default timeouts were in milliseconds where seconds were intended: messageTimeout is now 30 s (was 30 ms) and checkpointAfter 2 s (was 2 ms), matching the server defaults.

預設逾時值原本誤用了毫秒:messageTimeout 從 30 ms 改為 30 秒,checkpointAfter 從 2 ms 改為 2 秒,與伺服器預設一致。

v2.2.0

Settings from the environment從環境變數建立設定

2.2 makes it easy to keep connection details out of your source code, and fixes reading metadata from a stream that has none yet.

2.2 讓連線資訊更容易留在程式碼之外,並修正了讀取尚無 metadata 的 stream 時的行為。

🌱 ClientSettings.fromEnv(key:)

Build client settings straight from an environment variable holding a connection string — SWIFT_KURRENT_DB_URL by default. Handy for containers and CI.

直接從存放連線字串的環境變數建立 client 設定,預設讀 SWIFT_KURRENT_DB_URL。在 container 與 CI 裡特別方便。

let settings = try ClientSettings.fromEnv()
let custom = try ClientSettings.fromEnv(key: "MY_KURRENTDB_URL")

🔧 getMetadata()

Now returns nil instead of throwing when the stream has no metadata yet, as documented.

stream 還沒有 metadata 時,現在會依文件所述回傳 nil,而不是拋出錯誤。

v2.1.0

New write and read capabilities新的寫入與讀取能力

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 上,新增寫入與讀取能力、逐請求認證,並強化了持久訂閱的連線生命週期。

🧬 AppendRecords (Dynamic Consistency Boundary)

Atomically append across multiple streams with cross-stream consistency checks. Requires server support (KurrentDB 26.1+).

跨多個 stream 的原子寫入,帶前置一致性檢查(Dynamic Consistency Boundary)。需伺服器支援(KurrentDB 26.1+)。

🚀 BatchAppend

High-throughput pipelined multi-stream append over v1 BatchAppend — non-atomic, per-item results, for bulk writes.

以 v1 BatchAppend 一次 pipeline 多筆的高吞吐寫入 —— 非原子、逐筆回報,適合大量寫入。

🔍 Server-side $all filtering🔍 伺服器端 $all 過濾

Filter $all reads on the server by stream name or event type, with prefix or regex matching.

在伺服器端過濾 $all 讀取,可依 stream 名稱或 event type,支援 prefix 與 regex。

🔐 Per-call credentials & Bearer auth🔐 逐請求認證與 Bearer

Override authentication per operation via .authenticated(_:), and authenticate with Bearer tokens — for multi-tenant and per-request authorization.

以 .authenticated(_:) 逐操作覆寫認證身分,並支援 Bearer token —— 適用多租戶與逐請求授權。

🏷️ StreamFilter

SubscriptionFilter is renamed to StreamFilter (the old name is deprecated), now shared across subscriptions and filtered reads.

SubscriptionFilter 更名為 StreamFilter(舊名標記 deprecated),並在訂閱與過濾讀取間共用。

🔧 Persistent subscription lifecycle🔧 持久訂閱的生命週期

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 回報並附續傳位置。

Major release · v2.0.0重大版本 · v2.0.0

A ground-up redesign of the APIAPI 全面重新設計

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⚡️ Trailing closure 設定

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🧩 三層模組拆分

KurrentDB → GRPCEncapsulates → Generated. gRPC patterns live in their own module — reusable beyond KurrentDB.

KurrentDB → GRPCEncapsulates → Generated 三層拆分,gRPC 抽象可被其他客戶端重用。

🛟 1.x preserved as KurrentDB_V1🛟 1.x 保留為 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為什麼選擇 swift-kurrentdb

Native Swift原生 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 — getting started, appending events, projections, subscriptions, and more.

有完整的 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寫入與讀取事件

struct OrderPlaced: Codable, Sendable {
    let orderId: String
    let total: Double
}

// Create an event
let event = EventData(
    eventType: "OrderPlaced",
    model: OrderPlaced(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 {
    let readEvent = try response.event
    print("Event: \(readEvent.record.eventType)")
}

Subscribe訂閱

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

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

API at a glanceAPI 一覽

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 Version伺服器版本 Status狀態 Notes說明
KurrentDB 26.2✅Everything except creating transient projections (rejected as deprecated) · a multi-node cluster needs a Kurrent license建立 transient projection 以外全部支援(伺服器視為 deprecated 而拒絕)· 多節點 cluster 需要 Kurrent license
KurrentDB 26.1✅Full feature support全功能支援
KurrentDB 26.0✅Everything except AppendRecords (26.1+)AppendRecords(26.1+)以外全部支援
KurrentDB 25.1✅Everything except AppendRecords (26.1+)AppendRecords(26.1+)以外全部支援
EventStoreDB 24.x✅Core features · no multi-stream append or AppendRecords核心功能 · 無多 stream 原子寫入與 AppendRecords

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

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

Benchmarks效能基準

Offline benchmarks run on every push to main on Linux and are recorded on Bencher, one point per commit. The charts below are live; click one to open the interactive view. Full results for macOS and Linux, environment and method: Benchmarks page.

離線效能基準每次 push 到 main 都會在 Linux 上執行並記錄到 Bencher,每個 commit 一個資料點。下方圖表即時更新,點擊可開啟互動頁面。macOS 與 Linux 的完整數據、量測環境與方法見效能基準頁面。

EventData latency on Bencher ClientSettings latency on Bencher Options and StreamIdentifier latency on Bencher