Remove :storage-inmemory module, tests, and related code.
ci / JVM build + tests (push) Successful in 6m46s
ci / JVM build + tests (push) Successful in 6m46s
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
}
|
||||
|
||||
// KMP-реализация [ReflectionStore] поверх in-memory структур
|
||||
// (`MutableMap`/`MutableList` + `Mutex`) — для тестов, dev-режима,
|
||||
// embedded-сценариев (Android core, CLI, in-process кэш в клиенте)
|
||||
// и как образец для своей реализации.
|
||||
//
|
||||
// Зависимости: только `:reflection-api` (+ `:journal-api` транзитивно
|
||||
// через :reflection-api — ReflectionEvent.Created/и др. ссылаются на
|
||||
// MessageRecord enum'ы). Никакого I/O — pure in-memory.
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
|
||||
jvm()
|
||||
macosX64()
|
||||
macosArm64()
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
linuxX64()
|
||||
linuxArm64()
|
||||
mingwX64()
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
api(project(":reflection-api"))
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package pw.binom.agentik.reflection.inmemory
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import pw.binom.agentik.reflection.Reflection
|
||||
import pw.binom.agentik.reflection.ReflectionEvent
|
||||
import pw.binom.agentik.reflection.ReflectionStore
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* Thread-safe Map-импл [ReflectionStore].
|
||||
*
|
||||
* `weakSpots` хранятся как List<String> в самой структуре — JSON-сериализация
|
||||
* делается на уровне SQLDelight в SQLite-импле; здесь просто держим в памяти.
|
||||
*/
|
||||
class InMemoryReflectionStore : ReflectionStore {
|
||||
|
||||
private val byId: MutableMap<String, Reflection> = mutableMapOf()
|
||||
private val byConv: MutableMap<String, MutableList<String>> = mutableMapOf()
|
||||
private val all: MutableList<String> = mutableListOf() // FIFO порядок insert (для listRecent)
|
||||
private val events = MutableSharedFlow<ReflectionEvent>(extraBufferCapacity = 16)
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun insert(reflection: Reflection) {
|
||||
mutex.withLock {
|
||||
byId[reflection.id] = reflection
|
||||
byConv.getOrPut(reflection.conversationId ?: "") { mutableListOf() }.add(reflection.id)
|
||||
all.add(reflection.id)
|
||||
}
|
||||
events.tryEmit(ReflectionEvent.Created(reflection))
|
||||
}
|
||||
|
||||
override suspend fun get(id: String): Reflection? {
|
||||
mutex.withLock { return byId[id] }
|
||||
}
|
||||
|
||||
override suspend fun listRecent(limit: Int): List<Reflection> {
|
||||
mutex.withLock {
|
||||
return all.asReversed().asSequence()
|
||||
.mapNotNull { byId[it] }
|
||||
.take(limit)
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun listForConversation(conversationId: String, limit: Int): List<Reflection> {
|
||||
mutex.withLock {
|
||||
val ids = byConv[conversationId].orEmpty()
|
||||
return ids.asReversed().asSequence()
|
||||
.mapNotNull { byId[it] }
|
||||
.take(limit)
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deleteOlderThan(cutoff: Instant) {
|
||||
mutex.withLock {
|
||||
val toRemove = byId.values.filter { it.createdAt < cutoff }.map { it.id }
|
||||
for (id in toRemove) {
|
||||
byId.remove(id)
|
||||
all.remove(id)
|
||||
byConv.values.forEach { it.remove(id) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun count(): Int {
|
||||
mutex.withLock { return byId.size }
|
||||
}
|
||||
|
||||
override fun events(): Flow<ReflectionEvent> = events.asSharedFlow()
|
||||
|
||||
override fun close() {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package pw.binom.agentik.reflection.inmemory
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import pw.binom.agentik.reflection.Reflection
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.time.Instant
|
||||
|
||||
class InMemoryReflectionStoreTest {
|
||||
|
||||
private fun refl(
|
||||
id: String,
|
||||
conv: String? = null,
|
||||
ts: Instant = Instant.parse("2026-09-15T10:00:00Z"),
|
||||
score: Int = 3,
|
||||
summary: String = "ok",
|
||||
weak: List<String> = listOf("weak1", "weak2"),
|
||||
) = Reflection(
|
||||
id = id,
|
||||
conversationId = conv,
|
||||
createdAt = ts,
|
||||
turnsAnalyzed = 5,
|
||||
score = score,
|
||||
summary = summary,
|
||||
weakSpots = weak,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testInsertAndGetRoundtrip() = runTest {
|
||||
val store = InMemoryReflectionStore()
|
||||
store.insert(refl("r1"))
|
||||
val got = store.get("r1")
|
||||
assertNotNull(got)
|
||||
assertEquals("r1", got.id)
|
||||
assertEquals(listOf("weak1", "weak2"), got.weakSpots)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetReturnsNullForMissing() = runTest {
|
||||
val store = InMemoryReflectionStore()
|
||||
assertNull(store.get("nope"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListRecentSortedByCreatedAtDesc() = runTest {
|
||||
val store = InMemoryReflectionStore()
|
||||
val t0 = Instant.parse("2026-09-15T10:00:00Z")
|
||||
store.insert(refl("r1", ts = t0))
|
||||
store.insert(refl("r2", ts = t0 + kotlin.time.Duration.parse("PT60S")))
|
||||
store.insert(refl("r3", ts = t0 + kotlin.time.Duration.parse("PT120S")))
|
||||
|
||||
val recent = store.listRecent(limit = 3)
|
||||
assertEquals(listOf("r3", "r2", "r1"), recent.map { it.id })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListForConversationFilters() = runTest {
|
||||
val store = InMemoryReflectionStore()
|
||||
val t = Instant.parse("2026-09-15T10:00:00Z")
|
||||
store.insert(refl("r1", conv = "c-1"))
|
||||
store.insert(refl("r2", conv = "c-2"))
|
||||
store.insert(refl("r3", conv = "c-1"))
|
||||
val forC1 = store.listForConversation("c-1", limit = 10)
|
||||
assertEquals(2, forC1.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteOlderThanRemoves() = runTest {
|
||||
val store = InMemoryReflectionStore()
|
||||
val t0 = Instant.parse("2026-09-15T10:00:00Z")
|
||||
store.insert(refl("r1", ts = t0))
|
||||
store.insert(refl("r2", ts = t0 + kotlin.time.Duration.parse("PT1H")))
|
||||
store.deleteOlderThan(t0 + kotlin.time.Duration.parse("PT30M"))
|
||||
assertNull(store.get("r1"))
|
||||
assertNotNull(store.get("r2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCountReturnsTotal() = runTest {
|
||||
val store = InMemoryReflectionStore()
|
||||
assertEquals(0, store.count())
|
||||
store.insert(refl("r1"))
|
||||
store.insert(refl("r2"))
|
||||
assertEquals(2, store.count())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCloseIdempotent() = runTest {
|
||||
val store = InMemoryReflectionStore()
|
||||
store.close()
|
||||
store.close() // no-op
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user