diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.api b/normalized-cache-incubating/api/normalized-cache-incubating.api index 12312a9a..ef82d5cb 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.api @@ -45,8 +45,8 @@ public final class com/apollographql/cache/normalized/ApolloStore$ReadResult { } 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; + 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/api/MaxAgeProvider;)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;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;ILjava/lang/Object;)Lcom/apollographql/cache/normalized/ApolloStore; } public final class com/apollographql/cache/normalized/CacheInfo : com/apollographql/apollo/api/ExecutionContext$Element { @@ -148,8 +148,9 @@ public final class com/apollographql/cache/normalized/NormalizedCache { public static final fun configureApolloClientBuilder2 (Lcom/apollographql/apollo/ApolloClient$Builder;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/apollo/ApolloClient$Builder; public static final fun configureApolloClientBuilder2 (Lcom/apollographql/apollo/ApolloClient$Builder;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/apollo/ApolloClient$Builder; public static final fun configureApolloClientBuilder2 (Lcom/apollographql/apollo/ApolloClient$Builder;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/apollo/ApolloClient$Builder; - public static final fun configureApolloClientBuilder2 (Lcom/apollographql/apollo/ApolloClient$Builder;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;Z)Lcom/apollographql/apollo/ApolloClient$Builder; - public static synthetic fun configureApolloClientBuilder2$default (Lcom/apollographql/apollo/ApolloClient$Builder;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;ZILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder; + public static final fun configureApolloClientBuilder2 (Lcom/apollographql/apollo/ApolloClient$Builder;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/api/MaxAgeProvider;)Lcom/apollographql/apollo/ApolloClient$Builder; + public static final fun configureApolloClientBuilder2 (Lcom/apollographql/apollo/ApolloClient$Builder;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/api/MaxAgeProvider;Z)Lcom/apollographql/apollo/ApolloClient$Builder; + public static synthetic fun configureApolloClientBuilder2$default (Lcom/apollographql/apollo/ApolloClient$Builder;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/api/MaxAgeProvider;ZILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder; public static final fun doNotStore (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; public static final fun errorsReplaceCachedValues (Lcom/apollographql/apollo/api/MutableExecutionOptions;Z)Ljava/lang/Object; public static final fun fetchFromCache (Lcom/apollographql/apollo/api/ApolloRequest$Builder;Z)Lcom/apollographql/apollo/api/ApolloRequest$Builder; @@ -425,16 +426,26 @@ public final class com/apollographql/cache/normalized/api/MaxAgeContext { } public final class com/apollographql/cache/normalized/api/MaxAgeContext$Field { - public fun (Ljava/lang/String;Ljava/lang/String;Z)V + public fun (Ljava/lang/String;Lcom/apollographql/cache/normalized/api/MaxAgeContext$Type;)V public final fun getName ()Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public final fun isTypeComposite ()Z + public final fun getType ()Lcom/apollographql/cache/normalized/api/MaxAgeContext$Type; +} + +public final class com/apollographql/cache/normalized/api/MaxAgeContext$Type { + public fun (Ljava/lang/String;ZLjava/util/List;)V + public final fun getImplements ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public final fun isComposite ()Z } public abstract interface class com/apollographql/cache/normalized/api/MaxAgeProvider { public abstract fun getMaxAge-5sfh64U (Lcom/apollographql/cache/normalized/api/MaxAgeContext;)J } +public final class com/apollographql/cache/normalized/api/MaxAgeProviderKt { + public static final fun getDefaultMaxAgeProvider ()Lcom/apollographql/cache/normalized/api/MaxAgeProvider; +} + public abstract interface class com/apollographql/cache/normalized/api/MetadataGenerator { public abstract fun metadataForObject (Ljava/lang/Object;Lcom/apollographql/cache/normalized/api/MetadataGeneratorContext;)Ljava/util/Map; } @@ -575,10 +586,10 @@ public final class com/apollographql/cache/normalized/api/TypePolicyCacheKeyGene } public final class com/apollographql/cache/normalized/internal/NormalizerKt { - public static final fun normalized-MplSeLY (Lcom/apollographql/apollo/api/Executable$Data;Lcom/apollographql/apollo/api/Executable;Ljava/lang/String;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;)Ljava/util/Map; - public static final fun normalized-MplSeLY (Ljava/util/Map;Lcom/apollographql/apollo/api/Executable;Ljava/lang/String;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;)Ljava/util/Map; - public static synthetic fun normalized-MplSeLY$default (Lcom/apollographql/apollo/api/Executable$Data;Lcom/apollographql/apollo/api/Executable;Ljava/lang/String;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;ILjava/lang/Object;)Ljava/util/Map; - public static synthetic fun normalized-MplSeLY$default (Ljava/util/Map;Lcom/apollographql/apollo/api/Executable;Ljava/lang/String;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;ILjava/lang/Object;)Ljava/util/Map; + public static final fun normalized-7h_XKoo (Lcom/apollographql/apollo/api/Executable$Data;Lcom/apollographql/apollo/api/Executable;Ljava/lang/String;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;)Ljava/util/Map; + public static final fun normalized-7h_XKoo (Ljava/util/Map;Lcom/apollographql/apollo/api/Executable;Ljava/lang/String;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;)Ljava/util/Map; + public static synthetic fun normalized-7h_XKoo$default (Lcom/apollographql/apollo/api/Executable$Data;Lcom/apollographql/apollo/api/Executable;Ljava/lang/String;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;ILjava/lang/Object;)Ljava/util/Map; + public static synthetic fun normalized-7h_XKoo$default (Ljava/util/Map;Lcom/apollographql/apollo/api/Executable;Ljava/lang/String;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheKeyGenerator;Lcom/apollographql/cache/normalized/api/MetadataGenerator;Lcom/apollographql/cache/normalized/api/FieldKeyGenerator;Lcom/apollographql/cache/normalized/api/EmbeddedFieldsProvider;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;ILjava/lang/Object;)Ljava/util/Map; } public final class com/apollographql/cache/normalized/memory/MemoryCache : com/apollographql/cache/normalized/api/NormalizedCache { diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api index f4822de5..73743aac 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api @@ -272,14 +272,23 @@ final class com.apollographql.cache.normalized.api/MaxAgeContext { // com.apollo final fun (): kotlin.collections/List // com.apollographql.cache.normalized.api/MaxAgeContext.fieldPath.|(){}[0] final class Field { // com.apollographql.cache.normalized.api/MaxAgeContext.Field|null[0] - constructor (kotlin/String, kotlin/String, kotlin/Boolean) // com.apollographql.cache.normalized.api/MaxAgeContext.Field.|(kotlin.String;kotlin.String;kotlin.Boolean){}[0] + constructor (kotlin/String, com.apollographql.cache.normalized.api/MaxAgeContext.Type) // com.apollographql.cache.normalized.api/MaxAgeContext.Field.|(kotlin.String;com.apollographql.cache.normalized.api.MaxAgeContext.Type){}[0] - final val isTypeComposite // com.apollographql.cache.normalized.api/MaxAgeContext.Field.isTypeComposite|{}isTypeComposite[0] - final fun (): kotlin/Boolean // com.apollographql.cache.normalized.api/MaxAgeContext.Field.isTypeComposite.|(){}[0] final val name // com.apollographql.cache.normalized.api/MaxAgeContext.Field.name|{}name[0] final fun (): kotlin/String // com.apollographql.cache.normalized.api/MaxAgeContext.Field.name.|(){}[0] final val type // com.apollographql.cache.normalized.api/MaxAgeContext.Field.type|{}type[0] - final fun (): kotlin/String // com.apollographql.cache.normalized.api/MaxAgeContext.Field.type.|(){}[0] + final fun (): com.apollographql.cache.normalized.api/MaxAgeContext.Type // com.apollographql.cache.normalized.api/MaxAgeContext.Field.type.|(){}[0] + } + + final class Type { // com.apollographql.cache.normalized.api/MaxAgeContext.Type|null[0] + constructor (kotlin/String, kotlin/Boolean, kotlin.collections/List) // com.apollographql.cache.normalized.api/MaxAgeContext.Type.|(kotlin.String;kotlin.Boolean;kotlin.collections.List){}[0] + + final val implements // com.apollographql.cache.normalized.api/MaxAgeContext.Type.implements|{}implements[0] + final fun (): kotlin.collections/List // com.apollographql.cache.normalized.api/MaxAgeContext.Type.implements.|(){}[0] + final val isComposite // com.apollographql.cache.normalized.api/MaxAgeContext.Type.isComposite|{}isComposite[0] + final fun (): kotlin/Boolean // com.apollographql.cache.normalized.api/MaxAgeContext.Type.isComposite.|(){}[0] + final val name // com.apollographql.cache.normalized.api/MaxAgeContext.Type.name|{}name[0] + final fun (): kotlin/String // com.apollographql.cache.normalized.api/MaxAgeContext.Type.name.|(){}[0] } } @@ -530,6 +539,8 @@ final const val com.apollographql.cache.normalized/VERSION // com.apollographql. final val com.apollographql.cache.normalized.api/ConnectionRecordMerger // com.apollographql.cache.normalized.api/ConnectionRecordMerger|{}ConnectionRecordMerger[0] final fun (): com.apollographql.cache.normalized.api/FieldRecordMerger // com.apollographql.cache.normalized.api/ConnectionRecordMerger.|(){}[0] +final val com.apollographql.cache.normalized.api/DefaultMaxAgeProvider // com.apollographql.cache.normalized.api/DefaultMaxAgeProvider|{}DefaultMaxAgeProvider[0] + final fun (): com.apollographql.cache.normalized.api/MaxAgeProvider // com.apollographql.cache.normalized.api/DefaultMaxAgeProvider.|(){}[0] final val com.apollographql.cache.normalized/CacheAndNetworkInterceptor // com.apollographql.cache.normalized/CacheAndNetworkInterceptor|{}CacheAndNetworkInterceptor[0] final fun (): com.apollographql.apollo.interceptor/ApolloInterceptor // com.apollographql.cache.normalized/CacheAndNetworkInterceptor.|(){}[0] final val com.apollographql.cache.normalized/CacheFirstInterceptor // com.apollographql.cache.normalized/CacheFirstInterceptor|{}CacheFirstInterceptor[0] @@ -554,7 +565,7 @@ final val com.apollographql.cache.normalized/isFromCache // com.apollographql.ca final fun <#A1: com.apollographql.apollo.api/Operation.Data> (com.apollographql.apollo.api/ApolloResponse<#A1>).(): kotlin/Boolean // com.apollographql.cache.normalized/isFromCache.|@com.apollographql.apollo.api.ApolloResponse<0:0>(){0§}[0] final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/logCacheMisses(kotlin/Function1 = ...): 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/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 = ..., com.apollographql.cache.normalized.api/MaxAgeProvider = ..., 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;com.apollographql.cache.normalized.api.MaxAgeProvider;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/CacheKey).com.apollographql.cache.normalized.api/isRootKey(): kotlin/Boolean // com.apollographql.cache.normalized.api/isRootKey|isRootKey@com.apollographql.cache.normalized.api.CacheKey(){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/allRecords(): kotlin.collections/Map // com.apollographql.cache.normalized/allRecords|allRecords@com.apollographql.cache.normalized.api.NormalizedCache(){}[0] @@ -573,8 +584,8 @@ final fun (com.apollographql.cache.normalized/ApolloStore).com.apollographql.cac 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 (kotlin.collections/Map).com.apollographql.cache.normalized/getReachableCacheKeys(): kotlin.collections/Set // com.apollographql.cache.normalized/getReachableCacheKeys|getReachableCacheKeys@kotlin.collections.Map(){}[0] final fun <#A: com.apollographql.apollo.api/Executable.Data> (#A).com.apollographql.cache.normalized.api/withErrors(com.apollographql.apollo.api/Executable<#A>, kotlin.collections/List?, com.apollographql.apollo.api/CustomScalarAdapters = ...): kotlin.collections/Map // com.apollographql.cache.normalized.api/withErrors|withErrors@0:0(com.apollographql.apollo.api.Executable<0:0>;kotlin.collections.List?;com.apollographql.apollo.api.CustomScalarAdapters){0§}[0] -final fun <#A: com.apollographql.apollo.api/Executable.Data> (#A).com.apollographql.cache.normalized.internal/normalized(com.apollographql.apollo.api/Executable<#A>, com.apollographql.cache.normalized.api/CacheKey = ..., 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.collections/Map // com.apollographql.cache.normalized.internal/normalized|normalized@0:0(com.apollographql.apollo.api.Executable<0:0>;com.apollographql.cache.normalized.api.CacheKey;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){0§}[0] -final fun <#A: com.apollographql.apollo.api/Executable.Data> (kotlin.collections/Map).com.apollographql.cache.normalized.internal/normalized(com.apollographql.apollo.api/Executable<#A>, com.apollographql.cache.normalized.api/CacheKey = ..., 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.collections/Map // com.apollographql.cache.normalized.internal/normalized|normalized@kotlin.collections.Map(com.apollographql.apollo.api.Executable<0:0>;com.apollographql.cache.normalized.api.CacheKey;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){0§}[0] +final fun <#A: com.apollographql.apollo.api/Executable.Data> (#A).com.apollographql.cache.normalized.internal/normalized(com.apollographql.apollo.api/Executable<#A>, com.apollographql.cache.normalized.api/CacheKey = ..., 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 = ..., com.apollographql.cache.normalized.api/MaxAgeProvider = ...): kotlin.collections/Map // com.apollographql.cache.normalized.internal/normalized|normalized@0:0(com.apollographql.apollo.api.Executable<0:0>;com.apollographql.cache.normalized.api.CacheKey;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;com.apollographql.cache.normalized.api.MaxAgeProvider){0§}[0] +final fun <#A: com.apollographql.apollo.api/Executable.Data> (kotlin.collections/Map).com.apollographql.cache.normalized.internal/normalized(com.apollographql.apollo.api/Executable<#A>, com.apollographql.cache.normalized.api/CacheKey = ..., 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 = ..., com.apollographql.cache.normalized.api/MaxAgeProvider = ...): kotlin.collections/Map // com.apollographql.cache.normalized.internal/normalized|normalized@kotlin.collections.Map(com.apollographql.apollo.api.Executable<0:0>;com.apollographql.cache.normalized.api.CacheKey;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;com.apollographql.cache.normalized.api.MaxAgeProvider){0§}[0] final fun <#A: com.apollographql.apollo.api/Mutation.Data> (com.apollographql.apollo.api/ApolloRequest.Builder<#A>).com.apollographql.cache.normalized/optimisticUpdates(#A): com.apollographql.apollo.api/ApolloRequest.Builder<#A> // com.apollographql.cache.normalized/optimisticUpdates|optimisticUpdates@com.apollographql.apollo.api.ApolloRequest.Builder<0:0>(0:0){0§}[0] final fun <#A: com.apollographql.apollo.api/Mutation.Data> (com.apollographql.apollo/ApolloCall<#A>).com.apollographql.cache.normalized/optimisticUpdates(#A): com.apollographql.apollo/ApolloCall<#A> // com.apollographql.cache.normalized/optimisticUpdates|optimisticUpdates@com.apollographql.apollo.ApolloCall<0:0>(0:0){0§}[0] final fun <#A: com.apollographql.apollo.api/Operation.Data> (com.apollographql.apollo.api/ApolloRequest.Builder<#A>).com.apollographql.cache.normalized/fetchFromCache(kotlin/Boolean): com.apollographql.apollo.api/ApolloRequest.Builder<#A> // com.apollographql.cache.normalized/fetchFromCache|fetchFromCache@com.apollographql.apollo.api.ApolloRequest.Builder<0:0>(kotlin.Boolean){0§}[0] @@ -596,4 +607,4 @@ 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/storePartialResponses(kotlin/Boolean): kotlin/Nothing // 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/storeReceivedDate(kotlin/Boolean): #A // com.apollographql.cache.normalized/storeReceivedDate|storeReceivedDate@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 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.api/MaxAgeProvider = ...): 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;com.apollographql.cache.normalized.api.MaxAgeProvider){}[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 3cd8c925..f2fc4d79 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 @@ -16,11 +16,13 @@ import com.apollographql.cache.normalized.api.CacheResolver import com.apollographql.cache.normalized.api.DataWithErrors import com.apollographql.cache.normalized.api.DefaultEmbeddedFieldsProvider import com.apollographql.cache.normalized.api.DefaultFieldKeyGenerator +import com.apollographql.cache.normalized.api.DefaultMaxAgeProvider import com.apollographql.cache.normalized.api.DefaultRecordMerger import com.apollographql.cache.normalized.api.EmbeddedFieldsProvider import com.apollographql.cache.normalized.api.EmptyMetadataGenerator import com.apollographql.cache.normalized.api.FieldKeyGenerator import com.apollographql.cache.normalized.api.FieldPolicyCacheResolver +import com.apollographql.cache.normalized.api.MaxAgeProvider import com.apollographql.cache.normalized.api.MetadataGenerator import com.apollographql.cache.normalized.api.NormalizedCache import com.apollographql.cache.normalized.api.NormalizedCacheFactory @@ -307,6 +309,7 @@ fun ApolloStore( recordMerger: RecordMerger = DefaultRecordMerger, fieldKeyGenerator: FieldKeyGenerator = DefaultFieldKeyGenerator, embeddedFieldsProvider: EmbeddedFieldsProvider = DefaultEmbeddedFieldsProvider, + maxAgeProvider: MaxAgeProvider = DefaultMaxAgeProvider, ): ApolloStore = DefaultApolloStore( normalizedCacheFactory = normalizedCacheFactory, cacheKeyGenerator = cacheKeyGenerator, @@ -315,6 +318,7 @@ fun ApolloStore( recordMerger = recordMerger, fieldKeyGenerator = fieldKeyGenerator, embeddedFieldsProvider = embeddedFieldsProvider, + maxAgeProvider = maxAgeProvider, ) /** 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 a0962a0d..2ca1daea 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 @@ -27,11 +27,13 @@ import com.apollographql.cache.normalized.api.CacheKeyGenerator import com.apollographql.cache.normalized.api.CacheResolver import com.apollographql.cache.normalized.api.DefaultEmbeddedFieldsProvider import com.apollographql.cache.normalized.api.DefaultFieldKeyGenerator +import com.apollographql.cache.normalized.api.DefaultMaxAgeProvider import com.apollographql.cache.normalized.api.DefaultRecordMerger import com.apollographql.cache.normalized.api.EmbeddedFieldsProvider import com.apollographql.cache.normalized.api.EmptyMetadataGenerator import com.apollographql.cache.normalized.api.FieldKeyGenerator import com.apollographql.cache.normalized.api.FieldPolicyCacheResolver +import com.apollographql.cache.normalized.api.MaxAgeProvider import com.apollographql.cache.normalized.api.MetadataGenerator import com.apollographql.cache.normalized.api.NormalizedCacheFactory import com.apollographql.cache.normalized.api.RecordMerger @@ -70,6 +72,7 @@ fun ApolloClient.Builder.normalizedCache( recordMerger: RecordMerger = DefaultRecordMerger, fieldKeyGenerator: FieldKeyGenerator = DefaultFieldKeyGenerator, embeddedFieldsProvider: EmbeddedFieldsProvider = DefaultEmbeddedFieldsProvider, + maxAgeProvider: MaxAgeProvider = DefaultMaxAgeProvider, writeToCacheAsynchronously: Boolean = false, ): ApolloClient.Builder { return store( @@ -80,7 +83,8 @@ fun ApolloClient.Builder.normalizedCache( cacheResolver = cacheResolver, recordMerger = recordMerger, fieldKeyGenerator = fieldKeyGenerator, - embeddedFieldsProvider = embeddedFieldsProvider + embeddedFieldsProvider = embeddedFieldsProvider, + maxAgeProvider = maxAgeProvider, ), writeToCacheAsynchronously ) } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt index fb9f5b69..3f99c737 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt @@ -107,8 +107,22 @@ private fun NormalizedCache.removeStaleFields( val maxAge = maxAgeProvider.getMaxAge( MaxAgeContext( listOf( - MaxAgeContext.Field(name = "", type = record["__typename"] as? String ?: "", isTypeComposite = true), - MaxAgeContext.Field(name = field.key, type = field.value.guessType(allRecords), isTypeComposite = field.value is CacheKey), + MaxAgeContext.Field( + name = "", + type = MaxAgeContext.Type( + name = record["__typename"] as? String ?: "", + isComposite = true, + implements = emptyList(), + ) + ), + MaxAgeContext.Field( + name = field.key, + type = MaxAgeContext.Type( + name = field.value.guessType(allRecords), + isComposite = field.value is CacheKey, + implements = emptyList(), + ), + ) ) ) ).inWholeSeconds 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 ccef9cc0..6a578b47 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 @@ -3,7 +3,6 @@ package com.apollographql.cache.normalized.api import com.apollographql.apollo.api.CompiledField import com.apollographql.apollo.api.Executable import com.apollographql.apollo.api.MutableExecutionOptions -import com.apollographql.apollo.api.isComposite import com.apollographql.apollo.exception.CacheMissException import com.apollographql.apollo.mpp.currentTimeMillis import com.apollographql.cache.normalized.api.CacheResolver.ResolvedValue @@ -11,7 +10,6 @@ import com.apollographql.cache.normalized.maxStale import com.apollographql.cache.normalized.storeExpirationDate import com.apollographql.cache.normalized.storeReceivedDate import kotlin.jvm.JvmSuppressWildcards -import kotlin.time.Duration /** * Controls how fields are resolved from the cache. @@ -169,7 +167,7 @@ class CacheControlCacheResolver( constructor( delegateResolver: CacheResolver = FieldPolicyCacheResolver, ) : this( - maxAgeProvider = GlobalMaxAgeProvider(Duration.INFINITE), + maxAgeProvider = DefaultMaxAgeProvider, delegateResolver = delegateResolver, ) @@ -183,7 +181,7 @@ class CacheControlCacheResolver( val currentDate = currentTimeMillis() / 1000 val age = currentDate - receivedDate val fieldPath = context.path.map { - MaxAgeContext.Field(name = it.name, type = it.type.rawType().name, isTypeComposite = it.type.rawType().isComposite()) + it.toMaxAgeField() } val maxAge = maxAgeProvider.getMaxAge(MaxAgeContext(fieldPath)).inWholeSeconds val staleDuration = age - maxAge @@ -228,7 +226,7 @@ class CacheControlCacheResolver( } /** - * A cache resolver that uses `@fieldPolicy` annotations to resolve fields and delegates to [DefaultCacheResolver] otherwise + * A cache resolver that uses `@fieldPolicy` directives to resolve fields and delegates to [DefaultCacheResolver] otherwise */ object FieldPolicyCacheResolver : CacheResolver { override fun resolveField(context: ResolverContext): Any? { diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/MaxAgeProvider.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/MaxAgeProvider.kt index 59917286..c6443c76 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/MaxAgeProvider.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/api/MaxAgeProvider.kt @@ -1,5 +1,9 @@ package com.apollographql.cache.normalized.api +import com.apollographql.apollo.api.CompiledField +import com.apollographql.apollo.api.InterfaceType +import com.apollographql.apollo.api.ObjectType +import com.apollographql.apollo.api.isComposite import kotlin.time.Duration interface MaxAgeProvider { @@ -18,8 +22,13 @@ class MaxAgeContext( ) { class Field( val name: String, - val type: String, - val isTypeComposite: Boolean, + val type: Type, + ) + + class Type( + val name: String, + val isComposite: Boolean, + val implements: List, ) } @@ -30,6 +39,8 @@ class GlobalMaxAgeProvider(private val maxAge: Duration) : MaxAgeProvider { override fun getMaxAge(maxAgeContext: MaxAgeContext): Duration = maxAge } +val DefaultMaxAgeProvider: MaxAgeProvider = GlobalMaxAgeProvider(Duration.INFINITE) + sealed interface MaxAge { class Duration(val duration: kotlin.time.Duration) : MaxAge data object Inherit : MaxAge @@ -60,9 +71,13 @@ class SchemaCoordinatesMaxAgeProvider( } val fieldName = maxAgeContext.fieldPath.last().name - val fieldParentTypeName = maxAgeContext.fieldPath[maxAgeContext.fieldPath.lastIndex - 1].type - val fieldCoordinates = "$fieldParentTypeName.$fieldName" - val computedFieldMaxAge = when (val fieldMaxAge = maxAges[fieldCoordinates]) { + val fieldParentType = maxAgeContext.fieldPath[maxAgeContext.fieldPath.lastIndex - 1].type + val fieldParentTypeNames = (listOf(fieldParentType) + fieldParentType.allImplements()).map { it.name } + val fieldMaxAge = fieldParentTypeNames.firstNotNullOfOrNull { typeName -> + val fieldCoordinates = "$typeName.$fieldName" + maxAges[fieldCoordinates] + } + val computedFieldMaxAge = when (fieldMaxAge) { is MaxAge.Duration -> { fieldMaxAge.duration } @@ -87,8 +102,9 @@ class SchemaCoordinatesMaxAgeProvider( private fun getTypeMaxAge(maxAgeContext: MaxAgeContext): Duration { val field = maxAgeContext.fieldPath.last() - val fieldTypeName = field.type - return when (val typeMaxAge = maxAges[fieldTypeName]) { + val fieldTypeNames = (listOf(field.type) + field.type.allImplements()).map { it.name } + val typeMaxAge = fieldTypeNames.firstNotNullOfOrNull { maxAges[it] } + return when (typeMaxAge) { is MaxAge.Duration -> { typeMaxAge.duration } @@ -110,10 +126,57 @@ class SchemaCoordinatesMaxAgeProvider( private fun getFallbackMaxAge(maxAgeContext: MaxAgeContext): Duration { val field = maxAgeContext.fieldPath.last() val isRootField = maxAgeContext.fieldPath.size == 2 - return if (isRootField || field.isTypeComposite) { + return if (isRootField || field.type.isComposite) { defaultMaxAge } else { getParentMaxAge(maxAgeContext) } } } + +/** + * Returns all the implemented types. + * We go breadth first so they are returned in the order they are defined in the schema. + */ +private fun MaxAgeContext.Type.allImplements(): List { + val allImplements = mutableListOf() + val queue = ArrayDeque() + queue.addAll(implements) + while (queue.isNotEmpty()) { + val current = queue.removeFirst() + allImplements.add(current) + queue.addAll(current.implements) + } + return allImplements.distinct() +} + +internal fun CompiledField.toMaxAgeField(): MaxAgeContext.Field { + val type = type.rawType() + val implements: List = when (type) { + is ObjectType -> { + type.implements.map { it.toMaxAgeType() } + } + + is InterfaceType -> { + type.implements.map { it.toMaxAgeType() } + } + + else -> { + emptyList() + } + } + return MaxAgeContext.Field( + name = name, + type = MaxAgeContext.Type( + name = type.name, + isComposite = type.isComposite(), + implements = implements, + ) + ) +} + +private fun InterfaceType.toMaxAgeType(): MaxAgeContext.Type = MaxAgeContext.Type( + name = name, + isComposite = true, + implements = implements.map { it.toMaxAgeType() }, +) 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 8c73a790..9df925f8 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 @@ -20,6 +20,7 @@ import com.apollographql.cache.normalized.api.CacheResolver import com.apollographql.cache.normalized.api.DataWithErrors import com.apollographql.cache.normalized.api.EmbeddedFieldsProvider import com.apollographql.cache.normalized.api.FieldKeyGenerator +import com.apollographql.cache.normalized.api.MaxAgeProvider import com.apollographql.cache.normalized.api.MetadataGenerator import com.apollographql.cache.normalized.api.NormalizedCache import com.apollographql.cache.normalized.api.NormalizedCacheFactory @@ -44,6 +45,7 @@ internal class DefaultApolloStore( private val cacheResolver: CacheResolver, private val recordMerger: RecordMerger, private val embeddedFieldsProvider: EmbeddedFieldsProvider, + private val maxAgeProvider: MaxAgeProvider, ) : ApolloStore { private val changedKeysEvents = MutableSharedFlow>( /** @@ -115,6 +117,7 @@ internal class DefaultApolloStore( metadataGenerator = metadataGenerator, fieldKeyGenerator = fieldKeyGenerator, embeddedFieldsProvider = embeddedFieldsProvider, + maxAgeProvider = maxAgeProvider, ) } diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/Normalizer.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/Normalizer.kt index 355f436e..6f155b54 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/Normalizer.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/internal/Normalizer.kt @@ -18,18 +18,23 @@ import com.apollographql.cache.normalized.api.CacheKeyGeneratorContext import com.apollographql.cache.normalized.api.DataWithErrors import com.apollographql.cache.normalized.api.DefaultEmbeddedFieldsProvider import com.apollographql.cache.normalized.api.DefaultFieldKeyGenerator +import com.apollographql.cache.normalized.api.DefaultMaxAgeProvider import com.apollographql.cache.normalized.api.EmbeddedFieldsContext import com.apollographql.cache.normalized.api.EmbeddedFieldsProvider import com.apollographql.cache.normalized.api.EmptyMetadataGenerator import com.apollographql.cache.normalized.api.FieldKeyContext import com.apollographql.cache.normalized.api.FieldKeyGenerator +import com.apollographql.cache.normalized.api.MaxAgeContext +import com.apollographql.cache.normalized.api.MaxAgeProvider import com.apollographql.cache.normalized.api.MetadataGenerator import com.apollographql.cache.normalized.api.MetadataGeneratorContext import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.api.TypePolicyCacheKeyGenerator import com.apollographql.cache.normalized.api.append import com.apollographql.cache.normalized.api.isRootKey +import com.apollographql.cache.normalized.api.toMaxAgeField import com.apollographql.cache.normalized.api.withErrors +import kotlin.time.Duration /** * A [Normalizer] takes a [Map] and turns them into a flat list of [Record] @@ -42,6 +47,7 @@ internal class Normalizer( private val metadataGenerator: MetadataGenerator, private val fieldKeyGenerator: FieldKeyGenerator, private val embeddedFieldsProvider: EmbeddedFieldsProvider, + private val maxAgeProvider: MaxAgeProvider, ) { private val records = mutableMapOf() @@ -49,8 +55,9 @@ internal class Normalizer( map: DataWithErrors, selections: List, parentType: CompiledNamedType, + fieldPath: List, ): Map { - buildRecord(map, rootKey, selections, parentType) + buildRecord(map, rootKey, selections, parentType, fieldPath) return records } @@ -61,8 +68,6 @@ internal class Normalizer( ) /** - * - * * @param obj the json node representing the object * @param key the key for this record * @param selections the selections queried on this object @@ -73,6 +78,7 @@ internal class Normalizer( key: CacheKey, selections: List, parentType: CompiledNamedType, + fieldPath: List, ): Map { val typename = obj["__typename"] as? String @@ -107,13 +113,20 @@ internal class Normalizer( } else { key } + val newFieldPath = fieldPath + mergedField val value = replaceObjects( value = entry.value, - field = mergedField, + fieldPath = newFieldPath, type_ = mergedField.type, path = base?.append(fieldKey) ?: CacheKey(fieldKey), embeddedFields = embeddedFieldsProvider.getEmbeddedFields(EmbeddedFieldsContext(parentType)), ) + val maxAge = maxAgeProvider.getMaxAge(MaxAgeContext(newFieldPath.map { it.toMaxAgeField() })) + if (maxAge == Duration.ZERO) { + // This field should not be stored, do not include it in the record. + return@mapNotNull null + } + val metadata = if (entry.value is Error) { emptyMap() } else { @@ -136,8 +149,9 @@ internal class Normalizer( cacheKey: CacheKey, selections: List, parentType: CompiledNamedType, + fieldPath: List, ): CacheKey { - val fields = buildFields(obj, cacheKey, selections, parentType) + val fields = buildFields(obj, cacheKey, selections, parentType, fieldPath) val fieldValues = fields.mapValues { it.value.fieldValue } val metadata = fields.mapValues { it.value.metadata }.filterValues { it.isNotEmpty() } val record = Record( @@ -168,17 +182,18 @@ internal class Normalizer( * This function builds the list of records as a side effect * * @param value a json value from the response. Can be [com.apollographql.apollo.api.json.ApolloJsonElement] or [Error] - * @param field the field currently being normalized + * @param fieldPath the path to the field currently being normalized * @param type_ the type currently being normalized. It can be different from `field.type` for lists. * @param embeddedFields the embedded fields of the parent */ private fun replaceObjects( value: Any?, - field: CompiledField, + fieldPath: List, type_: CompiledType, path: CacheKey, embeddedFields: List, ): Any? { + val field = fieldPath.last() /** * Remove the NotNull decoration if needed */ @@ -199,7 +214,7 @@ internal class Normalizer( type is CompiledListType -> { check(value is List<*>) value.mapIndexed { index, item -> - replaceObjects(item, field, type.ofType, path.append(index.toString()), embeddedFields) + replaceObjects(item, fieldPath, type.ofType, path.append(index.toString()), embeddedFields) } } // Check for [isComposite] as we don't want to build a record for json scalars @@ -215,10 +230,10 @@ internal class Normalizer( key = path } if (embeddedFields.contains(field.name)) { - buildFields(value, key, field.selections, field.type.rawType()) + buildFields(value, key, field.selections, field.type.rawType(), fieldPath) .mapValues { it.value.fieldValue } } else { - buildRecord(value, key, field.selections, field.type.rawType()) + buildRecord(value, key, field.selections, field.type.rawType(), fieldPath) } } @@ -272,9 +287,10 @@ fun D.normalized( metadataGenerator: MetadataGenerator = EmptyMetadataGenerator, fieldKeyGenerator: FieldKeyGenerator = DefaultFieldKeyGenerator, embeddedFieldsProvider: EmbeddedFieldsProvider = DefaultEmbeddedFieldsProvider, + maxAgeProvider: MaxAgeProvider = DefaultMaxAgeProvider, ): Map { val dataWithErrors = this.withErrors(executable, null, customScalarAdapters) - return dataWithErrors.normalized(executable, rootKey, customScalarAdapters, cacheKeyGenerator, metadataGenerator, fieldKeyGenerator, embeddedFieldsProvider) + return dataWithErrors.normalized(executable, rootKey, customScalarAdapters, cacheKeyGenerator, metadataGenerator, fieldKeyGenerator, embeddedFieldsProvider, maxAgeProvider) } /** @@ -288,8 +304,9 @@ fun DataWithErrors.normalized( metadataGenerator: MetadataGenerator = EmptyMetadataGenerator, fieldKeyGenerator: FieldKeyGenerator = DefaultFieldKeyGenerator, embeddedFieldsProvider: EmbeddedFieldsProvider = DefaultEmbeddedFieldsProvider, + maxAgeProvider: MaxAgeProvider = DefaultMaxAgeProvider, ): Map { val variables = executable.variables(customScalarAdapters, withDefaultValues = true) - return Normalizer(variables, rootKey, cacheKeyGenerator, metadataGenerator, fieldKeyGenerator, embeddedFieldsProvider) - .normalize(this, executable.rootField().selections, executable.rootField().type.rawType()) + return Normalizer(variables, rootKey, cacheKeyGenerator, metadataGenerator, fieldKeyGenerator, embeddedFieldsProvider, maxAgeProvider) + .normalize(this, executable.rootField().selections, executable.rootField().type.rawType(), listOf(executable.rootField())) } diff --git a/test-utils/api/test-utils.api b/test-utils/api/test-utils.api index deee5231..a2db1fd7 100644 --- a/test-utils/api/test-utils.api +++ b/test-utils/api/test-utils.api @@ -4,3 +4,8 @@ public final class com/apollographql/cache/normalized/testing/CacheKeyKt { public static final fun keyToString-pWl1Des (Ljava/lang/String;)Ljava/lang/String; } +public final class com/apollographql/cache/normalized/testing/ErrorsKt { + public static final fun assertErrorsEquals (Lcom/apollographql/apollo/api/Error;Lcom/apollographql/apollo/api/Error;)V + public static final fun assertErrorsEquals (Ljava/lang/Iterable;Ljava/lang/Iterable;)V +} + diff --git a/test-utils/api/test-utils.klib.api b/test-utils/api/test-utils.klib.api index a99a94d6..285511c4 100644 --- a/test-utils/api/test-utils.klib.api +++ b/test-utils/api/test-utils.klib.api @@ -9,3 +9,5 @@ final fun (com.apollographql.cache.normalized.api/CacheKey).com.apollographql.cache.normalized.testing/append(kotlin/Array...): com.apollographql.cache.normalized.api/CacheKey // com.apollographql.cache.normalized.testing/append|append@com.apollographql.cache.normalized.api.CacheKey(kotlin.Array...){}[0] final fun (com.apollographql.cache.normalized.api/CacheKey).com.apollographql.cache.normalized.testing/fieldKey(kotlin/String): kotlin/String // com.apollographql.cache.normalized.testing/fieldKey|fieldKey@com.apollographql.cache.normalized.api.CacheKey(kotlin.String){}[0] final fun (com.apollographql.cache.normalized.api/CacheKey).com.apollographql.cache.normalized.testing/keyToString(): kotlin/String // com.apollographql.cache.normalized.testing/keyToString|keyToString@com.apollographql.cache.normalized.api.CacheKey(){}[0] +final fun com.apollographql.cache.normalized.testing/assertErrorsEquals(com.apollographql.apollo.api/Error?, com.apollographql.apollo.api/Error?) // com.apollographql.cache.normalized.testing/assertErrorsEquals|assertErrorsEquals(com.apollographql.apollo.api.Error?;com.apollographql.apollo.api.Error?){}[0] +final fun com.apollographql.cache.normalized.testing/assertErrorsEquals(kotlin.collections/Iterable?, kotlin.collections/Iterable?) // com.apollographql.cache.normalized.testing/assertErrorsEquals|assertErrorsEquals(kotlin.collections.Iterable?;kotlin.collections.Iterable?){}[0] diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index 06538cc8..2a4bca78 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -15,6 +15,7 @@ kotlin { getByName("commonMain") { dependencies { api(project(":normalized-cache-incubating")) + implementation(libs.kotlin.test) } } } diff --git a/test-utils/src/commonMain/kotlin/com/apollographql/cache/normalized/testing/Errors.kt b/test-utils/src/commonMain/kotlin/com/apollographql/cache/normalized/testing/Errors.kt new file mode 100644 index 00000000..1b397d28 --- /dev/null +++ b/test-utils/src/commonMain/kotlin/com/apollographql/cache/normalized/testing/Errors.kt @@ -0,0 +1,40 @@ +package com.apollographql.cache.normalized.testing + +import com.apollographql.apollo.api.Error +import com.apollographql.apollo.api.Error.Location +import kotlin.test.assertContentEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +/** + * Helps using assertEquals on Errors. + */ +private data class ComparableError( + val message: String, + val locations: List?, + val path: List?, +) + +fun assertErrorsEquals(expected: Iterable?, actual: Iterable?) = + assertContentEquals(expected?.map { + ComparableError( + message = it.message, + locations = it.locations, + path = it.path, + ) + }, actual?.map { + ComparableError( + message = it.message, + locations = it.locations, + path = it.path, + ) + }) + +fun assertErrorsEquals(expected: Error?, actual: Error?) { + if (expected == null) { + assertNull(actual) + return + } + assertNotNull(actual) + assertErrorsEquals(listOf(expected), listOf(actual)) +} diff --git a/tests/cache-control/build.gradle.kts b/tests/cache-control/build.gradle.kts index 42f1dfa7..29804102 100644 --- a/tests/cache-control/build.gradle.kts +++ b/tests/cache-control/build.gradle.kts @@ -25,6 +25,7 @@ kotlin { implementation(libs.apollo.mockserver) implementation(libs.kotlin.test) implementation(libs.turbine) + implementation("com.apollographql.cache:test-utils") } } @@ -50,4 +51,13 @@ apollo { argument("packageName", packageName.get()) } } + + service("doNotStore") { + packageName.set("donotstore") + srcDir("src/commonMain/graphql/doNotStore") + + plugin("com.apollographql.cache:normalized-cache-apollo-compiler-plugin") { + argument("packageName", packageName.get()) + } + } } diff --git a/tests/cache-control/src/commonMain/graphql/declarative/operations.graphql b/tests/cache-control/src/commonMain/graphql/declarative/operations.graphql index 0de01a4f..2841005a 100644 --- a/tests/cache-control/src/commonMain/graphql/declarative/operations.graphql +++ b/tests/cache-control/src/commonMain/graphql/declarative/operations.graphql @@ -110,3 +110,10 @@ query GetNodes { id } } + +query GetProject { + project { + id + name + } +} diff --git a/tests/cache-control/src/commonMain/graphql/declarative/schema.graphqls b/tests/cache-control/src/commonMain/graphql/declarative/schema.graphqls index 264ec286..037c004c 100644 --- a/tests/cache-control/src/commonMain/graphql/declarative/schema.graphqls +++ b/tests/cache-control/src/commonMain/graphql/declarative/schema.graphqls @@ -13,6 +13,7 @@ type Query { cachedBook: Book @cacheControl(maxAge: 60) reader: Reader @cacheControl(maxAge: 40) currentUserId: String + project: Project } type User @cacheControl(maxAge: 10) { @@ -66,3 +67,8 @@ type Book { type Reader { book: Book @cacheControl(inheritMaxAge: true) } + +type Project implements Node { + id: ID! + name: String! +} diff --git a/tests/cache-control/src/commonMain/graphql/doNotStore/extra.graphqls b/tests/cache-control/src/commonMain/graphql/doNotStore/extra.graphqls new file mode 100644 index 00000000..ed15747e --- /dev/null +++ b/tests/cache-control/src/commonMain/graphql/doNotStore/extra.graphqls @@ -0,0 +1,19 @@ +extend schema +@link( + url: "https://specs.apollo.dev/kotlin_labs/v0.3", + import: ["@typePolicy"] +) +@link( + url: "https://specs.apollo.dev/cache/v0.1", + import: ["@cacheControl", "@cacheControlField"] +) + +extend type User +@typePolicy(keyFields: "id") +@cacheControlField(name: "sensitiveScalar", maxAge: 0) + +extend type SensitiveObject @cacheControl(maxAge: 0) + +extend interface SensitiveInterface @cacheControl(maxAge: 0) + +extend type SignInResponse @cacheControlField(name: "token", maxAge: 0) diff --git a/tests/cache-control/src/commonMain/graphql/doNotStore/operations.graphql b/tests/cache-control/src/commonMain/graphql/doNotStore/operations.graphql new file mode 100644 index 00000000..8c6b09c8 --- /dev/null +++ b/tests/cache-control/src/commonMain/graphql/doNotStore/operations.graphql @@ -0,0 +1,31 @@ +query GetUser { + user { + name + email + sensitiveScalar + sensitiveObject { + password + } + project { + name + } + } +} + +mutation SignIn($email: String!, $password: String!) { + auth { + signIn(input: { email: $email, password: $password }) { + token + userData { + id + ...userData + } + } + } +} + +fragment userData on User { + id + name + email +} diff --git a/tests/cache-control/src/commonMain/graphql/doNotStore/schema.graphqls b/tests/cache-control/src/commonMain/graphql/doNotStore/schema.graphqls new file mode 100644 index 00000000..d0674850 --- /dev/null +++ b/tests/cache-control/src/commonMain/graphql/doNotStore/schema.graphqls @@ -0,0 +1,44 @@ +type Query { + user: User +} + +type User { + id: ID! + name: String! + email: String! + sensitiveScalar: String + sensitiveObject: SensitiveObject + project: Project +} + +type SensitiveObject{ + password: String +} + +interface SensitiveInterface { + topSecret: Boolean +} + +type Project implements SensitiveInterface { + id: ID! + name: String! + topSecret: Boolean +} + +type Mutation { + auth: Auth +} + +type Auth { + signIn(input: SignInInput): SignInResponse +} + +input SignInInput { + email: String! + password: String! +} + +type SignInResponse { + token: String + userData: User +} diff --git a/tests/cache-control/src/commonTest/kotlin/ClientSideCacheControlTest.kt b/tests/cache-control/src/commonTest/kotlin/ClientSideCacheControlTest.kt index 51d593c3..fca56942 100644 --- a/tests/cache-control/src/commonTest/kotlin/ClientSideCacheControlTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/ClientSideCacheControlTest.kt @@ -245,6 +245,16 @@ class ClientSideCacheControlTest { mergeUserQueryResults(client, 1) val userEmailResponse = client.query(GetUserEmailQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute() assertTrue(userEmailResponse.data?.user?.email == "john@doe.com") + + // Store records 10 second ago, less that max age for Node: should not cache miss + mergeProjectQueryResults(client, 10) + val projectResponse = client.query(declarative.GetProjectQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute() + assertTrue(projectResponse.data?.project?.name == "Stardust") + + // Store records 32 second ago, less than max age for Node: should cache miss + mergeProjectQueryResults(client, 32) + e = client.query(declarative.GetProjectQuery()).fetchPolicy(FetchPolicy.CacheOnly).execute().exception as CacheMissException + assertTrue(e.stale) } private fun mergeUserQueryResults(client: ApolloClient, secondsAgo: Int) { @@ -254,6 +264,15 @@ class ClientSideCacheControlTest { it.merge(records, receivedDate(currentTimeSeconds() - secondsAgo), DefaultRecordMerger) } } + + private fun mergeProjectQueryResults(client: ApolloClient, secondsAgo: Int) { + val data = declarative.GetProjectQuery.Data(declarative.GetProjectQuery.Project("42", "Stardust")) + val records = data.normalized(declarative.GetProjectQuery()).values + client.apolloStore.accessCache { + it.merge(records, receivedDate(currentTimeSeconds() - secondsAgo), DefaultRecordMerger) + } + } + } fun currentTimeSeconds() = currentTimeMillis() / 1000 diff --git a/tests/cache-control/src/commonTest/kotlin/DoNotStoreTest.kt b/tests/cache-control/src/commonTest/kotlin/DoNotStoreTest.kt new file mode 100644 index 00000000..e9dc040b --- /dev/null +++ b/tests/cache-control/src/commonTest/kotlin/DoNotStoreTest.kt @@ -0,0 +1,246 @@ +package test + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.ApolloRequest +import com.apollographql.apollo.api.ApolloResponse +import com.apollographql.apollo.api.Error +import com.apollographql.apollo.api.Operation +import com.apollographql.apollo.interceptor.ApolloInterceptor +import com.apollographql.apollo.interceptor.ApolloInterceptorChain +import com.apollographql.apollo.testing.internal.runTest +import com.apollographql.cache.normalized.FetchPolicy +import com.apollographql.cache.normalized.api.CacheHeaders +import com.apollographql.cache.normalized.api.CacheKey +import com.apollographql.cache.normalized.api.DefaultFieldKeyGenerator +import com.apollographql.cache.normalized.api.FieldKeyContext +import com.apollographql.cache.normalized.api.FieldKeyGenerator +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.fetchFromCache +import com.apollographql.cache.normalized.fetchPolicy +import com.apollographql.cache.normalized.fetchPolicyInterceptor +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.testing.append +import com.apollographql.cache.normalized.testing.assertErrorsEquals +import com.apollographql.cache.normalized.testing.keyToString +import com.apollographql.mockserver.MockServer +import com.apollographql.mockserver.enqueueString +import donotstore.GetUserQuery +import donotstore.SignInMutation +import donotstore.cache.Cache +import kotlinx.coroutines.flow.Flow +import okio.use +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.seconds + +class DoNotStoreTest { + private lateinit var mockServer: MockServer + + private fun setUp() { + mockServer = MockServer() + } + + private fun tearDown() { + mockServer.close() + } + + @Test + fun doNotStoreQueryMemory() { + doNotStoreQuery(MemoryCacheFactory()) + } + + @Test + fun doNotStoreQuerySql() { + doNotStoreQuery(SqlNormalizedCacheFactory()) + } + + @Test + fun doNotStoreQueryChained() { + doNotStoreQuery(MemoryCacheFactory().chain(SqlNormalizedCacheFactory())) + } + + private fun doNotStoreQuery(normalizedCacheFactory: NormalizedCacheFactory) = runTest(before = { setUp() }, after = { tearDown() }) { + mockServer.enqueueString( + // language=JSON + """ + { + "data": { + "user": { + "__typename": "User", + "id": "42", + "name": "John", + "email": "john@example.com", + "sensitiveScalar": "authToken", + "sensitiveObject": { + "password": "password" + }, + "project": { + "name": "Stardust" + } + } + } + } + """ + ) + val maxAgeProvider = SchemaCoordinatesMaxAgeProvider( + Cache.maxAges, + defaultMaxAge = 20.seconds, + ) + ApolloClient.Builder() + .serverUrl(mockServer.url()) + .normalizedCache( + normalizedCacheFactory = normalizedCacheFactory, + maxAgeProvider = maxAgeProvider, + ) + .build() + .use { apolloClient -> + apolloClient.apolloStore.clearAll() + val networkResponse = apolloClient.query(GetUserQuery()) + .fetchPolicy(FetchPolicy.NetworkOnly) + .execute() + assertEquals( + GetUserQuery.Data( + GetUserQuery.User( + __typename = "User", + id = "42", + name = "John", + email = "john@example.com", + sensitiveScalar = "authToken", + sensitiveObject = GetUserQuery.SensitiveObject("password"), + project = GetUserQuery.Project( + name = "Stardust", + ) + ) + ), + networkResponse.data + ) + val cacheResponse = apolloClient.query(GetUserQuery()) + .fetchPolicyInterceptor(PartialCacheOnlyInterceptor) + .execute() + assertErrorsEquals( + listOf( + Error.Builder("Object '${CacheKey("User:42").keyToString()}' has no field named 'sensitiveScalar' in the cache") + .path(listOf("user", "sensitiveScalar")).build(), + Error.Builder("Object '${CacheKey("User:42").keyToString()}' has no field named 'sensitiveObject' in the cache") + .path(listOf("user", "sensitiveObject")).build(), + Error.Builder("Object '${CacheKey("User:42").keyToString()}' has no field named 'project' in the cache") + .path(listOf("user", "project")).build(), + ), + cacheResponse.errors + ) + } + } + + @Test + fun doNotStoreMutationMemory() { + doNotStoreMutation(MemoryCacheFactory()) + } + + @Test + fun doNotStoreMutationSql() { + doNotStoreMutation(SqlNormalizedCacheFactory()) + } + + @Test + fun doNotStoreMutationChained() { + doNotStoreMutation(MemoryCacheFactory().chain(SqlNormalizedCacheFactory())) + } + + private fun doNotStoreMutation(normalizedCacheFactory: NormalizedCacheFactory) = runTest(before = { setUp() }, after = { tearDown() }) { + mockServer.enqueueString( + // language=JSON + """ + { + "data": { + "auth": { + "signIn": { + "token": "aaaabbbbccc", + "userData": { + "__typename": "User", + "id": "42", + "name": "John", + "email": "john@example.com" + } + } + } + } + } + """ + ) + val maxAgeProvider = SchemaCoordinatesMaxAgeProvider( + Cache.maxAges, + defaultMaxAge = 20.seconds, + ) + + // Do not store passwords in the cache + val fieldKeyGenerator = object : FieldKeyGenerator { + override fun getFieldKey(context: FieldKeyContext): String { + if (context.parentType == "Auth" && context.field.name == "signIn") { + return "signIn" + } + return DefaultFieldKeyGenerator.getFieldKey(context) + } + } + ApolloClient.Builder() + .serverUrl(mockServer.url()) + .normalizedCache( + normalizedCacheFactory = normalizedCacheFactory, + maxAgeProvider = maxAgeProvider, + fieldKeyGenerator = fieldKeyGenerator + ) + .build() + .use { apolloClient -> + apolloClient.apolloStore.clearAll() + val networkResponse = apolloClient.mutation(SignInMutation("scott", "tiger")) + .fetchPolicy(FetchPolicy.NetworkOnly) + .execute() + assertEquals( + SignInMutation.Data( + SignInMutation.Auth( + SignInMutation.SignIn( + token = "aaaabbbbccc", + userData = SignInMutation.UserData( + __typename = "User", + id = "42", + userData = donotstore.fragment.UserData( + id = "42", + name = "John", + email = "john@example.com", + __typename = "User" + + ) + ) + ) + ) + ), + networkResponse.data + ) + + apolloClient.apolloStore.accessCache { cache -> + val authRecord = cache.loadRecord(CacheKey("auth"), CacheHeaders.NONE)!! + // No password in field key + assertContentEquals(listOf("signIn"), authRecord.fields.keys) + + val signInRecord = cache.loadRecord(CacheKey("auth").append("signIn"), CacheHeaders.NONE)!! + // No token in record + assertContentEquals(listOf("userData"), signInRecord.fields.keys) + } + } + } +} + +private val PartialCacheOnlyInterceptor = object : ApolloInterceptor { + override fun intercept(request: ApolloRequest, chain: ApolloInterceptorChain): Flow> { + return chain.proceed( + request = request + .newBuilder() + .fetchFromCache(true) + .build() + ) + } +} diff --git a/tests/cache-control/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt b/tests/cache-control/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt index 5834bdb7..5b36ded8 100644 --- a/tests/cache-control/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt +++ b/tests/cache-control/src/commonTest/kotlin/SchemaCoordinatesMaxAgeProviderTest.kt @@ -1,6 +1,8 @@ package test import com.apollographql.apollo.api.CompiledField +import com.apollographql.apollo.api.InterfaceType +import com.apollographql.apollo.api.ObjectType import com.apollographql.apollo.api.isComposite import com.apollographql.cache.normalized.api.MaxAge import com.apollographql.cache.normalized.api.MaxAgeContext @@ -151,5 +153,36 @@ private fun CompiledField.field(name: String): CompiledField = fun CompiledField.path(vararg path: String): List = path.fold(listOf(this)) { acc, name -> acc + acc.last().field(name) } .map { - MaxAgeContext.Field(name = it.name, type = it.type.rawType().name, isTypeComposite = it.type.rawType().isComposite()) + it.toMaxAgeField() } + +private fun CompiledField.toMaxAgeField(): MaxAgeContext.Field { + val type = type.rawType() + val implements: List = when (type) { + is ObjectType -> { + type.implements.map { it.toMaxAgeType() } + } + + is InterfaceType -> { + type.implements.map { it.toMaxAgeType() } + } + + else -> { + emptyList() + } + } + return MaxAgeContext.Field( + name = name, + type = MaxAgeContext.Type( + name = type.name, + isComposite = type.isComposite(), + implements = implements, + ) + ) +} + +private fun InterfaceType.toMaxAgeType(): MaxAgeContext.Type = MaxAgeContext.Type( + name = name, + isComposite = true, + implements = implements.map { it.toMaxAgeType() }, +) diff --git a/tests/normalized-cache/src/commonMain/graphql/normalizer/HeroAndFriendsConnection.graphql b/tests/normalized-cache/src/commonMain/graphql/normalizer/HeroAndFriendsConnection.graphql new file mode 100644 index 00000000..55934e4a --- /dev/null +++ b/tests/normalized-cache/src/commonMain/graphql/normalizer/HeroAndFriendsConnection.graphql @@ -0,0 +1,14 @@ +query HeroAndFriendsConnection($episode: Episode) { + hero(episode: $episode) { + name + birthDate + friendsConnection { + totalCount + edges { + node { + id + } + } + } + } +} diff --git a/tests/normalized-cache/src/commonTest/kotlin/NormalizerTest.kt b/tests/normalized-cache/src/commonTest/kotlin/NormalizerTest.kt index 2a08e52d..ba18a988 100644 --- a/tests/normalized-cache/src/commonTest/kotlin/NormalizerTest.kt +++ b/tests/normalized-cache/src/commonTest/kotlin/NormalizerTest.kt @@ -4,8 +4,11 @@ import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.toApolloResponse import com.apollographql.cache.normalized.api.CacheHeaders import com.apollographql.cache.normalized.api.CacheKey +import com.apollographql.cache.normalized.api.DefaultMaxAgeProvider import com.apollographql.cache.normalized.api.DefaultRecordMerger import com.apollographql.cache.normalized.api.IdCacheKeyGenerator +import com.apollographql.cache.normalized.api.MaxAgeContext +import com.apollographql.cache.normalized.api.MaxAgeProvider import com.apollographql.cache.normalized.api.NormalizedCache import com.apollographql.cache.normalized.api.Record import com.apollographql.cache.normalized.internal.normalized @@ -13,6 +16,7 @@ import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.testing.append import httpcache.AllPlanetsQuery import normalizer.EpisodeHeroNameQuery +import normalizer.HeroAndFriendsConnectionQuery import normalizer.HeroAndFriendsNamesQuery import normalizer.HeroAndFriendsNamesWithIDForParentOnlyQuery import normalizer.HeroAndFriendsNamesWithIDsQuery @@ -27,6 +31,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertTrue +import kotlin.time.Duration /** * Tests for the normalization without an instance of [com.apollographql.apollo.ApolloClient] @@ -244,10 +249,33 @@ class NormalizerTest { assertEquals(lukeRecord["height({\"unit\":\"FOOT\"})"], 5.905512) } + @Test + fun testDoNotStore() { + val maxAgeProvider = object : MaxAgeProvider { + override fun getMaxAge(maxAgeContext: MaxAgeContext): Duration { + val field = maxAgeContext.fieldPath.last() + val parentField = maxAgeContext.fieldPath.getOrNull(maxAgeContext.fieldPath.lastIndex - 1) + // Don't store fields of type FriendsConnection nor fields inside FriendsConnection + if (field.type.name == "FriendsConnection" || parentField?.type?.name == "FriendsConnection") { + return Duration.ZERO + } + return Duration.INFINITE + } + } + val records = + records(HeroAndFriendsConnectionQuery(Episode.EMPIRE), "HeroAndFriendsConnectionResponse.json", maxAgeProvider = maxAgeProvider) + assertTrue(records[CacheKey("hero({\"episode\":\"EMPIRE\"})")]!!["friendsConnection"] == null) + assertTrue(records[CacheKey("hero({\"episode\":\"EMPIRE\"})").append("friendsConnection")]!!.isEmpty()) + } + companion object { - internal fun records(operation: Operation, name: String): Map { + internal fun records( + operation: Operation, + name: String, + maxAgeProvider: MaxAgeProvider = DefaultMaxAgeProvider, + ): Map { val response = testFixtureToJsonReader(name).toApolloResponse(operation) - return response.data!!.normalized(operation, cacheKeyGenerator = IdCacheKeyGenerator()) + return response.data!!.normalized(operation, cacheKeyGenerator = IdCacheKeyGenerator(), maxAgeProvider = maxAgeProvider) } private const val TEST_FIELD_KEY_JEDI = "hero({\"episode\":\"JEDI\"})" diff --git a/tests/normalized-cache/testFixtures/HeroAndFriendsConnectionResponse.json b/tests/normalized-cache/testFixtures/HeroAndFriendsConnectionResponse.json new file mode 100644 index 00000000..9c5c62a7 --- /dev/null +++ b/tests/normalized-cache/testFixtures/HeroAndFriendsConnectionResponse.json @@ -0,0 +1,36 @@ +{ + "data": { + "hero": { + "__typename": "Droid", + "name": "R2-D2", + "birthDate": "1977-05-25", + "friendsConnection": { + "__typename": "FriendsConnection", + "totalCount": 3, + "edges": [ + { + "__typename": "FriendsEdge", + "node": { + "__typename": "Human", + "id": "1000" + } + }, + { + "__typename": "FriendsEdge", + "node": { + "__typename": "Human", + "id": "1002" + } + }, + { + "__typename": "FriendsEdge", + "node": { + "__typename": "Human", + "id": "1003" + } + } + ] + } + } + } +} diff --git a/tests/partial-results/src/commonTest/kotlin/test/CachePartialResultTest.kt b/tests/partial-results/src/commonTest/kotlin/test/CachePartialResultTest.kt index 0db6be13..faf87012 100644 --- a/tests/partial-results/src/commonTest/kotlin/test/CachePartialResultTest.kt +++ b/tests/partial-results/src/commonTest/kotlin/test/CachePartialResultTest.kt @@ -4,7 +4,6 @@ import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.ApolloResponse import com.apollographql.apollo.api.Error -import com.apollographql.apollo.api.Error.Location import com.apollographql.apollo.api.Operation import com.apollographql.apollo.interceptor.ApolloInterceptor import com.apollographql.apollo.interceptor.ApolloInterceptorChain @@ -28,6 +27,7 @@ import com.apollographql.cache.normalized.normalizedCache import com.apollographql.cache.normalized.store import com.apollographql.cache.normalized.storeReceivedDate import com.apollographql.cache.normalized.testing.append +import com.apollographql.cache.normalized.testing.assertErrorsEquals import com.apollographql.cache.normalized.testing.keyToString import com.apollographql.mockserver.MockServer import com.apollographql.mockserver.enqueueString @@ -36,7 +36,6 @@ import okio.use import test.cache.Cache import test.fragment.UserFields import kotlin.test.Test -import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.time.Duration @@ -837,27 +836,3 @@ val PartialCacheOnlyInterceptor = object : ApolloInterceptor { ) } } - -/** - * Helps using assertEquals. - */ -private data class ComparableError( - val message: String, - val locations: List?, - val path: List?, -) - -private fun assertErrorsEquals(expected: Iterable?, actual: Iterable?) = - assertContentEquals(expected?.map { - ComparableError( - message = it.message, - locations = it.locations, - path = it.path, - ) - }, actual?.map { - ComparableError( - message = it.message, - locations = it.locations, - path = it.path, - ) - }) diff --git a/tests/store-errors/build.gradle.kts b/tests/store-errors/build.gradle.kts index eeb45fd7..0a79e85e 100644 --- a/tests/store-errors/build.gradle.kts +++ b/tests/store-errors/build.gradle.kts @@ -24,6 +24,7 @@ kotlin { implementation(libs.apollo.testing.support) implementation(libs.apollo.mockserver) implementation(libs.kotlin.test) + implementation("com.apollographql.cache:test-utils") } } diff --git a/tests/store-errors/src/commonTest/kotlin/test/StoreErrorsTest.kt b/tests/store-errors/src/commonTest/kotlin/test/StoreErrorsTest.kt index ae10fcb8..b37213e8 100644 --- a/tests/store-errors/src/commonTest/kotlin/test/StoreErrorsTest.kt +++ b/tests/store-errors/src/commonTest/kotlin/test/StoreErrorsTest.kt @@ -5,7 +5,6 @@ import com.apollographql.apollo.api.ApolloRequest import com.apollographql.apollo.api.ApolloResponse import com.apollographql.apollo.api.CustomScalarAdapters import com.apollographql.apollo.api.Error -import com.apollographql.apollo.api.Error.Location import com.apollographql.apollo.api.Operation import com.apollographql.apollo.interceptor.ApolloInterceptor import com.apollographql.apollo.interceptor.ApolloInterceptorChain @@ -22,15 +21,14 @@ import com.apollographql.cache.normalized.fetchPolicyInterceptor import com.apollographql.cache.normalized.memory.MemoryCacheFactory import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory import com.apollographql.cache.normalized.store +import com.apollographql.cache.normalized.testing.assertErrorsEquals import com.apollographql.mockserver.MockServer import com.apollographql.mockserver.enqueueString import kotlinx.coroutines.flow.Flow import okio.use import test.fragment.UserFields import kotlin.test.Test -import kotlin.test.assertContentEquals import kotlin.test.assertEquals -import kotlin.test.assertNotNull import kotlin.test.assertNull class StoreErrorsTest { @@ -785,36 +783,3 @@ val PartialCacheOnlyInterceptor = object : ApolloInterceptor { } } - -/** - * Helps using assertEquals. - */ -private data class ComparableError( - val message: String, - val locations: List?, - val path: List?, -) - -private fun assertErrorsEquals(expected: Iterable?, actual: Iterable?) = - assertContentEquals(expected?.map { - ComparableError( - message = it.message, - locations = it.locations, - path = it.path, - ) - }, actual?.map { - ComparableError( - message = it.message, - locations = it.locations, - path = it.path, - ) - }) - -private fun assertErrorsEquals(expected: Error?, actual: Error?) { - if (expected == null) { - assertNull(actual) - return - } - assertNotNull(actual) - assertErrorsEquals(listOf(expected), listOf(actual)) -}