diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a86fe1..3edf45d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Next version (unreleased) -PUT_CHANGELOG_HERE +- Add `ApolloStore.ALL_KEYS` to notify all watchers (#87) # Version 0.0.5 _2024-12-18_ diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.api b/normalized-cache-incubating/api/normalized-cache-incubating.api index 7b3b1474..b0ce82f6 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.api @@ -1,4 +1,5 @@ public abstract interface class com/apollographql/cache/normalized/ApolloStore { + public static final field Companion Lcom/apollographql/cache/normalized/ApolloStore$Companion; public abstract fun accessCache (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public abstract fun clearAll ()Z public abstract fun dispose ()V @@ -17,6 +18,10 @@ public abstract interface class com/apollographql/cache/normalized/ApolloStore { public abstract fun writeOptimisticUpdates (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/Operation$Data;Ljava/util/UUID;Lcom/apollographql/apollo/api/CustomScalarAdapters;)Ljava/util/Set; } +public final class com/apollographql/cache/normalized/ApolloStore$Companion { + public final fun getALL_KEYS ()Lkotlin/collections/AbstractSet; +} + public final class com/apollographql/cache/normalized/ApolloStore$DefaultImpls { public static synthetic fun readFragment$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/apollo/api/Fragment;Lcom/apollographql/cache/normalized/api/CacheKey;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;ILjava/lang/Object;)Lcom/apollographql/cache/normalized/ApolloStore$ReadResult; public static synthetic fun readOperation$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;ILjava/lang/Object;)Lcom/apollographql/cache/normalized/ApolloStore$ReadResult; diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api index 9ce76409..ffcf6097 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api @@ -85,6 +85,10 @@ abstract interface com.apollographql.cache.normalized/ApolloStore { // com.apoll final val data // com.apollographql.cache.normalized/ApolloStore.ReadResult.data|{}data[0] final fun (): #A1 // com.apollographql.cache.normalized/ApolloStore.ReadResult.data.|(){}[0] } + final object Companion { // com.apollographql.cache.normalized/ApolloStore.Companion|null[0] + final val ALL_KEYS // com.apollographql.cache.normalized/ApolloStore.Companion.ALL_KEYS|{}ALL_KEYS[0] + final fun (): kotlin.collections/AbstractSet // com.apollographql.cache.normalized/ApolloStore.Companion.ALL_KEYS.|(){}[0] + } } final class com.apollographql.cache.normalized.api/CacheControlCacheResolver : com.apollographql.cache.normalized.api/CacheResolver { // com.apollographql.cache.normalized.api/CacheControlCacheResolver|null[0] constructor (com.apollographql.cache.normalized.api/CacheResolver = ...) // com.apollographql.cache.normalized.api/CacheControlCacheResolver.|(com.apollographql.cache.normalized.api.CacheResolver){}[0] diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ApolloStore.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ApolloStore.kt index 5a8df39a..6395e463 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ApolloStore.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ApolloStore.kt @@ -37,9 +37,22 @@ import kotlin.reflect.KClass interface ApolloStore { /** * Exposes the keys of records that have changed. + * A special key [ALL_KEYS] is used to indicate that all records have changed. */ val changedKeys: SharedFlow> + companion object { + val ALL_KEYS = object : AbstractSet() { + override val size = 0 + + override fun iterator() = emptySet().iterator() + + override fun equals(other: Any?) = other === this + + override fun hashCode() = 0 + } + } + /** * Reads an operation from the store. * @@ -214,6 +227,8 @@ interface ApolloStore { /** * Publishes a set of keys that have changed. This will notify subscribers of [changedKeys]. * + * Pass [ALL_KEYS] to indicate that all records have changed, for instance after a [clearAll] operation. + * * @see changedKeys * * @param keys A set of keys of [Record] which have changed. diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultApolloStore.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultApolloStore.kt index f17402cd..2c4a8981 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultApolloStore.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/DefaultApolloStore.kt @@ -64,7 +64,7 @@ internal class DefaultApolloStore( } override suspend fun publish(keys: Set) { - if (keys.isEmpty()) { + if (keys.isEmpty() && keys !== ApolloStore.ALL_KEYS) { return } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/WatcherInterceptor.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/WatcherInterceptor.kt index f88df1d3..4e946a11 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/WatcherInterceptor.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/WatcherInterceptor.kt @@ -43,10 +43,10 @@ internal class WatcherInterceptor(val store: ApolloStore) : ApolloInterceptor, A emit(Unit) } .filter { changedKeys -> - if (changedKeys !is Set<*>) { - return@filter true - } - watchedKeys == null || changedKeys.intersect(watchedKeys!!).isNotEmpty() + changedKeys !is Set<*> || + changedKeys === ApolloStore.ALL_KEYS || + watchedKeys == null || + changedKeys.intersect(watchedKeys!!).isNotEmpty() }.map { if (it == Unit) { flowOf(ApolloResponse.Builder(request.operation, request.requestUuid).exception(WatcherSentinel).build()) diff --git a/tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt b/tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt index a6ef0506..025b1f99 100644 --- a/tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt +++ b/tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt @@ -1,5 +1,6 @@ package test +import app.cash.turbine.test import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.api.ApolloResponse import com.apollographql.apollo.api.CustomScalarAdapters @@ -642,6 +643,27 @@ class WatcherTest { job.cancel() } + @Test + fun publishAllKeys() = runTest(before = { setUp() }) { + val query = EpisodeHeroNameQuery(Episode.EMPIRE) + apolloClient.query(query) + .fetchPolicy(FetchPolicy.CacheOnly) + .watch() + .test { + // Start empty + assertIs(awaitItem().exception) + + // Add data to the cache + apolloClient.enqueueTestResponse(query, episodeHeroNameData) + apolloClient.query(query).fetchPolicy(FetchPolicy.NetworkOnly).execute() + assertEquals("R2-D2", awaitItem().data?.hero?.name) + + // Clear the cache + store.clearAll() + store.publish(ApolloStore.ALL_KEYS) + assertIs(awaitItem().exception) + } + } } internal suspend fun Channel.assertCount(count: Int) {