92b76c4e3c
Новый KMP-модуль :sync-core реализует спеку SYNC-SYSTEM.md с нуля, без
опоры на существующие :outbox-api / :journal-api / :client. Цель — собрать
работающую модель «append-only event journal + материализация +
replace-resync», проверить инварианты тестами, и уже потом думать, как это
распределить по существующим абстракциям.
Архитектура (§3-§5 спеки):
- Cursor(epoch, number) — пара (поколение, счётчик); @JvmInline value
class; монотонно возрастает внутри эпохи, при wipe/restart эпоха
меняется и клиент обязан делать replaceState.
- EventLog / EventLogWriter / EventLogReplica — append-only журнал +
материализация на стороне клиента (replica ничего не хранит в
журнале, только state + sync_state + pending).
- StateStore / Reducer<S, E> — детерминированная свёртка событий в
снапшот ChatState.
- PendingEvent + LocalId — offline-write очередь: PENDING → SENT/FAILED.
- SyncEngine — оркестратор: fetchUpdates → applyRemote → fetchState →
replaceState → postPending.
- SyncTransport — четыре endpoint'а (fetchUpdates / fetchState /
subscribeLive / postPending), сейчас реализован InProcessTransport
(in-memory), задел под HTTP+WS.
ksqlite-бэкенд (commonMain, ksqlite 0.1.4):
- SqliteEventLog — append-only с UNIQUE(event_id) для идемпотентности.
- SqliteEventLogReplica — клиентская сторона (processed_event_id,
pending_event, sync_state singleton-row).
- SqliteStateStore — chat_state / message_state + replaceState.
- SqliteSyncBundle — фабрика для тестов.
CursorExpired split (§6.3 спеки):
- WRONG_EPOCH — сервер сменил эпоху (wipe/restore/миграция).
- TOO_OLD — компакция унесла события ниже floor.
Оба → клиент обязан сделать replaceState (sync() делает это сам).
EventLogWriter.beginNewEpoch(newEpoch): Cursor — атомарная смена эпохи
на сервере: DELETE event_log + reset sqlite_sequence + UPDATE
compaction_state, в одной транзакции.
Сборка: jvm + linuxX64 (mingwX64 компилируется, тесты на linux хосте
пропускаются — ksqlite имеет нативные бинарники только под эти три).
Тесты (74, commonTest, проходят на jvm и linuxX64):
- CursorTest (14) — парсинг, валидация, isAfter/isBefore/compareTo.
- EpochMismatchTest (6) — WRONG_EPOCH / TOO_OLD → full resync.
- InvariantsTest — 7 инвариантов спеки на in-memory бэкенде.
- IdempotencyTest — повторный applyRemote по eventId = no-op.
- EditDeleteTest — message edit/delete через события.
- CompactionAndOrderTest — compact сдвигает minAvailableCursor.
- OfflineWriteTest — pending отправляется при следующем sync().
- AssistantTest — LocalAssistant возвращает Flow<DomainEvent>.
- KsqliteSyncSpecTest (15) — все спец-тесты на ksqlite.
- KsqlitePersistenceTest — file-based persistence.
Существующий код (:outbox-api / :journal-api / :client / :server) не
трогаем — это чистая референсная реализация для последующей миграции.
42 lines
2.0 KiB
Kotlin
42 lines
2.0 KiB
Kotlin
plugins {
|
||
alias(libs.plugins.kotlin.multiplatform)
|
||
alias(libs.plugins.kotlin.serialization)
|
||
}
|
||
|
||
// Референсная реализация ТЗ `SYNC-SYSTEM.md` — журнал событий с монотонным
|
||
// курсором + материализация (`Chat`/`Message` с `last_seq`), единый протокол
|
||
// для клиента и сервера. KMP, всё в commonMain/commonTest где это возможно;
|
||
// ksqlite-реализация — в commonMain (ksqlite публикует KMP-варианты для всех
|
||
// десктоп-целей), интеграционные тесты под реальной SQLite — в jvmTest.
|
||
//
|
||
// Цели сборки:
|
||
// - commonMain/commonTest содержат pure-Kotlin типы, интерфейсы, in-memory
|
||
// реализации и спец-тесты (SyncSpec). Работают на всех 9 KMP-целях.
|
||
// - jvmMain + jvmTest дополнительно содержат ksqlite-бэкенд (он же публикуется
|
||
// для linuxX64/mingwX64, но запускать с реальной SQLite-схемой удобнее под JVM).
|
||
//
|
||
// Почему нет iOS/macOS целей: ksqlite не публикует варианты для них. Если
|
||
// когда-нибудь понадобится, цели добавляются одной строкой.
|
||
|
||
kotlin {
|
||
jvmToolchain(21)
|
||
|
||
jvm()
|
||
linuxX64()
|
||
mingwX64()
|
||
|
||
sourceSets {
|
||
commonMain.dependencies {
|
||
implementation(libs.ksqlite)
|
||
implementation(libs.kotlinx.serialization.json)
|
||
implementation(libs.kotlinx.coroutines.core)
|
||
}
|
||
commonTest.dependencies {
|
||
implementation(kotlin("test"))
|
||
implementation(libs.kotlinx.coroutines.test)
|
||
// для файлового персистентного теста — KMP-обёртка вокруг fs.
|
||
implementation(libs.kotlinx.io.core)
|
||
}
|
||
}
|
||
}
|