From 23547dcebfcb97668de7e35465858453d2c82547 Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 18 Oct 2024 15:15:45 +0200 Subject: [PATCH 1/7] - ApolloStore.readOperation/readFragment can return cache headers - CacheResolver.resolveField can return cache headers - Add STALE cache header and CacheInfo.isStale - Rename expirationDate -> staleDate --- CHANGELOG.md | 2 +- .../api/normalized-cache-incubating.api | 43 ++++--- .../api/normalized-cache-incubating.klib.api | 41 +++++-- .../cache/normalized/ApolloStore.kt | 14 ++- .../cache/normalized/ClientCacheExtensions.kt | 35 +++--- .../normalized/api/ApolloCacheHeaders.kt | 9 +- .../cache/normalized/api/CacheResolver.kt | 77 +++++++----- .../api/OperationCacheExtensions.kt | 16 --- .../cache/normalized/api/Record.kt | 12 +- .../internal/ApolloCacheInterceptor.kt | 7 +- .../normalized/internal/CacheBatchReader.kt | 57 +++++++-- .../normalized/internal/DefaultApolloStore.kt | 22 ++-- .../cache/normalized/memory/MemoryCache.kt | 10 +- .../normalized/sql/SqlNormalizedCache.kt | 12 +- .../cache/normalized/sql/TrimTest.kt | 4 +- .../ClientAndServerSideExpirationTest.kt | 116 +++++++++++++++--- .../kotlin/ClientSideExpirationTest.kt | 22 ++-- .../kotlin/ServerSideExpirationTest.kt | 10 +- 18 files changed, 346 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fed56b57..389ff768 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Next version (unreleased) -- Expiration support (see [the documentation](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/expiration.html) for details) +- Cache control support (see [the documentation](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/cache-control.html) for details) - Compatibility with the IntelliJ plugin cache viewer (#42) - For consistency, `MemoryCacheFactory` and `MemoryCache` are now in the `com.apollographql.cache.normalized.memory` package - Remove deprecated symbols diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.api b/normalized-cache-incubating/api/normalized-cache-incubating.api index 2ef5f363..bf38ac18 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.api @@ -6,8 +6,8 @@ public abstract interface class com/apollographql/cache/normalized/ApolloStore { public abstract fun getChangedKeys ()Lkotlinx/coroutines/flow/SharedFlow; public abstract fun normalize (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/Operation$Data;Lcom/apollographql/apollo/api/CustomScalarAdapters;)Ljava/util/Map; public abstract fun publish (Ljava/util/Set;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun readFragment (Lcom/apollographql/apollo/api/Fragment;Lcom/apollographql/cache/normalized/api/CacheKey;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;)Lcom/apollographql/apollo/api/Fragment$Data; - public abstract fun readOperation (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;)Lcom/apollographql/apollo/api/Operation$Data; + public abstract fun readFragment (Lcom/apollographql/apollo/api/Fragment;Lcom/apollographql/cache/normalized/api/CacheKey;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;)Lcom/apollographql/cache/normalized/ApolloStore$ReadResult; + public abstract fun readOperation (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;)Lcom/apollographql/cache/normalized/ApolloStore$ReadResult; public abstract fun remove (Lcom/apollographql/cache/normalized/api/CacheKey;Z)Z public abstract fun remove (Ljava/util/List;Z)I public abstract fun rollbackOptimisticUpdates (Ljava/util/UUID;)Ljava/util/Set; @@ -17,8 +17,8 @@ public abstract interface class com/apollographql/cache/normalized/ApolloStore { } 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/apollo/api/Fragment$Data; - 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/apollo/api/Operation$Data; + 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; public static synthetic fun remove$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/cache/normalized/api/CacheKey;ZILjava/lang/Object;)Z public static synthetic fun remove$default (Lcom/apollographql/cache/normalized/ApolloStore;Ljava/util/List;ZILjava/lang/Object;)I public static synthetic fun writeFragment$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/apollo/api/Fragment;Lcom/apollographql/cache/normalized/api/CacheKey;Lcom/apollographql/apollo/api/Fragment$Data;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;ILjava/lang/Object;)Ljava/util/Set; @@ -26,6 +26,12 @@ public final class com/apollographql/cache/normalized/ApolloStore$DefaultImpls { public static synthetic fun writeOptimisticUpdates$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/Operation$Data;Ljava/util/UUID;Lcom/apollographql/apollo/api/CustomScalarAdapters;ILjava/lang/Object;)Ljava/util/Set; } +public final class com/apollographql/cache/normalized/ApolloStore$ReadResult { + public fun (Lcom/apollographql/apollo/api/Executable$Data;Lcom/apollographql/cache/normalized/api/CacheHeaders;)V + public final fun getCacheHeaders ()Lcom/apollographql/cache/normalized/api/CacheHeaders; + public final fun getData ()Lcom/apollographql/apollo/api/Executable$Data; +} + public final class com/apollographql/cache/normalized/ApolloStoreKt { public static final fun ApolloStore (Lcom/apollographql/cache/normalized/api/NormalizedCacheFactory;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/CacheResolver;Lcom/apollographql/cache/normalized/api/RecordMerger;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;)Lcom/apollographql/cache/normalized/ApolloStore; public static synthetic fun ApolloStore$default (Lcom/apollographql/cache/normalized/api/NormalizedCacheFactory;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/CacheResolver;Lcom/apollographql/cache/normalized/api/RecordMerger;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;ILjava/lang/Object;)Lcom/apollographql/cache/normalized/ApolloStore; @@ -33,7 +39,7 @@ public final class com/apollographql/cache/normalized/ApolloStoreKt { public final class com/apollographql/cache/normalized/CacheInfo : com/apollographql/apollo/api/ExecutionContext$Element { public static final field Key Lcom/apollographql/cache/normalized/CacheInfo$Key; - public synthetic fun (JJJJZLcom/apollographql/apollo/exception/CacheMissException;Lcom/apollographql/apollo/exception/ApolloException;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (JJJJZLcom/apollographql/apollo/exception/CacheMissException;Lcom/apollographql/apollo/exception/ApolloException;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getCacheEndMillis ()J public final fun getCacheMissException ()Lcom/apollographql/apollo/exception/CacheMissException; public final fun getCacheStartMillis ()J @@ -42,6 +48,7 @@ public final class com/apollographql/cache/normalized/CacheInfo : com/apollograp public final fun getNetworkException ()Lcom/apollographql/apollo/exception/ApolloException; public final fun getNetworkStartMillis ()J public final fun isCacheHit ()Z + public final fun isStale ()Z public final fun newBuilder ()Lcom/apollographql/cache/normalized/CacheInfo$Builder; } @@ -55,6 +62,7 @@ public final class com/apollographql/cache/normalized/CacheInfo$Builder { public final fun networkEndMillis (J)Lcom/apollographql/cache/normalized/CacheInfo$Builder; public final fun networkException (Lcom/apollographql/apollo/exception/ApolloException;)Lcom/apollographql/cache/normalized/CacheInfo$Builder; public final fun networkStartMillis (J)Lcom/apollographql/cache/normalized/CacheInfo$Builder; + public final fun stale (Z)Lcom/apollographql/cache/normalized/CacheInfo$Builder; } public final class com/apollographql/cache/normalized/CacheInfo$Key : com/apollographql/apollo/api/ExecutionContext$Key { @@ -114,9 +122,9 @@ public final class com/apollographql/cache/normalized/NormalizedCache { public static final fun refetchPolicyInterceptor (Lcom/apollographql/apollo/api/MutableExecutionOptions;Lcom/apollographql/apollo/interceptor/ApolloInterceptor;)Ljava/lang/Object; public static final fun store (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/cache/normalized/ApolloStore;Z)Lcom/apollographql/apollo/ApolloClient$Builder; public static synthetic fun store$default (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/cache/normalized/ApolloStore;ZILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder; - public static final fun storeExpirationDate (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; public static final fun storePartialResponses (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; public static final fun storeReceiveDate (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; + public static final fun storeStaleDate (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; public static final fun watch (Lcom/apollographql/apollo/ApolloCall;)Lkotlinx/coroutines/flow/Flow; public static final fun watch (Lcom/apollographql/apollo/ApolloCall;Lcom/apollographql/apollo/api/Query$Data;)Lkotlinx/coroutines/flow/Flow; public static final fun writeToCacheAsynchronously (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; @@ -129,11 +137,18 @@ public final class com/apollographql/cache/normalized/VersionKt { public final class com/apollographql/cache/normalized/api/ApolloCacheHeaders { public static final field DO_NOT_STORE Ljava/lang/String; public static final field EVICT_AFTER_READ Ljava/lang/String; - public static final field EXPIRATION_DATE Ljava/lang/String; public static final field INSTANCE Lcom/apollographql/cache/normalized/api/ApolloCacheHeaders; public static final field MAX_STALE Ljava/lang/String; public static final field MEMORY_CACHE_ONLY Ljava/lang/String; public static final field RECEIVED_DATE Ljava/lang/String; + public static final field STALE Ljava/lang/String; + public static final field STALE_DATE Ljava/lang/String; +} + +public final class com/apollographql/cache/normalized/api/CacheControlCacheResolver : com/apollographql/cache/normalized/api/CacheResolver { + public fun ()V + public fun (Lcom/apollographql/cache/normalized/api/MaxAgeProvider;)V + public fun resolveField (Lcom/apollographql/cache/normalized/api/ResolverContext;)Ljava/lang/Object; } public final class com/apollographql/cache/normalized/api/CacheHeaders { @@ -199,6 +214,12 @@ public abstract interface class com/apollographql/cache/normalized/api/CacheReso public abstract fun resolveField (Lcom/apollographql/cache/normalized/api/ResolverContext;)Ljava/lang/Object; } +public final class com/apollographql/cache/normalized/api/CacheResolver$ResolvedValue { + public fun (Ljava/lang/Object;Lcom/apollographql/cache/normalized/api/CacheHeaders;)V + public final fun getCacheHeaders ()Lcom/apollographql/cache/normalized/api/CacheHeaders; + public final fun getValue ()Ljava/lang/Object; +} + public final class com/apollographql/cache/normalized/api/ConnectionEmbeddedFieldsProvider : com/apollographql/cache/normalized/api/EmbeddedFieldsProvider { public static final field Companion Lcom/apollographql/cache/normalized/api/ConnectionEmbeddedFieldsProvider$Companion; public fun (Ljava/util/Map;Ljava/util/Set;)V @@ -256,12 +277,6 @@ public final class com/apollographql/cache/normalized/api/EmptyMetadataGenerator public fun metadataForObject (Ljava/lang/Object;Lcom/apollographql/cache/normalized/api/MetadataGeneratorContext;)Ljava/util/Map; } -public final class com/apollographql/cache/normalized/api/ExpirationCacheResolver : com/apollographql/cache/normalized/api/CacheResolver { - public fun ()V - public fun (Lcom/apollographql/cache/normalized/api/MaxAgeProvider;)V - public fun resolveField (Lcom/apollographql/cache/normalized/api/ResolverContext;)Ljava/lang/Object; -} - public final class com/apollographql/cache/normalized/api/FieldKeyContext { public fun (Ljava/lang/String;Lcom/apollographql/apollo/api/CompiledField;Lcom/apollographql/apollo/api/Executable$Variables;)V public final fun getField ()Lcom/apollographql/apollo/api/CompiledField; @@ -448,8 +463,8 @@ public final class com/apollographql/cache/normalized/api/Record$Companion { } public final class com/apollographql/cache/normalized/api/RecordKt { - public static final fun expirationDate (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;)Ljava/lang/Long; public static final fun receivedDate (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;)Ljava/lang/Long; + public static final fun staleDate (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;)Ljava/lang/Long; public static final fun withDates (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/cache/normalized/api/Record; } diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api index 3cac6ab0..57c874d8 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api @@ -21,6 +21,13 @@ abstract interface com.apollographql.cache.normalized.api/CacheKeyGenerator { // } abstract interface com.apollographql.cache.normalized.api/CacheResolver { // com.apollographql.cache.normalized.api/CacheResolver|null[0] abstract fun resolveField(com.apollographql.cache.normalized.api/ResolverContext): kotlin/Any? // com.apollographql.cache.normalized.api/CacheResolver.resolveField|resolveField(com.apollographql.cache.normalized.api.ResolverContext){}[0] + final class ResolvedValue { // com.apollographql.cache.normalized.api/CacheResolver.ResolvedValue|null[0] + constructor (kotlin/Any?, com.apollographql.cache.normalized.api/CacheHeaders) // com.apollographql.cache.normalized.api/CacheResolver.ResolvedValue.|(kotlin.Any?;com.apollographql.cache.normalized.api.CacheHeaders){}[0] + final val cacheHeaders // com.apollographql.cache.normalized.api/CacheResolver.ResolvedValue.cacheHeaders|{}cacheHeaders[0] + final fun (): com.apollographql.cache.normalized.api/CacheHeaders // com.apollographql.cache.normalized.api/CacheResolver.ResolvedValue.cacheHeaders.|(){}[0] + final val value // com.apollographql.cache.normalized.api/CacheResolver.ResolvedValue.value|{}value[0] + final fun (): kotlin/Any? // com.apollographql.cache.normalized.api/CacheResolver.ResolvedValue.value.|(){}[0] + } } abstract interface com.apollographql.cache.normalized.api/EmbeddedFieldsProvider { // com.apollographql.cache.normalized.api/EmbeddedFieldsProvider|null[0] abstract fun getEmbeddedFields(com.apollographql.cache.normalized.api/EmbeddedFieldsContext): kotlin.collections/List // com.apollographql.cache.normalized.api/EmbeddedFieldsProvider.getEmbeddedFields|getEmbeddedFields(com.apollographql.cache.normalized.api.EmbeddedFieldsContext){}[0] @@ -53,10 +60,10 @@ abstract interface com.apollographql.cache.normalized.api/RecordMerger { // com. abstract fun merge(com.apollographql.cache.normalized.api/Record, com.apollographql.cache.normalized.api/Record): kotlin/Pair> // com.apollographql.cache.normalized.api/RecordMerger.merge|merge(com.apollographql.cache.normalized.api.Record;com.apollographql.cache.normalized.api.Record){}[0] } abstract interface com.apollographql.cache.normalized/ApolloStore { // com.apollographql.cache.normalized/ApolloStore|null[0] - abstract fun <#A1: com.apollographql.apollo.api/Fragment.Data> readFragment(com.apollographql.apollo.api/Fragment<#A1>, com.apollographql.cache.normalized.api/CacheKey, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): #A1 // com.apollographql.cache.normalized/ApolloStore.readFragment|readFragment(com.apollographql.apollo.api.Fragment<0:0>;com.apollographql.cache.normalized.api.CacheKey;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§}[0] + abstract fun <#A1: com.apollographql.apollo.api/Fragment.Data> readFragment(com.apollographql.apollo.api/Fragment<#A1>, com.apollographql.cache.normalized.api/CacheKey, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): com.apollographql.cache.normalized/ApolloStore.ReadResult<#A1> // com.apollographql.cache.normalized/ApolloStore.readFragment|readFragment(com.apollographql.apollo.api.Fragment<0:0>;com.apollographql.cache.normalized.api.CacheKey;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§}[0] abstract fun <#A1: com.apollographql.apollo.api/Fragment.Data> writeFragment(com.apollographql.apollo.api/Fragment<#A1>, com.apollographql.cache.normalized.api/CacheKey, #A1, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): kotlin.collections/Set // com.apollographql.cache.normalized/ApolloStore.writeFragment|writeFragment(com.apollographql.apollo.api.Fragment<0:0>;com.apollographql.cache.normalized.api.CacheKey;0:0;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§}[0] abstract fun <#A1: com.apollographql.apollo.api/Operation.Data> normalize(com.apollographql.apollo.api/Operation<#A1>, #A1, com.apollographql.apollo.api/CustomScalarAdapters): kotlin.collections/Map // com.apollographql.cache.normalized/ApolloStore.normalize|normalize(com.apollographql.apollo.api.Operation<0:0>;0:0;com.apollographql.apollo.api.CustomScalarAdapters){0§}[0] - abstract fun <#A1: com.apollographql.apollo.api/Operation.Data> readOperation(com.apollographql.apollo.api/Operation<#A1>, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): #A1 // com.apollographql.cache.normalized/ApolloStore.readOperation|readOperation(com.apollographql.apollo.api.Operation<0:0>;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§}[0] + abstract fun <#A1: com.apollographql.apollo.api/Operation.Data> readOperation(com.apollographql.apollo.api/Operation<#A1>, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): com.apollographql.cache.normalized/ApolloStore.ReadResult<#A1> // com.apollographql.cache.normalized/ApolloStore.readOperation|readOperation(com.apollographql.apollo.api.Operation<0:0>;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§}[0] abstract fun <#A1: com.apollographql.apollo.api/Operation.Data> writeOperation(com.apollographql.apollo.api/Operation<#A1>, #A1, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): kotlin.collections/Set // com.apollographql.cache.normalized/ApolloStore.writeOperation|writeOperation(com.apollographql.apollo.api.Operation<0:0>;0:0;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§}[0] abstract fun <#A1: com.apollographql.apollo.api/Operation.Data> writeOptimisticUpdates(com.apollographql.apollo.api/Operation<#A1>, #A1, com.benasher44.uuid/Uuid, com.apollographql.apollo.api/CustomScalarAdapters = ...): kotlin.collections/Set // com.apollographql.cache.normalized/ApolloStore.writeOptimisticUpdates|writeOptimisticUpdates(com.apollographql.apollo.api.Operation<0:0>;0:0;com.benasher44.uuid.Uuid;com.apollographql.apollo.api.CustomScalarAdapters){0§}[0] abstract fun <#A1: kotlin/Any?> accessCache(kotlin/Function1): #A1 // com.apollographql.cache.normalized/ApolloStore.accessCache|accessCache(kotlin.Function1){0§}[0] @@ -69,6 +76,18 @@ abstract interface com.apollographql.cache.normalized/ApolloStore { // com.apoll abstract suspend fun publish(kotlin.collections/Set) // com.apollographql.cache.normalized/ApolloStore.publish|publish(kotlin.collections.Set){}[0] abstract val changedKeys // com.apollographql.cache.normalized/ApolloStore.changedKeys|{}changedKeys[0] abstract fun (): kotlinx.coroutines.flow/SharedFlow> // com.apollographql.cache.normalized/ApolloStore.changedKeys.|(){}[0] + final class <#A1: com.apollographql.apollo.api/Executable.Data> ReadResult { // com.apollographql.cache.normalized/ApolloStore.ReadResult|null[0] + constructor (#A1, com.apollographql.cache.normalized.api/CacheHeaders) // com.apollographql.cache.normalized/ApolloStore.ReadResult.|(1:0;com.apollographql.cache.normalized.api.CacheHeaders){}[0] + final val cacheHeaders // com.apollographql.cache.normalized/ApolloStore.ReadResult.cacheHeaders|{}cacheHeaders[0] + final fun (): com.apollographql.cache.normalized.api/CacheHeaders // com.apollographql.cache.normalized/ApolloStore.ReadResult.cacheHeaders.|(){}[0] + final val data // com.apollographql.cache.normalized/ApolloStore.ReadResult.data|{}data[0] + final fun (): #A1 // com.apollographql.cache.normalized/ApolloStore.ReadResult.data.|(){}[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/CacheControlCacheResolver.|(){}[0] + constructor (com.apollographql.cache.normalized.api/MaxAgeProvider) // com.apollographql.cache.normalized.api/CacheControlCacheResolver.|(com.apollographql.cache.normalized.api.MaxAgeProvider){}[0] + final fun resolveField(com.apollographql.cache.normalized.api/ResolverContext): kotlin/Any? // com.apollographql.cache.normalized.api/CacheControlCacheResolver.resolveField|resolveField(com.apollographql.cache.normalized.api.ResolverContext){}[0] } final class com.apollographql.cache.normalized.api/CacheHeaders { // com.apollographql.cache.normalized.api/CacheHeaders|null[0] final class Builder { // com.apollographql.cache.normalized.api/CacheHeaders.Builder|null[0] @@ -129,11 +148,6 @@ final class com.apollographql.cache.normalized.api/EmbeddedFieldsContext { // co final val parentType // com.apollographql.cache.normalized.api/EmbeddedFieldsContext.parentType|{}parentType[0] final fun (): com.apollographql.apollo.api/CompiledNamedType // com.apollographql.cache.normalized.api/EmbeddedFieldsContext.parentType.|(){}[0] } -final class com.apollographql.cache.normalized.api/ExpirationCacheResolver : com.apollographql.cache.normalized.api/CacheResolver { // com.apollographql.cache.normalized.api/ExpirationCacheResolver|null[0] - constructor () // com.apollographql.cache.normalized.api/ExpirationCacheResolver.|(){}[0] - constructor (com.apollographql.cache.normalized.api/MaxAgeProvider) // com.apollographql.cache.normalized.api/ExpirationCacheResolver.|(com.apollographql.cache.normalized.api.MaxAgeProvider){}[0] - final fun resolveField(com.apollographql.cache.normalized.api/ResolverContext): kotlin/Any? // com.apollographql.cache.normalized.api/ExpirationCacheResolver.resolveField|resolveField(com.apollographql.cache.normalized.api.ResolverContext){}[0] -} final class com.apollographql.cache.normalized.api/FieldKeyContext { // com.apollographql.cache.normalized.api/FieldKeyContext|null[0] constructor (kotlin/String, com.apollographql.apollo.api/CompiledField, com.apollographql.apollo.api/Executable.Variables) // com.apollographql.cache.normalized.api/FieldKeyContext.|(kotlin.String;com.apollographql.apollo.api.CompiledField;com.apollographql.apollo.api.Executable.Variables){}[0] final val field // com.apollographql.cache.normalized.api/FieldKeyContext.field|{}field[0] @@ -273,6 +287,7 @@ final class com.apollographql.cache.normalized/CacheInfo : com.apollographql.apo final fun networkEndMillis(kotlin/Long): com.apollographql.cache.normalized/CacheInfo.Builder // com.apollographql.cache.normalized/CacheInfo.Builder.networkEndMillis|networkEndMillis(kotlin.Long){}[0] final fun networkException(com.apollographql.apollo.exception/ApolloException?): com.apollographql.cache.normalized/CacheInfo.Builder // com.apollographql.cache.normalized/CacheInfo.Builder.networkException|networkException(com.apollographql.apollo.exception.ApolloException?){}[0] final fun networkStartMillis(kotlin/Long): com.apollographql.cache.normalized/CacheInfo.Builder // com.apollographql.cache.normalized/CacheInfo.Builder.networkStartMillis|networkStartMillis(kotlin.Long){}[0] + final fun stale(kotlin/Boolean): com.apollographql.cache.normalized/CacheInfo.Builder // com.apollographql.cache.normalized/CacheInfo.Builder.stale|stale(kotlin.Boolean){}[0] } final fun newBuilder(): com.apollographql.cache.normalized/CacheInfo.Builder // com.apollographql.cache.normalized/CacheInfo.newBuilder|newBuilder(){}[0] final object Key : com.apollographql.apollo.api/ExecutionContext.Key // com.apollographql.cache.normalized/CacheInfo.Key|null[0] @@ -284,6 +299,8 @@ final class com.apollographql.cache.normalized/CacheInfo : com.apollographql.apo final fun (): kotlin/Long // com.apollographql.cache.normalized/CacheInfo.cacheStartMillis.|(){}[0] final val isCacheHit // com.apollographql.cache.normalized/CacheInfo.isCacheHit|{}isCacheHit[0] final fun (): kotlin/Boolean // com.apollographql.cache.normalized/CacheInfo.isCacheHit.|(){}[0] + final val isStale // com.apollographql.cache.normalized/CacheInfo.isStale|{}isStale[0] + final fun (): kotlin/Boolean // com.apollographql.cache.normalized/CacheInfo.isStale.|(){}[0] final val key // com.apollographql.cache.normalized/CacheInfo.key|{}key[0] final fun (): com.apollographql.apollo.api/ExecutionContext.Key<*> // com.apollographql.cache.normalized/CacheInfo.key.|(){}[0] final val networkEndMillis // com.apollographql.cache.normalized/CacheInfo.networkEndMillis|{}networkEndMillis[0] @@ -313,8 +330,8 @@ final enum class com.apollographql.cache.normalized/FetchPolicy : kotlin/Enum = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/logCacheMisses|logCacheMisses@com.apollographql.apollo.ApolloClient.Builder(kotlin.Function1){}[0] final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/normalizedCache(com.apollographql.cache.normalized.api/NormalizedCacheFactory, com.apollographql.cache.normalized.api/CacheKeyGenerator = ..., com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/CacheResolver = ..., com.apollographql.cache.normalized.api/RecordMerger = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ..., kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/normalizedCache|normalizedCache@com.apollographql.apollo.ApolloClient.Builder(com.apollographql.cache.normalized.api.NormalizedCacheFactory;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.RecordMerger;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider;kotlin.Boolean){}[0] final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/store(com.apollographql.cache.normalized/ApolloStore, kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/store|store@com.apollographql.apollo.ApolloClient.Builder(com.apollographql.cache.normalized.ApolloStore;kotlin.Boolean){}[0] -final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/expirationDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/expirationDate|expirationDate@com.apollographql.cache.normalized.api.Record(kotlin.String){}[0] final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/receivedDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/receivedDate|receivedDate@com.apollographql.cache.normalized.api.Record(kotlin.String){}[0] +final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/staleDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/staleDate|staleDate@com.apollographql.cache.normalized.api.Record(kotlin.String){}[0] final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/withDates(kotlin/String?, kotlin/String?): com.apollographql.cache.normalized.api/Record // com.apollographql.cache.normalized.api/withDates|withDates@com.apollographql.cache.normalized.api.Record(kotlin.String?;kotlin.String?){}[0] final fun (kotlin.collections/Collection?).com.apollographql.cache.normalized.api/dependentKeys(): kotlin.collections/Set // com.apollographql.cache.normalized.api/dependentKeys|dependentKeys@kotlin.collections.Collection?(){}[0] final fun <#A: com.apollographql.apollo.api/Executable.Data> (com.apollographql.apollo.api/Executable<#A>).com.apollographql.cache.normalized.api/normalize(#A, com.apollographql.apollo.api/CustomScalarAdapters, com.apollographql.cache.normalized.api/CacheKeyGenerator, com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ..., kotlin/String): kotlin.collections/Map // com.apollographql.cache.normalized.api/normalize|normalize@com.apollographql.apollo.api.Executable<0:0>(0:0;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider;kotlin.String){0§}[0] @@ -335,9 +352,9 @@ final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOption final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/memoryCacheOnly(kotlin/Boolean): #A // com.apollographql.cache.normalized/memoryCacheOnly|memoryCacheOnly@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/refetchPolicy(com.apollographql.cache.normalized/FetchPolicy): #A // com.apollographql.cache.normalized/refetchPolicy|refetchPolicy@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(com.apollographql.cache.normalized.FetchPolicy){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/refetchPolicyInterceptor(com.apollographql.apollo.interceptor/ApolloInterceptor): #A // com.apollographql.cache.normalized/refetchPolicyInterceptor|refetchPolicyInterceptor@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(com.apollographql.apollo.interceptor.ApolloInterceptor){0§}[0] -final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/storeExpirationDate(kotlin/Boolean): #A // com.apollographql.cache.normalized/storeExpirationDate|storeExpirationDate@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/storePartialResponses(kotlin/Boolean): #A // com.apollographql.cache.normalized/storePartialResponses|storePartialResponses@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/storeReceiveDate(kotlin/Boolean): #A // com.apollographql.cache.normalized/storeReceiveDate|storeReceiveDate@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] +final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/storeStaleDate(kotlin/Boolean): #A // com.apollographql.cache.normalized/storeStaleDate|storeStaleDate@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/writeToCacheAsynchronously(kotlin/Boolean): #A // com.apollographql.cache.normalized/writeToCacheAsynchronously|writeToCacheAsynchronously@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun com.apollographql.cache.normalized/ApolloStore(com.apollographql.cache.normalized.api/NormalizedCacheFactory, com.apollographql.cache.normalized.api/CacheKeyGenerator = ..., com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/CacheResolver = ..., com.apollographql.cache.normalized.api/RecordMerger = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ...): com.apollographql.cache.normalized/ApolloStore // com.apollographql.cache.normalized/ApolloStore|ApolloStore(com.apollographql.cache.normalized.api.NormalizedCacheFactory;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.RecordMerger;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider){}[0] final object com.apollographql.cache.normalized.api/ApolloCacheHeaders { // com.apollographql.cache.normalized.api/ApolloCacheHeaders|null[0] @@ -345,14 +362,16 @@ final object com.apollographql.cache.normalized.api/ApolloCacheHeaders { // com. final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.DO_NOT_STORE.|(){}[0] final const val EVICT_AFTER_READ // com.apollographql.cache.normalized.api/ApolloCacheHeaders.EVICT_AFTER_READ|{}EVICT_AFTER_READ[0] final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.EVICT_AFTER_READ.|(){}[0] - final const val EXPIRATION_DATE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.EXPIRATION_DATE|{}EXPIRATION_DATE[0] - final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.EXPIRATION_DATE.|(){}[0] final const val MAX_STALE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.MAX_STALE|{}MAX_STALE[0] final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.MAX_STALE.|(){}[0] final const val MEMORY_CACHE_ONLY // com.apollographql.cache.normalized.api/ApolloCacheHeaders.MEMORY_CACHE_ONLY|{}MEMORY_CACHE_ONLY[0] final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.MEMORY_CACHE_ONLY.|(){}[0] final const val RECEIVED_DATE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.RECEIVED_DATE|{}RECEIVED_DATE[0] final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.RECEIVED_DATE.|(){}[0] + final const val STALE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.STALE|{}STALE[0] + final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.STALE.|(){}[0] + final const val STALE_DATE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.STALE_DATE|{}STALE_DATE[0] + final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.STALE_DATE.|(){}[0] } final object com.apollographql.cache.normalized.api/DefaultCacheResolver : com.apollographql.cache.normalized.api/CacheResolver { // com.apollographql.cache.normalized.api/DefaultCacheResolver|null[0] final fun resolveField(com.apollographql.cache.normalized.api/ResolverContext): kotlin/Any? // com.apollographql.cache.normalized.api/DefaultCacheResolver.resolveField|resolveField(com.apollographql.cache.normalized.api.ResolverContext){}[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 901752ad..4c5e1bba 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 @@ -1,6 +1,7 @@ package com.apollographql.cache.normalized import com.apollographql.apollo.api.CustomScalarAdapters +import com.apollographql.apollo.api.Executable import com.apollographql.apollo.api.Fragment import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.json.JsonNumber @@ -48,13 +49,13 @@ interface ApolloStore { * @throws [com.apollographql.apollo.exception.CacheMissException] on cache miss * @throws [com.apollographql.apollo.exception.ApolloException] on other cache read errors * - * @return the operation data + * @return the operation data with optional headers from the [NormalizedCache] */ fun readOperation( operation: Operation, customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty, cacheHeaders: CacheHeaders = CacheHeaders.NONE, - ): D + ): ReadResult /** * Read a GraphQL fragment from the store. @@ -66,14 +67,14 @@ interface ApolloStore { * @throws [com.apollographql.apollo.exception.CacheMissException] on cache miss * @throws [com.apollographql.apollo.exception.ApolloException] on other cache read errors * - * @return the fragment data + * @return the fragment data with optional headers from the [NormalizedCache] */ fun readFragment( fragment: Fragment, cacheKey: CacheKey, customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty, cacheHeaders: CacheHeaders = CacheHeaders.NONE, - ): D + ): ReadResult /** * Write an operation data to the store. @@ -200,6 +201,11 @@ interface ApolloStore { * Release resources associated with this store. */ fun dispose() + + class ReadResult( + val data: D, + val cacheHeaders: CacheHeaders, + ) } fun ApolloStore( diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt index 28eb8b25..fef97f28 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt @@ -273,28 +273,28 @@ fun MutableExecutionOptions.storeReceiveDate(storeReceiveDate: Boolean) = ) /** - * @param storeExpirationDate Whether to store the expiration date in the cache. + * @param storeStaleDate Whether to store the stale date in the cache. * - * The expiration date is computed from the response HTTP headers + * The stale date is computed from the response HTTP headers * * Default: false */ -fun MutableExecutionOptions.storeExpirationDate(storeExpirationDate: Boolean): T { - addExecutionContext(StoreExpirationDateContext(storeExpirationDate)) +fun MutableExecutionOptions.storeStaleDate(storeStaleDate: Boolean): T { + addExecutionContext(StoreStaleDateContext(storeStaleDate)) if (this is ApolloClient.Builder) { - check(interceptors.none { it is StoreExpirationInterceptor }) { - "Apollo: storeExpirationDate() can only be called once on ApolloClient.Builder()" + check(interceptors.none { it is StoreStaleDateInterceptor }) { + "Apollo: storeStaleDate() can only be called once on ApolloClient.Builder()" } - addInterceptor(StoreExpirationInterceptor()) + addInterceptor(StoreStaleDateInterceptor()) } @Suppress("UNCHECKED_CAST") return this as T } -private class StoreExpirationInterceptor : ApolloInterceptor { +private class StoreStaleDateInterceptor : ApolloInterceptor { override fun intercept(request: ApolloRequest, chain: ApolloInterceptorChain): Flow> { return chain.proceed(request).map { - val store = request.executionContext[StoreExpirationDateContext]?.value + val store = request.executionContext[StoreStaleDateContext]?.value if (store != true) { return@map it } @@ -312,7 +312,7 @@ private class StoreExpirationInterceptor : ApolloInterceptor { }.firstOrNull() ?: return@map it val age = headers.get("age")?.toIntOrNull() - val expires = if (age != null) { + val staleDate = if (age != null) { currentTimeMillis() / 1000 + maxAge - age } else { currentTimeMillis() / 1000 + maxAge @@ -321,7 +321,7 @@ private class StoreExpirationInterceptor : ApolloInterceptor { return@map it.newBuilder() .cacheHeaders( it.cacheHeaders.newBuilder() - .addHeader(ApolloCacheHeaders.EXPIRATION_DATE, expires.toString()) + .addHeader(ApolloCacheHeaders.STALE_DATE, staleDate.toString()) .build() ) .build() @@ -418,6 +418,7 @@ class CacheInfo private constructor( val isCacheHit: Boolean, val cacheMissException: CacheMissException?, val networkException: ApolloException?, + val isStale: Boolean, ) : ExecutionContext.Element { override val key: ExecutionContext.Key<*> get() = Key @@ -432,6 +433,7 @@ class CacheInfo private constructor( .cacheHit(isCacheHit) .cacheMissException(cacheMissException) .networkException(networkException) + .stale(isStale) } class Builder { @@ -442,6 +444,7 @@ class CacheInfo private constructor( private var cacheHit: Boolean = false private var cacheMissException: CacheMissException? = null private var networkException: ApolloException? = null + private var stale: Boolean = false fun cacheStartMillis(cacheStartMillis: Long) = apply { this.cacheStartMillis = cacheStartMillis @@ -471,6 +474,9 @@ class CacheInfo private constructor( this.networkException = networkException } + fun stale(stale: Boolean) = apply { + this.stale = stale + } fun build(): CacheInfo = CacheInfo( cacheStartMillis = cacheStartMillis, @@ -479,7 +485,8 @@ class CacheInfo private constructor( networkEndMillis = networkEndMillis, isCacheHit = cacheHit, cacheMissException = cacheMissException, - networkException = networkException + networkException = networkException, + isStale = stale, ) } } @@ -545,11 +552,11 @@ internal class StoreReceiveDateContext(val value: Boolean) : ExecutionContext.El companion object Key : ExecutionContext.Key } -internal class StoreExpirationDateContext(val value: Boolean) : ExecutionContext.Element { +internal class StoreStaleDateContext(val value: Boolean) : ExecutionContext.Element { override val key: ExecutionContext.Key<*> get() = Key - companion object Key : ExecutionContext.Key + companion object Key : ExecutionContext.Key } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ApolloCacheHeaders.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ApolloCacheHeaders.kt index c7cc29ce..d988b886 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ApolloCacheHeaders.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ApolloCacheHeaders.kt @@ -25,12 +25,17 @@ object ApolloCacheHeaders { const val RECEIVED_DATE = "apollo-received-date" /** - * The value of this header will be stored in the [Record]'s expiration date. + * The value of this header will be stored in the [Record]'s stale date. */ - const val EXPIRATION_DATE = "apollo-expiration-date" + const val STALE_DATE = "apollo-stale-date" /** * How long to accept stale fields */ const val MAX_STALE = "apollo-max-stale" + + /** + * True if the returned data is considered stale + */ + const val STALE = "apollo-stale" } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt index 07e0e851..9dd777d2 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt @@ -5,9 +5,10 @@ import com.apollographql.apollo.api.Executable import com.apollographql.apollo.api.MutableExecutionOptions import com.apollographql.apollo.exception.CacheMissException import com.apollographql.apollo.mpp.currentTimeMillis +import com.apollographql.cache.normalized.api.CacheResolver.ResolvedValue import com.apollographql.cache.normalized.maxStale -import com.apollographql.cache.normalized.storeExpirationDate import com.apollographql.cache.normalized.storeReceiveDate +import com.apollographql.cache.normalized.storeStaleDate import kotlin.jvm.JvmSuppressWildcards import kotlin.time.Duration @@ -68,9 +69,14 @@ interface CacheResolver { * @param context the field to resolve and associated information to resolve it * * @return a value that can go in a [Record]. No type checking is done. It is the responsibility of implementations to return the correct - * type + * type. The value can be wrapped in a [ResolvedValue] to provide additional information. */ fun resolveField(context: ResolverContext): Any? + + class ResolvedValue( + val value: Any?, + val cacheHeaders: CacheHeaders, + ) } class ResolverContext( @@ -133,63 +139,74 @@ object DefaultCacheResolver : CacheResolver { /** * A cache resolver that raises a cache miss if the field's received date is older than its max age - * (configurable via [maxAgeProvider]) or its expiration date has passed. + * (configurable via [maxAgeProvider]) or its stale date has passed. * * Received dates are stored by calling `storeReceiveDate(true)` on your `ApolloClient`. * - * Expiration dates are stored by calling `storeExpirationDate(true)` on your `ApolloClient`. + * Stale dates are stored by calling `storeStaleDate(true)` on your `ApolloClient`. * * A maximum staleness can be configured via the [ApolloCacheHeaders.MAX_STALE] cache header. * * @see MutableExecutionOptions.storeReceiveDate - * @see MutableExecutionOptions.storeExpirationDate + * @see MutableExecutionOptions.storeStaleDate * @see MutableExecutionOptions.maxStale */ -class ExpirationCacheResolver( +class CacheControlCacheResolver( private val maxAgeProvider: MaxAgeProvider, ) : CacheResolver { /** - * Creates a new [ExpirationCacheResolver] with no max ages. Use this constructor if you want to consider only the expiration dates. + * Creates a new [CacheControlCacheResolver] with no max ages. Use this constructor if you want to consider only the stale dates. */ constructor() : this(maxAgeProvider = GlobalMaxAgeProvider(Duration.INFINITE)) override fun resolveField(context: ResolverContext): Any? { - val resolvedField = FieldPolicyCacheResolver.resolveField(context) + var isStale = false if (context.parent is Record) { val field = context.field - val maxStale = context.cacheHeaders.headerValue(ApolloCacheHeaders.MAX_STALE)?.toLongOrNull() ?: 0L - val currentDate = currentTimeMillis() / 1000 - - // Consider the field's max age (client side) - val fieldMaxAge = maxAgeProvider.getMaxAge(MaxAgeContext(context.path)).inWholeSeconds - val fieldReceivedDate = context.parent.receivedDate(field.name) - if (fieldReceivedDate != null) { - val fieldAge = currentDate - fieldReceivedDate - val stale = fieldAge - fieldMaxAge - if (stale >= maxStale) { + val receivedDate = context.parent.receivedDate(field.name) + // Consider the client controlled max age + if (receivedDate != null) { + val currentDate = currentTimeMillis() / 1000 + val age = currentDate - receivedDate + val maxAge = maxAgeProvider.getMaxAge(MaxAgeContext(context.path)).inWholeSeconds + val staleDuration = age - maxAge + val maxStale = context.cacheHeaders.headerValue(ApolloCacheHeaders.MAX_STALE)?.toLongOrNull() ?: 0L + if (staleDuration >= maxStale) { throw CacheMissException( - context.parentKey, - context.fieldKeyGenerator.getFieldKey(FieldKeyContext(context.parentType, context.field, context.variables)), - true + key = context.parentKey, + fieldName = context.fieldKeyGenerator.getFieldKey(FieldKeyContext(context.parentType, context.field, context.variables)), + stale = true ) } + if (staleDuration >= 0) isStale = true } - // Consider the field's expiration date (server side) - val fieldExpirationDate = context.parent.expirationDate(field.name) - if (fieldExpirationDate != null) { - val stale = currentDate - fieldExpirationDate - if (stale >= maxStale) { + // Consider the server controlled max age + val staleDate = context.parent.staleDate(field.name) + if (staleDate != null) { + val currentDate = currentTimeMillis() / 1000 + val staleDuration = currentDate - staleDate + val maxStale = context.cacheHeaders.headerValue(ApolloCacheHeaders.MAX_STALE)?.toLongOrNull() ?: 0L + if (staleDuration >= maxStale) { throw CacheMissException( - context.parentKey, - context.fieldKeyGenerator.getFieldKey(FieldKeyContext(context.parentType, context.field, context.variables)), - true + key = context.parentKey, + fieldName = context.fieldKeyGenerator.getFieldKey(FieldKeyContext(context.parentType, context.field, context.variables)), + stale = true ) } + if (staleDuration >= 0) isStale = true } } - return resolvedField + val value = FieldPolicyCacheResolver.resolveField(context) + return if (isStale) { + ResolvedValue( + value = value, + cacheHeaders = CacheHeaders.Builder().addHeader(ApolloCacheHeaders.STALE, "true").build(), + ) + } else { + value + } } } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/OperationCacheExtensions.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/OperationCacheExtensions.kt index d4acacf3..6741e464 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/OperationCacheExtensions.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/OperationCacheExtensions.kt @@ -1,10 +1,8 @@ package com.apollographql.cache.normalized.api -import com.apollographql.apollo.api.Adapter import com.apollographql.apollo.api.CustomScalarAdapters import com.apollographql.apollo.api.Executable import com.apollographql.apollo.api.Operation -import com.apollographql.apollo.api.json.MapJsonReader import com.apollographql.apollo.api.json.MapJsonWriter import com.apollographql.apollo.api.variables import com.apollographql.cache.normalized.internal.CacheBatchReader @@ -120,17 +118,3 @@ fun Collection?.dependentKeys(): Set { it.fieldKeys() }?.toSet() ?: emptySet() } - -internal fun CacheBatchReaderData.toData( - adapter: Adapter, - customScalarAdapters: CustomScalarAdapters, - variables: Executable.Variables, -): D { - val reader = MapJsonReader( - root = toMap(), - ) - - return adapter.fromJson(reader, customScalarAdapters.newBuilder().falseVariables(variables.valueMap.filter { it.value == false }.keys) - .build() - ) -} diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt index aca06061..995fb26c 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt @@ -1,5 +1,6 @@ package com.apollographql.cache.normalized.api +import com.apollographql.apollo.annotations.ApolloInternal import com.apollographql.apollo.api.json.ApolloJsonElement import com.apollographql.cache.normalized.internal.RecordWeigher.calculateBytes import com.benasher44.uuid.Uuid @@ -87,8 +88,9 @@ class Record( } } -fun Record.withDates(receivedDate: String?, expirationDate: String?): Record { - if (receivedDate == null && expirationDate == null) { +@ApolloInternal +fun Record.withDates(receivedDate: String?, staleDate: String?): Record { + if (receivedDate == null && staleDate == null) { return this } return Record( @@ -100,8 +102,8 @@ fun Record.withDates(receivedDate: String?, expirationDate: String?): Record { receivedDate?.let { put(ApolloCacheHeaders.RECEIVED_DATE, it.toLong()) } - expirationDate?.let { - put(ApolloCacheHeaders.EXPIRATION_DATE, it.toLong()) + staleDate?.let { + put(ApolloCacheHeaders.STALE_DATE, it.toLong()) } } } @@ -110,7 +112,7 @@ fun Record.withDates(receivedDate: String?, expirationDate: String?): Record { fun Record.receivedDate(field: String) = metadata[field]?.get(ApolloCacheHeaders.RECEIVED_DATE) as? Long -fun Record.expirationDate(field: String) = metadata[field]?.get(ApolloCacheHeaders.EXPIRATION_DATE) as? Long +fun Record.staleDate(field: String) = metadata[field]?.get(ApolloCacheHeaders.STALE_DATE) as? Long /** diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/ApolloCacheInterceptor.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/ApolloCacheInterceptor.kt index 10690708..531a6197 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/ApolloCacheInterceptor.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/ApolloCacheInterceptor.kt @@ -208,7 +208,7 @@ internal class ApolloCacheInterceptor( val operation = request.operation val startMillis = currentTimeMillis() - val data = try { + val readResult = try { var cacheHeaders = request.cacheHeaders if (request.memoryCacheOnly) { cacheHeaders += CacheHeaders.Builder().addHeader(ApolloCacheHeaders.MEMORY_CACHE_ONLY, "true").build() @@ -231,6 +231,7 @@ internal class ApolloCacheInterceptor( .cacheEndMillis(currentTimeMillis()) .cacheHit(false) .cacheMissException(e) + .stale(e.stale) .build() ) .isLast(true) @@ -241,13 +242,15 @@ internal class ApolloCacheInterceptor( requestUuid = request.requestUuid, operation = operation, ) - .data(data) + .data(readResult.data) .addExecutionContext(request.executionContext) + .cacheHeaders(readResult.cacheHeaders) .cacheInfo( CacheInfo.Builder() .cacheStartMillis(startMillis) .cacheEndMillis(currentTimeMillis()) .cacheHit(true) + .stale(readResult.cacheHeaders.headerValue(ApolloCacheHeaders.STALE) == "true") .build() ) .isLast(true) diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/CacheBatchReader.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/CacheBatchReader.kt index bf362492..3e239af8 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/CacheBatchReader.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/CacheBatchReader.kt @@ -1,10 +1,14 @@ package com.apollographql.cache.normalized.internal +import com.apollographql.apollo.api.Adapter import com.apollographql.apollo.api.CompiledField import com.apollographql.apollo.api.CompiledFragment import com.apollographql.apollo.api.CompiledSelection +import com.apollographql.apollo.api.CustomScalarAdapters import com.apollographql.apollo.api.Executable +import com.apollographql.apollo.api.json.MapJsonReader import com.apollographql.apollo.exception.CacheMissException +import com.apollographql.cache.normalized.api.ApolloCacheHeaders import com.apollographql.cache.normalized.api.CacheHeaders import com.apollographql.cache.normalized.api.CacheKey import com.apollographql.cache.normalized.api.CacheResolver @@ -15,10 +19,8 @@ import com.apollographql.cache.normalized.api.ResolverContext import kotlin.jvm.JvmSuppressWildcards /** - * A resolver that solves the "N+1" problem by batching all SQL queries at a given depth - * It respects skip/include directives - * - * Returns the data in [toMap] + * A resolver that solves the "N+1" problem by batching all SQL queries at a given depth. + * It respects skip/include directives. */ internal class CacheBatchReader( private val cache: ReadOnlyNormalizedCache, @@ -48,6 +50,11 @@ internal class CacheBatchReader( */ private val data = mutableMapOf, Map>() + /** + * True if at least one of the resolved fields is stale + */ + private var isStale = false + private val pendingReferences = mutableListOf() private class CollectState(val variables: Executable.Variables) { @@ -132,7 +139,7 @@ internal class CacheBatchReader( fieldKeyGenerator = fieldKeyGenerator, path = pendingReference.fieldPath + it, ) - ) + ).unwrap() value.registerCacheKeys(pendingReference.path + it.responseName, pendingReference.fieldPath + it, it.selections, it.type.rawType().name) it.responseName to value @@ -142,7 +149,22 @@ internal class CacheBatchReader( } } - return CacheBatchReaderData(data) + return CacheBatchReaderData(data, CacheHeaders.Builder().apply { if (isStale) addHeader(ApolloCacheHeaders.STALE, "true") }.build()) + } + + private fun Any?.unwrap(): Any? { + return when (this) { + is CacheResolver.ResolvedValue -> { + if (cacheHeaders.headerValue(ApolloCacheHeaders.STALE) == "true") { + isStale = true + } + this.value + } + + else -> { + this + } + } } /** @@ -193,20 +215,31 @@ internal class CacheBatchReader( fieldKeyGenerator = fieldKeyGenerator, path = fieldPath + it, ) - ) + ).unwrap() value.registerCacheKeys(path + it.responseName, fieldPath + it, it.selections, it.type.rawType().name) - - it.responseName to value - }.toMap() + } } } } - internal data class CacheBatchReaderData( + internal class CacheBatchReaderData( private val data: Map, Map>, + val cacheHeaders: CacheHeaders, ) { + fun toData( + adapter: Adapter, + customScalarAdapters: CustomScalarAdapters, + variables: Executable.Variables, + ): D { + val reader = MapJsonReader(toMap()) + return adapter.fromJson( + reader, + customScalarAdapters.newBuilder().falseVariables(variables.valueMap.filter { it.value == false }.keys).build() + ) + } + @Suppress("UNCHECKED_CAST") - fun toMap(): Map { + private fun toMap(): Map { return data[emptyList()].replaceCacheKeys(emptyList()) as Map } 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 6235f3e4..9312d9bc 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 @@ -5,6 +5,7 @@ import com.apollographql.apollo.api.Fragment import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.variables import com.apollographql.cache.normalized.ApolloStore +import com.apollographql.cache.normalized.ApolloStore.ReadResult import com.apollographql.cache.normalized.api.CacheHeaders import com.apollographql.cache.normalized.api.CacheKey import com.apollographql.cache.normalized.api.CacheKeyGenerator @@ -18,7 +19,6 @@ import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.RecordMerger import com.apollographql.cache.normalized.api.normalize import com.apollographql.cache.normalized.api.readDataFromCacheInternal -import com.apollographql.cache.normalized.api.toData import com.benasher44.uuid.Uuid import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow @@ -115,16 +115,20 @@ internal class DefaultApolloStore( operation: Operation, customScalarAdapters: CustomScalarAdapters, cacheHeaders: CacheHeaders, - ): D { + ): ReadResult { val variables = operation.variables(customScalarAdapters, true) - return operation.readDataFromCacheInternal( + val batchReaderData = operation.readDataFromCacheInternal( cache = cache, cacheResolver = cacheResolver, cacheHeaders = cacheHeaders, cacheKey = CacheKey.rootKey(), variables = variables, fieldKeyGenerator = fieldKeyGenerator, - ).toData(operation.adapter(), customScalarAdapters, variables) + ) + return ReadResult( + data = batchReaderData.toData(operation.adapter(), customScalarAdapters, variables), + cacheHeaders = batchReaderData.cacheHeaders, + ) } override fun readFragment( @@ -132,17 +136,21 @@ internal class DefaultApolloStore( cacheKey: CacheKey, customScalarAdapters: CustomScalarAdapters, cacheHeaders: CacheHeaders, - ): D { + ): ReadResult { val variables = fragment.variables(customScalarAdapters, true) - return fragment.readDataFromCacheInternal( + val batchReaderData = fragment.readDataFromCacheInternal( cache = cache, cacheResolver = cacheResolver, cacheHeaders = cacheHeaders, cacheKey = cacheKey, variables = variables, fieldKeyGenerator = fieldKeyGenerator, - ).toData(fragment.adapter(), customScalarAdapters, variables) + ) + return ReadResult( + data = batchReaderData.toData(fragment.adapter(), customScalarAdapters, variables), + cacheHeaders = batchReaderData.cacheHeaders, + ) } override fun accessCache(block: (NormalizedCache) -> R): R { diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt index 6f80f6e0..d0e3f7cf 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt @@ -7,10 +7,10 @@ import com.apollographql.cache.normalized.api.NormalizedCache import com.apollographql.cache.normalized.api.NormalizedCacheFactory import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.RecordMerger +import com.apollographql.cache.normalized.api.withDates import com.apollographql.cache.normalized.internal.Lock -import com.apollographql.cache.normalized.memory.internal.LruCache import com.apollographql.cache.normalized.internal.patternToRegex -import com.apollographql.cache.normalized.api.withDates +import com.apollographql.cache.normalized.memory.internal.LruCache import kotlin.jvm.JvmOverloads import kotlin.reflect.KClass @@ -132,14 +132,14 @@ class MemoryCache( private fun internalMerge(record: Record, cacheHeaders: CacheHeaders, recordMerger: RecordMerger): Set { val receivedDate = cacheHeaders.headerValue(ApolloCacheHeaders.RECEIVED_DATE) - val expirationDate = cacheHeaders.headerValue(ApolloCacheHeaders.EXPIRATION_DATE) + val staleDate = cacheHeaders.headerValue(ApolloCacheHeaders.STALE_DATE) val oldRecord = loadRecord(record.key, cacheHeaders) val changedKeys = if (oldRecord == null) { - lruCache[record.key] = record.withDates(receivedDate = receivedDate, expirationDate = expirationDate) + lruCache[record.key] = record.withDates(receivedDate = receivedDate, staleDate = staleDate) record.fieldKeys() } else { val (mergedRecord, changedKeys) = recordMerger.merge(existing = oldRecord, incoming = record) - lruCache[record.key] = mergedRecord.withDates(receivedDate = receivedDate, expirationDate = expirationDate) + lruCache[record.key] = mergedRecord.withDates(receivedDate = receivedDate, staleDate = staleDate) changedKeys } return changedKeys diff --git a/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt b/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt index feb4c2c6..788b94c0 100644 --- a/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt +++ b/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt @@ -149,7 +149,7 @@ class SqlNormalizedCache internal constructor( private fun internalUpdateRecords(records: Collection, cacheHeaders: CacheHeaders, recordMerger: RecordMerger): Set { var updatedRecordKeys: Set = emptySet() val receivedDate = cacheHeaders.headerValue(ApolloCacheHeaders.RECEIVED_DATE) - val expirationDate = cacheHeaders.headerValue(ApolloCacheHeaders.EXPIRATION_DATE) + val staleDate = cacheHeaders.headerValue(ApolloCacheHeaders.STALE_DATE) recordDatabase.transaction { val oldRecords = internalGetRecords( keys = records.map { it.key }, @@ -158,12 +158,12 @@ class SqlNormalizedCache internal constructor( updatedRecordKeys = records.flatMap { record -> val oldRecord = oldRecords[record.key] if (oldRecord == null) { - recordDatabase.insert(record.withDates(receivedDate = receivedDate, expirationDate = expirationDate)) + recordDatabase.insert(record.withDates(receivedDate = receivedDate, staleDate = staleDate)) record.fieldKeys() } else { val (mergedRecord, changedKeys) = recordMerger.merge(existing = oldRecord, incoming = record) if (mergedRecord.isNotEmpty()) { - recordDatabase.update(mergedRecord.withDates(receivedDate = receivedDate, expirationDate = expirationDate)) + recordDatabase.update(mergedRecord.withDates(receivedDate = receivedDate, staleDate = staleDate)) } changedKeys } @@ -177,16 +177,16 @@ class SqlNormalizedCache internal constructor( */ private fun internalUpdateRecord(record: Record, cacheHeaders: CacheHeaders, recordMerger: RecordMerger): Set { val receivedDate = cacheHeaders.headerValue(ApolloCacheHeaders.RECEIVED_DATE) - val expirationDate = cacheHeaders.headerValue(ApolloCacheHeaders.EXPIRATION_DATE) + val staleDate = cacheHeaders.headerValue(ApolloCacheHeaders.STALE_DATE) return recordDatabase.transaction { val oldRecord = recordDatabase.select(record.key) if (oldRecord == null) { - recordDatabase.insert(record.withDates(receivedDate = receivedDate, expirationDate = expirationDate)) + recordDatabase.insert(record.withDates(receivedDate = receivedDate, staleDate = staleDate)) record.fieldKeys() } else { val (mergedRecord, changedKeys) = recordMerger.merge(existing = oldRecord, incoming = record) if (mergedRecord.isNotEmpty()) { - recordDatabase.update(mergedRecord.withDates(receivedDate = receivedDate, expirationDate = expirationDate)) + recordDatabase.update(mergedRecord.withDates(receivedDate = receivedDate, staleDate = staleDate)) } changedKeys } diff --git a/normalized-cache-sqlite-incubating/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/TrimTest.kt b/normalized-cache-sqlite-incubating/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/TrimTest.kt index 970f7e54..a93c9ccf 100644 --- a/normalized-cache-sqlite-incubating/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/TrimTest.kt +++ b/normalized-cache-sqlite-incubating/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/TrimTest.kt @@ -27,7 +27,7 @@ class TrimTest { fields = mapOf("key" to "value"), mutationId = null, metadata = emptyMap() - ).withDates(receivedDate = "0", expirationDate = null) + ).withDates(receivedDate = "0", staleDate = null) cache.merge(oldRecord, CacheHeaders.NONE, recordMerger = DefaultRecordMerger) val newRecords = 0.until(2 * 1024).map { @@ -36,7 +36,7 @@ class TrimTest { fields = mapOf("key" to largeString), mutationId = null, metadata = emptyMap() - ).withDates(receivedDate = it.toString(), expirationDate = null) + ).withDates(receivedDate = it.toString(), staleDate = null) } cache.merge(newRecords, CacheHeaders.NONE, recordMerger = DefaultRecordMerger) diff --git a/tests/expiration/src/commonTest/kotlin/ClientAndServerSideExpirationTest.kt b/tests/expiration/src/commonTest/kotlin/ClientAndServerSideExpirationTest.kt index 83bf8a45..17703246 100644 --- a/tests/expiration/src/commonTest/kotlin/ClientAndServerSideExpirationTest.kt +++ b/tests/expiration/src/commonTest/kotlin/ClientAndServerSideExpirationTest.kt @@ -2,20 +2,21 @@ package test import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.exception.CacheMissException -import com.apollographql.apollo.mpp.currentTimeMillis import com.apollographql.apollo.testing.internal.runTest import com.apollographql.cache.normalized.FetchPolicy -import com.apollographql.cache.normalized.api.ExpirationCacheResolver +import com.apollographql.cache.normalized.api.CacheControlCacheResolver import com.apollographql.cache.normalized.api.MaxAge import com.apollographql.cache.normalized.api.NormalizedCacheFactory import com.apollographql.cache.normalized.api.SchemaCoordinatesMaxAgeProvider import com.apollographql.cache.normalized.apolloStore import com.apollographql.cache.normalized.cacheHeaders +import com.apollographql.cache.normalized.cacheInfo import com.apollographql.cache.normalized.fetchPolicy +import com.apollographql.cache.normalized.maxStale import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.normalizedCache import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory -import com.apollographql.cache.normalized.storeExpirationDate +import com.apollographql.cache.normalized.storeStaleDate import com.apollographql.mockserver.MockResponse import com.apollographql.mockserver.MockServer import programmatic.GetUserEmailQuery @@ -27,26 +28,26 @@ import kotlin.time.Duration.Companion.seconds class ClientAndServerSideExpirationTest { @Test - fun memoryCache() { - test(MemoryCacheFactory()) + fun cacheMissesMemory() { + cacheMisses(MemoryCacheFactory()) } @Test - fun sqlCache() { - test(SqlNormalizedCacheFactory()) + fun cacheMissesSql() { + cacheMisses(SqlNormalizedCacheFactory()) } @Test - fun chainedCache() { - test(MemoryCacheFactory().chain(SqlNormalizedCacheFactory())) + fun cacheMissesChained() { + cacheMisses(MemoryCacheFactory().chain(SqlNormalizedCacheFactory())) } - private fun test(normalizedCacheFactory: NormalizedCacheFactory) = runTest { + private fun cacheMisses(normalizedCacheFactory: NormalizedCacheFactory) = runTest { val mockServer = MockServer() val client = ApolloClient.Builder() .normalizedCache( normalizedCacheFactory = normalizedCacheFactory, - cacheResolver = ExpirationCacheResolver( + cacheResolver = CacheControlCacheResolver( SchemaCoordinatesMaxAgeProvider( mapOf( "User.email" to MaxAge.Duration(2.seconds), @@ -55,7 +56,7 @@ class ClientAndServerSideExpirationTest { ) ) ) - .storeExpirationDate(true) + .storeStaleDate(true) .serverUrl(mockServer.url()) .build() client.apolloStore.clearAll() @@ -72,14 +73,14 @@ class ClientAndServerSideExpirationTest { } """.trimIndent() - // Store data with an expiration date 10s in the future, and a received date 10s in the past + // Store data with a stale date 10s in the future, and a received date 10s in the past mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=10") .body(data) .build() ) - client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly).cacheHeaders(cacheHeaders(currentTimeMillis() / 1000 - 10)).execute() + client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly).cacheHeaders(receivedDate(currentTimeSeconds() - 10)).execute() // Read User.name from cache -> it should succeed val userNameResponse = client.query(GetUserNameQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute() @@ -90,17 +91,98 @@ class ClientAndServerSideExpirationTest { var e = userEmailResponse.exception as CacheMissException assertTrue(e.stale) - // Store data with an expired date of now + // Store data with an expired date of now, and a received date of now mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=0") .body(data) .build() ) - client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly).execute() - // Read User.name from cache -> it should fail + client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly).cacheHeaders(receivedDate(currentTimeSeconds())).execute() + + // Read User.email from cache -> it should fail userEmailResponse = client.query(GetUserEmailQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute() e = userEmailResponse.exception as CacheMissException assertTrue(e.stale) } + + @Test + fun isStaleMemory() { + isStale(MemoryCacheFactory()) + } + + @Test + fun isStaleSql() { + isStale(SqlNormalizedCacheFactory()) + } + + @Test + fun isStaleChained() { + isStale(MemoryCacheFactory().chain(SqlNormalizedCacheFactory())) + } + + private fun isStale(normalizedCacheFactory: NormalizedCacheFactory) = runTest { + val mockServer = MockServer() + val client = ApolloClient.Builder() + .normalizedCache( + normalizedCacheFactory = normalizedCacheFactory, + cacheResolver = CacheControlCacheResolver( + SchemaCoordinatesMaxAgeProvider( + mapOf( + "User.email" to MaxAge.Duration(2.seconds), + ), + defaultMaxAge = 20.seconds, + ) + ) + ) + .storeStaleDate(true) + .serverUrl(mockServer.url()) + .build() + client.apolloStore.clearAll() + + val data = """ + { + "data": { + "user": { + "name": "John", + "email": "john@doe.com", + "admin": true + } + } + } + """.trimIndent() + + // Store data with a stale date 10s in the future, and a received date 10s in the past + mockServer.enqueue( + MockResponse.Builder() + .addHeader("Cache-Control", "max-age=10") + .body(data) + .build() + ) + client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly).cacheHeaders(receivedDate(currentTimeSeconds() - 10)).execute() + + // Read User.name from cache -> it should succeed, and not indicate that it's stale + val userNameResponse = client.query(GetUserNameQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute() + assertTrue(userNameResponse.data?.user?.name == "John") + assertTrue(userNameResponse.cacheInfo?.isStale == false) + + // Read User.email from cache, with a maxStale of 10 -> it should succeed but indicate that it's stale + var userEmailResponse = client.query(GetUserEmailQuery()).fetchPolicy(FetchPolicy.CacheOnly).maxStale(10.seconds).execute() + assertTrue(userEmailResponse.data?.user?.email == "john@doe.com") + assertTrue(userEmailResponse.cacheInfo?.isStale == true) + + // Store data with an expired date of now, and a received date of now + mockServer.enqueue( + MockResponse.Builder() + .addHeader("Cache-Control", "max-age=0") + .body(data) + .build() + ) + client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly).cacheHeaders(receivedDate(currentTimeSeconds())).execute() + + // Read User.email from cache, with a maxStale of 10 -> it should succeed but indicate that it's stale + userEmailResponse = client.query(GetUserEmailQuery()).fetchPolicy(FetchPolicy.CacheOnly).maxStale(10.seconds).execute() + assertTrue(userEmailResponse.data?.user?.email == "john@doe.com") + assertTrue(userEmailResponse.cacheInfo?.isStale == true) + } } diff --git a/tests/expiration/src/commonTest/kotlin/ClientSideExpirationTest.kt b/tests/expiration/src/commonTest/kotlin/ClientSideExpirationTest.kt index c8ce0d4b..5c18a41e 100644 --- a/tests/expiration/src/commonTest/kotlin/ClientSideExpirationTest.kt +++ b/tests/expiration/src/commonTest/kotlin/ClientSideExpirationTest.kt @@ -7,9 +7,9 @@ import com.apollographql.apollo.mpp.currentTimeMillis import com.apollographql.apollo.testing.internal.runTest import com.apollographql.cache.normalized.FetchPolicy import com.apollographql.cache.normalized.api.ApolloCacheHeaders +import com.apollographql.cache.normalized.api.CacheControlCacheResolver import com.apollographql.cache.normalized.api.CacheHeaders import com.apollographql.cache.normalized.api.DefaultRecordMerger -import com.apollographql.cache.normalized.api.ExpirationCacheResolver import com.apollographql.cache.normalized.api.GlobalMaxAgeProvider import com.apollographql.cache.normalized.api.MaxAge import com.apollographql.cache.normalized.api.NormalizedCacheFactory @@ -83,7 +83,7 @@ class ClientSideExpirationTest { val client = ApolloClient.Builder() .normalizedCache( normalizedCacheFactory = normalizedCacheFactory, - cacheResolver = ExpirationCacheResolver(GlobalMaxAgeProvider(maxAge.seconds)), + cacheResolver = CacheControlCacheResolver(GlobalMaxAgeProvider(maxAge.seconds)), ) .serverUrl("unused") .build() @@ -96,7 +96,7 @@ class ClientSideExpirationTest { client.apolloStore.accessCache { // store records in the past - it.merge(records, cacheHeaders(currentTimeMillis() / 1000 - 15), DefaultRecordMerger) + it.merge(records, receivedDate(currentTimeSeconds() - 15), DefaultRecordMerger) } val e = client.query(GetUserQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute().exception as CacheMissException @@ -110,7 +110,7 @@ class ClientSideExpirationTest { client.apolloStore.accessCache { // update records to be in the present - it.merge(records, cacheHeaders(currentTimeMillis() / 1000), DefaultRecordMerger) + it.merge(records, receivedDate(currentTimeSeconds()), DefaultRecordMerger) } val response2 = client.query(GetUserQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute() @@ -130,7 +130,7 @@ class ClientSideExpirationTest { val client = ApolloClient.Builder() .normalizedCache( normalizedCacheFactory = normalizedCacheFactory, - cacheResolver = ExpirationCacheResolver(maxAgeProvider), + cacheResolver = CacheControlCacheResolver(maxAgeProvider), ) .serverUrl("unused") .build() @@ -185,7 +185,7 @@ class ClientSideExpirationTest { val data = GetCompanyQuery.Data(GetCompanyQuery.Company("42")) val records = GetCompanyQuery().normalize(data, CustomScalarAdapters.Empty, TypePolicyCacheKeyGenerator).values client.apolloStore.accessCache { - it.merge(records, cacheHeaders(currentTimeMillis() / 1000 - secondsAgo), DefaultRecordMerger) + it.merge(records, receivedDate(currentTimeSeconds() - secondsAgo), DefaultRecordMerger) } } @@ -198,7 +198,7 @@ class ClientSideExpirationTest { val client = ApolloClient.Builder() .normalizedCache( normalizedCacheFactory = normalizedCacheFactory, - cacheResolver = ExpirationCacheResolver(maxAgeProvider), + cacheResolver = CacheControlCacheResolver(maxAgeProvider), ) .serverUrl("unused") .build() @@ -253,11 +253,13 @@ class ClientSideExpirationTest { val data = GetUserQuery.Data(GetUserQuery.User("John", "john@doe.com", true)) val records = GetUserQuery().normalize(data, CustomScalarAdapters.Empty, TypePolicyCacheKeyGenerator).values client.apolloStore.accessCache { - it.merge(records, cacheHeaders(currentTimeMillis() / 1000 - secondsAgo), DefaultRecordMerger) + it.merge(records, receivedDate(currentTimeSeconds() - secondsAgo), DefaultRecordMerger) } } } -fun cacheHeaders(receivedDate: Long): CacheHeaders { - return CacheHeaders.Builder().addHeader(ApolloCacheHeaders.RECEIVED_DATE, receivedDate.toString()).build() +fun currentTimeSeconds() = currentTimeMillis() / 1000 + +fun receivedDate(receivedDateSeconds: Long): CacheHeaders { + return CacheHeaders.Builder().addHeader(ApolloCacheHeaders.RECEIVED_DATE, receivedDateSeconds.toString()).build() } diff --git a/tests/expiration/src/commonTest/kotlin/ServerSideExpirationTest.kt b/tests/expiration/src/commonTest/kotlin/ServerSideExpirationTest.kt index 53ecba73..fae058e3 100644 --- a/tests/expiration/src/commonTest/kotlin/ServerSideExpirationTest.kt +++ b/tests/expiration/src/commonTest/kotlin/ServerSideExpirationTest.kt @@ -5,7 +5,7 @@ import com.apollographql.apollo.api.ApolloResponse import com.apollographql.apollo.exception.CacheMissException import com.apollographql.apollo.testing.internal.runTest import com.apollographql.cache.normalized.FetchPolicy -import com.apollographql.cache.normalized.api.ExpirationCacheResolver +import com.apollographql.cache.normalized.api.CacheControlCacheResolver import com.apollographql.cache.normalized.api.NormalizedCacheFactory import com.apollographql.cache.normalized.apolloStore import com.apollographql.cache.normalized.fetchPolicy @@ -13,7 +13,7 @@ import com.apollographql.cache.normalized.maxStale import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.normalizedCache import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory -import com.apollographql.cache.normalized.storeExpirationDate +import com.apollographql.cache.normalized.storeStaleDate import com.apollographql.mockserver.MockResponse import com.apollographql.mockserver.MockServer import programmatic.GetUserQuery @@ -43,9 +43,9 @@ class ServerSideExpirationTest { val client = ApolloClient.Builder() .normalizedCache( normalizedCacheFactory = normalizedCacheFactory, - cacheResolver = ExpirationCacheResolver(), + cacheResolver = CacheControlCacheResolver(), ) - .storeExpirationDate(true) + .storeStaleDate(true) .serverUrl(mockServer.url()) .build() client.apolloStore.clearAll() @@ -65,7 +65,7 @@ class ServerSideExpirationTest { var response: ApolloResponse - // store data with an expiration date in the future + // store data with a stale date in the future mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=10") From aa1d90ce827bda84351192dd34f2e8e34fde940d Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 18 Oct 2024 16:03:22 +0200 Subject: [PATCH 2/7] Rename expiration -> cache control in tests --- tests/{expiration => cache-control}/build.gradle.kts | 0 .../src/commonMain/graphql/declarative/operations.graphql | 0 .../src/commonMain/graphql/declarative/schema.graphqls | 0 .../src/commonMain/graphql/programmatic/operations.graphql | 0 .../src/commonMain/graphql/programmatic/schema.graphqls | 0 .../kotlin/ClientAndServerSideCacheControlTest.kt} | 6 +++--- .../src/commonTest/kotlin/ClientSideCacheControlTest.kt} | 2 +- .../kotlin/SchemaCoordinatesMaxAgeProviderTest.kt | 0 .../src/commonTest/kotlin/ServerSideCacheControlTest.kt} | 4 ++-- 9 files changed, 6 insertions(+), 6 deletions(-) rename tests/{expiration => cache-control}/build.gradle.kts (100%) rename tests/{expiration => cache-control}/src/commonMain/graphql/declarative/operations.graphql (100%) rename tests/{expiration => cache-control}/src/commonMain/graphql/declarative/schema.graphqls (100%) rename tests/{expiration => cache-control}/src/commonMain/graphql/programmatic/operations.graphql (100%) rename tests/{expiration => cache-control}/src/commonMain/graphql/programmatic/schema.graphqls (100%) rename tests/{expiration/src/commonTest/kotlin/ClientAndServerSideExpirationTest.kt => cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt} (97%) rename tests/{expiration/src/commonTest/kotlin/ClientSideExpirationTest.kt => cache-control/src/commonTest/kotlin/ClientSideCacheControlTest.kt} (99%) rename tests/{expiration => cache-control}/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt (100%) rename tests/{expiration/src/commonTest/kotlin/ServerSideExpirationTest.kt => cache-control/src/commonTest/kotlin/ServerSideCacheControlTest.kt} (98%) diff --git a/tests/expiration/build.gradle.kts b/tests/cache-control/build.gradle.kts similarity index 100% rename from tests/expiration/build.gradle.kts rename to tests/cache-control/build.gradle.kts diff --git a/tests/expiration/src/commonMain/graphql/declarative/operations.graphql b/tests/cache-control/src/commonMain/graphql/declarative/operations.graphql similarity index 100% rename from tests/expiration/src/commonMain/graphql/declarative/operations.graphql rename to tests/cache-control/src/commonMain/graphql/declarative/operations.graphql diff --git a/tests/expiration/src/commonMain/graphql/declarative/schema.graphqls b/tests/cache-control/src/commonMain/graphql/declarative/schema.graphqls similarity index 100% rename from tests/expiration/src/commonMain/graphql/declarative/schema.graphqls rename to tests/cache-control/src/commonMain/graphql/declarative/schema.graphqls diff --git a/tests/expiration/src/commonMain/graphql/programmatic/operations.graphql b/tests/cache-control/src/commonMain/graphql/programmatic/operations.graphql similarity index 100% rename from tests/expiration/src/commonMain/graphql/programmatic/operations.graphql rename to tests/cache-control/src/commonMain/graphql/programmatic/operations.graphql diff --git a/tests/expiration/src/commonMain/graphql/programmatic/schema.graphqls b/tests/cache-control/src/commonMain/graphql/programmatic/schema.graphqls similarity index 100% rename from tests/expiration/src/commonMain/graphql/programmatic/schema.graphqls rename to tests/cache-control/src/commonMain/graphql/programmatic/schema.graphqls diff --git a/tests/expiration/src/commonTest/kotlin/ClientAndServerSideExpirationTest.kt b/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt similarity index 97% rename from tests/expiration/src/commonTest/kotlin/ClientAndServerSideExpirationTest.kt rename to tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt index 17703246..15153856 100644 --- a/tests/expiration/src/commonTest/kotlin/ClientAndServerSideExpirationTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt @@ -26,7 +26,7 @@ import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class ClientAndServerSideExpirationTest { +class ClientAndServerSideCacheControlTest { @Test fun cacheMissesMemory() { cacheMisses(MemoryCacheFactory()) @@ -91,7 +91,7 @@ class ClientAndServerSideExpirationTest { var e = userEmailResponse.exception as CacheMissException assertTrue(e.stale) - // Store data with an expired date of now, and a received date of now + // Store data with a stale date of now, and a received date of now mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=0") @@ -171,7 +171,7 @@ class ClientAndServerSideExpirationTest { assertTrue(userEmailResponse.data?.user?.email == "john@doe.com") assertTrue(userEmailResponse.cacheInfo?.isStale == true) - // Store data with an expired date of now, and a received date of now + // Store data with a slate date of now, and a received date of now mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=0") diff --git a/tests/expiration/src/commonTest/kotlin/ClientSideExpirationTest.kt b/tests/cache-control/src/commonTest/kotlin/ClientSideCacheControlTest.kt similarity index 99% rename from tests/expiration/src/commonTest/kotlin/ClientSideExpirationTest.kt rename to tests/cache-control/src/commonTest/kotlin/ClientSideCacheControlTest.kt index 5c18a41e..5269787b 100644 --- a/tests/expiration/src/commonTest/kotlin/ClientSideExpirationTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/ClientSideCacheControlTest.kt @@ -31,7 +31,7 @@ import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class ClientSideExpirationTest { +class ClientSideCacheControlTest { @Test fun globalMaxAgeMemoryCache() { globalMaxAge(MemoryCacheFactory()) diff --git a/tests/expiration/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt b/tests/cache-control/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt similarity index 100% rename from tests/expiration/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt rename to tests/cache-control/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt diff --git a/tests/expiration/src/commonTest/kotlin/ServerSideExpirationTest.kt b/tests/cache-control/src/commonTest/kotlin/ServerSideCacheControlTest.kt similarity index 98% rename from tests/expiration/src/commonTest/kotlin/ServerSideExpirationTest.kt rename to tests/cache-control/src/commonTest/kotlin/ServerSideCacheControlTest.kt index fae058e3..ff83c1fd 100644 --- a/tests/expiration/src/commonTest/kotlin/ServerSideExpirationTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/ServerSideCacheControlTest.kt @@ -21,7 +21,7 @@ import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class ServerSideExpirationTest { +class ServerSideCacheControlTest { @Test fun memoryCache() { test(MemoryCacheFactory()) @@ -77,7 +77,7 @@ class ServerSideExpirationTest { response = client.query(query).fetchPolicy(FetchPolicy.CacheOnly).execute() assertTrue(response.data?.user?.name == "John") - // store expired data + // store stale data mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=0") From 47058c1919a82daa274446219ce61e5ebdd13872 Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 18 Oct 2024 17:29:00 +0200 Subject: [PATCH 3/7] Revert expiration date -> stale date --- .../api/normalized-cache-incubating.api | 6 ++--- .../api/normalized-cache-incubating.klib.api | 8 +++--- .../cache/normalized/ClientCacheExtensions.kt | 26 +++++++++---------- .../normalized/api/ApolloCacheHeaders.kt | 4 +-- .../cache/normalized/api/CacheResolver.kt | 16 ++++++------ .../cache/normalized/api/Record.kt | 10 +++---- .../cache/normalized/memory/MemoryCache.kt | 6 ++--- .../normalized/sql/SqlNormalizedCache.kt | 12 ++++----- .../cache/normalized/sql/TrimTest.kt | 4 +-- .../ClientAndServerSideCacheControlTest.kt | 12 ++++----- .../kotlin/ServerSideCacheControlTest.kt | 6 ++--- 11 files changed, 55 insertions(+), 55 deletions(-) diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.api b/normalized-cache-incubating/api/normalized-cache-incubating.api index bf38ac18..f8f7bd7e 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.api @@ -122,9 +122,9 @@ public final class com/apollographql/cache/normalized/NormalizedCache { public static final fun refetchPolicyInterceptor (Lcom/apollographql/apollo/api/MutableExecutionOptions;Lcom/apollographql/apollo/interceptor/ApolloInterceptor;)Ljava/lang/Object; public static final fun store (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/cache/normalized/ApolloStore;Z)Lcom/apollographql/apollo/ApolloClient$Builder; public static synthetic fun store$default (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/cache/normalized/ApolloStore;ZILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder; + public static final fun storeExpirationDate (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; public static final fun storePartialResponses (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; public static final fun storeReceiveDate (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; - public static final fun storeStaleDate (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; public static final fun watch (Lcom/apollographql/apollo/ApolloCall;)Lkotlinx/coroutines/flow/Flow; public static final fun watch (Lcom/apollographql/apollo/ApolloCall;Lcom/apollographql/apollo/api/Query$Data;)Lkotlinx/coroutines/flow/Flow; public static final fun writeToCacheAsynchronously (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; @@ -137,12 +137,12 @@ public final class com/apollographql/cache/normalized/VersionKt { public final class com/apollographql/cache/normalized/api/ApolloCacheHeaders { public static final field DO_NOT_STORE Ljava/lang/String; public static final field EVICT_AFTER_READ Ljava/lang/String; + public static final field EXPIRATION_DATE Ljava/lang/String; public static final field INSTANCE Lcom/apollographql/cache/normalized/api/ApolloCacheHeaders; public static final field MAX_STALE Ljava/lang/String; public static final field MEMORY_CACHE_ONLY Ljava/lang/String; public static final field RECEIVED_DATE Ljava/lang/String; public static final field STALE Ljava/lang/String; - public static final field STALE_DATE Ljava/lang/String; } public final class com/apollographql/cache/normalized/api/CacheControlCacheResolver : com/apollographql/cache/normalized/api/CacheResolver { @@ -463,8 +463,8 @@ public final class com/apollographql/cache/normalized/api/Record$Companion { } public final class com/apollographql/cache/normalized/api/RecordKt { + public static final fun expirationDate (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;)Ljava/lang/Long; public static final fun receivedDate (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;)Ljava/lang/Long; - public static final fun staleDate (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;)Ljava/lang/Long; public static final fun withDates (Lcom/apollographql/cache/normalized/api/Record;Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/cache/normalized/api/Record; } diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api index 57c874d8..dbbe3b47 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api @@ -330,8 +330,8 @@ final enum class com.apollographql.cache.normalized/FetchPolicy : kotlin/Enum = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/logCacheMisses|logCacheMisses@com.apollographql.apollo.ApolloClient.Builder(kotlin.Function1){}[0] final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/normalizedCache(com.apollographql.cache.normalized.api/NormalizedCacheFactory, com.apollographql.cache.normalized.api/CacheKeyGenerator = ..., com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/CacheResolver = ..., com.apollographql.cache.normalized.api/RecordMerger = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ..., kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/normalizedCache|normalizedCache@com.apollographql.apollo.ApolloClient.Builder(com.apollographql.cache.normalized.api.NormalizedCacheFactory;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.RecordMerger;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider;kotlin.Boolean){}[0] final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/store(com.apollographql.cache.normalized/ApolloStore, kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/store|store@com.apollographql.apollo.ApolloClient.Builder(com.apollographql.cache.normalized.ApolloStore;kotlin.Boolean){}[0] +final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/expirationDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/expirationDate|expirationDate@com.apollographql.cache.normalized.api.Record(kotlin.String){}[0] final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/receivedDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/receivedDate|receivedDate@com.apollographql.cache.normalized.api.Record(kotlin.String){}[0] -final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/staleDate(kotlin/String): kotlin/Long? // com.apollographql.cache.normalized.api/staleDate|staleDate@com.apollographql.cache.normalized.api.Record(kotlin.String){}[0] final fun (com.apollographql.cache.normalized.api/Record).com.apollographql.cache.normalized.api/withDates(kotlin/String?, kotlin/String?): com.apollographql.cache.normalized.api/Record // com.apollographql.cache.normalized.api/withDates|withDates@com.apollographql.cache.normalized.api.Record(kotlin.String?;kotlin.String?){}[0] final fun (kotlin.collections/Collection?).com.apollographql.cache.normalized.api/dependentKeys(): kotlin.collections/Set // com.apollographql.cache.normalized.api/dependentKeys|dependentKeys@kotlin.collections.Collection?(){}[0] final fun <#A: com.apollographql.apollo.api/Executable.Data> (com.apollographql.apollo.api/Executable<#A>).com.apollographql.cache.normalized.api/normalize(#A, com.apollographql.apollo.api/CustomScalarAdapters, com.apollographql.cache.normalized.api/CacheKeyGenerator, com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ..., kotlin/String): kotlin.collections/Map // com.apollographql.cache.normalized.api/normalize|normalize@com.apollographql.apollo.api.Executable<0:0>(0:0;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider;kotlin.String){0§}[0] @@ -352,9 +352,9 @@ final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOption final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/memoryCacheOnly(kotlin/Boolean): #A // com.apollographql.cache.normalized/memoryCacheOnly|memoryCacheOnly@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/refetchPolicy(com.apollographql.cache.normalized/FetchPolicy): #A // com.apollographql.cache.normalized/refetchPolicy|refetchPolicy@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(com.apollographql.cache.normalized.FetchPolicy){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/refetchPolicyInterceptor(com.apollographql.apollo.interceptor/ApolloInterceptor): #A // com.apollographql.cache.normalized/refetchPolicyInterceptor|refetchPolicyInterceptor@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(com.apollographql.apollo.interceptor.ApolloInterceptor){0§}[0] +final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/storeExpirationDate(kotlin/Boolean): #A // com.apollographql.cache.normalized/storeExpirationDate|storeExpirationDate@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/storePartialResponses(kotlin/Boolean): #A // com.apollographql.cache.normalized/storePartialResponses|storePartialResponses@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/storeReceiveDate(kotlin/Boolean): #A // com.apollographql.cache.normalized/storeReceiveDate|storeReceiveDate@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] -final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/storeStaleDate(kotlin/Boolean): #A // com.apollographql.cache.normalized/storeStaleDate|storeStaleDate@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun <#A: kotlin/Any?> (com.apollographql.apollo.api/MutableExecutionOptions<#A>).com.apollographql.cache.normalized/writeToCacheAsynchronously(kotlin/Boolean): #A // com.apollographql.cache.normalized/writeToCacheAsynchronously|writeToCacheAsynchronously@com.apollographql.apollo.api.MutableExecutionOptions<0:0>(kotlin.Boolean){0§}[0] final fun com.apollographql.cache.normalized/ApolloStore(com.apollographql.cache.normalized.api/NormalizedCacheFactory, com.apollographql.cache.normalized.api/CacheKeyGenerator = ..., com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/CacheResolver = ..., com.apollographql.cache.normalized.api/RecordMerger = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ...): com.apollographql.cache.normalized/ApolloStore // com.apollographql.cache.normalized/ApolloStore|ApolloStore(com.apollographql.cache.normalized.api.NormalizedCacheFactory;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.RecordMerger;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider){}[0] final object com.apollographql.cache.normalized.api/ApolloCacheHeaders { // com.apollographql.cache.normalized.api/ApolloCacheHeaders|null[0] @@ -362,6 +362,8 @@ final object com.apollographql.cache.normalized.api/ApolloCacheHeaders { // com. final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.DO_NOT_STORE.|(){}[0] final const val EVICT_AFTER_READ // com.apollographql.cache.normalized.api/ApolloCacheHeaders.EVICT_AFTER_READ|{}EVICT_AFTER_READ[0] final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.EVICT_AFTER_READ.|(){}[0] + final const val EXPIRATION_DATE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.EXPIRATION_DATE|{}EXPIRATION_DATE[0] + final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.EXPIRATION_DATE.|(){}[0] final const val MAX_STALE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.MAX_STALE|{}MAX_STALE[0] final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.MAX_STALE.|(){}[0] final const val MEMORY_CACHE_ONLY // com.apollographql.cache.normalized.api/ApolloCacheHeaders.MEMORY_CACHE_ONLY|{}MEMORY_CACHE_ONLY[0] @@ -370,8 +372,6 @@ final object com.apollographql.cache.normalized.api/ApolloCacheHeaders { // com. final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.RECEIVED_DATE.|(){}[0] final const val STALE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.STALE|{}STALE[0] final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.STALE.|(){}[0] - final const val STALE_DATE // com.apollographql.cache.normalized.api/ApolloCacheHeaders.STALE_DATE|{}STALE_DATE[0] - final fun (): kotlin/String // com.apollographql.cache.normalized.api/ApolloCacheHeaders.STALE_DATE.|(){}[0] } final object com.apollographql.cache.normalized.api/DefaultCacheResolver : com.apollographql.cache.normalized.api/CacheResolver { // com.apollographql.cache.normalized.api/DefaultCacheResolver|null[0] final fun resolveField(com.apollographql.cache.normalized.api/ResolverContext): kotlin/Any? // com.apollographql.cache.normalized.api/DefaultCacheResolver.resolveField|resolveField(com.apollographql.cache.normalized.api.ResolverContext){}[0] diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt index fef97f28..2dbb1850 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/ClientCacheExtensions.kt @@ -273,28 +273,28 @@ fun MutableExecutionOptions.storeReceiveDate(storeReceiveDate: Boolean) = ) /** - * @param storeStaleDate Whether to store the stale date in the cache. + * @param storeExpirationDate Whether to store the expiration date in the cache. * - * The stale date is computed from the response HTTP headers + * The expiration date is computed from the response HTTP headers * * Default: false */ -fun MutableExecutionOptions.storeStaleDate(storeStaleDate: Boolean): T { - addExecutionContext(StoreStaleDateContext(storeStaleDate)) +fun MutableExecutionOptions.storeExpirationDate(storeExpirationDate: Boolean): T { + addExecutionContext(StoreExpirationDateContext(storeExpirationDate)) if (this is ApolloClient.Builder) { - check(interceptors.none { it is StoreStaleDateInterceptor }) { - "Apollo: storeStaleDate() can only be called once on ApolloClient.Builder()" + check(interceptors.none { it is StoreExpirationDateInterceptor }) { + "Apollo: storeExpirationDate() can only be called once on ApolloClient.Builder()" } - addInterceptor(StoreStaleDateInterceptor()) + addInterceptor(StoreExpirationDateInterceptor()) } @Suppress("UNCHECKED_CAST") return this as T } -private class StoreStaleDateInterceptor : ApolloInterceptor { +private class StoreExpirationDateInterceptor : ApolloInterceptor { override fun intercept(request: ApolloRequest, chain: ApolloInterceptorChain): Flow> { return chain.proceed(request).map { - val store = request.executionContext[StoreStaleDateContext]?.value + val store = request.executionContext[StoreExpirationDateContext]?.value if (store != true) { return@map it } @@ -312,7 +312,7 @@ private class StoreStaleDateInterceptor : ApolloInterceptor { }.firstOrNull() ?: return@map it val age = headers.get("age")?.toIntOrNull() - val staleDate = if (age != null) { + val expires = if (age != null) { currentTimeMillis() / 1000 + maxAge - age } else { currentTimeMillis() / 1000 + maxAge @@ -321,7 +321,7 @@ private class StoreStaleDateInterceptor : ApolloInterceptor { return@map it.newBuilder() .cacheHeaders( it.cacheHeaders.newBuilder() - .addHeader(ApolloCacheHeaders.STALE_DATE, staleDate.toString()) + .addHeader(ApolloCacheHeaders.EXPIRATION_DATE, expires.toString()) .build() ) .build() @@ -552,11 +552,11 @@ internal class StoreReceiveDateContext(val value: Boolean) : ExecutionContext.El companion object Key : ExecutionContext.Key } -internal class StoreStaleDateContext(val value: Boolean) : ExecutionContext.Element { +internal class StoreExpirationDateContext(val value: Boolean) : ExecutionContext.Element { override val key: ExecutionContext.Key<*> get() = Key - companion object Key : ExecutionContext.Key + companion object Key : ExecutionContext.Key } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ApolloCacheHeaders.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ApolloCacheHeaders.kt index d988b886..2accbf90 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ApolloCacheHeaders.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/ApolloCacheHeaders.kt @@ -25,9 +25,9 @@ object ApolloCacheHeaders { const val RECEIVED_DATE = "apollo-received-date" /** - * The value of this header will be stored in the [Record]'s stale date. + * The value of this header will be stored in the [Record]'s expiration date. */ - const val STALE_DATE = "apollo-stale-date" + const val EXPIRATION_DATE = "apollo-expiration-date" /** * How long to accept stale fields diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt index 9dd777d2..3b676d05 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/CacheResolver.kt @@ -7,8 +7,8 @@ import com.apollographql.apollo.exception.CacheMissException import com.apollographql.apollo.mpp.currentTimeMillis import com.apollographql.cache.normalized.api.CacheResolver.ResolvedValue import com.apollographql.cache.normalized.maxStale +import com.apollographql.cache.normalized.storeExpirationDate import com.apollographql.cache.normalized.storeReceiveDate -import com.apollographql.cache.normalized.storeStaleDate import kotlin.jvm.JvmSuppressWildcards import kotlin.time.Duration @@ -139,23 +139,23 @@ object DefaultCacheResolver : CacheResolver { /** * A cache resolver that raises a cache miss if the field's received date is older than its max age - * (configurable via [maxAgeProvider]) or its stale date has passed. + * (configurable via [maxAgeProvider]) or if its expiration date has passed. * * Received dates are stored by calling `storeReceiveDate(true)` on your `ApolloClient`. * - * Stale dates are stored by calling `storeStaleDate(true)` on your `ApolloClient`. + * Expiration dates are stored by calling `storeExpirationDate(true)` on your `ApolloClient`. * * A maximum staleness can be configured via the [ApolloCacheHeaders.MAX_STALE] cache header. * * @see MutableExecutionOptions.storeReceiveDate - * @see MutableExecutionOptions.storeStaleDate + * @see MutableExecutionOptions.storeExpirationDate * @see MutableExecutionOptions.maxStale */ class CacheControlCacheResolver( private val maxAgeProvider: MaxAgeProvider, ) : CacheResolver { /** - * Creates a new [CacheControlCacheResolver] with no max ages. Use this constructor if you want to consider only the stale dates. + * Creates a new [CacheControlCacheResolver] with no max ages. Use this constructor if you want to consider only the expiration dates. */ constructor() : this(maxAgeProvider = GlobalMaxAgeProvider(Duration.INFINITE)) @@ -182,10 +182,10 @@ class CacheControlCacheResolver( } // Consider the server controlled max age - val staleDate = context.parent.staleDate(field.name) - if (staleDate != null) { + val expirationDate = context.parent.expirationDate(field.name) + if (expirationDate != null) { val currentDate = currentTimeMillis() / 1000 - val staleDuration = currentDate - staleDate + val staleDuration = currentDate - expirationDate val maxStale = context.cacheHeaders.headerValue(ApolloCacheHeaders.MAX_STALE)?.toLongOrNull() ?: 0L if (staleDuration >= maxStale) { throw CacheMissException( diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt index 995fb26c..65f17e0a 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/Record.kt @@ -89,8 +89,8 @@ class Record( } @ApolloInternal -fun Record.withDates(receivedDate: String?, staleDate: String?): Record { - if (receivedDate == null && staleDate == null) { +fun Record.withDates(receivedDate: String?, expirationDate: String?): Record { + if (receivedDate == null && expirationDate == null) { return this } return Record( @@ -102,8 +102,8 @@ fun Record.withDates(receivedDate: String?, staleDate: String?): Record { receivedDate?.let { put(ApolloCacheHeaders.RECEIVED_DATE, it.toLong()) } - staleDate?.let { - put(ApolloCacheHeaders.STALE_DATE, it.toLong()) + expirationDate?.let { + put(ApolloCacheHeaders.EXPIRATION_DATE, it.toLong()) } } } @@ -112,7 +112,7 @@ fun Record.withDates(receivedDate: String?, staleDate: String?): Record { fun Record.receivedDate(field: String) = metadata[field]?.get(ApolloCacheHeaders.RECEIVED_DATE) as? Long -fun Record.staleDate(field: String) = metadata[field]?.get(ApolloCacheHeaders.STALE_DATE) as? Long +fun Record.expirationDate(field: String) = metadata[field]?.get(ApolloCacheHeaders.EXPIRATION_DATE) as? Long /** diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt index d0e3f7cf..a6a60444 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/memory/MemoryCache.kt @@ -132,14 +132,14 @@ class MemoryCache( private fun internalMerge(record: Record, cacheHeaders: CacheHeaders, recordMerger: RecordMerger): Set { val receivedDate = cacheHeaders.headerValue(ApolloCacheHeaders.RECEIVED_DATE) - val staleDate = cacheHeaders.headerValue(ApolloCacheHeaders.STALE_DATE) + val expirationDate = cacheHeaders.headerValue(ApolloCacheHeaders.EXPIRATION_DATE) val oldRecord = loadRecord(record.key, cacheHeaders) val changedKeys = if (oldRecord == null) { - lruCache[record.key] = record.withDates(receivedDate = receivedDate, staleDate = staleDate) + lruCache[record.key] = record.withDates(receivedDate = receivedDate, expirationDate = expirationDate) record.fieldKeys() } else { val (mergedRecord, changedKeys) = recordMerger.merge(existing = oldRecord, incoming = record) - lruCache[record.key] = mergedRecord.withDates(receivedDate = receivedDate, staleDate = staleDate) + lruCache[record.key] = mergedRecord.withDates(receivedDate = receivedDate, expirationDate = expirationDate) changedKeys } return changedKeys diff --git a/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt b/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt index 788b94c0..feb4c2c6 100644 --- a/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt +++ b/normalized-cache-sqlite-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/sql/SqlNormalizedCache.kt @@ -149,7 +149,7 @@ class SqlNormalizedCache internal constructor( private fun internalUpdateRecords(records: Collection, cacheHeaders: CacheHeaders, recordMerger: RecordMerger): Set { var updatedRecordKeys: Set = emptySet() val receivedDate = cacheHeaders.headerValue(ApolloCacheHeaders.RECEIVED_DATE) - val staleDate = cacheHeaders.headerValue(ApolloCacheHeaders.STALE_DATE) + val expirationDate = cacheHeaders.headerValue(ApolloCacheHeaders.EXPIRATION_DATE) recordDatabase.transaction { val oldRecords = internalGetRecords( keys = records.map { it.key }, @@ -158,12 +158,12 @@ class SqlNormalizedCache internal constructor( updatedRecordKeys = records.flatMap { record -> val oldRecord = oldRecords[record.key] if (oldRecord == null) { - recordDatabase.insert(record.withDates(receivedDate = receivedDate, staleDate = staleDate)) + recordDatabase.insert(record.withDates(receivedDate = receivedDate, expirationDate = expirationDate)) record.fieldKeys() } else { val (mergedRecord, changedKeys) = recordMerger.merge(existing = oldRecord, incoming = record) if (mergedRecord.isNotEmpty()) { - recordDatabase.update(mergedRecord.withDates(receivedDate = receivedDate, staleDate = staleDate)) + recordDatabase.update(mergedRecord.withDates(receivedDate = receivedDate, expirationDate = expirationDate)) } changedKeys } @@ -177,16 +177,16 @@ class SqlNormalizedCache internal constructor( */ private fun internalUpdateRecord(record: Record, cacheHeaders: CacheHeaders, recordMerger: RecordMerger): Set { val receivedDate = cacheHeaders.headerValue(ApolloCacheHeaders.RECEIVED_DATE) - val staleDate = cacheHeaders.headerValue(ApolloCacheHeaders.STALE_DATE) + val expirationDate = cacheHeaders.headerValue(ApolloCacheHeaders.EXPIRATION_DATE) return recordDatabase.transaction { val oldRecord = recordDatabase.select(record.key) if (oldRecord == null) { - recordDatabase.insert(record.withDates(receivedDate = receivedDate, staleDate = staleDate)) + recordDatabase.insert(record.withDates(receivedDate = receivedDate, expirationDate = expirationDate)) record.fieldKeys() } else { val (mergedRecord, changedKeys) = recordMerger.merge(existing = oldRecord, incoming = record) if (mergedRecord.isNotEmpty()) { - recordDatabase.update(mergedRecord.withDates(receivedDate = receivedDate, staleDate = staleDate)) + recordDatabase.update(mergedRecord.withDates(receivedDate = receivedDate, expirationDate = expirationDate)) } changedKeys } diff --git a/normalized-cache-sqlite-incubating/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/TrimTest.kt b/normalized-cache-sqlite-incubating/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/TrimTest.kt index a93c9ccf..970f7e54 100644 --- a/normalized-cache-sqlite-incubating/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/TrimTest.kt +++ b/normalized-cache-sqlite-incubating/src/jvmTest/kotlin/com/apollographql/cache/normalized/sql/TrimTest.kt @@ -27,7 +27,7 @@ class TrimTest { fields = mapOf("key" to "value"), mutationId = null, metadata = emptyMap() - ).withDates(receivedDate = "0", staleDate = null) + ).withDates(receivedDate = "0", expirationDate = null) cache.merge(oldRecord, CacheHeaders.NONE, recordMerger = DefaultRecordMerger) val newRecords = 0.until(2 * 1024).map { @@ -36,7 +36,7 @@ class TrimTest { fields = mapOf("key" to largeString), mutationId = null, metadata = emptyMap() - ).withDates(receivedDate = it.toString(), staleDate = null) + ).withDates(receivedDate = it.toString(), expirationDate = null) } cache.merge(newRecords, CacheHeaders.NONE, recordMerger = DefaultRecordMerger) diff --git a/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt b/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt index 15153856..28852e19 100644 --- a/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt @@ -16,7 +16,7 @@ import com.apollographql.cache.normalized.maxStale import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.normalizedCache import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory -import com.apollographql.cache.normalized.storeStaleDate +import com.apollographql.cache.normalized.storeExpirationDate import com.apollographql.mockserver.MockResponse import com.apollographql.mockserver.MockServer import programmatic.GetUserEmailQuery @@ -56,7 +56,7 @@ class ClientAndServerSideCacheControlTest { ) ) ) - .storeStaleDate(true) + .storeExpirationDate(true) .serverUrl(mockServer.url()) .build() client.apolloStore.clearAll() @@ -73,7 +73,7 @@ class ClientAndServerSideCacheControlTest { } """.trimIndent() - // Store data with a stale date 10s in the future, and a received date 10s in the past + // Store data with an expiration date 10s in the future, and a received date 10s in the past mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=10") @@ -91,7 +91,7 @@ class ClientAndServerSideCacheControlTest { var e = userEmailResponse.exception as CacheMissException assertTrue(e.stale) - // Store data with a stale date of now, and a received date of now + // Store data with an expiration date of now, and a received date of now mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=0") @@ -135,7 +135,7 @@ class ClientAndServerSideCacheControlTest { ) ) ) - .storeStaleDate(true) + .storeExpirationDate(true) .serverUrl(mockServer.url()) .build() client.apolloStore.clearAll() @@ -152,7 +152,7 @@ class ClientAndServerSideCacheControlTest { } """.trimIndent() - // Store data with a stale date 10s in the future, and a received date 10s in the past + // Store data with an expiration date 10s in the future, and a received date 10s in the past mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=10") diff --git a/tests/cache-control/src/commonTest/kotlin/ServerSideCacheControlTest.kt b/tests/cache-control/src/commonTest/kotlin/ServerSideCacheControlTest.kt index ff83c1fd..777e0913 100644 --- a/tests/cache-control/src/commonTest/kotlin/ServerSideCacheControlTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/ServerSideCacheControlTest.kt @@ -13,7 +13,7 @@ import com.apollographql.cache.normalized.maxStale import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.normalizedCache import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory -import com.apollographql.cache.normalized.storeStaleDate +import com.apollographql.cache.normalized.storeExpirationDate import com.apollographql.mockserver.MockResponse import com.apollographql.mockserver.MockServer import programmatic.GetUserQuery @@ -45,7 +45,7 @@ class ServerSideCacheControlTest { normalizedCacheFactory = normalizedCacheFactory, cacheResolver = CacheControlCacheResolver(), ) - .storeStaleDate(true) + .storeExpirationDate(true) .serverUrl(mockServer.url()) .build() client.apolloStore.clearAll() @@ -65,7 +65,7 @@ class ServerSideCacheControlTest { var response: ApolloResponse - // store data with a stale date in the future + // store data with an expiration date in the future mockServer.enqueue( MockResponse.Builder() .addHeader("Cache-Control", "max-age=10") From 1e2fbb4318f857047888937faaa713afc3e2d37f Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 18 Oct 2024 18:01:08 +0200 Subject: [PATCH 4/7] Update documentation --- Writerside/doc.tree | 2 +- .../{expiration.md => cache-control.md} | 102 +++++++++++------- 2 files changed, 63 insertions(+), 41 deletions(-) rename Writerside/topics/{expiration.md => cache-control.md} (58%) diff --git a/Writerside/doc.tree b/Writerside/doc.tree index d659899d..a26bafe6 100644 --- a/Writerside/doc.tree +++ b/Writerside/doc.tree @@ -10,5 +10,5 @@ - + diff --git a/Writerside/topics/expiration.md b/Writerside/topics/cache-control.md similarity index 58% rename from Writerside/topics/expiration.md rename to Writerside/topics/cache-control.md index 1b1512bf..548e6026 100644 --- a/Writerside/topics/expiration.md +++ b/Writerside/topics/cache-control.md @@ -1,4 +1,8 @@ -# Expiration +# Cache control + +The cache control feature takes the freshness of fields into consideration when accessing the cache. This is also sometimes referred to as TTL (Time To Live) or expiration. + +Freshness can be configured by the server, by the client, or both. ## Server-controlled @@ -6,48 +10,50 @@ When receiving a response from the server, the [`Cache-Control` HTTP header](htt > Apollo Server can be configured to include the `Cache-Control` header in responses. See the [caching documentation](https://www.apollographql.com/docs/apollo-server/performance/caching/) for more information. -The cache can be configured to store the **expiration date** of the received fields in the corresponding records. To do so, call [`.storeExpirationDate(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/store-expiration-date.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.storeExpirationDate(storeExpirationDate:%20Boolean):%20T), and set your client's cache resolver to [`ExpirationCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-expiration-cache-resolver/index.html): +The cache can be configured to store the **expiration date** of the received fields in the corresponding records. To do so, call [`.storeExpirationDate(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/store-expiration-date.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.storeExpirationDate(storeExpirationDate:%20Boolean):%20T), and set your client's cache resolver to [ +`CacheControlCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-cache-control-cache-resolver/index.html): ```kotlin val apolloClient = ApolloClient.builder() - .serverUrl("https://example.com/graphql") - .storeExpirationDate(true) - .normalizedCache( - normalizedCacheFactory = /*...*/, - cacheResolver = ExpirationCacheResolver(), - ) - .build() + .serverUrl("https://example.com/graphql") + .storeExpirationDate(true) + .normalizedCache( + normalizedCacheFactory = /*...*/, + cacheResolver = CacheControlCacheResolver(), + ) + .build() ``` -**Expiration dates** will be stored and when a field is resolved, the cache resolver will check if the field is expired. If so, it will throw a `CacheMissException`. +**Expiration dates** will be stored and when a field is resolved, the cache resolver will check if the field is stale. If so, it will throw a `CacheMissException`. ## Client-controlled When storing fields, the cache can also store their **received date**. This date can then be compared to the current date when resolving a field to determine if its age is above its **maximum age**. -To store the **received date** of fields, call [`.storeReceivedDate(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/store-receive-date.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.storeReceiveDate(storeReceiveDate:%20Boolean):%20T), and set your client's cache resolver to [`ExpirationCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-expiration-cache-resolver/index.html): +To store the **received date** of fields, call [`.storeReceivedDate(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/store-receive-date.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.storeReceiveDate(storeReceiveDate:%20Boolean):%20T), and set your client's cache resolver to [ +`CacheControlCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-cache-control-cache-resolver/index.html): ```kotlin val apolloClient = ApolloClient.builder() - .serverUrl("https://example.com/graphql") - .storeReceivedDate(true) - .normalizedCache( - normalizedCacheFactory = /*...*/, - cacheResolver = ExpirationCacheResolver(maxAgeProvider), - ) - .build() + .serverUrl("https://example.com/graphql") + .storeReceivedDate(true) + .normalizedCache( + normalizedCacheFactory = /*...*/, + cacheResolver = CacheControlCacheResolver(maxAgeProvider), + ) + .build() ``` > Expiration dates and received dates can be both stored to combine server-controlled and client-controlled expiration strategies. -The **maximum age** of fields can be configured either programmatically, or declaratively in the schema. This is done by passing a [`MaxAgeProvider`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-max-age-provider/index.html?query=interface%20MaxAgeProvider) to the `ExpirationCacheResolver`. +The **maximum age** of fields can be configured either programmatically, or declaratively in the schema. This is done by passing a [`MaxAgeProvider`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-max-age-provider/index.html?query=interface%20MaxAgeProvider) to the `CacheControlCacheResolver`. ### Global max age -To set a global maximum age for all fields, pass a [`GlobalMaxAgeProvider`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-global-max-age-provider/index.html?query=class%20GlobalMaxAgeProvider(maxAge:%20Duration)%20:%20MaxAgeProvider) to the `ExpirationCacheResolver`: +To set a global maximum age for all fields, pass a [`GlobalMaxAgeProvider`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-global-max-age-provider/index.html?query=class%20GlobalMaxAgeProvider(maxAge:%20Duration)%20:%20MaxAgeProvider) to the `CacheControlCacheResolver`: ```kotlin - cacheResolver = ExpirationCacheResolver(GlobalMaxAgeProvider(1.hours)), + cacheResolver = CacheControlCacheResolver(GlobalMaxAgeProvider(1.hours)), ``` ### Max age per type and field @@ -57,17 +63,20 @@ To set a global maximum age for all fields, pass a [`GlobalMaxAgeProvider`](http Use a [`SchemaCoordinatesMaxAgeProvider`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-schema-coordinates-max-age-provider/index.html?query=class%20SchemaCoordinatesMaxAgeProvider(maxAges:%20Map%3CString,%20MaxAge%3E,%20defaultMaxAge:%20Duration)%20:%20MaxAgeProvider) to specify a max age per type and/or field: ```kotlin - cacheResolver = ExpirationCacheResolver(SchemaCoordinatesMaxAgeProvider( - maxAges = mapOf( - "Query.cachedBook" to MaxAge.Duration(60.seconds), - "Query.reader" to MaxAge.Duration(40.seconds), - "Post" to MaxAge.Duration(4.minutes), - "Book.cachedTitle" to MaxAge.Duration(30.seconds), - "Reader.book" to MaxAge.Inherit, - ), - defaultMaxAge = 1.hours, - )), +cacheResolver = CacheControlCacheResolver( + SchemaCoordinatesMaxAgeProvider( + maxAges = mapOf( + "Query.cachedBook" to MaxAge.Duration(60.seconds), + "Query.reader" to MaxAge.Duration(40.seconds), + "Post" to MaxAge.Duration(4.minutes), + "Book.cachedTitle" to MaxAge.Duration(30.seconds), + "Reader.book" to MaxAge.Inherit, + ), + defaultMaxAge = 1.hours, + ) +), ``` + Note that this provider replicates the behavior of Apollo Server's [`@cacheControl` directive](https://www.apollographql.com/docs/apollo-server/performance/caching/#default-maxage) when it comes to defaults and the meaning of `Inherit`. #### Declaratively @@ -110,19 +119,32 @@ apollo { This will generate a map in `yourpackage.cache.Cache.maxAges`, that you can pass to the `SchemaCoordinatesMaxAgeProvider`: ```kotlin - cacheResolver = ExpirationCacheResolver(SchemaCoordinatesMaxAgeProvider( - maxAges = Cache.maxAges, - defaultMaxAge = 1.hours, - )), +cacheResolver = CacheControlCacheResolver( + SchemaCoordinatesMaxAgeProvider( + maxAges = Cache.maxAges, + defaultMaxAge = 1.hours, + ) +), ``` ## Maximum staleness -If expired fields are acceptable up to a certain value, you can set a maximum staleness duration. This duration is the maximum time that an expired field will be resolved without resulting in a cache miss. To set this duration, call `.maxStale(Duration)` either globally on your client, or per operation: +If stale fields are acceptable up to a certain value, you can set a maximum staleness duration. This duration is the maximum time that a stale field will be resolved without resulting in a cache miss. To set this duration, call [`.maxStale(Duration)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/max-stale.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.maxStale(maxStale:%20Duration):%20T) either globally on your client, or per operation: ```kotlin -client.query(MyQuery()) - .fetchPolicy(FetchPolicy.CacheOnly) - .maxStale(1.hours) - .execute() +val response = client.query(MyQuery()) + .fetchPolicy(FetchPolicy.CacheOnly) + .maxStale(1.hours) + .execute() +``` + +### `isStale` + +With `maxStale`, it is possible to get data from the cache even if it is stale. To know if the response contains stale fields, you can check [`CacheInfo.isStale`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/-cache-info/is-stale.html): + +```kotlin +if (response.cacheInfo?.isStale == true) { + // The response contains at least one stale field +} + ``` From 850b3527bdb914dddefcbc00afb67d7b6eb9554e Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 18 Oct 2024 19:27:01 +0200 Subject: [PATCH 5/7] Add a test for queryNetworkIfStale --- gradle/libs.versions.toml | 1 + tests/cache-control/build.gradle.kts | 1 + .../ClientAndServerSideCacheControlTest.kt | 119 ++++++++++++++++++ 3 files changed, 121 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccbf7f72..fe0a1cee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ sqldelight-jvm = { group = "app.cash.sqldelight", name = "sqlite-driver", versio sqldelight-native = { group = "app.cash.sqldelight", name = "native-driver", version.ref = "sqldelight" } sqldelight-runtime = { group = "app.cash.sqldelight", name = "runtime", version.ref = "sqldelight" } truth = "com.google.truth:truth:1.1.3" +turbine = "app.cash.turbine:turbine:1.2.0" slf4j-nop = "org.slf4j:slf4j-nop:2.0.13" androidx-sqlite = { group = "androidx.sqlite", name = "sqlite", version.ref = "androidx-sqlite" } androidx-sqlite-framework = { group = "androidx.sqlite", name = "sqlite-framework", version.ref = "androidx-sqlite" } diff --git a/tests/cache-control/build.gradle.kts b/tests/cache-control/build.gradle.kts index d5aba3fb..76e07f49 100644 --- a/tests/cache-control/build.gradle.kts +++ b/tests/cache-control/build.gradle.kts @@ -48,6 +48,7 @@ kotlin { implementation(libs.apollo.testing.support) implementation(libs.apollo.mockserver) implementation(libs.kotlin.test) + implementation(libs.turbine) } } diff --git a/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt b/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt index 28852e19..7a796555 100644 --- a/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/ClientAndServerSideCacheControlTest.kt @@ -1,10 +1,14 @@ package test +import app.cash.turbine.test import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.ApolloResponse +import com.apollographql.apollo.api.Query import com.apollographql.apollo.exception.CacheMissException import com.apollographql.apollo.testing.internal.runTest import com.apollographql.cache.normalized.FetchPolicy import com.apollographql.cache.normalized.api.CacheControlCacheResolver +import com.apollographql.cache.normalized.api.GlobalMaxAgeProvider import com.apollographql.cache.normalized.api.MaxAge import com.apollographql.cache.normalized.api.NormalizedCacheFactory import com.apollographql.cache.normalized.api.SchemaCoordinatesMaxAgeProvider @@ -12,6 +16,7 @@ import com.apollographql.cache.normalized.apolloStore import com.apollographql.cache.normalized.cacheHeaders import com.apollographql.cache.normalized.cacheInfo import com.apollographql.cache.normalized.fetchPolicy +import com.apollographql.cache.normalized.isFromCache import com.apollographql.cache.normalized.maxStale import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.normalizedCache @@ -19,11 +24,16 @@ import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory import com.apollographql.cache.normalized.storeExpirationDate import com.apollographql.mockserver.MockResponse import com.apollographql.mockserver.MockServer +import com.apollographql.mockserver.enqueueString +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import programmatic.GetUserEmailQuery import programmatic.GetUserNameQuery import programmatic.GetUserQuery import kotlin.test.Test +import kotlin.test.assertFalse import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.seconds class ClientAndServerSideCacheControlTest { @@ -185,4 +195,113 @@ class ClientAndServerSideCacheControlTest { assertTrue(userEmailResponse.data?.user?.email == "john@doe.com") assertTrue(userEmailResponse.cacheInfo?.isStale == true) } + + @Test + fun queryNetworkIfStaleMemory() { + queryNetworkIfStale(MemoryCacheFactory()) + } + + @Test + fun queryNetworkIfStaleSql() { + queryNetworkIfStale(SqlNormalizedCacheFactory()) + } + + @Test + fun queryNetworkIfStaleChained() { + queryNetworkIfStale(MemoryCacheFactory().chain(SqlNormalizedCacheFactory())) + } + + private fun queryNetworkIfStale(normalizedCacheFactory: NormalizedCacheFactory) = runTest { + val mockServer = MockServer() + val client = ApolloClient.Builder() + .normalizedCache( + normalizedCacheFactory = normalizedCacheFactory, + cacheResolver = CacheControlCacheResolver(GlobalMaxAgeProvider(1.days)), + ) + .serverUrl(mockServer.url()) + .build() + client.apolloStore.clearAll() + + val data = """ + { + "data": { + "user": { + "name": "John", + "email": "john@doe.com", + "admin": true + } + } + } + """.trimIndent() + + val query = GetUserQuery() + + // Store data with a received date of now + mockServer.enqueueString(data) + client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly) + .cacheHeaders(receivedDate(currentTimeSeconds())) + .execute() + // Should get 1 item from the cache (fresh) + client.queryNetworkIfStale(query).test { + val response = awaitItem() + assertTrue(response.data?.user?.name == "John") + assertTrue(response.cacheInfo?.isCacheHit == true) + assertTrue(response.cacheInfo?.isStale == false) + awaitComplete() + } + + // Store data with a received date of 1.5 days ago + mockServer.enqueueString(data) + client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly) + .cacheHeaders(receivedDate(currentTimeSeconds() - 1.5.days.inWholeSeconds)) + .execute() + // Should get 2 items: 1 from the cache (stale within max stale) and 1 from the network + mockServer.enqueueString(data) + client.queryNetworkIfStale(query).test { + val response1 = awaitItem() + assertTrue(response1.data?.user?.name == "John") + assertTrue(response1.cacheInfo?.isCacheHit == true) + assertTrue(response1.cacheInfo?.isStale == true) + val response2 = awaitItem() + assertTrue(response2.data?.user?.name == "John") + assertFalse(response2.isFromCache) + awaitComplete() + } + + // Store data with a received date of 2.5 days ago + mockServer.enqueueString(data) + client.query(GetUserQuery()).fetchPolicy(FetchPolicy.NetworkOnly) + .cacheHeaders(receivedDate(currentTimeSeconds() - 2.5.days.inWholeSeconds)) + .execute() + // Should get 1 item from the network (stale above max stale) + mockServer.enqueueString(data) + client.queryNetworkIfStale(query).test { + val response = awaitItem() + assertTrue(response.data?.user?.name == "John") + assertFalse(response.isFromCache) + awaitComplete() + } + } +} + +private fun ApolloClient.queryNetworkIfStale(query: Query): Flow> = flow { + val cacheResponse = query(query).fetchPolicy(FetchPolicy.CacheOnly).maxStale(1.days).execute() + when { + cacheResponse.exception is CacheMissException -> { + // Stale above maxStale (or not cached): emit the network response only + emit(query(query).fetchPolicy(FetchPolicy.NetworkOnly).execute()) + } + + cacheResponse.cacheInfo?.isStale == true -> { + // Stale within maxStale: emit the cache response and also the network response + emit(cacheResponse) + emit(query(query).fetchPolicy(FetchPolicy.NetworkOnly).execute()) + } + + else -> { + // Fresh: emit the cache response only + emit(cacheResponse) + } + } } + From 2ae79543c4df5e80445446f6aba1217e708e9cdf Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 18 Oct 2024 19:43:22 +0200 Subject: [PATCH 6/7] Fix indentation in doc --- Writerside/topics/cache-control.md | 35 ++++++++++++++---------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/Writerside/topics/cache-control.md b/Writerside/topics/cache-control.md index 548e6026..c9d3cd96 100644 --- a/Writerside/topics/cache-control.md +++ b/Writerside/topics/cache-control.md @@ -10,18 +10,17 @@ When receiving a response from the server, the [`Cache-Control` HTTP header](htt > Apollo Server can be configured to include the `Cache-Control` header in responses. See the [caching documentation](https://www.apollographql.com/docs/apollo-server/performance/caching/) for more information. -The cache can be configured to store the **expiration date** of the received fields in the corresponding records. To do so, call [`.storeExpirationDate(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/store-expiration-date.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.storeExpirationDate(storeExpirationDate:%20Boolean):%20T), and set your client's cache resolver to [ -`CacheControlCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-cache-control-cache-resolver/index.html): +The cache can be configured to store the **expiration date** of the received fields in the corresponding records. To do so, call [`.storeExpirationDate(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/store-expiration-date.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.storeExpirationDate(storeExpirationDate:%20Boolean):%20T), and set your client's cache resolver to [`CacheControlCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-cache-control-cache-resolver/index.html): ```kotlin val apolloClient = ApolloClient.builder() - .serverUrl("https://example.com/graphql") - .storeExpirationDate(true) - .normalizedCache( - normalizedCacheFactory = /*...*/, - cacheResolver = CacheControlCacheResolver(), - ) - .build() + .serverUrl("https://example.com/graphql") + .storeExpirationDate(true) + .normalizedCache( + normalizedCacheFactory = /*...*/, + cacheResolver = CacheControlCacheResolver(), + ) + .build() ``` **Expiration dates** will be stored and when a field is resolved, the cache resolver will check if the field is stale. If so, it will throw a `CacheMissException`. @@ -30,18 +29,17 @@ val apolloClient = ApolloClient.builder() When storing fields, the cache can also store their **received date**. This date can then be compared to the current date when resolving a field to determine if its age is above its **maximum age**. -To store the **received date** of fields, call [`.storeReceivedDate(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/store-receive-date.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.storeReceiveDate(storeReceiveDate:%20Boolean):%20T), and set your client's cache resolver to [ -`CacheControlCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-cache-control-cache-resolver/index.html): +To store the **received date** of fields, call [`.storeReceivedDate(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized/store-receive-date.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.storeReceiveDate(storeReceiveDate:%20Boolean):%20T), and set your client's cache resolver to [`CacheControlCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/kdoc/normalized-cache-incubating/com.apollographql.cache.normalized.api/-cache-control-cache-resolver/index.html): ```kotlin val apolloClient = ApolloClient.builder() - .serverUrl("https://example.com/graphql") - .storeReceivedDate(true) - .normalizedCache( - normalizedCacheFactory = /*...*/, - cacheResolver = CacheControlCacheResolver(maxAgeProvider), - ) - .build() + .serverUrl("https://example.com/graphql") + .storeReceivedDate(true) + .normalizedCache( + normalizedCacheFactory = /*...*/, + cacheResolver = CacheControlCacheResolver(maxAgeProvider), + ) + .build() ``` > Expiration dates and received dates can be both stored to combine server-controlled and client-controlled expiration strategies. @@ -146,5 +144,4 @@ With `maxStale`, it is possible to get data from the cache even if it is stale. if (response.cacheInfo?.isStale == true) { // The response contains at least one stale field } - ``` From 85e660204455452df4c314743261b76d9e2f5208 Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 18 Oct 2024 20:00:53 +0200 Subject: [PATCH 7/7] Fix tests --- .../commonTest/kotlin/ConnectionPaginationTest.kt | 12 ++++++------ .../kotlin/ConnectionProgrammaticPaginationTest.kt | 12 ++++++------ .../kotlin/ConnectionWithNodesPaginationTest.kt | 12 ++++++------ .../commonTest/kotlin/CursorBasedPaginationTest.kt | 12 ++++++------ tests/pagination/src/commonTest/kotlin/EmbedTest.kt | 2 +- .../kotlin/OffsetBasedWithArrayPaginationTest.kt | 12 ++++++------ .../OffsetBasedWithPageAndInputPaginationTest.kt | 12 ++++++------ .../kotlin/OffsetBasedWithPagePaginationTest.kt | 12 ++++++------ 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/tests/pagination/src/commonTest/kotlin/ConnectionPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/ConnectionPaginationTest.kt index d768cf15..deca3d04 100644 --- a/tests/pagination/src/commonTest/kotlin/ConnectionPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/ConnectionPaginationTest.kt @@ -70,7 +70,7 @@ class ConnectionPaginationTest { } } apolloStore.writeOperation(query1, data1) - var dataFromStore = apolloStore.readOperation(query1) + var dataFromStore = apolloStore.readOperation(query1).data assertEquals(data1, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -99,7 +99,7 @@ class ConnectionPaginationTest { } } apolloStore.writeOperation(query2, data2) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data var expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -162,7 +162,7 @@ class ConnectionPaginationTest { } } apolloStore.writeOperation(query3, data3) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -237,7 +237,7 @@ class ConnectionPaginationTest { } } apolloStore.writeOperation(query4, data4) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -324,7 +324,7 @@ class ConnectionPaginationTest { } } apolloStore.writeOperation(query5, data5) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -340,7 +340,7 @@ class ConnectionPaginationTest { } } apolloStore.writeOperation(query6, data6) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) } diff --git a/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticPaginationTest.kt index a6c124a0..f20ea377 100644 --- a/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/ConnectionProgrammaticPaginationTest.kt @@ -80,7 +80,7 @@ class ConnectionProgrammaticPaginationTest { } } apolloStore.writeOperation(query1, data1) - var dataFromStore = apolloStore.readOperation(query1) + var dataFromStore = apolloStore.readOperation(query1).data assertEquals(data1, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -109,7 +109,7 @@ class ConnectionProgrammaticPaginationTest { } } apolloStore.writeOperation(query2, data2) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data var expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -172,7 +172,7 @@ class ConnectionProgrammaticPaginationTest { } } apolloStore.writeOperation(query3, data3) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -247,7 +247,7 @@ class ConnectionProgrammaticPaginationTest { } } apolloStore.writeOperation(query4, data4) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -334,7 +334,7 @@ class ConnectionProgrammaticPaginationTest { } } apolloStore.writeOperation(query5, data5) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -350,7 +350,7 @@ class ConnectionProgrammaticPaginationTest { } } apolloStore.writeOperation(query6, data6) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) } diff --git a/tests/pagination/src/commonTest/kotlin/ConnectionWithNodesPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/ConnectionWithNodesPaginationTest.kt index 9e7d2cec..50130b82 100644 --- a/tests/pagination/src/commonTest/kotlin/ConnectionWithNodesPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/ConnectionWithNodesPaginationTest.kt @@ -63,7 +63,7 @@ class ConnectionWithNodesPaginationTest { } } apolloStore.writeOperation(query1, data1) - var dataFromStore = apolloStore.readOperation(query1) + var dataFromStore = apolloStore.readOperation(query1).data assertEquals(data1, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -86,7 +86,7 @@ class ConnectionWithNodesPaginationTest { } } apolloStore.writeOperation(query2, data2) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data var expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -131,7 +131,7 @@ class ConnectionWithNodesPaginationTest { } } apolloStore.writeOperation(query3, data3) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -182,7 +182,7 @@ class ConnectionWithNodesPaginationTest { } } apolloStore.writeOperation(query4, data4) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -239,7 +239,7 @@ class ConnectionWithNodesPaginationTest { } } apolloStore.writeOperation(query5, data5) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -256,7 +256,7 @@ class ConnectionWithNodesPaginationTest { } } apolloStore.writeOperation(query6, data6) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) } diff --git a/tests/pagination/src/commonTest/kotlin/CursorBasedPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/CursorBasedPaginationTest.kt index 1248e1b0..1449e92a 100644 --- a/tests/pagination/src/commonTest/kotlin/CursorBasedPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/CursorBasedPaginationTest.kt @@ -77,7 +77,7 @@ class CursorBasedPaginationTest { } } apolloStore.writeOperation(query1, data1) - var dataFromStore = apolloStore.readOperation(query1) + var dataFromStore = apolloStore.readOperation(query1).data assertEquals(data1, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -106,7 +106,7 @@ class CursorBasedPaginationTest { } } apolloStore.writeOperation(query2, data2) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data var expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -169,7 +169,7 @@ class CursorBasedPaginationTest { } } apolloStore.writeOperation(query3, data3) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -244,7 +244,7 @@ class CursorBasedPaginationTest { } } apolloStore.writeOperation(query4, data4) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserConnection { pageInfo = buildPageInfo { @@ -331,7 +331,7 @@ class CursorBasedPaginationTest { } } apolloStore.writeOperation(query5, data5) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -347,7 +347,7 @@ class CursorBasedPaginationTest { } } apolloStore.writeOperation(query6, data6) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) } diff --git a/tests/pagination/src/commonTest/kotlin/EmbedTest.kt b/tests/pagination/src/commonTest/kotlin/EmbedTest.kt index 21047176..10674e39 100644 --- a/tests/pagination/src/commonTest/kotlin/EmbedTest.kt +++ b/tests/pagination/src/commonTest/kotlin/EmbedTest.kt @@ -50,7 +50,7 @@ class EmbedTest { ) ) client.apolloStore.writeOperation(query, data) - val dataFromStore = client.apolloStore.readOperation(query) + val dataFromStore = client.apolloStore.readOperation(query).data assertEquals(data, dataFromStore) } } diff --git a/tests/pagination/src/commonTest/kotlin/OffsetBasedWithArrayPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/OffsetBasedWithArrayPaginationTest.kt index c0eb3638..69925d93 100644 --- a/tests/pagination/src/commonTest/kotlin/OffsetBasedWithArrayPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/OffsetBasedWithArrayPaginationTest.kt @@ -54,7 +54,7 @@ class OffsetBasedWithArrayPaginationTest { ) } apolloStore.writeOperation(query1, data1) - var dataFromStore = apolloStore.readOperation(query1) + var dataFromStore = apolloStore.readOperation(query1).data assertEquals(data1, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -67,7 +67,7 @@ class OffsetBasedWithArrayPaginationTest { ) } apolloStore.writeOperation(query2, data2) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data var expectedData = UsersQuery.Data { users = listOf( buildUser { id = "42" }, @@ -89,7 +89,7 @@ class OffsetBasedWithArrayPaginationTest { ) } apolloStore.writeOperation(query3, data3) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = listOf( buildUser { id = "42" }, @@ -111,7 +111,7 @@ class OffsetBasedWithArrayPaginationTest { ) } apolloStore.writeOperation(query4, data4) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = listOf( buildUser { id = "40" }, @@ -135,7 +135,7 @@ class OffsetBasedWithArrayPaginationTest { ) } apolloStore.writeOperation(query5, data5) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -145,7 +145,7 @@ class OffsetBasedWithArrayPaginationTest { users = emptyList() } apolloStore.writeOperation(query6, data6) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) } diff --git a/tests/pagination/src/commonTest/kotlin/OffsetBasedWithPageAndInputPaginationTest.kt b/tests/pagination/src/commonTest/kotlin/OffsetBasedWithPageAndInputPaginationTest.kt index 120a4437..07b0bd70 100644 --- a/tests/pagination/src/commonTest/kotlin/OffsetBasedWithPageAndInputPaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/OffsetBasedWithPageAndInputPaginationTest.kt @@ -64,7 +64,7 @@ class OffsetBasedWithPageAndInputPaginationTest { } } apolloStore.writeOperation(query1, data1) - var dataFromStore = apolloStore.readOperation(query1) + var dataFromStore = apolloStore.readOperation(query1).data assertEquals(data1, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -79,7 +79,7 @@ class OffsetBasedWithPageAndInputPaginationTest { } } apolloStore.writeOperation(query2, data2) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data var expectedData = UsersQuery.Data { users = buildUserPage { users = listOf( @@ -105,7 +105,7 @@ class OffsetBasedWithPageAndInputPaginationTest { } } apolloStore.writeOperation(query3, data3) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserPage { users = listOf( @@ -131,7 +131,7 @@ class OffsetBasedWithPageAndInputPaginationTest { } } apolloStore.writeOperation(query4, data4) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserPage { users = listOf( @@ -159,7 +159,7 @@ class OffsetBasedWithPageAndInputPaginationTest { } } apolloStore.writeOperation(query5, data5) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -171,7 +171,7 @@ class OffsetBasedWithPageAndInputPaginationTest { } } apolloStore.writeOperation(query6, data6) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) } diff --git a/tests/pagination/src/commonTest/kotlin/OffsetBasedWithPagePaginationTest.kt b/tests/pagination/src/commonTest/kotlin/OffsetBasedWithPagePaginationTest.kt index d1dbb939..e6e4ddec 100644 --- a/tests/pagination/src/commonTest/kotlin/OffsetBasedWithPagePaginationTest.kt +++ b/tests/pagination/src/commonTest/kotlin/OffsetBasedWithPagePaginationTest.kt @@ -58,7 +58,7 @@ class OffsetBasedWithPagePaginationTest { } } apolloStore.writeOperation(query1, data1) - var dataFromStore = apolloStore.readOperation(query1) + var dataFromStore = apolloStore.readOperation(query1).data assertEquals(data1, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -73,7 +73,7 @@ class OffsetBasedWithPagePaginationTest { } } apolloStore.writeOperation(query2, data2) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data var expectedData = UsersQuery.Data { users = buildUserPage { users = listOf( @@ -99,7 +99,7 @@ class OffsetBasedWithPagePaginationTest { } } apolloStore.writeOperation(query3, data3) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserPage { users = listOf( @@ -125,7 +125,7 @@ class OffsetBasedWithPagePaginationTest { } } apolloStore.writeOperation(query4, data4) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data expectedData = UsersQuery.Data { users = buildUserPage { users = listOf( @@ -153,7 +153,7 @@ class OffsetBasedWithPagePaginationTest { } } apolloStore.writeOperation(query5, data5) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) @@ -165,7 +165,7 @@ class OffsetBasedWithPagePaginationTest { } } apolloStore.writeOperation(query6, data6) - dataFromStore = apolloStore.readOperation(query1) + dataFromStore = apolloStore.readOperation(query1).data assertEquals(data5, dataFromStore) assertChainedCachesAreEqual(apolloStore) }