This version reworks the CacheResolver
API to hopefully clarify its usage as well as allow more advanced use cases if needed.
ObjectIdGenerator
and CacheResolver
APIs
This version splits the CacheResolver
API in two distinct parts:
ObjectIdGenerator.cacheKeyForObject
- takes Json data as input and returns a unique id for an object.
- is used after a network request
- is used during normalization when writing to the cache
CacheResolver.resolveField
- takes a GraphQL field and operation variables as input and generates data for this field
- this data can be a
CacheKey
for objects but it can also be any other data if needed. In that respect, it's closer to a resolver as might be found in apollo-server - is used before a network request
- is used when reading the cache
Previously, both methods were in CacheResolver
even if under the hood, the code path were very different. By separating them, it makes it explicit and also makes it possible to only implement one of them.
Note: In general, prefer using @typePolicy
and @fieldPolicy
that provide a declarative/easier way to manage normalization and resolution.
ObjectIdGenerator
is usually the most common one. It gives objects a unique id:
type Product {
uid: String!
name: String!
price: Float!
}
// An ObjectIdGenerator that uses the "uid" property if it exists
object UidObjectIdGenerator : ObjectIdGenerator {
override fun cacheKeyForObject(obj: Map<String, Any?>, context: ObjectIdGeneratorContext): CacheKey? {
val typename = obj["__typename"]?.toString()
val uid = obj["uid"]?.toString()
return if (typename != null && uid != null) {
CacheKey.from(typename, listOf(uid))
} else {
null
}
}
}
CacheResolver
allows resolving a specific field from a query:
query GetProduct($uid: String!) {
product(uid: $uid) {
uid
name
price
}
}
object UidCacheResolver: CacheResolver {
override fun resolveField(field: CompiledField, variables: Executable.Variables, parent: Map<String, Any?>, parentId: String): Any? {
var type = field.type
if (type is CompiledNotNullType) {
type = type.ofType
}
if (type !is ObjectType) {
// This only works for concrete types
return MapCacheResolver.resolveField(field, variables, parent, parentId)
}
val uid = field.resolveArgument("uid", variables)?.toString()
if (uid != null) {
return CacheKey.from(type.name, listOf(uid))
}
// Always fallback to the default resolver
return MapCacheResolver.resolveField(field, variables, parent, parentId)
}
}
Bug fixes
- Be robust to SDL schemas that already contain builtin definitions (#3241)