mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
Rename PurpleQuartz to Iolite.
This commit is contained in:
@@ -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
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Iolite requires no consumer-specific shrinking rules.
|
||||||
+7
-7
@@ -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 {
|
||||||
+14
-14
@@ -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,
|
||||||
+3
-3
@@ -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,
|
||||||
+3
-3
@@ -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
-1
@@ -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
|
||||||
+2
-2
@@ -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
-1
@@ -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
|
||||||
+3
-3
@@ -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)
|
||||||
+12
-12
@@ -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 {
|
||||||
+7
-7
@@ -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
|
||||||
+10
-10
@@ -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),
|
||||||
+26
-26
@@ -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
-1
@@ -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
|
||||||
+6
-6
@@ -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 +0,0 @@
|
|||||||
# PurpleQuartz requires no consumer-specific shrinking rules.
|
|
||||||
+1
-1
@@ -15,4 +15,4 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "zapstore"
|
rootProject.name = "zapstore"
|
||||||
include(":purplequartz")
|
include(":iolite")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user