refactor(quartz): tighten projection package layout + relay flows from store
Three changes wrapped together:
1. relay moves up to IEventStore. The interface gains an abstract
`val relay: NormalizedRelayUrl?` and SQLiteEventStore, EventStore,
FsEventStore, InternedEventStore, and ObservableEventStore all
override it (decorators forward inner.relay). EventStoreProjection
loses its `relay` ctor param and uses `store.relay` for NIP-62
vanish scoping; ObservableEventStore.observe overloads lose their
relay arg. The relay was always a property of the store anyway —
threading it through projection construction was redundant.
2. StoreChange inlines into ObservableEventStore. The sealed type is
now ObservableEventStore.StoreChange (with Insert / DeleteByFilter /
DeleteExpired as nested cases) since it's strictly a contract of
the change stream, never used independently. StoreChange.kt deleted.
3. Package reshuffle:
store/projection/ObservableEventStore.kt → store/ObservableEventStore.kt
store/projection/EventStoreProjection.kt → cache/projection/EventStoreProjection.kt
store/projection/StoreChange.kt → inlined into ObservableEventStore
ObservableEventStore is a store decorator and lives next to
IEventStore. EventStoreProjection is a cache primitive (alongside
EventInterner / InternedEventStore) and lives under cache/. The
`observe(filters, scope)` convenience moved from a method on
ObservableEventStore to a top-level extension function in
cache/projection/, which avoids the otherwise-circular
store → cache/projection → store dependency.
7/7 interner + 17/17 projection + all other store tests pass.
https://claude.ai/code/session_01Jny85MTu1ynKgFBgysfWu5
This commit is contained in:
+3
@@ -22,6 +22,7 @@ package com.vitorpamplona.quartz.nip01Core.cache.interning
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,8 @@ class InternedEventStore(
|
||||
private val inner: IEventStore,
|
||||
private val interner: EventInterner = EventInterner.Default,
|
||||
) : IEventStore {
|
||||
override val relay: NormalizedRelayUrl? get() = inner.relay
|
||||
|
||||
override suspend fun insert(event: Event) = inner.insert(event)
|
||||
|
||||
override suspend fun transaction(body: IEventStore.ITransaction.() -> Unit) = inner.transaction(body)
|
||||
|
||||
+22
-6
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.projection
|
||||
package com.vitorpamplona.quartz.nip01Core.cache.projection
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
@@ -26,7 +26,8 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isReplaceable
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.store.ObservableEventStore
|
||||
import com.vitorpamplona.quartz.nip01Core.store.ObservableEventStore.StoreChange
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpirationBefore
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
@@ -76,8 +77,8 @@ import kotlinx.coroutines.yield
|
||||
* handle owned by the same author (for GiftWrap, the recipient).
|
||||
* Cross-author deletions are inert.
|
||||
* - **NIP-62 right to vanish.** A [RequestToVanishEvent] whose
|
||||
* `shouldVanishFrom([relay])` is true drops every handle from
|
||||
* the same author with `created_at < vanish.created_at`.
|
||||
* `shouldVanishFrom(store.relay)` is true drops every handle
|
||||
* from the same author with `created_at < vanish.created_at`.
|
||||
* - **NIP-40 expiration.** Events whose `expiration` tag has
|
||||
* already lapsed at the moment they arrive are dropped before
|
||||
* they ever enter [items].
|
||||
@@ -115,7 +116,6 @@ import kotlinx.coroutines.yield
|
||||
class EventStoreProjection<T : Event>(
|
||||
private val store: ObservableEventStore,
|
||||
private val filters: List<Filter>,
|
||||
private val relay: NormalizedRelayUrl?,
|
||||
scope: CoroutineScope,
|
||||
private val nowProvider: () -> Long = TimeUtils::now,
|
||||
) : AutoCloseable {
|
||||
@@ -199,7 +199,7 @@ class EventStoreProjection<T : Event>(
|
||||
if (event is DeletionEvent) {
|
||||
if (handleDeletion(event)) changed = true
|
||||
}
|
||||
if (event is RequestToVanishEvent && event.shouldVanishFrom(relay)) {
|
||||
if (event is RequestToVanishEvent && event.shouldVanishFrom(store.relay)) {
|
||||
if (dropWhere { ev -> ownerOf(ev) == event.pubKey && ev.createdAt < event.createdAt }) changed = true
|
||||
}
|
||||
|
||||
@@ -408,3 +408,19 @@ class EventStoreProjection<T : Event>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: open a projection over this observable store. NIP-62
|
||||
* vanish handling is scoped by the inner store's `relay`. Cancel
|
||||
* [scope] (or call [EventStoreProjection.close]) to release the
|
||||
* projection.
|
||||
*/
|
||||
fun <T : Event> ObservableEventStore.observe(
|
||||
filters: List<Filter>,
|
||||
scope: CoroutineScope,
|
||||
): EventStoreProjection<T> = EventStoreProjection(this, filters, scope)
|
||||
|
||||
fun <T : Event> ObservableEventStore.observe(
|
||||
filter: Filter,
|
||||
scope: CoroutineScope,
|
||||
): EventStoreProjection<T> = EventStoreProjection(this, listOf(filter), scope)
|
||||
@@ -22,8 +22,17 @@ package com.vitorpamplona.quartz.nip01Core.store
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
|
||||
interface IEventStore : AutoCloseable {
|
||||
/**
|
||||
* Relay URL this store is acting on behalf of, or `null` for an
|
||||
* unscoped store. Used by NIP-62 right-to-vanish handling: only
|
||||
* vanish requests whose `relays` list contains this URL (or
|
||||
* `ALL_RELAYS`) cascade.
|
||||
*/
|
||||
val relay: NormalizedRelayUrl?
|
||||
|
||||
suspend fun insert(event: Event)
|
||||
|
||||
interface ITransaction {
|
||||
|
||||
+43
-29
@@ -18,16 +18,14 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.projection
|
||||
package com.vitorpamplona.quartz.nip01Core.store
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.isEphemeral
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
@@ -50,18 +48,21 @@ import kotlinx.coroutines.flow.asSharedFlow
|
||||
* [changes] so projections can render them while they live. Already
|
||||
* expired ephemerals are silently dropped.
|
||||
*
|
||||
* Wrap any store you want to observe — [SQLiteEventStore], FS-backed,
|
||||
* an in-memory test fake — and feed [EventStoreProjection] from the
|
||||
* Wrap any store you want to observe — `SQLiteEventStore`, FS-backed,
|
||||
* an in-memory test fake — and feed `EventStoreProjection` from the
|
||||
* resulting [changes] flow.
|
||||
*
|
||||
* Reads (`query`, `count`) and out-of-band writes (`delete`,
|
||||
* `deleteExpiredEvents`) forward to the inner store unchanged. The
|
||||
* latter are *not* surfaced on [changes] — see the projection's
|
||||
* docstring for the rationale.
|
||||
* `deleteExpiredEvents`) forward to the inner store; the latter are
|
||||
* also surfaced on [changes] as [StoreChange.DeleteByFilter] /
|
||||
* [StoreChange.DeleteExpired] so projections can drop the matching
|
||||
* slots in memory without re-querying.
|
||||
*/
|
||||
class ObservableEventStore(
|
||||
val inner: IEventStore,
|
||||
) : IEventStore {
|
||||
override val relay: NormalizedRelayUrl? get() = inner.relay
|
||||
|
||||
private val _changes =
|
||||
MutableSharedFlow<StoreChange>(
|
||||
replay = 0,
|
||||
@@ -72,15 +73,47 @@ class ObservableEventStore(
|
||||
/**
|
||||
* Stream of mutations accepted by the observable layer. One
|
||||
* emission per successful [insert] (or per accepted event in a
|
||||
* [transaction] body), one emission per [delete] / [delete] /
|
||||
* [transaction] body), one emission per [delete] /
|
||||
* [deleteExpiredEvents] call. Rejected inserts and rolled-back
|
||||
* transactions emit nothing.
|
||||
*
|
||||
* Projections consume this stream — see [EventStoreProjection]
|
||||
* Projections consume this stream — see `EventStoreProjection`
|
||||
* for how each [StoreChange] is interpreted.
|
||||
*/
|
||||
val changes: SharedFlow<StoreChange> = _changes.asSharedFlow()
|
||||
|
||||
/**
|
||||
* Mutations published by [changes]. Projections react to these to
|
||||
* keep their in-memory view in sync with the underlying store.
|
||||
*
|
||||
* - [Insert] is emitted for every event accepted by the
|
||||
* observable layer (persistable or ephemeral). Carries the
|
||||
* event itself so the projection can run its NIP-01 / NIP-09 /
|
||||
* NIP-62 interpretation.
|
||||
* - [DeleteByFilter] is emitted for every `delete(filter)` /
|
||||
* `delete(filters)` call on the observable. Carries the same
|
||||
* filters the store used so projections can apply
|
||||
* [Filter.match] in memory and drop the matching slots without
|
||||
* re-querying.
|
||||
* - [DeleteExpired] is emitted for every `deleteExpiredEvents()`
|
||||
* sweep. The optional [DeleteExpired.asOf] cutoff lets the
|
||||
* store pin the timestamp it actually used, so the projection
|
||||
* drops exactly the events the store dropped.
|
||||
*/
|
||||
sealed interface StoreChange {
|
||||
data class Insert(
|
||||
val event: Event,
|
||||
) : StoreChange
|
||||
|
||||
data class DeleteByFilter(
|
||||
val filters: List<Filter>,
|
||||
) : StoreChange
|
||||
|
||||
data class DeleteExpired(
|
||||
val asOf: Long? = null,
|
||||
) : StoreChange
|
||||
}
|
||||
|
||||
override suspend fun insert(event: Event) {
|
||||
if (event.kind.isEphemeral()) {
|
||||
// Ephemeral kinds bypass persistence. We still drop ones
|
||||
@@ -162,24 +195,5 @@ class ObservableEventStore(
|
||||
_changes.emit(StoreChange.DeleteExpired(asOf))
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a reactive [EventStoreProjection] over this observable
|
||||
* store. [relay] scopes NIP-62 vanish handling — pass the relay
|
||||
* URL the events are arriving from, or `null` to apply only
|
||||
* unscoped (`ALL_RELAYS`) vanish requests. Cancel [scope] (or
|
||||
* call [EventStoreProjection.close]) to release the projection.
|
||||
*/
|
||||
fun <T : Event> observe(
|
||||
filters: List<Filter>,
|
||||
relay: NormalizedRelayUrl?,
|
||||
scope: CoroutineScope,
|
||||
): EventStoreProjection<T> = EventStoreProjection(this, filters, relay, scope)
|
||||
|
||||
fun <T : Event> observe(
|
||||
filter: Filter,
|
||||
relay: NormalizedRelayUrl?,
|
||||
scope: CoroutineScope,
|
||||
): EventStoreProjection<T> = observe(listOf(filter), relay, scope)
|
||||
|
||||
override fun close() = inner.close()
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.projection
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
|
||||
/**
|
||||
* Mutations published by [ObservableEventStore.changes]. Projections
|
||||
* react to these to keep their in-memory view in sync with the
|
||||
* underlying store.
|
||||
*
|
||||
* - [Insert] is emitted for every event accepted by the observable
|
||||
* layer (persistable or ephemeral). Carries the event itself so
|
||||
* the projection can run its NIP-01 / NIP-09 / NIP-62
|
||||
* interpretation.
|
||||
* - [DeleteByFilter] is emitted for every `delete(filter)` /
|
||||
* `delete(filters)` call on the observable. Carries the same
|
||||
* filters the store used so projections can apply
|
||||
* [Filter.match] in memory and drop the matching slots without
|
||||
* re-querying.
|
||||
* - [DeleteExpired] is emitted for every `deleteExpiredEvents()`
|
||||
* sweep. The optional [DeleteExpired.asOf] cutoff lets the store
|
||||
* pin the timestamp it actually used, so the projection drops
|
||||
* exactly the events the store dropped.
|
||||
*/
|
||||
sealed interface StoreChange {
|
||||
data class Insert(
|
||||
val event: Event,
|
||||
) : StoreChange
|
||||
|
||||
data class DeleteByFilter(
|
||||
val filters: List<Filter>,
|
||||
) : StoreChange
|
||||
|
||||
data class DeleteExpired(
|
||||
val asOf: Long? = null,
|
||||
) : StoreChange
|
||||
}
|
||||
+1
-1
@@ -34,7 +34,7 @@ import com.vitorpamplona.quartz.nip01Core.store.IEventStore
|
||||
*/
|
||||
class EventStore(
|
||||
dbName: String? = "events.db",
|
||||
val relay: NormalizedRelayUrl? = "wss://quartz.local/".normalizeRelayUrl(),
|
||||
override val relay: NormalizedRelayUrl? = "wss://quartz.local/".normalizeRelayUrl(),
|
||||
val indexStrategy: IndexingStrategy = DefaultIndexingStrategy(),
|
||||
) : IEventStore {
|
||||
val store = SQLiteEventStore(BundledSQLiteDriver(), dbName, relay, indexStrategy)
|
||||
|
||||
+12
-20
@@ -18,14 +18,14 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store.projection
|
||||
package com.vitorpamplona.quartz.nip01Core.cache.projection
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip01Core.store.projection.ObservableEventStore
|
||||
import com.vitorpamplona.quartz.nip01Core.store.ObservableEventStore
|
||||
import com.vitorpamplona.quartz.nip01Core.store.sqlite.EventStore
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
@@ -96,7 +96,7 @@ class EventStoreProjectionTest {
|
||||
observable.insert(a)
|
||||
observable.insert(b)
|
||||
|
||||
val projection = observable.observe<TextNoteEvent>(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope)
|
||||
val projection = observable.observe<TextNoteEvent>(Filter(kinds = listOf(TextNoteEvent.KIND)), scope)
|
||||
projection.ready.await()
|
||||
|
||||
val items = projection.items.value
|
||||
@@ -112,7 +112,7 @@ class EventStoreProjectionTest {
|
||||
val a = signer.sign(TextNoteEvent.build("a", createdAt = 100))
|
||||
observable.insert(a)
|
||||
|
||||
val projection = observable.observe<TextNoteEvent>(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope)
|
||||
val projection = observable.observe<TextNoteEvent>(Filter(kinds = listOf(TextNoteEvent.KIND)), scope)
|
||||
projection.ready.await()
|
||||
val before = projection.items.value
|
||||
assertEquals(1, before.size)
|
||||
@@ -132,7 +132,7 @@ class EventStoreProjectionTest {
|
||||
val text = signer.sign(TextNoteEvent.build("a", createdAt = 100))
|
||||
observable.insert(text)
|
||||
|
||||
val projection = observable.observe<TextNoteEvent>(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope)
|
||||
val projection = observable.observe<TextNoteEvent>(Filter(kinds = listOf(TextNoteEvent.KIND)), scope)
|
||||
projection.ready.await()
|
||||
val seed = projection.items.value
|
||||
|
||||
@@ -154,7 +154,6 @@ class EventStoreProjectionTest {
|
||||
val projection =
|
||||
observable.observe<MetadataEvent>(
|
||||
Filter(kinds = listOf(MetadataEvent.KIND), authors = listOf(v1.pubKey)),
|
||||
store.relay,
|
||||
scope,
|
||||
)
|
||||
projection.ready.await()
|
||||
@@ -186,7 +185,6 @@ class EventStoreProjectionTest {
|
||||
authors = listOf(v1.pubKey),
|
||||
tags = mapOf("d" to listOf("blog")),
|
||||
),
|
||||
store.relay,
|
||||
scope,
|
||||
)
|
||||
projection.ready.await()
|
||||
@@ -222,7 +220,6 @@ class EventStoreProjectionTest {
|
||||
val projection =
|
||||
observable.observe<MetadataEvent>(
|
||||
Filter(kinds = listOf(MetadataEvent.KIND), authors = listOf(v1.pubKey)),
|
||||
store.relay,
|
||||
scope,
|
||||
)
|
||||
projection.ready.await()
|
||||
@@ -251,7 +248,7 @@ class EventStoreProjectionTest {
|
||||
observable.insert(a)
|
||||
observable.insert(b)
|
||||
|
||||
val projection = observable.observe<Event>(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope)
|
||||
val projection = observable.observe<Event>(Filter(kinds = listOf(TextNoteEvent.KIND)), scope)
|
||||
projection.ready.await()
|
||||
assertEquals(2, projection.items.value.size)
|
||||
|
||||
@@ -273,7 +270,7 @@ class EventStoreProjectionTest {
|
||||
val a = signer.sign(TextNoteEvent.build("a", createdAt = 100))
|
||||
observable.insert(a)
|
||||
|
||||
val projection = observable.observe<Event>(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope)
|
||||
val projection = observable.observe<Event>(Filter(kinds = listOf(TextNoteEvent.KIND)), scope)
|
||||
projection.ready.await()
|
||||
val seed = projection.items.value
|
||||
assertEquals(1, seed.size)
|
||||
@@ -301,7 +298,7 @@ class EventStoreProjectionTest {
|
||||
observable.insert(a)
|
||||
observable.insert(b)
|
||||
|
||||
val projection = observable.observe<Event>(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope)
|
||||
val projection = observable.observe<Event>(Filter(kinds = listOf(TextNoteEvent.KIND)), scope)
|
||||
projection.ready.await()
|
||||
assertEquals(2, projection.items.value.size)
|
||||
|
||||
@@ -330,7 +327,7 @@ class EventStoreProjectionTest {
|
||||
val a = signer.sign(TextNoteEvent.build("a", createdAt = time))
|
||||
observable.insert(a)
|
||||
|
||||
val projection = observable.observe<Event>(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope)
|
||||
val projection = observable.observe<Event>(Filter(kinds = listOf(TextNoteEvent.KIND)), scope)
|
||||
projection.ready.await()
|
||||
val seed = projection.items.value
|
||||
|
||||
@@ -365,7 +362,6 @@ class EventStoreProjectionTest {
|
||||
val projection =
|
||||
observable.observe<Event>(
|
||||
Filter(kinds = listOf(TextNoteEvent.KIND)),
|
||||
store.relay,
|
||||
scope,
|
||||
)
|
||||
projection.ready.await()
|
||||
@@ -400,7 +396,6 @@ class EventStoreProjectionTest {
|
||||
val projection =
|
||||
observable.observe<Event>(
|
||||
Filter(kinds = listOf(TextNoteEvent.KIND)),
|
||||
store.relay,
|
||||
scope,
|
||||
)
|
||||
projection.ready.await()
|
||||
@@ -426,7 +421,6 @@ class EventStoreProjectionTest {
|
||||
val projection =
|
||||
observable.observe<TextNoteEvent>(
|
||||
Filter(kinds = listOf(TextNoteEvent.KIND), limit = 2),
|
||||
store.relay,
|
||||
scope,
|
||||
)
|
||||
projection.ready.await()
|
||||
@@ -466,7 +460,6 @@ class EventStoreProjectionTest {
|
||||
val projection =
|
||||
observable.observe<TextNoteEvent>(
|
||||
listOf(filterA, filterB),
|
||||
store.relay,
|
||||
scope,
|
||||
)
|
||||
projection.ready.await()
|
||||
@@ -490,7 +483,6 @@ class EventStoreProjectionTest {
|
||||
val projection =
|
||||
observable.observe<TextNoteEvent>(
|
||||
Filter(kinds = listOf(TextNoteEvent.KIND), limit = 2),
|
||||
store.relay,
|
||||
scope,
|
||||
)
|
||||
projection.ready.await()
|
||||
@@ -510,7 +502,7 @@ class EventStoreProjectionTest {
|
||||
val a = signer.sign(TextNoteEvent.build("a", createdAt = 100))
|
||||
observable.insert(a)
|
||||
|
||||
val projection = observable.observe<TextNoteEvent>(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope)
|
||||
val projection = observable.observe<TextNoteEvent>(Filter(kinds = listOf(TextNoteEvent.KIND)), scope)
|
||||
projection.ready.await()
|
||||
projection.close()
|
||||
|
||||
@@ -532,7 +524,7 @@ class EventStoreProjectionTest {
|
||||
runBlocking {
|
||||
val ephemeralKind = 22_000
|
||||
val projection =
|
||||
observable.observe<Event>(Filter(kinds = listOf(ephemeralKind)), store.relay, scope)
|
||||
observable.observe<Event>(Filter(kinds = listOf(ephemeralKind)), scope)
|
||||
projection.ready.await()
|
||||
assertTrue(projection.items.value.isEmpty())
|
||||
|
||||
@@ -555,7 +547,7 @@ class EventStoreProjectionTest {
|
||||
// A fresh projection on the same store gets nothing — the
|
||||
// event was only ever live, not durable.
|
||||
val freshProjection =
|
||||
observable.observe<Event>(Filter(kinds = listOf(ephemeralKind)), store.relay, scope)
|
||||
observable.observe<Event>(Filter(kinds = listOf(ephemeralKind)), scope)
|
||||
freshProjection.ready.await()
|
||||
assertTrue(freshProjection.items.value.isEmpty())
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ open class FsEventStore(
|
||||
* `null`, only "ALL_RELAYS" vanish requests cascade — matches
|
||||
* `SQLiteEventStore`'s relay arg semantics.
|
||||
*/
|
||||
private val relay: NormalizedRelayUrl? = null,
|
||||
override val relay: NormalizedRelayUrl? = null,
|
||||
/**
|
||||
* How to render an event to JSON before writing the canonical file.
|
||||
* Default is the compact NIP-01 form ([Event.toJson]); CLIs that
|
||||
|
||||
Reference in New Issue
Block a user