refactor: simplify NostrClient API for beginner-friendliness

Comprehensive rename of the NostrClient relay infrastructure to use
intuitive, self-documenting names instead of protocol-level jargon.

Interface/class renames:
- INostrClient → NostrClient (interface)
- NostrClient → DefaultNostrClient (implementation)
- IRequestListener → SubscriptionListener
- IRelayClientListener → RelayConnectionListener
- IOpenNostrRequest → SubscriptionHandle
- NostrClientStaticReq → StaticSubscription
- NostrClientDynamicReq → DynamicSubscription

Method renames:
- openReqSubscription() → subscribe()
- close(subId) → unsubscribe(subId)
- send() → publish()
- queryCount() → count()
- subscribe/unsubscribe(listener) → addConnectionListener/removeConnectionListener
- renewFilters() → syncFilters()

Callback renames:
- onEose → onCaughtUp
- isLive → isRealTime
- onStartReq → onSubscriptionStarted
- onCloseReq → onSubscriptionClosed

Extension function renames:
- query() → fetchAll()
- downloadFirstEvent() → fetchFirst()
- req() → subscribe()
- reqAsFlow() → subscribeAsFlow()
- reqUntilEoseAsFlow() → fetchAsFlow()
- sendAndWaitForResponse() → publishAndConfirm()
- queryCountSuspend() → count()
- queryCountMergedHll() → countMerged()
- reqBypassingRelayLimits() → fetchAllPages()
- updateFilter() → refresh() on SubscriptionHandle

https://claude.ai/code/session_01JPcYCcRx5eZN4GvgGxGwbf
This commit is contained in:
Claude
2026-03-28 03:23:20 +00:00
parent 0054c01921
commit 4e2717c5c5
169 changed files with 888 additions and 892 deletions
@@ -21,8 +21,8 @@
package com.vitorpamplona.amethyst.commons.chess
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -49,7 +49,7 @@ enum class RelayFetchStatus {
/**
* One-shot relay fetch helper for chess events.
*
* Follows the existing INostrClient + IRequestListener + Channel pattern
* Follows the existing NostrClient + SubscriptionListener + Channel pattern
* from quartz (see NostrClientSingleDownloadExt.kt).
*
* Each fetch opens a subscription, collects events until EOSE from all relays,
@@ -57,12 +57,12 @@ enum class RelayFetchStatus {
* no state is cached between fetches.
*/
class ChessRelayFetchHelper(
private val client: INostrClient,
private val client: NostrClient,
) {
/**
* Fetch events matching filters from relays, waiting for EOSE.
*
* @param filters Map of relay → filter list (same format as INostrClient.openReqSubscription)
* @param filters Map of relay → filter list (same format as NostrClient.subscribe)
* @param timeoutMs Max time to wait for relays to respond (default from ChessConfig)
* @param onProgress Optional callback for progress updates per relay
* @return Deduplicated list of events received before timeout/EOSE
@@ -88,10 +88,10 @@ class ChessRelayFetchHelper(
}
val listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -100,7 +100,7 @@ class ChessRelayFetchHelper(
onProgress?.invoke(RelayFetchProgress(relay, RelayFetchStatus.RECEIVING, count))
}
override fun onEose(
override fun onCaughtUp(
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -114,7 +114,7 @@ class ChessRelayFetchHelper(
}
}
client.openReqSubscription(subId, filters, listener)
client.subscribe(subId, filters, listener)
val eoseResult = withTimeoutOrNull(timeoutMs) { allEose.await() }
// Mark timed-out relays
@@ -127,7 +127,7 @@ class ChessRelayFetchHelper(
}
}
client.close(subId)
client.unsubscribe(subId)
return events.values.toList()
}
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.amethyst.commons.domain.nip46
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip46RemoteSigner.signer.NostrSignerRemote
import kotlinx.coroutines.flow.first
@@ -32,7 +32,7 @@ object BunkerLoginUseCase {
suspend fun execute(
bunkerUri: String,
ephemeralSigner: NostrSignerInternal,
client: INostrClient,
client: NostrClient,
): BunkerLoginResult {
val remoteSigner = NostrSignerRemote.fromBunkerUri(bunkerUri, ephemeralSigner, client)
remoteSigner.openSubscription()
@@ -24,8 +24,8 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.req
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.StaticSubscription
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
@@ -83,7 +83,7 @@ object NostrConnectLoginUseCase {
suspend fun awaitAndLogin(
uriData: NostrConnectUri,
client: INostrClient,
client: NostrClient,
): BunkerLoginResult {
val connectData =
waitForConnectRequest(
@@ -117,19 +117,22 @@ object NostrConnectLoginUseCase {
ephemeralPubKey: HexKey,
relays: Set<NormalizedRelayUrl>,
expectedSecret: String,
client: INostrClient,
client: NostrClient,
): ConnectRequestData {
val deferred = CompletableDeferred<NostrConnectEvent>()
val subscription =
client.req(
relays = relays.toList(),
filter =
Filter(
kinds = listOf(NostrConnectEvent.KIND),
tags = mapOf("p" to listOf(ephemeralPubKey)),
since = TimeUtils.now() - 60,
),
StaticSubscription(
client,
relays.associateWith {
listOf(
Filter(
kinds = listOf(NostrConnectEvent.KIND),
tags = mapOf("p" to listOf(ephemeralPubKey)),
since = TimeUtils.now() - 60,
),
)
},
) { event ->
if (event is NostrConnectEvent && !deferred.isCompleted) {
deferred.complete(event)
@@ -198,7 +201,7 @@ object NostrConnectLoginUseCase {
ephemeralSigner: NostrSignerInternal,
connectData: ConnectRequestData,
relays: Set<NormalizedRelayUrl>,
client: INostrClient,
client: NostrClient,
) {
val ackResponse = BunkerResponseAck(id = connectData.requestId!!)
val ackEvent =
@@ -207,7 +210,7 @@ object NostrConnectLoginUseCase {
remoteKey = connectData.signerPubkey,
signer = ephemeralSigner,
)
client.send(ackEvent, relays)
client.publish(ackEvent, relays)
}
private fun generateSecret(): String {
@@ -27,8 +27,8 @@ import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.Subscription
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -55,7 +55,7 @@ import kotlinx.coroutines.CoroutineScope
* ```
*/
class FeedMetadataCoordinator(
private val client: INostrClient,
private val client: NostrClient,
private val scope: CoroutineScope,
private val indexRelays: Set<NormalizedRelayUrl>,
private val preloader: MetadataPreloader? = null,
@@ -79,10 +79,10 @@ class FeedMetadataCoordinator(
// Create listener to pass events to the callback
val listener =
if (onEvent != null) {
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -93,7 +93,7 @@ class FeedMetadataCoordinator(
null
}
client.openReqSubscription(
client.subscribe(
subId = newSubId(),
filters = filterMap,
listener = listener,
@@ -24,7 +24,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubEose
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -48,7 +48,7 @@ data class MetadataQueryState(
* - Caches EOSE to avoid re-fetching known metadata
*/
class MetadataFilterAssembler(
client: INostrClient,
client: NostrClient,
allKeys: () -> Set<MetadataQueryState>,
) : SingleSubEoseManager<MetadataQueryState>(client, allKeys, invalidateAfterEose = true) {
override fun distinct(key: MetadataQueryState): Any = key.pubkeys.hashCode()
@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.commons.relayClient.assemblers
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.SingleSubEoseManager
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -48,7 +48,7 @@ data class ReactionsQueryState(
* - Caches EOSE to avoid re-fetching known reactions
*/
class ReactionsFilterAssembler(
client: INostrClient,
client: NostrClient,
allKeys: () -> Set<ReactionsQueryState>,
) : SingleSubEoseManager<ReactionsQueryState>(client, allKeys, invalidateAfterEose = true) {
override fun distinct(key: ReactionsQueryState): Any = key.noteIds.hashCode()
@@ -21,14 +21,14 @@
package com.vitorpamplona.amethyst.commons.relayClient.eoseManagers
import com.vitorpamplona.amethyst.commons.service.BundledUpdate
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
import com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions.SubscriptionController
import kotlinx.coroutines.Dispatchers
abstract class BaseEoseManager<T>(
val client: INostrClient,
val client: NostrClient,
val allKeys: () -> Set<T>,
val sampleTime: Long = 500,
) : IEoseManager {
@@ -38,7 +38,7 @@ abstract class BaseEoseManager<T>(
fun getSubscription(subId: String) = orchestrator.getSub(subId)
fun requestNewSubscription(listener: IRequestListener) = orchestrator.requestNewSubscription(newSubId(), listener)
fun requestNewSubscription(listener: SubscriptionListener) = orchestrator.requestNewSubscription(newSubId(), listener)
fun dismissSubscription(subId: String) = orchestrator.dismissSubscription(subId)
@@ -23,10 +23,10 @@ package com.vitorpamplona.amethyst.commons.relayClient.eoseManagers
import com.vitorpamplona.amethyst.commons.relays.EOSECache
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions.Subscription
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -46,7 +46,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
* shared among multiple query states that map to the same key.
*/
abstract class PerKeyEoseManager<T, K : Any>(
client: INostrClient,
client: NostrClient,
allKeys: () -> Set<T>,
val invalidateAfterEose: Boolean = false,
cacheSize: Int = 200,
@@ -82,8 +82,8 @@ abstract class PerKeyEoseManager<T, K : Any>(
*/
open fun newSub(queryState: T): Subscription =
requestNewSubscription(
object : IRequestListener {
override fun onEose(
object : SubscriptionListener {
override fun onCaughtUp(
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -92,11 +92,11 @@ abstract class PerKeyEoseManager<T, K : Any>(
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
if (isLive) {
if (isRealTime) {
newEose(queryState, relay, TimeUtils.now(), forFilters)
}
}
@@ -23,10 +23,10 @@ package com.vitorpamplona.amethyst.commons.relayClient.eoseManagers
import com.vitorpamplona.amethyst.commons.relays.EOSERelayList
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -44,7 +44,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
* shares all EOSEs among all users.
*/
abstract class SingleSubEoseManager<T>(
client: INostrClient,
client: NostrClient,
allKeys: () -> Set<T>,
val invalidateAfterEose: Boolean = false,
) : BaseEoseManager<T>(client, allKeys) {
@@ -66,8 +66,8 @@ abstract class SingleSubEoseManager<T>(
val sub =
requestNewSubscription(
object : IRequestListener {
override fun onEose(
object : SubscriptionListener {
override fun onCaughtUp(
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -76,11 +76,11 @@ abstract class SingleSubEoseManager<T>(
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
if (isLive) {
if (isRealTime) {
newEose(relay, TimeUtils.now(), forFilters)
}
}