From 05ced58be74a29c5d9e0dac49877ddf7b9f527c6 Mon Sep 17 00:00:00 2001 From: BoD Date: Thu, 19 Dec 2024 18:41:18 +0100 Subject: [PATCH 1/4] Add ApolloStore.ALL_KEYS to notify all watchers --- .../cache/normalized/ApolloStore.kt | 15 ++++++++++++ .../normalized/internal/DefaultApolloStore.kt | 2 +- .../normalized/internal/WatcherInterceptor.kt | 8 +++---- .../src/commonTest/kotlin/WatcherTest.kt | 23 +++++++++++++++++++ 4 files changed, 43 insertions(+), 5 deletions(-) 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..64e8c33a 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 @@ -44,6 +45,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertNull +import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes class WatcherTest { @@ -642,6 +644,27 @@ class WatcherTest { job.cancel() } + @Test + fun publishAllKeys() = runTest(before = { setUp() }) { + val query = EpisodeHeroNameQuery(Episode.EMPIRE) + apolloClient.query(query) + .fetchPolicy(FetchPolicy.CacheOnly) + .watch() + .test(timeout = 3.hours) { + // 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) { From b682b113c439429e5cdb0847e8877afc0b40d4ee Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 20 Dec 2024 10:19:44 +0100 Subject: [PATCH 2/4] Update API dump --- .../api/normalized-cache-incubating.api | 5 +++++ .../api/normalized-cache-incubating.klib.api | 4 ++++ 2 files changed, 9 insertions(+) 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] From 306b96cbafcc2f5e34648209a05d1008aa30535c Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 20 Dec 2024 10:33:19 +0100 Subject: [PATCH 3/4] Update Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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_ From 9b2f497cc705b349329c19f8e4c7ee15ce38e6ee Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 20 Dec 2024 11:16:01 +0100 Subject: [PATCH 4/4] Remove debug --- tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt b/tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt index 64e8c33a..025b1f99 100644 --- a/tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt +++ b/tests/normalized-cache/src/commonTest/kotlin/WatcherTest.kt @@ -45,7 +45,6 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertNull -import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes class WatcherTest { @@ -650,7 +649,7 @@ class WatcherTest { apolloClient.query(query) .fetchPolicy(FetchPolicy.CacheOnly) .watch() - .test(timeout = 3.hours) { + .test { // Start empty assertIs(awaitItem().exception)