Skip to content

Commit

Permalink
Add EmbeddedFieldsProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
BoD committed Apr 3, 2024
1 parent 3440006 commit a45c0ff
Show file tree
Hide file tree
Showing 16 changed files with 619 additions and 82 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.apollographql.apollo3.cache.normalized.api

import com.apollographql.apollo3.annotations.ApolloExperimental
import com.apollographql.apollo3.api.CompiledNamedType
import com.apollographql.apollo3.api.InterfaceType
import com.apollographql.apollo3.api.ObjectType

@ApolloExperimental
interface EmbeddedFieldsProvider {
fun getEmbeddedFields(context: EmbeddedFieldsContext): List<String>
}

@ApolloExperimental
class EmbeddedFieldsContext(
val parentType: CompiledNamedType,
)

@ApolloExperimental
object DefaultEmbeddedFieldsProvider : EmbeddedFieldsProvider {
override fun getEmbeddedFields(context: EmbeddedFieldsContext): List<String> {
return context.parentType.embeddedFields
}
}

private val CompiledNamedType.embeddedFields: List<String>
get() = when (this) {
is ObjectType -> embeddedFields
is InterfaceType -> embeddedFields
else -> emptyList()
}

@ApolloExperimental
class ConnectionEmbeddedFieldsProvider(
connectionFields: Map<String, List<String>>,
connectionTypes: Set<String>,
) : EmbeddedFieldsProvider {
companion object {
private val connectionFieldsToEmbed = listOf("pageInfo", "edges")
}

private val embeddedFields = connectionFields + connectionTypes.associateWith { connectionFieldsToEmbed }

override fun getEmbeddedFields(context: EmbeddedFieldsContext): List<String> {
return embeddedFields[context.parentType.name].orEmpty()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.apollographql.apollo3.cache.normalized.api

import com.apollographql.apollo3.annotations.ApolloExperimental
import com.apollographql.apollo3.annotations.ApolloInternal
import com.apollographql.apollo3.api.Adapter
import com.apollographql.apollo3.api.CustomScalarAdapters
Expand All @@ -17,62 +16,28 @@ fun <D : Operation.Data> Operation<D>.normalize(
data: D,
customScalarAdapters: CustomScalarAdapters,
cacheKeyGenerator: CacheKeyGenerator,
) = normalize(data, customScalarAdapters, cacheKeyGenerator, EmptyMetadataGenerator, DefaultFieldNameGenerator, CacheKey.rootKey().key)

@ApolloExperimental
fun <D : Operation.Data> Operation<D>.normalize(
data: D,
customScalarAdapters: CustomScalarAdapters,
cacheKeyGenerator: CacheKeyGenerator,
metadataGenerator: MetadataGenerator,
fieldNameGenerator: FieldNameGenerator,
) = normalize(data, customScalarAdapters, cacheKeyGenerator, metadataGenerator, fieldNameGenerator, CacheKey.rootKey().key)


@Suppress("UNCHECKED_CAST")
fun <D : Executable.Data> Executable<D>.normalize(
data: D,
customScalarAdapters: CustomScalarAdapters,
cacheKeyGenerator: CacheKeyGenerator,
rootKey: String,
): Map<String, Record> {
val writer = MapJsonWriter()
adapter().toJson(writer, customScalarAdapters, data)
val variables = variables(customScalarAdapters, true)
return Normalizer(variables, rootKey, cacheKeyGenerator, EmptyMetadataGenerator, DefaultFieldNameGenerator)
.normalize(writer.root() as Map<String, Any?>, rootField().selections, rootField().type.rawType())
}
metadataGenerator: MetadataGenerator = EmptyMetadataGenerator,
fieldNameGenerator: FieldNameGenerator = DefaultFieldNameGenerator,
embeddedFieldsProvider: EmbeddedFieldsProvider = DefaultEmbeddedFieldsProvider,
) = normalize(data, customScalarAdapters, cacheKeyGenerator, metadataGenerator, fieldNameGenerator, embeddedFieldsProvider, CacheKey.rootKey().key)

@ApolloExperimental
@Suppress("UNCHECKED_CAST")
fun <D : Executable.Data> Executable<D>.normalize(
data: D,
customScalarAdapters: CustomScalarAdapters,
cacheKeyGenerator: CacheKeyGenerator,
metadataGenerator: MetadataGenerator,
fieldNameGenerator: FieldNameGenerator,
metadataGenerator: MetadataGenerator = EmptyMetadataGenerator,
fieldNameGenerator: FieldNameGenerator = DefaultFieldNameGenerator,
embeddedFieldsProvider: EmbeddedFieldsProvider = DefaultEmbeddedFieldsProvider,
rootKey: String,
): Map<String, Record> {
val writer = MapJsonWriter()
adapter().toJson(writer, customScalarAdapters, data)
val variables = variables(customScalarAdapters)
return Normalizer(variables, rootKey, cacheKeyGenerator, metadataGenerator, fieldNameGenerator)
@Suppress("UNCHECKED_CAST")
return Normalizer(variables, rootKey, cacheKeyGenerator, metadataGenerator, fieldNameGenerator, embeddedFieldsProvider)
.normalize(writer.root() as Map<String, Any?>, rootField().selections, rootField().type.rawType())
}

fun <D : Executable.Data> Executable<D>.readDataFromCache(
customScalarAdapters: CustomScalarAdapters,
cache: ReadOnlyNormalizedCache,
cacheResolver: CacheResolver,
cacheHeaders: CacheHeaders,
): D = readDataFromCache(
cacheKey = CacheKey.rootKey(),
customScalarAdapters = customScalarAdapters,
cache = cache,
cacheResolver = cacheResolver,
cacheHeaders = cacheHeaders,
)

@JvmOverloads
fun <D : Executable.Data> Executable<D>.readDataFromCache(
cacheKey: CacheKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import com.apollographql.apollo3.api.CompiledNotNullType
import com.apollographql.apollo3.api.CompiledSelection
import com.apollographql.apollo3.api.CompiledType
import com.apollographql.apollo3.api.Executable
import com.apollographql.apollo3.api.InterfaceType
import com.apollographql.apollo3.api.ObjectType
import com.apollographql.apollo3.api.isComposite
import com.apollographql.apollo3.cache.normalized.api.CacheKey
import com.apollographql.apollo3.cache.normalized.api.CacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.api.CacheKeyGeneratorContext
import com.apollographql.apollo3.cache.normalized.api.EmbeddedFieldsContext
import com.apollographql.apollo3.cache.normalized.api.EmbeddedFieldsProvider
import com.apollographql.apollo3.cache.normalized.api.FieldNameContext
import com.apollographql.apollo3.cache.normalized.api.FieldNameGenerator
import com.apollographql.apollo3.cache.normalized.api.MetadataGenerator
Expand All @@ -30,11 +30,12 @@ internal class Normalizer(
private val cacheKeyGenerator: CacheKeyGenerator,
private val metadataGenerator: MetadataGenerator,
private val fieldNameGenerator: FieldNameGenerator,
private val embeddedFieldsProvider: EmbeddedFieldsProvider,
) {
private val records = mutableMapOf<String, Record>()

fun normalize(map: Map<String, Any?>, selections: List<CompiledSelection>, parentType: CompiledNamedType): Map<String, Record> {
buildRecord(map, rootKey, selections, parentType.name, parentType.embeddedFields)
buildRecord(map, rootKey, selections, parentType)

return records
}
Expand All @@ -56,12 +57,11 @@ internal class Normalizer(
obj: Map<String, Any?>,
key: String,
selections: List<CompiledSelection>,
parentType: String,
embeddedFields: List<String>,
parentType: CompiledNamedType,
): Map<String, FieldInfo> {

val typename = obj["__typename"] as? String
val allFields = collectFields(selections, parentType, typename)
val allFields = collectFields(selections, parentType.name, typename)

val fields = obj.entries.mapNotNull { entry ->
val compiledFields = allFields.filter { it.responseName == entry.key }
Expand All @@ -83,7 +83,7 @@ internal class Normalizer(
.condition(emptyList())
.build()

val fieldKey = fieldNameGenerator.getFieldName(FieldNameContext(parentType, mergedField, variables))
val fieldKey = fieldNameGenerator.getFieldName(FieldNameContext(parentType.name, mergedField, variables))

val base = if (key == CacheKey.rootKey().key) {
// If we're at the root level, skip `QUERY_ROOT` altogether to save a few bytes
Expand All @@ -92,11 +92,11 @@ internal class Normalizer(
key
}
val value = replaceObjects(
entry.value,
mergedField,
mergedField.type,
base.append(fieldKey),
embeddedFields
value = entry.value,
field = mergedField,
type_ = mergedField.type,
path = base.append(fieldKey),
embeddedFields = embeddedFieldsProvider.getEmbeddedFields(EmbeddedFieldsContext(parentType)),
)
val metadata = metadataGenerator.metadataForObject(entry.value, MetadataGeneratorContext(field = mergedField, variables))
fieldKey to FieldInfo(value, metadata)
Expand All @@ -117,10 +117,9 @@ internal class Normalizer(
obj: Map<String, Any?>,
key: String,
selections: List<CompiledSelection>,
parentType: String,
embeddedFields: List<String>,
parentType: CompiledNamedType,
): CacheKey {
val fields = buildFields(obj, key, selections, parentType, embeddedFields)
val fields = buildFields(obj, key, selections, parentType)
val fieldValues = fields.mapValues { it.value.fieldValue }
val metadata = fields.mapValues { it.value.metadata }.filterValues { it.isNotEmpty() }
val record = Record(
Expand Down Expand Up @@ -197,26 +196,20 @@ internal class Normalizer(
key = path
}
if (embeddedFields.contains(field.name)) {
buildFields(value, key, field.selections, field.type.rawType().name, field.type.rawType().embeddedFields)
buildFields(value, key, field.selections, field.type.rawType())
.mapValues { it.value.fieldValue }
} else {
buildRecord(value, key, field.selections, field.type.rawType().name, field.type.rawType().embeddedFields)
buildRecord(value, key, field.selections, field.type.rawType())
}
}

else -> {
// scalar
value
}
}
}

private val CompiledNamedType.embeddedFields: List<String>
get() = when (this) {
is ObjectType -> embeddedFields
is InterfaceType -> embeddedFields
else -> emptyList()
}

private class CollectState {
val fields = mutableListOf<CompiledField>()
}
Expand All @@ -227,6 +220,7 @@ internal class Normalizer(
is CompiledField -> {
state.fields.add(it)
}

is CompiledFragment -> {
if (typename in it.possibleTypes || it.typeCondition == parentType) {
collectFields(it.selections, parentType, typename, state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import com.apollographql.apollo3.cache.normalized.api.CacheHeaders
import com.apollographql.apollo3.cache.normalized.api.CacheKey
import com.apollographql.apollo3.cache.normalized.api.CacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.api.CacheResolver
import com.apollographql.apollo3.cache.normalized.api.DefaultEmbeddedFieldsProvider
import com.apollographql.apollo3.cache.normalized.api.DefaultFieldNameGenerator
import com.apollographql.apollo3.cache.normalized.api.DefaultRecordMerger
import com.apollographql.apollo3.cache.normalized.api.EmbeddedFieldsProvider
import com.apollographql.apollo3.cache.normalized.api.EmptyMetadataGenerator
import com.apollographql.apollo3.cache.normalized.api.FieldNameGenerator
import com.apollographql.apollo3.cache.normalized.api.FieldPolicyApolloResolver
import com.apollographql.apollo3.cache.normalized.api.FieldPolicyCacheResolver
import com.apollographql.apollo3.cache.normalized.api.MetadataGenerator
import com.apollographql.apollo3.cache.normalized.api.NormalizedCache
Expand Down Expand Up @@ -211,21 +214,24 @@ fun ApolloStore(
cacheResolver = cacheResolver,
recordMerger = DefaultRecordMerger,
fieldNameGenerator = DefaultFieldNameGenerator,
embeddedFieldsProvider = DefaultEmbeddedFieldsProvider,
)

@ApolloExperimental
fun ApolloStore(
normalizedCacheFactory: NormalizedCacheFactory,
cacheKeyGenerator: CacheKeyGenerator,
metadataGenerator: MetadataGenerator,
apolloResolver: ApolloResolver,
cacheKeyGenerator: CacheKeyGenerator = TypePolicyCacheKeyGenerator,
metadataGenerator: MetadataGenerator = EmptyMetadataGenerator,
apolloResolver: ApolloResolver = FieldPolicyApolloResolver,
recordMerger: RecordMerger = DefaultRecordMerger,
fieldNameGenerator: FieldNameGenerator = DefaultFieldNameGenerator,
embeddedFieldsProvider: EmbeddedFieldsProvider = DefaultEmbeddedFieldsProvider,
): ApolloStore = DefaultApolloStore(
normalizedCacheFactory = normalizedCacheFactory,
cacheKeyGenerator = cacheKeyGenerator,
metadataGenerator = metadataGenerator,
cacheResolver = apolloResolver,
recordMerger = recordMerger,
fieldNameGenerator = fieldNameGenerator,
embeddedFieldsProvider = embeddedFieldsProvider,
)
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ import com.apollographql.apollo3.cache.normalized.api.ApolloResolver
import com.apollographql.apollo3.cache.normalized.api.CacheHeaders
import com.apollographql.apollo3.cache.normalized.api.CacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.api.CacheResolver
import com.apollographql.apollo3.cache.normalized.api.DefaultEmbeddedFieldsProvider
import com.apollographql.apollo3.cache.normalized.api.DefaultFieldNameGenerator
import com.apollographql.apollo3.cache.normalized.api.DefaultRecordMerger
import com.apollographql.apollo3.cache.normalized.api.EmbeddedFieldsProvider
import com.apollographql.apollo3.cache.normalized.api.EmptyMetadataGenerator
import com.apollographql.apollo3.cache.normalized.api.FieldNameGenerator
import com.apollographql.apollo3.cache.normalized.api.FieldPolicyApolloResolver
import com.apollographql.apollo3.cache.normalized.api.FieldPolicyCacheResolver
import com.apollographql.apollo3.cache.normalized.api.MetadataGenerator
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
Expand Down Expand Up @@ -117,11 +122,12 @@ fun ApolloClient.Builder.normalizedCache(
@JvmName("configureApolloClientBuilder2")
fun ApolloClient.Builder.normalizedCache(
normalizedCacheFactory: NormalizedCacheFactory,
cacheKeyGenerator: CacheKeyGenerator,
metadataGenerator: MetadataGenerator,
apolloResolver: ApolloResolver,
recordMerger: RecordMerger,
cacheKeyGenerator: CacheKeyGenerator = TypePolicyCacheKeyGenerator,
metadataGenerator: MetadataGenerator = EmptyMetadataGenerator,
apolloResolver: ApolloResolver = FieldPolicyApolloResolver,
recordMerger: RecordMerger = DefaultRecordMerger,
fieldNameGenerator: FieldNameGenerator = DefaultFieldNameGenerator,
embeddedFieldsProvider: EmbeddedFieldsProvider = DefaultEmbeddedFieldsProvider,
writeToCacheAsynchronously: Boolean = false,
): ApolloClient.Builder {
return store(
Expand All @@ -132,6 +138,7 @@ fun ApolloClient.Builder.normalizedCache(
apolloResolver = apolloResolver,
recordMerger = recordMerger,
fieldNameGenerator = fieldNameGenerator,
embeddedFieldsProvider = embeddedFieldsProvider
), writeToCacheAsynchronously)
}

Expand Down Expand Up @@ -657,4 +664,4 @@ val <D : Operation.Data> ApolloResponse<D>.cacheHeaders
*/
@Deprecated("Use fetchPolicy(FetchPolicy.CacheAndNetwork) instead", ReplaceWith("fetchPolicy(FetchPolicy.CacheAndNetwork).toFlow()"), level = DeprecationLevel.ERROR)
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v3_7_5)
fun <D : Query.Data> ApolloCall<D>.executeCacheAndNetwork(): Flow<ApolloResponse<D>> = TODO()
fun <D : Query.Data> ApolloCall<D>.executeCacheAndNetwork(): Flow<ApolloResponse<D>> = TODO()
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.apollographql.apollo3.cache.normalized.api.CacheHeaders
import com.apollographql.apollo3.cache.normalized.api.CacheKey
import com.apollographql.apollo3.cache.normalized.api.CacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.api.CacheResolver
import com.apollographql.apollo3.cache.normalized.api.EmbeddedFieldsProvider
import com.apollographql.apollo3.cache.normalized.api.FieldNameGenerator
import com.apollographql.apollo3.cache.normalized.api.MetadataGenerator
import com.apollographql.apollo3.cache.normalized.api.NormalizedCache
Expand All @@ -36,6 +37,7 @@ internal class DefaultApolloStore(
private val metadataGenerator: MetadataGenerator,
private val cacheResolver: Any,
private val recordMerger: RecordMerger,
private val embeddedFieldsProvider: EmbeddedFieldsProvider,
) : ApolloStore {
private val changedKeysEvents = MutableSharedFlow<Set<String>>(
/**
Expand Down Expand Up @@ -110,6 +112,7 @@ internal class DefaultApolloStore(
cacheKeyGenerator = cacheKeyGenerator,
metadataGenerator = metadataGenerator,
fieldNameGenerator = fieldNameGenerator,
embeddedFieldsProvider = embeddedFieldsProvider,
)
}

Expand Down Expand Up @@ -163,6 +166,7 @@ internal class DefaultApolloStore(
cacheKeyGenerator = cacheKeyGenerator,
metadataGenerator = metadataGenerator,
fieldNameGenerator = fieldNameGenerator,
embeddedFieldsProvider = embeddedFieldsProvider,
).values.toSet()

return cache.merge(records, cacheHeaders, recordMerger)
Expand All @@ -181,6 +185,7 @@ internal class DefaultApolloStore(
cacheKeyGenerator = cacheKeyGenerator,
metadataGenerator = metadataGenerator,
fieldNameGenerator = fieldNameGenerator,
embeddedFieldsProvider = embeddedFieldsProvider,
rootKey = cacheKey.key
).values

Expand All @@ -199,6 +204,7 @@ internal class DefaultApolloStore(
cacheKeyGenerator = cacheKeyGenerator,
metadataGenerator = metadataGenerator,
fieldNameGenerator = fieldNameGenerator,
embeddedFieldsProvider = embeddedFieldsProvider,
).values.map { record ->
Record(
key = record.key,
Expand Down
6 changes: 6 additions & 0 deletions tests/pagination/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,10 @@ apollo {
@OptIn(ApolloExperimental::class)
generateDataBuilders.set(true)
}
service("pagination.connectionProgrammatic") {
packageName.set("pagination.connectionProgrammatic")
srcDir("src/commonMain/graphql/pagination/connectionProgrammatic")
@OptIn(ApolloExperimental::class)
generateDataBuilders.set(true)
}
}
Loading

0 comments on commit a45c0ff

Please sign in to comment.