From ecbb7ea10cdc18a7c64dd7ff77e25ab0cc6f97de Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Apr 2026 14:51:16 +0000 Subject: [PATCH] refactor(quartz): tighten projection package layout + relay flows from store MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../cache/interning/InternedEventStore.kt | 3 + .../projection/EventStoreProjection.kt | 28 ++++++-- .../quartz/nip01Core/store/IEventStore.kt | 9 +++ .../{projection => }/ObservableEventStore.kt | 72 +++++++++++-------- .../nip01Core/store/projection/StoreChange.kt | 57 --------------- .../nip01Core/store/sqlite/EventStore.kt | 2 +- .../projection/EventStoreProjectionTest.kt | 32 ++++----- .../quartz/nip01Core/store/fs/FsEventStore.kt | 2 +- 8 files changed, 91 insertions(+), 114 deletions(-) rename quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/{store => cache}/projection/EventStoreProjection.kt (94%) rename quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/{projection => }/ObservableEventStore.kt (77%) delete mode 100644 quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/StoreChange.kt rename quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip01Core/{store => cache}/projection/EventStoreProjectionTest.kt (95%) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/cache/interning/InternedEventStore.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/cache/interning/InternedEventStore.kt index 8403bf8ef..2bea9daae 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/cache/interning/InternedEventStore.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/cache/interning/InternedEventStore.kt @@ -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) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/EventStoreProjection.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/cache/projection/EventStoreProjection.kt similarity index 94% rename from quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/EventStoreProjection.kt rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/cache/projection/EventStoreProjection.kt index f70e93ac3..36ec6b9d1 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/EventStoreProjection.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/cache/projection/EventStoreProjection.kt @@ -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( private val store: ObservableEventStore, private val filters: List, - private val relay: NormalizedRelayUrl?, scope: CoroutineScope, private val nowProvider: () -> Long = TimeUtils::now, ) : AutoCloseable { @@ -199,7 +199,7 @@ class EventStoreProjection( 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( } } } + +/** + * 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 ObservableEventStore.observe( + filters: List, + scope: CoroutineScope, +): EventStoreProjection = EventStoreProjection(this, filters, scope) + +fun ObservableEventStore.observe( + filter: Filter, + scope: CoroutineScope, +): EventStoreProjection = EventStoreProjection(this, listOf(filter), scope) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/IEventStore.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/IEventStore.kt index e70773c29..d376b8e7d 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/IEventStore.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/IEventStore.kt @@ -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 { diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/ObservableEventStore.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/ObservableEventStore.kt similarity index 77% rename from quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/ObservableEventStore.kt rename to quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/ObservableEventStore.kt index 0355d9710..a65c1e5e1 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/ObservableEventStore.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/ObservableEventStore.kt @@ -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( 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 = _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, + ) : 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 observe( - filters: List, - relay: NormalizedRelayUrl?, - scope: CoroutineScope, - ): EventStoreProjection = EventStoreProjection(this, filters, relay, scope) - - fun observe( - filter: Filter, - relay: NormalizedRelayUrl?, - scope: CoroutineScope, - ): EventStoreProjection = observe(listOf(filter), relay, scope) - override fun close() = inner.close() } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/StoreChange.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/StoreChange.kt deleted file mode 100644 index c268583f8..000000000 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/StoreChange.kt +++ /dev/null @@ -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, - ) : StoreChange - - data class DeleteExpired( - val asOf: Long? = null, - ) : StoreChange -} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt index 0a0f1a75b..ef9510611 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt @@ -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) diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/EventStoreProjectionTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip01Core/cache/projection/EventStoreProjectionTest.kt similarity index 95% rename from quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/EventStoreProjectionTest.kt rename to quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip01Core/cache/projection/EventStoreProjectionTest.kt index 259e053f6..70edbdef5 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/projection/EventStoreProjectionTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip01Core/cache/projection/EventStoreProjectionTest.kt @@ -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(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope) + val projection = observable.observe(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(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope) + val projection = observable.observe(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(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope) + val projection = observable.observe(Filter(kinds = listOf(TextNoteEvent.KIND)), scope) projection.ready.await() val seed = projection.items.value @@ -154,7 +154,6 @@ class EventStoreProjectionTest { val projection = observable.observe( 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( 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(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope) + val projection = observable.observe(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(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope) + val projection = observable.observe(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(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope) + val projection = observable.observe(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(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope) + val projection = observable.observe(Filter(kinds = listOf(TextNoteEvent.KIND)), scope) projection.ready.await() val seed = projection.items.value @@ -365,7 +362,6 @@ class EventStoreProjectionTest { val projection = observable.observe( Filter(kinds = listOf(TextNoteEvent.KIND)), - store.relay, scope, ) projection.ready.await() @@ -400,7 +396,6 @@ class EventStoreProjectionTest { val projection = observable.observe( Filter(kinds = listOf(TextNoteEvent.KIND)), - store.relay, scope, ) projection.ready.await() @@ -426,7 +421,6 @@ class EventStoreProjectionTest { val projection = observable.observe( Filter(kinds = listOf(TextNoteEvent.KIND), limit = 2), - store.relay, scope, ) projection.ready.await() @@ -466,7 +460,6 @@ class EventStoreProjectionTest { val projection = observable.observe( listOf(filterA, filterB), - store.relay, scope, ) projection.ready.await() @@ -490,7 +483,6 @@ class EventStoreProjectionTest { val projection = observable.observe( 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(Filter(kinds = listOf(TextNoteEvent.KIND)), store.relay, scope) + val projection = observable.observe(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(Filter(kinds = listOf(ephemeralKind)), store.relay, scope) + observable.observe(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(Filter(kinds = listOf(ephemeralKind)), store.relay, scope) + observable.observe(Filter(kinds = listOf(ephemeralKind)), scope) freshProjection.ready.await() assertTrue(freshProjection.items.value.isEmpty()) diff --git a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/fs/FsEventStore.kt b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/fs/FsEventStore.kt index 8da5d14b5..c91cf7445 100644 --- a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/fs/FsEventStore.kt +++ b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/fs/FsEventStore.kt @@ -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