swift-ddd-kit

Domain-Driven Design, Event Sourcing and CQRS for Server-Side Swift

為 Server-Side Swift 打造的 Domain-Driven Design、Event Sourcing 與 CQRS 框架

Swift 6.1+ · KurrentDB as the event store · Ubuntu and macOS 15+

Swift 6.1+ · 以 KurrentDB 作為事件儲存 · Ubuntu 與 macOS 15+

Latest release Swift versions Platforms License Build status

Install安裝

Add the package to your Package.swift. DDDKit is the umbrella product; it re-exports the core protocols, the event sourcing layer, the KurrentDB adapter, the in-memory event bus and read model persistence.

在 Package.swift 加入套件。DDDKit 是 umbrella product,會一併重新導出核心協定、event sourcing 層、KurrentDB 轉接層、記憶體內 event bus 與 read model 持久化。

dependencies: [
    .package(url: "https://github.com/gradyzhuo/swift-ddd-kit.git", from: "1.3.0")
],
targets: [
    .target(
        name: "MyContext",
        dependencies: [
            .product(name: "DDDKit", package: "swift-ddd-kit"),
        ]
    )
]

🧪 Trying 1.4? The template variables framework ships as a pre-release. Pin from: "1.4.0-beta.9" to pick it up; the API may still move before 1.4.0 is final.

🧪 想試 1.4? Template variables 框架目前以 pre-release 發佈。將版本設為 from: "1.4.0-beta.9" 即可取得;在 1.4.0 正式版之前 API 仍可能調整。

What's new in版本更新

Pre-release · 1.4.0-beta.9預覽版 · 1.4.0-beta.9

Template variables frameworkTemplate variables 框架

1.4 adds a small framework for filling templates from typed values. A variables.yaml declares placeholders with typed inputs; VariablesGeneratorPlugin generates a Sendable protocol with one async throws -> String method per variable; PlaceholderSubstitution fills %Placeholder% tokens in a single pass.

1.4 新增一個以型別化數值填入模板的小框架。variables.yaml 宣告帶有型別化 input 的 placeholder; VariablesGeneratorPlugin 為每個變數生成一個 async throws -> String 方法, 組成一個 Sendable protocol;PlaceholderSubstitution 以單一遍歷填入 %Placeholder%。

TagTag Change變更
1.4.0-beta.9Template variables framework: TemplateVariables, TemplateVariablesDefinition, VariablesGeneratorPlugin. ForwardingRule.translate returns an array.Template variables 框架:TemplateVariables、TemplateVariablesDefinition、VariablesGeneratorPlugin。ForwardingRule.translate 改為回傳陣列。
Unreleased未發行Postgres read models, production-ready: monotonic snapshots by default, runner-managed subscription groups, KurrentProjection.rebuild, stable readModelTypeName, and the generated apply() no longer aborts a fold.Postgres read model 上線強化:預設 monotonic snapshot、runner 自行管理 subscription group、KurrentProjection.rebuild、穩定的 readModelTypeName,且生成的 apply() 不再中斷整個 fold。

Earlier 1.4.0 pre-releases belong to a retired line.

較早的 1.4.0 預覽版屬於已停用的版本線。

Latest stable · 1.3.0最新穩定版 · 1.3.0

Cross-context events over Apache Pulsar跨 Bounded Context 的事件:Apache Pulsar

Domain events stay inside their context. What crosses the boundary is a Published Language event, and 1.3 adds both halves of that trip over Apache Pulsar, with neither side depending on a Pulsar client library.

Domain event 留在自己的 context 裡;跨越邊界的是 Published Language 事件。1.3 補上這段旅程在 Apache Pulsar 上的兩端, 而且兩端都不依賴 Pulsar 的 client library。

PublishedLanguage

The zero-dependency wire envelope, PublishedLanguageEvent, shared by producers and consumers.

零依賴的 wire envelope PublishedLanguageEvent,由發送端與接收端共用。

ContextForwarder

Produce side. Subscribes to the context's own KurrentDB, translates events through ForwardingRules and posts them to Pulsar's REST produce endpoint. Bearer-token auth and a self-healing ForwarderGroup.

發送端。訂閱自身 context 的 KurrentDB,透過 ForwardingRule 轉譯後 POST 到 Pulsar 的 REST produce 端點。支援 Bearer token 驗證與會自我修復的 ForwarderGroup。

ContextReceiver

Consume side, portable. Drives a PulsarMessageSource into your handler with permit-based flow control, settlement rules and a DeadLetterMonitor. Fully testable offline.

接收端,可攜。透過 permit 流量控制把 PulsarMessageSource 的訊息送進你的 handler,內含結算規則與 DeadLetterMonitor。可完全離線測試。

ContextReceiverWebSocket

The concrete transport over Pulsar's WebSocket consumer protocol. Linux-only for now: the upstream WebSocket dependency does not build on the macOS 26 SDK.

走 Pulsar WebSocket consumer 協定的實際 transport。目前僅支援 Linux:上游的 WebSocket 依賴在 macOS 26 SDK 上無法編譯。

Things only a live broker teaches. REST produce takes a raw JSON string while the WebSocket consumer returns it base64-encoded. Delivery is at-least-once, so derive downstream ids from the upstream eventId to stay idempotent. Set maxRedeliverCount and deadLetterTopic, or a message you gave up on will redeliver forever.

只有真實 broker 才會教你的事。 REST produce 收原始 JSON 字串,WebSocket consumer 卻回傳 base64 編碼。投遞是 at-least-once,請從上游 eventId 推導下游 id 以保持冪等。務必設定 maxRedeliverCount 與 deadLetterTopic,否則放棄的訊息會無限重送。

1.2.01.2.0

Test without a server: an in-memory KurrentDB double不用伺服器也能測:記憶體內的 KurrentDB 替身

swift-kurrentdb's RecordedEvent and Streams cannot be constructed from outside that package, so a fake could never conform to the client protocol. 1.2 adds a narrow EventStoreClient seam covering exactly what the coordinator and the projection runners call, and ships two conformances: the live one, and one that needs no server at all.

swift-kurrentdb 的 RecordedEvent 與 Streams 無法在套件外建構,因此假物件永遠無法 conform 原本的 client 協定。 1.2 新增一個窄的 EventStoreClient seam,只涵蓋 coordinator 與 projection runner 實際呼叫的操作,並提供兩個實作:一個接真實伺服器,一個完全不需要伺服器。

InMemoryEventStoreClient

In the new KurrentSupportInMemory product. Real optimistic-concurrency semantics, $ce- category routing, replay-on-subscribe for "seed then subscribe" tests, and an opt-in deliveryDelay to test eventual consistency on purpose.

位於新的 KurrentSupportInMemory product。真實的 optimistic concurrency 語意、$ce- category 路由、訂閱時重播(方便「先塞資料再訂閱」的測試),以及可選的 deliveryDelay 用來刻意測試最終一致性。

LiveEventStoreClient

The production conformance wrapping a real KurrentDBClient. The client: KurrentDBClient convenience initializers stay, so existing call sites do not change.

包著真實 KurrentDBClient 的生產環境實作。client: KurrentDBClient 便利初始化器保留,既有呼叫端不需修改。

KurrentDBPool (TestUtility)

KurrentDBClient.withPooledIntegrationTestClient borrows a client from a pool when KURRENTDB_POOL_URLS is set and falls back to the fixed cluster otherwise. Opt-in only.

KurrentDBClient.withPooledIntegrationTestClient 在設定 KURRENTDB_POOL_URLS 時從連線池借用 client,否則退回固定叢集。純選用。

Breaking: mappers receive any RecordedEventLike破壞性變更:mapper 改收 any RecordedEventLike

1.11.1
func mapping(eventData: RecordedEvent) throws
    -> (any DomainEvent)? {
    switch eventData.eventType { ... }
}

let stream = record.streamIdentifier.name
1.21.2
func mapping(eventData: any RecordedEventLike) throws
    -> (any DomainEvent)? {
    switch eventData.eventType { ... }   // unchanged body
}

let stream = record.streamName

The same applies to KurrentProjection's register(extractInput:) closures. Code that only switches on eventType and calls decode(to:) is source-compatible after updating the parameter type. The dddkit scaffolding CLI moved out to swift-pangu-cli in this release.

KurrentProjection 的 register(extractInput:) closure 也一樣。只 switch eventType、呼叫 decode(to:) 的程式碼,改參數型別後即可相容。此版也把 dddkit 腳手架 CLI 拆到 swift-pangu-cli。

1.1.0 · 1.1.11.1.0 · 1.1.1

A scaffolding CLI, and a safer first save專案腳手架 CLI,與更安全的首次寫入

1.1.0 added the dddkit command-line tool for scaffolding a runnable starter project (aggregate, use cases, KurrentDB wiring) plus a release pipeline that publishes it as a GitHub Release, a signed apt repository and a Homebrew tap. 1.1.1 fixed a correctness hole in the write path.

1.1.0 新增 dddkit 命令列工具,可產生可直接執行的起始專案(aggregate、use case、KurrentDB 接線), 並附上把它發佈為 GitHub Release、簽章 apt repository 與 Homebrew tap 的 release pipeline。1.1.1 修補了寫入路徑上的一個正確性漏洞。

First save expects .noStream首次寫入預期 .noStream 1.1.1

A never-persisted aggregate has no version. KurrentStorageCoordinator.append used to send expectedRevision: .any in that case, which skips the concurrency check entirely. It now sends .noStream, so a duplicate or concurrent create collides with the existing stream instead of silently succeeding.

從未持久化的 aggregate 沒有版本號。過去 KurrentStorageCoordinator.append 在這種情況送出 expectedRevision: .any,等於完全跳過並行檢查。現在改送 .noStream,重複或並行的建立會撞到既有 stream,而不是靜默成功。

Where the CLI lives nowCLI 現在的位置

Since 1.2 the scaffolding tool is maintained separately as swift-pangu-cli (pangu project create), with no dependency on this package's library targets.

自 1.2 起,腳手架工具獨立維護為 swift-pangu-cli(pangu project create),不再依賴本套件的 library target。

Breaking · 1.0.0 · 1.0.1破壞性變更 · 1.0.0 · 1.0.1

Ambient context and pluggable metadataAmbient context 與可替換的 metadata

0.x carried audit metadata to the store as an external: [String: String]? dictionary threaded through every save. 1.0 replaces that with a typed ambient context: define your own EventMetadata schema, set it once at the use case entry, and structured concurrency carries it to EventStore.append. Aggregates, events and use cases never see it.

0.x 用 external: [String: String]? 字典把稽核 metadata 一路傳進每個 save。 1.0 改成型別化的 ambient context:自行定義 EventMetadata schema,在 use case 入口設定一次, 由 structured concurrency 帶到 EventStore.append。Aggregate、event 與 use case 完全不需要碰它。

0.x0.x
try await repository.save(
    aggregateRoot: order,
    external: ["operatorId": input.operatorId]
)
1.01.0
struct AuditMetadata: EventMetadata {
    let operatorId: String
    let tenantId: String
}

let meta = AuditMetadata(operatorId: ..., tenantId: ...)
try await EventMetadataContext<AuditMetadata>.withValue(meta) {
    try await repository.save(aggregateRoot: order)
}

Renames at a glance重新命名一覽

Concept項目 0.x 1.0
Storage protocol儲存抽象 protocolEventStorageCoordinatorEventStore
Repository / Projector associated typeRepository / Projector 關聯型別StorageCoordinatorStore
Repository / Projector propertyRepository / Projector 屬性coordinatorstore
Metadata channel on appendappend 的 metadata 通道external: [String:String]?metadata: Metadata?
Kurrent store genericsKurrent 儲存實作泛型KurrentStorageCoordinator<S>KurrentStorageCoordinator<S, Metadata>
Event type discriminator事件型別判斷RecordedEvent.mappingClassNameRecordedEvent.eventType
CustomMetadata{ className, external }{ operatorId }

1.0.1 fixed a double-apply bug in find(byId:hiddingDeleted: false) when reconstructing a soft-deleted aggregate.

1.0.1 修正 find(byId:hiddingDeleted: false) 重建已軟刪除 aggregate 時的重複 apply 錯誤。

0.x → 1.0 Migration Guide0.x → 1.0 遷移指南

Step-by-step changes, before/after data flow, and a machine-readable manifest for AI agents. Written in Traditional Chinese.

逐項變更、新舊資料流對照,以及給 AI agent 的機器可讀遷移清單。

Why swift-ddd-kit為什麼選擇 swift-ddd-kit

The Swift server ecosystem has matured, but the building blocks of a production DDD architecture, aggregate roots, event-sourced repositories, CQRS projectors and event migration, have been missing. swift-ddd-kit fills that gap.

Swift 後端生態已經成熟,但 production 等級 DDD 架構的建構元件,aggregate root、event-sourced repository、CQRS projector、事件 migration,一直缺席。swift-ddd-kit 補上這一塊。

Events are the source of truth事件就是事實的來源

State is never stored directly. Every change is an immutable DomainEvent, and state is rebuilt by replaying them through when(happened:).

狀態從不直接儲存。每次變更都是一個不可變的 DomainEvent,狀態透過 when(happened:) 重播事件重建。

Aggregates guard invariantsAggregate 守住不變條件

Business rules live in AggregateRoot. apply(event:) mutates state, runs ensureInvariant() and queues uncommitted events. No anemic models mutated from outside.

商業規則放在 AggregateRoot。apply(event:) 變更狀態、執行 ensureInvariant() 並排入未提交事件。沒有從外部被改動的貧血模型。

Write and read stay apart讀寫分離

The write side appends events; EventSourcingProjector folds them into ReadModels on the read side. A persistent-subscription runner keeps projections live.

寫入端只附加事件;讀取端由 EventSourcingProjector 把事件折疊成 ReadModel。Persistent subscription runner 讓投影持續更新。

Swift 6 strict concurrencySwift 6 strict concurrency

Native async/await throughout. Ambient metadata rides on TaskLocal, so audit and tenant data never leak into domain types.

全面使用原生 async/await。Ambient metadata 搭在 TaskLocal 上,稽核與租戶資料永遠不會滲入 domain 型別。

Less boilerplate, checked at build time更少樣板,在建置期檢查

SPM build-tool plugins turn event.yaml, projection-model.yaml and variables.yaml into Swift. Mistakes surface as build failures, not runtime surprises.

SPM build-tool plugin 把 event.yaml、projection-model.yaml 與 variables.yaml 轉成 Swift。錯誤在建置時就浮現,而不是執行期才發現。

Testable without infrastructure不需基礎設施也能測

InMemoryEventStoreClient stands in for KurrentDB, ContextReceiver is fully offline-testable, and TestUtility covers the integration runs that do need a server.

InMemoryEventStoreClient 代替 KurrentDB,ContextReceiver 可完全離線測試,TestUtility 則涵蓋真的需要伺服器的整合測試。

How it works運作方式

The write side and the read side share no code path. Both talk to KurrentDB on their own: commands append events, queries read those same events to build query-optimised models.

寫入端與讀取端沒有共用的程式路徑,各自對 KurrentDB 存取:command 附加事件,query 讀取同一批事件建立查詢最佳化的模型。

  1. Define events定義事件

    Conform to DomainEvent, or generate them from event.yaml.

    讓事件 conform DomainEvent,或從 event.yaml 生成。

  2. Model the aggregate建立 aggregate

    Implement AggregateRoot; when(happened:) turns each event into state.

    實作 AggregateRoot;when(happened:) 把每個事件轉成狀態。

  3. Map stored records back把儲存的記錄映射回來

    An EventTypeMapper decodes KurrentDB records into typed events.

    EventTypeMapper 把 KurrentDB 的記錄解碼回型別化的事件。

  4. Save儲存

    repository.save(aggregateRoot:) appends the uncommitted events with optimistic concurrency.

    repository.save(aggregateRoot:) 以 optimistic concurrency 附加未提交的事件。

  5. Find讀取

    repository.find(byId:) replays the stream and rebuilds the aggregate.

    repository.find(byId:) 重播 stream、重建 aggregate。

  6. Project投影

    An EventSourcingProjector folds events into a ReadModel, on demand or through a persistent subscription.

    EventSourcingProjector 把事件折疊成 ReadModel,可即時計算,也可透過 persistent subscription 持續更新。

Quick Start快速上手

1. Events1. 事件

import DDDKit

struct OrderCreated: DomainEvent {
    var id: UUID = .init()
    var occurred: Date = .now
    var aggregateRootId: String
    let customerId: String
}

struct OrderCancelled: DeletedEvent {
    var id: UUID = .init()
    var occurred: Date = .now
    let aggregateRootId: String
}

2. Aggregate root2. Aggregate root

final class Order: AggregateRoot {
    typealias DeletedEventType = OrderCancelled

    let id: String
    private(set) var customerId: String = ""
    var metadata: AggregateRootMetadata = .init()

    init(id: String, customerId: String) throws {
        self.id = id
        try apply(event: OrderCreated(aggregateRootId: id, customerId: customerId))
    }

    required init?(events: [any DomainEvent]) throws {
        guard let first = events.first as? OrderCreated else { return nil }
        self.id = first.aggregateRootId
        try apply(events: events)
    }

    func when(happened event: some DomainEvent) throws {
        switch event {
        case let e as OrderCreated: customerId = e.customerId
        default: break
        }
    }
}

3. Mapper and repository3. Mapper 與 repository

struct OrderEventMapper: EventTypeMapper {
    func mapping(eventData: any RecordedEventLike) throws -> (any DomainEvent)? {
        switch eventData.eventType {
        case "OrderCreated":   return try eventData.decode(to: OrderCreated.self)
        case "OrderCancelled": return try eventData.decode(to: OrderCancelled.self)
        default:               return nil
        }
    }
}

final class OrderRepository: EventSourcingRepository {
    typealias AggregateRootType = Order
    typealias Store = KurrentStorageCoordinator<Order, CustomMetadata>

    let store: Store
    init(client: KurrentDBClient) {
        store = .init(client: client, eventMapper: OrderEventMapper())
    }
}

4. Save and find4. 儲存與讀取

let client = KurrentDBClient(settings: .localhost())
let repository = OrderRepository(client: client)

let order = try Order(id: "order-001", customerId: "customer-42")
try await repository.save(aggregateRoot: order)

let found = try await repository.find(byId: "order-001")   // replayed from the stream

try await repository.delete(byId: "order-001")   // soft delete: appends OrderCancelled
try await repository.purge(byId: "order-001")    // hard delete: removes the stream

5. Metadata without touching the domain5. 不碰 domain 的 metadata

struct AuditMetadata: EventMetadata {
    let operatorId: String
    let tenantId: String
}

// Bind the schema on the store …
typealias Store = KurrentStorageCoordinator<Order, AuditMetadata>

// … and set it once at the use case entry. save() picks it up automatically.
try await EventMetadataContext<AuditMetadata>.withValue(meta) {
    try await repository.save(aggregateRoot: order)
}

A ready-to-run starter project with all of this wired up is one command away with swift-pangu-cli: pangu project create.

想要一個已經接好線、可直接執行的起始專案,用 swift-pangu-cli 一行搞定:pangu project create。

Modules模組

Pick only what a context needs. DDDKit covers the write and read side against KurrentDB; everything else is an optional product.

只挑 context 需要的部分。DDDKit 涵蓋對 KurrentDB 的寫入與讀取端;其餘都是選用的 product。

ProductProduct What it gives you提供什麼
Core核心
DDDKitUmbrella: DDDCore (Entity, AggregateRoot, DomainEvent, DomainEventBus), EventSourcing (EventStore, EventSourcingRepository, EventSourcingProjector, ReadModel), KurrentSupport (KurrentStorageCoordinator, EventTypeMapper, persistent-subscription runners), EventBus and ReadModelPersistence.Umbrella:DDDCore(Entity、AggregateRoot、DomainEvent、DomainEventBus)、EventSourcing(EventStore、EventSourcingRepository、EventSourcingProjector、ReadModel)、KurrentSupport(KurrentStorageCoordinator、EventTypeMapper、persistent subscription runner)、EventBus 與 ReadModelPersistence。
MigrationUtilityThe Migration protocol and handlers for evolving event schemas.Migration 協定與 handler,用於演進事件 schema。
Read model persistenceRead model 持久化
ReadModelPersistenceReadModelStore protocol plus an in-memory store for read model snapshots.ReadModelStore 協定與記憶體內的 read model 快照儲存。
ReadModelPersistencePostgres · PostgresSupportPostgreSQL + JSONB backed ReadModelStore, and the Postgres helpers it builds on.以 PostgreSQL + JSONB 實作的 ReadModelStore,以及它依賴的 Postgres 輔助工具。
Cross-context messaging跨 context 訊息
PublishedLanguagePublishedLanguageEvent, the wire envelope shared by both sides.雙方共用的 wire envelope PublishedLanguageEvent。
ContextForwarderProduce side over Pulsar's REST endpoint: PulsarRESTPublisher, ForwarderGroup.透過 Pulsar REST 端點的發送端:PulsarRESTPublisher、ForwarderGroup。
ContextReceiver · ContextReceiverWebSocketConsume side: portable runner and dead-letter monitor, plus the Linux-only WebSocket transport.接收端:可攜的 runner 與 dead-letter monitor,加上僅限 Linux 的 WebSocket transport。
Template variables模板變數
TemplateVariablesRuntime substitution engine: PlaceholderSubstitution.substitute(_:values:) fills %Placeholder% tokens in a single pass. Pre-release in 1.4.Runtime 替換引擎:PlaceholderSubstitution.substitute(_:values:) 以單一遍歷填入 %Placeholder%。1.4 預覽版。
TemplateVariablesDefinitionThe variables.yaml model, parser and generator, for building further generators. Pre-release in 1.4.variables.yaml 的模型、解析器與生成器,供建立更多生成器使用。1.4 預覽版。
Testing測試
KurrentSupportInMemoryInMemoryEventStoreClient: a KurrentDB double that needs no server.InMemoryEventStoreClient:不需要伺服器的 KurrentDB 替身。
TestUtilityIntegration-test helpers against a live KurrentDB: TestBundle, stream cleanup, the optional KurrentDBPool client.對真實 KurrentDB 做整合測試的輔助:TestBundle、stream 清理、選用的 KurrentDBPool client。
Code generation程式碼生成
DomainEventGeneratorThe YAML parsing and Swift emission library behind the plugins and the generate CLI.plugin 與 generate CLI 背後的 YAML 解析與 Swift 輸出函式庫。

Code generation程式碼生成

Three SPM build-tool plugins read YAML next to your sources and emit Swift at build time; one command plugin emits KurrentDB projection definitions on demand. The same logic is reachable by hand through swift run generate.

三個 SPM build-tool plugin 讀取原始碼旁的 YAML,在建置時輸出 Swift;另有一個 command plugin 可隨時輸出 KurrentDB projection 定義。同樣的邏輯也可以透過 swift run generate 手動執行。

DomainEventGeneratorPlugin

event.yaml → created / domain / deleted event structs and an EventTypeMapper for the aggregate.

event.yaml → created / domain / deleted 事件 struct,以及該 aggregate 的 EventTypeMapper。

OrderCreated:
  kind: createdEvent
  aggregateRootId:
    alias: orderId
  properties:
    customerId: String
    totalAmount: Double

OrderCancelled:
  kind: deletedEvent

ModelGeneratorPlugin

projection-model.yaml → ReadModel types and the projector scaffolding for the query side.

projection-model.yaml → 查詢端的 ReadModel 型別與 projector 骨架。

OrderSummary:
  model: readModel
  events:
    - OrderCreated
    - OrderAmountUpdated

GenerateKurrentDBProjectionsPlugin

A command plugin: swift package generate-kurrentdb-projections emits the KurrentDB projection definitions your read side subscribes to, from the same projection-model.yaml.

Command plugin:swift package generate-kurrentdb-projections 從同一份 projection-model.yaml 輸出讀取端要訂閱的 KurrentDB projection 定義。

VariablesGeneratorPlugin

variables.yaml → a Sendable variables protocol with one async throws -> String method per variable, plus the __value(of:inputs:) dispatch seam. Pre-release in 1.4.

variables.yaml → 一個 Sendable 變數 protocol,每個變數一個 async throws -> String 方法,另附 __value(of:inputs:) 分派入口。1.4 預覽版。

CustomerName:
  placeholder: CustomerName
  inputs:
    - customerId: String
swift run generate event        Sources/MyContext/event.yaml \
    --configuration Sources/MyContext/event-generator-config.yaml -o Generated/Events.swift
swift run generate projector    Sources/MyContext/projection-model.yaml \
    --events Sources/MyContext/event.yaml -o Generated/Projectors.swift
swift run generate variables    Sources/MyContext/variables.yaml    -o Generated/Variables.swift

Guides文件

API ReferenceAPI 文件

DocC reference for every module of the latest release, with a version menu for older minors and main.

最新版本每個模組的 DocC 參考文件,可用版本選單切換到較舊的 minor 版本與 main。

Getting Started: Postgres read models快速上手:Postgres read model

Project KurrentDB events into a queryable Postgres table in five steps, with write modes and rebuilding.

五個步驟把 KurrentDB 事件投影到可查詢的 Postgres 資料表,並說明寫入模式與重建。

README

The full walkthrough: core concepts, CQRS projectors and runners, metadata, code generation, template variables and Pulsar.

完整導覽:核心概念、CQRS projector 與 runner、metadata、程式碼生成、模板變數與 Pulsar。

Migration Guide 0.x → 1.0遷移指南 0.x → 1.0

For downstream projects: diagrams, a change catalogue and a machine-readable manifest for AI agents. Traditional Chinese.

給下游專案:圖表、變更總表,以及給 AI agent 的機器可讀遷移清單。

MIGRATION.md

Running log of every schema and API change that needs action downstream, including the 1.4 ForwardingRule.translate change.

所有需要下游處理的 schema 與 API 變更紀錄,包含 1.4 的 ForwardingRule.translate 調整。

Design specs設計文件

The design documents behind each feature, from the ambient-context redesign to the Pulsar forwarder and receiver.

每個功能背後的設計文件,從 ambient context 重構到 Pulsar 發送端與接收端。

All releases所有版本

Every tag from 0.1.0 onward with generated notes, including the 1.4 pre-releases.

從 0.1.0 起每個 tag 的 release notes,包含 1.4 的預覽版。

swift-kurrentdb

The KurrentDB client this framework is built on.

本框架所依賴的 KurrentDB client。