refactor(frontend, build): update PingViewModel initialization, resolve view model via Koin, and clean yarn dependencies

Injected `PingViewModel` via Koin to align with dependency injection best practices. Suppressed Gradle deprecation warnings and added the `frontend.core.sync` dependency. Cleaned up outdated packages in `yarn.lock`.
This commit is contained in:
2026-01-12 19:47:59 +01:00
parent 32e43b8fb0
commit 9e12018208
23 changed files with 438 additions and 1287 deletions
-2
View File
@@ -43,8 +43,6 @@ kotlin {
implementation(libs.sqldelight.driver.web)
// NPM deps used by `sqlite.worker.js` (OPFS-backed SQLite WASM worker)
implementation(npm("@cashapp/sqldelight-sqljs-worker", "2.2.1"))
// Use a published build tag from the official package.
implementation(npm("@sqlite.org/sqlite-wasm", "3.51.1-build2"))
}
@@ -4,6 +4,14 @@ CREATE TABLE Task (
is_completed INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE PingEvent (
-- UUIDv7 as String (cursor-friendly and backend-compatible)
id TEXT NOT NULL PRIMARY KEY,
message TEXT NOT NULL,
-- Derived from UUIDv7 timestamp (epoch millis) for sorting/display
last_modified INTEGER NOT NULL
);
selectAll:
SELECT *
FROM Task;
@@ -15,3 +23,26 @@ VALUES ?;
delete:
DELETE FROM Task
WHERE id = ?;
selectPingEventsSince:
SELECT *
FROM PingEvent
WHERE id > ?
ORDER BY id;
selectLatestPingEventId:
SELECT id
FROM PingEvent
ORDER BY id DESC
LIMIT 1;
upsertPingEvents:
-- SQLite dialect configured for this project is 3.18 (no UPSERT support).
-- Use INSERT OR REPLACE as pragmatic upsert.
INSERT OR REPLACE INTO PingEvent(id, message, last_modified)
VALUES ?;
upsertPingEvent:
-- Single-row convenience upsert (used by repositories).
INSERT OR REPLACE INTO PingEvent(id, message, last_modified)
VALUES (?, ?, ?);
+33
View File
@@ -0,0 +1,33 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
// Targets are configured centrally in the shells/feature modules; here we just provide common code.
jvm()
js(IR) {
browser()
}
sourceSets {
commonMain.dependencies {
implementation(projects.core.coreDomain)
// Networking
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.client.serialization.kotlinx.json)
// Serialization
implementation(libs.kotlinx.serialization.json)
// DI
implementation(libs.koin.core)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
@@ -0,0 +1,54 @@
package at.mocode.frontend.core.sync
import at.mocode.core.sync.Syncable
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.parameter
/**
* Minimaler Repository-Contract für Delta-Sync.
*/
interface SyncableRepository<T : Syncable> {
/**
* Cursor für Delta-Sync.
*
* Konvention: UUIDv7 als String (Backend kann `>` vergleichen) oder ein kompatibler Cursor.
*
* @return letzter bekannter Cursor lokal oder `null`, wenn noch keine Daten existieren.
*/
suspend fun getLatestSince(): String?
/** Insert oder Update (Upsert) der übergebenen Items. */
suspend fun upsert(items: List<T>)
}
/**
* Generischer Sync-Manager.
*
* Konvention Backend:
* - GET `/api/{entity-plural}/sync?since={timestamp}`
* - Response: `List<T>`
*/
class SyncManager(
val ktorClient: HttpClient
) {
suspend inline fun <reified T : Syncable> performSync(
repository: SyncableRepository<T>,
endpointPath: String
) {
val since = repository.getLatestSince()
val remoteItems: List<T> = ktorClient
.get(endpointPath) {
// `since` optional
if (since != null) parameter("since", since)
}
.body()
if (remoteItems.isNotEmpty()) {
repository.upsert(remoteItems)
}
}
}
@@ -0,0 +1,12 @@
package at.mocode.frontend.core.sync.di
import at.mocode.frontend.core.sync.SyncManager
import org.koin.dsl.module
/**
* Zentrales Koin-Modul für den Sync-Core.
*/
val syncModule = module {
// Provides a singleton instance of SyncManager, using the globally provided HttpClient.
single { SyncManager(get()) }
}