fix(server): event capturing and necessary logic for smoke test.

This commit is contained in:
2026-05-17 03:17:12 +04:00
parent 0c1876a549
commit 7d46f46f01
39 changed files with 1097 additions and 67 deletions
+1
View File
@@ -8,6 +8,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
implementation(project(":core:events"))
implementation(project(":core:sessions"))
implementation(project(":core:artifacts"))
implementation "org.xerial:sqlite-jdbc"
testImplementation(testFixtures(project(":testing:contracts")))
testImplementation "org.junit.jupiter:junit-jupiter"
@@ -0,0 +1,96 @@
package com.correx.infrastructure.persistence.artifact
import com.correx.core.artifacts.ArtifactProjector
import com.correx.core.artifacts.ArtifactReducer
import com.correx.core.artifacts.ArtifactState
import com.correx.core.artifacts.repository.ArtifactRepository
import com.correx.core.events.events.ArtifactArchivedEvent
import com.correx.core.events.events.ArtifactCreatedEvent
import com.correx.core.events.events.ArtifactRejectedEvent
import com.correx.core.events.events.ArtifactRelationshipAddedEvent
import com.correx.core.events.events.ArtifactSupersededEvent
import com.correx.core.events.events.ArtifactValidatedEvent
import com.correx.core.events.events.ArtifactValidatingEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap
class LiveArtifactRepository(
private val eventStore: EventStore,
private val reducer: ArtifactReducer,
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
) : ArtifactRepository {
private val artifactCache = ConcurrentHashMap<SessionId, ConcurrentHashMap<ArtifactId, ArtifactState>>()
private val stageIndex = ConcurrentHashMap<SessionId, ConcurrentHashMap<ArtifactId, StageId>>()
private val subscriptions = ConcurrentHashMap<SessionId, Job>()
private val projector = ArtifactProjector(reducer)
override fun getBySession(sessionId: SessionId): Map<ArtifactId, ArtifactState> {
ensureSubscribed(sessionId)
return artifactCache[sessionId]?.toMap() ?: emptyMap()
}
override fun getByStage(sessionId: SessionId, stageId: StageId): Map<ArtifactId, ArtifactState> {
ensureSubscribed(sessionId)
val cache = artifactCache[sessionId] ?: emptyMap<ArtifactId, ArtifactState>()
val index = stageIndex[sessionId] ?: emptyMap<ArtifactId, StageId>()
return cache.filter { (artifactId, _) -> index[artifactId] == stageId }
}
override fun getById(sessionId: SessionId, artifactId: ArtifactId): ArtifactState? {
ensureSubscribed(sessionId)
return artifactCache[sessionId]?.get(artifactId)
}
override fun getByIds(sessionId: SessionId, artifactIds: Set<ArtifactId>): Map<ArtifactId, ArtifactState> {
ensureSubscribed(sessionId)
val cache = artifactCache[sessionId] ?: return emptyMap()
return artifactIds.mapNotNull { id -> cache[id]?.let { id to it } }.toMap()
}
private fun ensureSubscribed(sessionId: SessionId) {
subscriptions.computeIfAbsent(sessionId) {
scope.launch {
eventStore.subscribe(sessionId).collect { event ->
processEvent(sessionId, event)
}
}
}
}
private fun processEvent(sessionId: SessionId, event: StoredEvent) {
val artifactId = extractArtifactId(event) ?: return
val sessionArtifacts = artifactCache.computeIfAbsent(sessionId) { ConcurrentHashMap() }
val currentState = sessionArtifacts[artifactId] ?: projector.initial()
val newState = projector.apply(currentState, event)
sessionArtifacts[artifactId] = newState
val payload = event.payload
if (payload is ArtifactCreatedEvent) {
val sessionStages = stageIndex.computeIfAbsent(sessionId) { ConcurrentHashMap() }
sessionStages[artifactId] = payload.stageId
}
}
private fun extractArtifactId(event: StoredEvent): ArtifactId? =
when (val p = event.payload) {
is ArtifactCreatedEvent -> p.artifactId
is ArtifactValidatingEvent -> p.artifactId
is ArtifactValidatedEvent -> p.artifactId
is ArtifactRejectedEvent -> p.artifactId
is ArtifactSupersededEvent -> p.artifactId
is ArtifactArchivedEvent -> p.artifactId
is ArtifactRelationshipAddedEvent -> p.sourceId
else -> null
}
}