Rename PurpleQuartz to Iolite.

This commit is contained in:
franzap
2026-09-16 14:37:16 -03:00
parent d8aaec91c6
commit cccb841734
27 changed files with 126 additions and 126 deletions
+3 -3
View File
@@ -8,7 +8,7 @@
It combines app discovery, publisher identity, direct APK distribution, and social trust signals into a different model for Android app distribution. It combines app discovery, publisher identity, direct APK distribution, and social trust signals into a different model for Android app distribution.
This repo is the [Kotlin](https://kotlinlang.org) / [Jetpack Compose](https://developer.android.com/compose) client for [Zapstore](https://zapstore.dev). Catalog listings, curated stacks, releases, and publisher profiles sync from Nostr relays through the local `purplequartz` module. This repo is the [Kotlin](https://kotlinlang.org) / [Jetpack Compose](https://developer.android.com/compose) client for [Zapstore](https://zapstore.dev). Catalog listings, curated stacks, releases, and publisher profiles sync from Nostr relays through the local `iolite` module.
## Get the app ## Get the app
@@ -43,7 +43,7 @@ Or call Gradle directly:
./gradlew test ./gradlew test
``` ```
`./gradlew test` runs unit tests for the app and `purplequartz`. Instrumented UI tests need a connected device or emulator: `./gradlew test` runs unit tests for the app and `iolite`. Instrumented UI tests need a connected device or emulator:
```bash ```bash
./gradlew connectedDebugAndroidTest ./gradlew connectedDebugAndroidTest
@@ -52,7 +52,7 @@ Or call Gradle directly:
## Layout ## Layout
- `src/main/kotlin/dev/zapstore/app/` — Compose UI, navigation, view models, and catalog wiring - `src/main/kotlin/dev/zapstore/app/` — Compose UI, navigation, view models, and catalog wiring
- `purplequartz/` — local-first Nostr client (SQLite event store, relay sessions, outbox routing) on top of [Quartz](https://github.com/vitorpamplona/amethyst) - `iolite/` — local-first Nostr client (SQLite event store, relay sessions, outbox routing) on top of [Quartz](https://github.com/vitorpamplona/amethyst)
## Contributing ## Contributing
+2 -2
View File
@@ -7,7 +7,7 @@ plugins {
android { android {
namespace = "dev.zapstore.app" namespace = "dev.zapstore.app"
compileSdk = providers.gradleProperty("purplequartz.compileSdk").orElse("37").get().toInt() compileSdk = providers.gradleProperty("iolite.compileSdk").orElse("37").get().toInt()
defaultConfig { defaultConfig {
applicationId = "dev.zapstore.beta" applicationId = "dev.zapstore.beta"
@@ -28,7 +28,7 @@ kotlin {
} }
dependencies { dependencies {
implementation(project(":purplequartz")) implementation(project(":iolite"))
implementation(platform(libs.compose.bom)) implementation(platform(libs.compose.bom))
implementation(libs.activity.compose) implementation(libs.activity.compose)
implementation(libs.compose.foundation) implementation(libs.compose.foundation)
@@ -3,8 +3,8 @@ plugins {
} }
android { android {
namespace = "dev.zapstore.purplequartz" namespace = "dev.zapstore.iolite"
compileSdk = providers.gradleProperty("purplequartz.compileSdk").orElse("37").get().toInt() compileSdk = providers.gradleProperty("iolite.compileSdk").orElse("37").get().toInt()
defaultConfig { defaultConfig {
minSdk = 29 minSdk = 29
+1
View File
@@ -0,0 +1 @@
# Iolite requires no consumer-specific shrinking rules.
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress import androidx.test.filters.SdkSuppress
@@ -102,7 +102,7 @@ class QuartzCompatibilityInstrumentedTest {
@Test @Test
fun facadeRecreationReadsPersistedRowsWithoutNetwork() = runBlocking { fun facadeRecreationReadsPersistedRowsWithoutNetwork() = runBlocking {
val context = InstrumentationRegistry.getInstrumentation().targetContext val context = InstrumentationRegistry.getInstrumentation().targetContext
val databaseName = "purplequartz-offline-${UUID.randomUUID()}.db" val databaseName = "iolite-offline-${UUID.randomUUID()}.db"
val path = context.getDatabasePath(databaseName).absolutePath val path = context.getDatabasePath(databaseName).absolutePath
val event = knownValidEvent() val event = knownValidEvent()
context.deleteDatabase(databaseName) context.deleteDatabase(databaseName)
@@ -118,11 +118,11 @@ class QuartzCompatibilityInstrumentedTest {
try { try {
repeat(2) { repeat(2) {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val purpleQuartz = PurpleQuartz.create( val purpleQuartz = Iolite.create(
context = context, context = context,
websocketBuilder = BasicOkHttpWebSocket.Builder { okHttpClient }, websocketBuilder = BasicOkHttpWebSocket.Builder { okHttpClient },
parentScope = scope, parentScope = scope,
config = PurpleQuartzConfig(databaseName = databaseName), config = IoliteConfig(databaseName = databaseName),
) )
try { try {
val state = purpleQuartz.query(Filter(ids = listOf(event.id))).first() val state = purpleQuartz.query(Filter(ids = listOf(event.id))).first()
@@ -143,15 +143,15 @@ class QuartzCompatibilityInstrumentedTest {
@Test @Test
fun api29ConsumerConstructsQueriesAllSourcesAndCloses() = runBlocking { fun api29ConsumerConstructsQueriesAllSourcesAndCloses() = runBlocking {
val context = InstrumentationRegistry.getInstrumentation().targetContext val context = InstrumentationRegistry.getInstrumentation().targetContext
val databaseName = "purplequartz-consumer-${UUID.randomUUID()}.db" val databaseName = "iolite-consumer-${UUID.randomUUID()}.db"
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val okHttpClient = OkHttpClient() val okHttpClient = OkHttpClient()
val relay = "ws://127.0.0.1:1".normalizeRelayUrl() val relay = "ws://127.0.0.1:1".normalizeRelayUrl()
val purpleQuartz = PurpleQuartz.create( val purpleQuartz = Iolite.create(
context = context, context = context,
websocketBuilder = BasicOkHttpWebSocket.Builder { okHttpClient }, websocketBuilder = BasicOkHttpWebSocket.Builder { okHttpClient },
parentScope = scope, parentScope = scope,
config = PurpleQuartzConfig(databaseName = databaseName), config = IoliteConfig(databaseName = databaseName),
) )
try { try {
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
@@ -51,13 +51,13 @@ import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import kotlin.time.Duration import kotlin.time.Duration
class PurpleQuartz private constructor( class Iolite private constructor(
private val store: ObservableEventStore, private val store: ObservableEventStore,
private val client: INostrClient, private val client: INostrClient,
private val scope: CoroutineScope, private val scope: CoroutineScope,
private val job: Job, private val job: Job,
private val databasePath: String, private val databasePath: String,
private val config: PurpleQuartzConfig, private val config: IoliteConfig,
private val eventVerifier: (Event) -> Boolean, private val eventVerifier: (Event) -> Boolean,
private val connectivityManager: ConnectivityManager?, private val connectivityManager: ConnectivityManager?,
private val refreshCache: QueryRefreshCache, private val refreshCache: QueryRefreshCache,
@@ -126,7 +126,7 @@ class PurpleQuartz private constructor(
fun query(filters: List<Filter>, options: QueryOptions = QueryOptions.local()): Flow<QueryState> = callbackFlow { fun query(filters: List<Filter>, options: QueryOptions = QueryOptions.local()): Flow<QueryState> = callbackFlow {
if (closed.get()) { if (closed.get()) {
trySend(QueryState(emptyList(), QueryPhase.Failed, error = QueryError.Lifecycle("PurpleQuartz is closed"))) trySend(QueryState(emptyList(), QueryPhase.Failed, error = QueryError.Lifecycle("Iolite is closed")))
close() close()
return@callbackFlow return@callbackFlow
} }
@@ -140,7 +140,7 @@ class PurpleQuartz private constructor(
val session = QuerySession(snapshot, options, this) val session = QuerySession(snapshot, options, this)
synchronized(sessionsLock) { synchronized(sessionsLock) {
if (closed.get()) { if (closed.get()) {
trySend(QueryState(emptyList(), QueryPhase.Failed, error = QueryError.Lifecycle("PurpleQuartz is closed"))) trySend(QueryState(emptyList(), QueryPhase.Failed, error = QueryError.Lifecycle("Iolite is closed")))
close() close()
return@callbackFlow return@callbackFlow
} }
@@ -253,7 +253,7 @@ class PurpleQuartz private constructor(
} }
private suspend fun <T> withStoreOperation(block: suspend () -> T): T { private suspend fun <T> withStoreOperation(block: suspend () -> T): T {
check(!closed.get()) { "PurpleQuartz is closing" } check(!closed.get()) { "Iolite is closing" }
return block() return block()
} }
@@ -902,7 +902,7 @@ class PurpleQuartz private constructor(
cleanup() cleanup()
val terminal = lastState.copy( val terminal = lastState.copy(
phase = QueryPhase.Failed, phase = QueryPhase.Failed,
error = QueryError.Lifecycle("PurpleQuartz is closed"), error = QueryError.Lifecycle("Iolite is closed"),
) )
lastState = terminal lastState = terminal
producer.trySend(terminal) producer.trySend(terminal)
@@ -936,8 +936,8 @@ class PurpleQuartz private constructor(
context: Context, context: Context,
websocketBuilder: WebsocketBuilder, websocketBuilder: WebsocketBuilder,
parentScope: CoroutineScope, parentScope: CoroutineScope,
config: PurpleQuartzConfig = PurpleQuartzConfig(), config: IoliteConfig = IoliteConfig(),
): PurpleQuartz { ): Iolite {
config.validate() config.validate()
val parentJob = requireActiveParentJob(parentScope) val parentJob = requireActiveParentJob(parentScope)
val appContext = context.applicationContext val appContext = context.applicationContext
@@ -945,7 +945,7 @@ class PurpleQuartz private constructor(
val databaseExisted = databaseFile.exists() val databaseExisted = databaseFile.exists()
val path = databaseFile.canonicalPath val path = databaseFile.canonicalPath
synchronized(openDatabases) { synchronized(openDatabases) {
check(path !in openDatabases) { "A PurpleQuartz instance already owns this database" } check(path !in openDatabases) { "A Iolite instance already owns this database" }
openDatabases += path openDatabases += path
} }
val job = SupervisorJob(parentJob) val job = SupervisorJob(parentJob)
@@ -960,7 +960,7 @@ class PurpleQuartz private constructor(
databaseExisted = databaseExisted, databaseExisted = databaseExisted,
) )
client = NostrClient(websocketBuilder, scope) client = NostrClient(websocketBuilder, scope)
return PurpleQuartz( return Iolite(
store, store,
client, client,
scope, scope,
@@ -990,17 +990,17 @@ class PurpleQuartz private constructor(
eventStore: IEventStore, eventStore: IEventStore,
client: INostrClient, client: INostrClient,
parentScope: CoroutineScope, parentScope: CoroutineScope,
config: PurpleQuartzConfig = PurpleQuartzConfig(), config: IoliteConfig = IoliteConfig(),
databasePath: String = "test-${System.nanoTime()}", databasePath: String = "test-${System.nanoTime()}",
eventVerifier: (Event) -> Boolean = DEFAULT_EVENT_VERIFIER, eventVerifier: (Event) -> Boolean = DEFAULT_EVENT_VERIFIER,
refreshCache: QueryRefreshCache = InMemoryQueryRefreshCache(), refreshCache: QueryRefreshCache = InMemoryQueryRefreshCache(),
clock: EpochMillisClock = SYSTEM_CLOCK, clock: EpochMillisClock = SYSTEM_CLOCK,
dispatcher: CoroutineDispatcher = Dispatchers.IO, dispatcher: CoroutineDispatcher = Dispatchers.IO,
): PurpleQuartz { ): Iolite {
config.validate() config.validate()
val job = SupervisorJob(requireActiveParentJob(parentScope)) val job = SupervisorJob(requireActiveParentJob(parentScope))
val scope = CoroutineScope(parentScope.coroutineContext + job + dispatcher) val scope = CoroutineScope(parentScope.coroutineContext + job + dispatcher)
return PurpleQuartz( return Iolite(
store = ObservableEventStore(eventStore), store = ObservableEventStore(eventStore),
client = client, client = client,
scope = scope, scope = scope,
@@ -1,12 +1,12 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
data class PurpleQuartzConfig( data class IoliteConfig(
val databaseName: String = "purplequartz.db", val databaseName: String = "iolite.db",
val oneShotTimeout: Duration = 30.seconds, val oneShotTimeout: Duration = 30.seconds,
val ingestionCapacity: Int = 1_024, val ingestionCapacity: Int = 1_024,
val expirationSweepInterval: Duration = 1.minutes, val expirationSweepInterval: Duration = 1.minutes,
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
@@ -15,7 +15,7 @@ import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
/** /**
* NIP-65 outbox routing on top of [PurpleQuartz]. * NIP-65 outbox routing on top of [Iolite].
* *
* Resolution is local-first: kind-10002 relay lists are read from the local * Resolution is local-first: kind-10002 relay lists are read from the local
* store and only fetched from [bootstrapRelays] when the TTL'd freshness cache * store and only fetched from [bootstrapRelays] when the TTL'd freshness cache
@@ -24,7 +24,7 @@ import kotlin.time.Duration.Companion.seconds
* store holds. * store holds.
*/ */
class OutboxRouter( class OutboxRouter(
private val client: PurpleQuartz, private val client: Iolite,
private val bootstrapRelays: Set<NormalizedRelayUrl>, private val bootstrapRelays: Set<NormalizedRelayUrl>,
private val cacheDuration: Duration = 6.hours, private val cacheDuration: Duration = 6.hours,
private val resolveTimeout: Duration = 10.seconds, private val resolveTimeout: Duration = 10.seconds,
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
@@ -96,7 +96,7 @@ internal class SharedPreferencesQueryRefreshCache private constructor(
} }
companion object { companion object {
private const val PREFERENCES_NAME = "dev.zapstore.purplequartz.query-refresh-v1" private const val PREFERENCES_NAME = "dev.zapstore.iolite.query-refresh-v1"
private const val MAX_ENTRIES = 1_024 private const val MAX_ENTRIES = 1_024
fun create( fun create(
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -46,8 +46,8 @@ internal object QuerySync {
internal val QueryState.sync: QueryPhase internal val QueryState.sync: QueryPhase
get() = phase get() = phase
internal fun PurpleQuartz.query(filter: Filter, source: QuerySource): Flow<QueryState> = internal fun Iolite.query(filter: Filter, source: QuerySource): Flow<QueryState> =
query(filter, source.options) query(filter, source.options)
internal fun PurpleQuartz.query(filters: List<Filter>, source: QuerySource): Flow<QueryState> = internal fun Iolite.query(filters: List<Filter>, source: QuerySource): Flow<QueryState> =
query(filters, source.options) query(filters, source.options)
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient
@@ -30,7 +30,7 @@ class ObservationTest {
@Test @Test
fun `sessions do not re-emit on other sessions inserts`() = runBlocking { fun `sessions do not re-emit on other sessions inserts`() = runBlocking {
val client = MultiSubClient() val client = MultiSubClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
RecordingEventStore(), RecordingEventStore(),
client, client,
this, this,
@@ -64,7 +64,7 @@ class ObservationTest {
@Test @Test
fun `replaceable update re-emits with the new version`() = runBlocking { fun `replaceable update re-emits with the new version`() = runBlocking {
val client = MultiSubClient() val client = MultiSubClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
RecordingEventStore(), RecordingEventStore(),
client, client,
this, this,
@@ -96,11 +96,11 @@ class ObservationTest {
fun `events are persisted via a single batch at eose`() = runBlocking { fun `events are persisted via a single batch at eose`() = runBlocking {
val client = MultiSubClient() val client = MultiSubClient()
val store = RecordingEventStore() val store = RecordingEventStore()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
store, store,
client, client,
this, this,
config = PurpleQuartzConfig(ingestFlushInterval = 10.seconds), config = IoliteConfig(ingestFlushInterval = 10.seconds),
eventVerifier = { true }, eventVerifier = { true },
) )
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
@@ -134,11 +134,11 @@ class ObservationTest {
@Test @Test
fun `remote burst is coalesced and complete state contains every event`() = runBlocking { fun `remote burst is coalesced and complete state contains every event`() = runBlocking {
val client = MultiSubClient() val client = MultiSubClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
RecordingEventStore(), RecordingEventStore(),
client, client,
this, this,
config = PurpleQuartzConfig(ingestBatchSize = 1_000, ingestFlushInterval = 10.seconds), config = IoliteConfig(ingestBatchSize = 1_000, ingestFlushInterval = 10.seconds),
eventVerifier = { true }, eventVerifier = { true },
) )
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
@@ -169,11 +169,11 @@ class ObservationTest {
val store = RecordingEventStore(rejectBatchInserts = true) val store = RecordingEventStore(rejectBatchInserts = true)
val events = (0 until 100).map { signedEvent(content = "duplicate-$it") } val events = (0 until 100).map { signedEvent(content = "duplicate-$it") }
events.forEach { store.insert(it) } events.forEach { store.insert(it) }
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
store, store,
client, client,
this, this,
config = PurpleQuartzConfig(ingestBatchSize = 100, ingestFlushInterval = 10.seconds), config = IoliteConfig(ingestBatchSize = 100, ingestFlushInterval = 10.seconds),
eventVerifier = { true }, eventVerifier = { true },
) )
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
@@ -199,11 +199,11 @@ class ObservationTest {
@Test @Test
fun `slow local collector cannot block remote persistence`() = runBlocking { fun `slow local collector cannot block remote persistence`() = runBlocking {
val client = MultiSubClient() val client = MultiSubClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
RecordingEventStore(), RecordingEventStore(),
client, client,
this, this,
config = PurpleQuartzConfig(ingestBatchSize = 500, ingestFlushInterval = 10.seconds), config = IoliteConfig(ingestBatchSize = 500, ingestFlushInterval = 10.seconds),
eventVerifier = { true }, eventVerifier = { true },
) )
val slowCollection = launch { val slowCollection = launch {
@@ -234,7 +234,7 @@ class ObservationTest {
} }
private fun kotlinx.coroutines.CoroutineScope.collect( private fun kotlinx.coroutines.CoroutineScope.collect(
purpleQuartz: PurpleQuartz, purpleQuartz: Iolite,
filter: Filter, filter: Filter,
states: MutableList<QueryState>, states: MutableList<QueryState>,
): Job = launch { ): Job = launch {
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient
@@ -37,7 +37,7 @@ class OutboxRouterTest {
arrayOf("r", "http://insecure.example"), arrayOf("r", "http://insecure.example"),
))) )))
val client = RouterTestClient() val client = RouterTestClient()
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this) val purpleQuartz = Iolite.createForTesting(store, client, this)
val router = OutboxRouter(purpleQuartz, bootstrap) val router = OutboxRouter(purpleQuartz, bootstrap)
var resolved: Set<NormalizedRelayUrl>? = null var resolved: Set<NormalizedRelayUrl>? = null
@@ -60,7 +60,7 @@ class OutboxRouterTest {
val filter = Filter(authors = listOf(author), kinds = listOf(OutboxRouter.RELAY_LIST_KIND), limit = 1) val filter = Filter(authors = listOf(author), kinds = listOf(OutboxRouter.RELAY_LIST_KIND), limit = 1)
refreshCache.recordRefresh(QueryFingerprint.create(listOf(filter), bootstrap), System.currentTimeMillis()) refreshCache.recordRefresh(QueryFingerprint.create(listOf(filter), bootstrap), System.currentTimeMillis())
val client = RouterTestClient() val client = RouterTestClient()
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this, refreshCache = refreshCache) val purpleQuartz = Iolite.createForTesting(store, client, this, refreshCache = refreshCache)
val router = OutboxRouter(purpleQuartz, bootstrap) val router = OutboxRouter(purpleQuartz, bootstrap)
val resolved = withTimeout(2.seconds) { router.resolveReadRelays(author) } val resolved = withTimeout(2.seconds) { router.resolveReadRelays(author) }
@@ -75,7 +75,7 @@ class OutboxRouterTest {
val store = RouterTestStore() val store = RouterTestStore()
store.insert(relayListEvent(arrayOf(arrayOf("r", outbox.url)))) store.insert(relayListEvent(arrayOf(arrayOf("r", outbox.url))))
val client = RouterTestClient() val client = RouterTestClient()
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this) val purpleQuartz = Iolite.createForTesting(store, client, this)
val router = OutboxRouter(purpleQuartz, bootstrap) val router = OutboxRouter(purpleQuartz, bootstrap)
val collection = launch { val collection = launch {
@@ -100,7 +100,7 @@ class OutboxRouterTest {
val store = RouterTestStore() val store = RouterTestStore()
store.insert(relayListEvent(arrayOf(arrayOf("r", outbox.url)))) store.insert(relayListEvent(arrayOf(arrayOf("r", outbox.url))))
val client = RouterTestClient() val client = RouterTestClient()
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this) val purpleQuartz = Iolite.createForTesting(store, client, this)
val router = OutboxRouter(purpleQuartz, bootstrap) val router = OutboxRouter(purpleQuartz, bootstrap)
val collection = launch { val collection = launch {
@@ -125,7 +125,7 @@ class OutboxRouterTest {
fun `queryWithOutbox keeps the fallback relays when nothing resolves`() = runBlocking { fun `queryWithOutbox keeps the fallback relays when nothing resolves`() = runBlocking {
val store = RouterTestStore() val store = RouterTestStore()
val client = RouterTestClient() val client = RouterTestClient()
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this) val purpleQuartz = Iolite.createForTesting(store, client, this)
val router = OutboxRouter(purpleQuartz, bootstrap) val router = OutboxRouter(purpleQuartz, bootstrap)
val collection = launch { val collection = launch {
@@ -149,7 +149,7 @@ class OutboxRouterTest {
fun `resolveReadRelays returns empty when the fetch cannot complete`() = runBlocking { fun `resolveReadRelays returns empty when the fetch cannot complete`() = runBlocking {
val store = RouterTestStore() val store = RouterTestStore()
val client = RouterTestClient() val client = RouterTestClient()
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this) val purpleQuartz = Iolite.createForTesting(store, client, this)
val router = OutboxRouter(purpleQuartz, bootstrap, resolveTimeout = 1.seconds) val router = OutboxRouter(purpleQuartz, bootstrap, resolveTimeout = 1.seconds)
var resolved: Set<NormalizedRelayUrl>? = null var resolved: Set<NormalizedRelayUrl>? = null
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
@@ -21,14 +21,14 @@ import org.junit.Test
class PublicContractsTest { class PublicContractsTest {
@Test @Test
fun `config rejects invalid lifecycle values`() { fun `config rejects invalid lifecycle values`() {
assertFails { PurpleQuartzConfig(databaseName = "../store.db").validate() } assertFails { IoliteConfig(databaseName = "../store.db").validate() }
assertFails { PurpleQuartzConfig(oneShotTimeout = 0.milliseconds).validate() } assertFails { IoliteConfig(oneShotTimeout = 0.milliseconds).validate() }
assertFails { PurpleQuartzConfig(ingestionCapacity = 0).validate() } assertFails { IoliteConfig(ingestionCapacity = 0).validate() }
assertFails { PurpleQuartzConfig(expirationSweepInterval = 0.milliseconds).validate() } assertFails { IoliteConfig(expirationSweepInterval = 0.milliseconds).validate() }
assertFails { PurpleQuartzConfig(ingestBatchSize = 0).validate() } assertFails { IoliteConfig(ingestBatchSize = 0).validate() }
assertFails { PurpleQuartzConfig(ingestFlushInterval = 0.milliseconds).validate() } assertFails { IoliteConfig(ingestFlushInterval = 0.milliseconds).validate() }
assertFails { PurpleQuartzConfig(pruneRules = mapOf(9735 to 0.milliseconds)).validate() } assertFails { IoliteConfig(pruneRules = mapOf(9735 to 0.milliseconds)).validate() }
assertFails { PurpleQuartzConfig(pruneRules = mapOf(-1 to 1.hours)).validate() } assertFails { IoliteConfig(pruneRules = mapOf(-1 to 1.hours)).validate() }
} }
@Test @Test
@@ -60,7 +60,7 @@ class PublicContractsTest {
@Test @Test
fun `published Quartz APIs used by the facade remain callable`() { fun `published Quartz APIs used by the facade remain callable`() {
// This is deliberately a compile-level compatibility gate for the public APIs // This is deliberately a compile-level compatibility gate for the public APIs
// PurpleQuartz relies on. Behavioral compatibility is covered by Android tests. // Iolite relies on. Behavioral compatibility is covered by Android tests.
val invalidEvent = Event( val invalidEvent = Event(
id = "0".repeat(64), id = "0".repeat(64),
pubKey = "0".repeat(64), pubKey = "0".repeat(64),
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient
@@ -45,7 +45,7 @@ class QueryInvariantTest {
cachedFor = 6.hours, cachedFor = 6.hours,
) )
val firstClient = ControlledClient() val firstClient = ControlledClient()
val first = PurpleQuartz.createForTesting( val first = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
firstClient, firstClient,
this, this,
@@ -65,7 +65,7 @@ class QueryInvariantTest {
first.close() first.close()
val secondClient = ControlledClient() val secondClient = ControlledClient()
val second = PurpleQuartz.createForTesting( val second = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
secondClient, secondClient,
this, this,
@@ -98,7 +98,7 @@ class QueryInvariantTest {
val event = signedEvent(content = "persisted") val event = signedEvent(content = "persisted")
store.insert(event) store.insert(event)
val client = ControlledClient() val client = ControlledClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
store, store,
client, client,
this, this,
@@ -127,7 +127,7 @@ class QueryInvariantTest {
) )
cache.recordRefresh(QueryFingerprint.create(listOf(filter), setOf(relay)), clock.now()) cache.recordRefresh(QueryFingerprint.create(listOf(filter), setOf(relay)), clock.now())
val client = ControlledClient() val client = ControlledClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
client, client,
this, this,
@@ -160,7 +160,7 @@ class QueryInvariantTest {
val client = ControlledClient() val client = ControlledClient()
val store = ControlledEventStore() val store = ControlledEventStore()
store.insert(signedEvent(content = "cached")) store.insert(signedEvent(content = "cached"))
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
store, store,
client, client,
this, this,
@@ -200,11 +200,11 @@ class QueryInvariantTest {
val timeoutCache = InMemoryQueryRefreshCache() val timeoutCache = InMemoryQueryRefreshCache()
val timeoutClient = ControlledClient() val timeoutClient = ControlledClient()
val timeoutQuartz = PurpleQuartz.createForTesting( val timeoutQuartz = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
timeoutClient, timeoutClient,
this, this,
config = PurpleQuartzConfig(oneShotTimeout = 50.milliseconds), config = IoliteConfig(oneShotTimeout = 50.milliseconds),
eventVerifier = { true }, eventVerifier = { true },
refreshCache = timeoutCache, refreshCache = timeoutCache,
) )
@@ -218,7 +218,7 @@ class QueryInvariantTest {
val failureCache = InMemoryQueryRefreshCache() val failureCache = InMemoryQueryRefreshCache()
val failureClient = ControlledClient() val failureClient = ControlledClient()
val failureQuartz = PurpleQuartz.createForTesting( val failureQuartz = Iolite.createForTesting(
ControlledEventStore(failInserts = true), ControlledEventStore(failInserts = true),
failureClient, failureClient,
this, this,
@@ -247,7 +247,7 @@ class QueryInvariantTest {
cachedFor = 1.hours, cachedFor = 1.hours,
) )
val fingerprint = QueryFingerprint.create(listOf(filter), setOf(relay)) val fingerprint = QueryFingerprint.create(listOf(filter), setOf(relay))
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
ControlledEventStore(failQueriesAfter = 1), ControlledEventStore(failQueriesAfter = 1),
client, client,
this, this,
@@ -271,7 +271,7 @@ class QueryInvariantTest {
@Test @Test
fun `reconnect advances generation and accepts replacement EOSE`() = runBlocking { fun `reconnect advances generation and accepts replacement EOSE`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
client, client,
this, this,
@@ -304,7 +304,7 @@ class QueryInvariantTest {
@Test @Test
fun `local and remote stream preserves items through disconnect and reconnect`() = runBlocking { fun `local and remote stream preserves items through disconnect and reconnect`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
client, client,
this, this,
@@ -346,7 +346,7 @@ class QueryInvariantTest {
fun `facade close emits lifecycle failure and completes active collectors`() = runBlocking { fun `facade close emits lifecycle failure and completes active collectors`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val store = ControlledEventStore() val store = ControlledEventStore()
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this, eventVerifier = { true }) val purpleQuartz = Iolite.createForTesting(store, client, this, eventVerifier = { true })
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
val collection = collect(purpleQuartz, QuerySource.Remote(setOf(relay)), states) val collection = collect(purpleQuartz, QuerySource.Remote(setOf(relay)), states)
@@ -368,7 +368,7 @@ class QueryInvariantTest {
fun `local first completion waits for commit and final projection`() = runBlocking { fun `local first completion waits for commit and final projection`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val store = ControlledEventStore(blockInserts = true) val store = ControlledEventStore(blockInserts = true)
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this, eventVerifier = { true }) val purpleQuartz = Iolite.createForTesting(store, client, this, eventVerifier = { true })
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
val source = QuerySource.LocalAndRemote(setOf(relay), RemoteMode.OneShot(5.seconds)) val source = QuerySource.LocalAndRemote(setOf(relay), RemoteMode.OneShot(5.seconds))
val collection = collect(purpleQuartz, source, states) val collection = collect(purpleQuartz, source, states)
@@ -394,8 +394,8 @@ class QueryInvariantTest {
fun `timeout unsubscribes immediately then drains accepted persistence`() = runBlocking { fun `timeout unsubscribes immediately then drains accepted persistence`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val store = ControlledEventStore(blockInserts = true) val store = ControlledEventStore(blockInserts = true)
val config = PurpleQuartzConfig(oneShotTimeout = 100.milliseconds) val config = IoliteConfig(oneShotTimeout = 100.milliseconds)
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this, config, eventVerifier = { true }) val purpleQuartz = Iolite.createForTesting(store, client, this, config, eventVerifier = { true })
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
val source = QuerySource.Remote(setOf(relay), RemoteMode.OneShot()) val source = QuerySource.Remote(setOf(relay), RemoteMode.OneShot())
val collection = collect(purpleQuartz, source, states) val collection = collect(purpleQuartz, source, states)
@@ -426,8 +426,8 @@ class QueryInvariantTest {
fun `bounded ingestion fails closed instead of dropping EOSE`() = runBlocking { fun `bounded ingestion fails closed instead of dropping EOSE`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val store = ControlledEventStore(blockInserts = true) val store = ControlledEventStore(blockInserts = true)
val config = PurpleQuartzConfig(ingestionCapacity = 1, ingestBatchSize = 1) val config = IoliteConfig(ingestionCapacity = 1, ingestBatchSize = 1)
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this, config, eventVerifier = { true }) val purpleQuartz = Iolite.createForTesting(store, client, this, config, eventVerifier = { true })
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
val source = QuerySource.Remote(setOf(relay), RemoteMode.OneShot(5.seconds)) val source = QuerySource.Remote(setOf(relay), RemoteMode.OneShot(5.seconds))
val collection = collect(purpleQuartz, source, states) val collection = collect(purpleQuartz, source, states)
@@ -451,7 +451,7 @@ class QueryInvariantTest {
fun `remote persistence failure keeps directly emitted item visible`() = runBlocking { fun `remote persistence failure keeps directly emitted item visible`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val store = ControlledEventStore(failInserts = true) val store = ControlledEventStore(failInserts = true)
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this, eventVerifier = { true }) val purpleQuartz = Iolite.createForTesting(store, client, this, eventVerifier = { true })
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
val collection = collect(purpleQuartz, QuerySource.Remote(setOf(relay)), states) val collection = collect(purpleQuartz, QuerySource.Remote(setOf(relay)), states)
val event = signedEvent(content = "visible-before-save") val event = signedEvent(content = "visible-before-save")
@@ -472,11 +472,11 @@ class QueryInvariantTest {
val store = ControlledEventStore() val store = ControlledEventStore()
val nowMillis = 1_800_000_000_000L val nowMillis = 1_800_000_000_000L
val clock = MutableEpochClock(nowMillis) val clock = MutableEpochClock(nowMillis)
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
store, store,
ControlledClient(), ControlledClient(),
this, this,
config = PurpleQuartzConfig( config = IoliteConfig(
expirationSweepInterval = 50.milliseconds, expirationSweepInterval = 50.milliseconds,
pruneRules = mapOf(9735 to 1.hours), pruneRules = mapOf(9735 to 1.hours),
), ),
@@ -503,7 +503,7 @@ class QueryInvariantTest {
@Test @Test
fun `relay CLOSED exposes closed state and fails the query`() = runBlocking { fun `relay CLOSED exposes closed state and fails the query`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
client, client,
this, this,
@@ -527,7 +527,7 @@ class QueryInvariantTest {
fun `one closed relay does not terminate a healthy multi-relay stream`() = runBlocking { fun `one closed relay does not terminate a healthy multi-relay stream`() = runBlocking {
val otherRelay = "wss://other.example".normalizeRelayUrl() val otherRelay = "wss://other.example".normalizeRelayUrl()
val client = ControlledClient() val client = ControlledClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
client, client,
this, this,
@@ -558,7 +558,7 @@ class QueryInvariantTest {
@Test @Test
fun `subscription startup failure is classified as incompatible dependency`() = runBlocking { fun `subscription startup failure is classified as incompatible dependency`() = runBlocking {
val client = ControlledClient(failSubscribe = true) val client = ControlledClient(failSubscribe = true)
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
client, client,
this, this,
@@ -576,7 +576,7 @@ class QueryInvariantTest {
@Test @Test
fun `stream query emits every ingested event at eose`() = runBlocking { fun `stream query emits every ingested event at eose`() = runBlocking {
val client = ControlledClient() val client = ControlledClient()
val purpleQuartz = PurpleQuartz.createForTesting( val purpleQuartz = Iolite.createForTesting(
ControlledEventStore(), ControlledEventStore(),
client, client,
this, this,
@@ -604,7 +604,7 @@ class QueryInvariantTest {
} }
private fun kotlinx.coroutines.CoroutineScope.collect( private fun kotlinx.coroutines.CoroutineScope.collect(
purpleQuartz: PurpleQuartz, purpleQuartz: Iolite,
source: QuerySource, source: QuerySource,
states: MutableList<QueryState>, states: MutableList<QueryState>,
): Job = launch { ): Job = launch {
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl
@@ -1,4 +1,4 @@
package dev.zapstore.purplequartz package dev.zapstore.iolite
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient
@@ -26,7 +26,7 @@ class QueryWiringTest {
@Test @Test
fun `local query emits an empty local seed without subscribing`() = runBlocking { fun `local query emits an empty local seed without subscribing`() = runBlocking {
val client = RecordingClient() val client = RecordingClient()
val purpleQuartz = PurpleQuartz.createForTesting(MemoryEventStore(), client, this) val purpleQuartz = Iolite.createForTesting(MemoryEventStore(), client, this)
val states = purpleQuartz.query(Filter(kinds = listOf(1))).take(1).toList() val states = purpleQuartz.query(Filter(kinds = listOf(1))).take(1).toList()
@@ -39,7 +39,7 @@ class QueryWiringTest {
@Test @Test
fun `remote request uses exactly the supplied relay set`() = runBlocking { fun `remote request uses exactly the supplied relay set`() = runBlocking {
val client = RecordingClient() val client = RecordingClient()
val purpleQuartz = PurpleQuartz.createForTesting(MemoryEventStore(), client, this) val purpleQuartz = Iolite.createForTesting(MemoryEventStore(), client, this)
val relays = setOf( val relays = setOf(
"wss://one.example".normalizeRelayUrl(), "wss://one.example".normalizeRelayUrl(),
"wss://two.example".normalizeRelayUrl(), "wss://two.example".normalizeRelayUrl(),
@@ -66,7 +66,7 @@ class QueryWiringTest {
@Test @Test
fun `foreground refresh bypasses relay retry delays`() = runBlocking { fun `foreground refresh bypasses relay retry delays`() = runBlocking {
val client = RecordingClient() val client = RecordingClient()
val purpleQuartz = PurpleQuartz.createForTesting(MemoryEventStore(), client, this) val purpleQuartz = Iolite.createForTesting(MemoryEventStore(), client, this)
purpleQuartz.refreshConnections() purpleQuartz.refreshConnections()
@@ -77,7 +77,7 @@ class QueryWiringTest {
@Test @Test
fun `relay traffic toggle disconnects and reconnects the client`() = runBlocking { fun `relay traffic toggle disconnects and reconnects the client`() = runBlocking {
val client = RecordingClient() val client = RecordingClient()
val purpleQuartz = PurpleQuartz.createForTesting(MemoryEventStore(), client, this) val purpleQuartz = Iolite.createForTesting(MemoryEventStore(), client, this)
purpleQuartz.setRelayTrafficEnabled(false) purpleQuartz.setRelayTrafficEnabled(false)
purpleQuartz.setRelayTrafficEnabled(true) purpleQuartz.setRelayTrafficEnabled(true)
@@ -93,7 +93,7 @@ class QueryWiringTest {
val store = MemoryEventStore() val store = MemoryEventStore()
val event = testEvent("offline") val event = testEvent("offline")
store.insert(event) store.insert(event)
val purpleQuartz = PurpleQuartz.createForTesting(store, client, this) val purpleQuartz = Iolite.createForTesting(store, client, this)
val relay = "wss://offline.example".normalizeRelayUrl() val relay = "wss://offline.example".normalizeRelayUrl()
purpleQuartz.setRelayTrafficEnabled(false) purpleQuartz.setRelayTrafficEnabled(false)
val states = CopyOnWriteArrayList<QueryState>() val states = CopyOnWriteArrayList<QueryState>()
-1
View File
@@ -1 +0,0 @@
# PurpleQuartz requires no consumer-specific shrinking rules.
+1 -1
View File
@@ -15,4 +15,4 @@ dependencyResolutionManagement {
} }
rootProject.name = "zapstore" rootProject.name = "zapstore"
include(":purplequartz") include(":iolite")
+13 -13
View File
@@ -5,14 +5,14 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.sockets.okhttp.BasicOkHttpWebSocket import com.vitorpamplona.quartz.nip01Core.relay.sockets.okhttp.BasicOkHttpWebSocket
import dev.zapstore.purplequartz.OutboxRouter import dev.zapstore.iolite.OutboxRouter
import dev.zapstore.purplequartz.PurpleQuartz import dev.zapstore.iolite.Iolite
import dev.zapstore.purplequartz.PurpleQuartzConfig import dev.zapstore.iolite.IoliteConfig
import dev.zapstore.purplequartz.QueryOptions import dev.zapstore.iolite.QueryOptions
import dev.zapstore.purplequartz.QueryPhase import dev.zapstore.iolite.QueryPhase
import dev.zapstore.purplequartz.QueryState import dev.zapstore.iolite.QueryState
import dev.zapstore.purplequartz.RemoteMode import dev.zapstore.iolite.RemoteMode
import dev.zapstore.purplequartz.SourceMode import dev.zapstore.iolite.SourceMode
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@@ -163,14 +163,14 @@ interface CatalogRepository {
fun setRelayTrafficEnabled(enabled: Boolean) = Unit fun setRelayTrafficEnabled(enabled: Boolean) = Unit
} }
class PurpleQuartzCatalogRepository(context: Context) : CatalogRepository { class IoliteCatalogRepository(context: Context) : CatalogRepository {
private val applicationContext = context.applicationContext private val applicationContext = context.applicationContext
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private val profileFlows = ConcurrentHashMap<String, Flow<ProfileInfo?>>() private val profileFlows = ConcurrentHashMap<String, Flow<ProfileInfo?>>()
private val appAuthorFlows = ConcurrentHashMap<String, Flow<String?>>() private val appAuthorFlows = ConcurrentHashMap<String, Flow<String?>>()
private val c1AuthorFlows = ConcurrentHashMap<String, Flow<String?>>() private val c1AuthorFlows = ConcurrentHashMap<String, Flow<String?>>()
private val relaySigner = MutableStateFlow<String?>(null) private val relaySigner = MutableStateFlow<String?>(null)
private var client: PurpleQuartz? = null private var client: Iolite? = null
private var outboxRouter: OutboxRouter? = null private var outboxRouter: OutboxRouter? = null
@Volatile @Volatile
private var relayTrafficEnabled = true private var relayTrafficEnabled = true
@@ -181,12 +181,12 @@ class PurpleQuartzCatalogRepository(context: Context) : CatalogRepository {
} }
} }
private fun client(): PurpleQuartz = private fun client(): Iolite =
client ?: PurpleQuartz.create( client ?: Iolite.create(
applicationContext, applicationContext,
BasicOkHttpWebSocket.Builder { OkHttpClient() }, BasicOkHttpWebSocket.Builder { OkHttpClient() },
scope, scope,
config = PurpleQuartzConfig( config = IoliteConfig(
// Zap receipts are the unbounded firehose. Addressable catalog kinds are // Zap receipts are the unbounded firehose. Addressable catalog kinds are
// bounded by supersession; assets back the release feed and stay. // bounded by supersession; assets back the release feed and stay.
pruneRules = mapOf(Catalog.zapReceiptKind to 90.days), pruneRules = mapOf(Catalog.zapReceiptKind to 90.days),
@@ -9,8 +9,8 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import dev.zapstore.purplequartz.QueryPhase import dev.zapstore.iolite.QueryPhase
import dev.zapstore.purplequartz.RemoteMode import dev.zapstore.iolite.RemoteMode
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -7,7 +7,7 @@ import androidx.lifecycle.ProcessLifecycleOwner
class ZapstoreApplication : Application() { class ZapstoreApplication : Application() {
val catalogRepository: CatalogRepository by lazy { val catalogRepository: CatalogRepository by lazy {
PurpleQuartzCatalogRepository(this) IoliteCatalogRepository(this)
} }
override fun onCreate() { override fun onCreate() {
@@ -3,11 +3,11 @@ package dev.zapstore.app
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import dev.zapstore.purplequartz.QueryOptions import dev.zapstore.iolite.QueryOptions
import dev.zapstore.purplequartz.QueryState import dev.zapstore.iolite.QueryState
import dev.zapstore.purplequartz.QueryPhase import dev.zapstore.iolite.QueryPhase
import dev.zapstore.purplequartz.RemoteMode import dev.zapstore.iolite.RemoteMode
import dev.zapstore.purplequartz.SourceMode import dev.zapstore.iolite.SourceMode
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow