Skip to content

Commit

Permalink
Fragment optimistic updates (#60)
Browse files Browse the repository at this point in the history
* Add ApolloStore.writeOptimisticUpdates for fragments

* Tweak ApolloStore KDoc

* Update changelog

* Update API Dump
  • Loading branch information
BoD authored Nov 7, 2024
1 parent 53cae47 commit 5181d41
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 133 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- For consistency, `MemoryCacheFactory` and `MemoryCache` are now in the `com.apollographql.cache.normalized.memory` package
- Remove deprecated symbols
- Add `IdCacheKeyGenerator` and `IdCacheKeyResolver` (#41)
- Add `ApolloStore.writeOptimisticUpdates` API for fragments (#55)

# Version 0.0.3
_2024-09-20_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public abstract interface class com/apollographql/cache/normalized/ApolloStore {
public abstract fun rollbackOptimisticUpdates (Ljava/util/UUID;)Ljava/util/Set;
public abstract fun writeFragment (Lcom/apollographql/apollo/api/Fragment;Lcom/apollographql/cache/normalized/api/CacheKey;Lcom/apollographql/apollo/api/Fragment$Data;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;)Ljava/util/Set;
public abstract fun writeOperation (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/Operation$Data;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;)Ljava/util/Set;
public abstract fun writeOptimisticUpdates (Lcom/apollographql/apollo/api/Fragment;Lcom/apollographql/cache/normalized/api/CacheKey;Lcom/apollographql/apollo/api/Fragment$Data;Ljava/util/UUID;Lcom/apollographql/apollo/api/CustomScalarAdapters;)Ljava/util/Set;
public abstract fun writeOptimisticUpdates (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/Operation$Data;Ljava/util/UUID;Lcom/apollographql/apollo/api/CustomScalarAdapters;)Ljava/util/Set;
}

Expand All @@ -23,6 +24,7 @@ public final class com/apollographql/cache/normalized/ApolloStore$DefaultImpls {
public static synthetic fun remove$default (Lcom/apollographql/cache/normalized/ApolloStore;Ljava/util/List;ZILjava/lang/Object;)I
public static synthetic fun writeFragment$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/apollo/api/Fragment;Lcom/apollographql/cache/normalized/api/CacheKey;Lcom/apollographql/apollo/api/Fragment$Data;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;ILjava/lang/Object;)Ljava/util/Set;
public static synthetic fun writeOperation$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/Operation$Data;Lcom/apollographql/apollo/api/CustomScalarAdapters;Lcom/apollographql/cache/normalized/api/CacheHeaders;ILjava/lang/Object;)Ljava/util/Set;
public static synthetic fun writeOptimisticUpdates$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/apollo/api/Fragment;Lcom/apollographql/cache/normalized/api/CacheKey;Lcom/apollographql/apollo/api/Fragment$Data;Ljava/util/UUID;Lcom/apollographql/apollo/api/CustomScalarAdapters;ILjava/lang/Object;)Ljava/util/Set;
public static synthetic fun writeOptimisticUpdates$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/Operation$Data;Ljava/util/UUID;Lcom/apollographql/apollo/api/CustomScalarAdapters;ILjava/lang/Object;)Ljava/util/Set;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ abstract interface com.apollographql.cache.normalized.api/RecordMerger { // com.
abstract interface com.apollographql.cache.normalized/ApolloStore { // com.apollographql.cache.normalized/ApolloStore|null[0]
abstract fun <#A1: com.apollographql.apollo.api/Fragment.Data> readFragment(com.apollographql.apollo.api/Fragment<#A1>, com.apollographql.cache.normalized.api/CacheKey, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): com.apollographql.cache.normalized/ApolloStore.ReadResult<#A1> // com.apollographql.cache.normalized/ApolloStore.readFragment|readFragment(com.apollographql.apollo.api.Fragment<0:0>;com.apollographql.cache.normalized.api.CacheKey;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§<com.apollographql.apollo.api.Fragment.Data>}[0]
abstract fun <#A1: com.apollographql.apollo.api/Fragment.Data> writeFragment(com.apollographql.apollo.api/Fragment<#A1>, com.apollographql.cache.normalized.api/CacheKey, #A1, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): kotlin.collections/Set<kotlin/String> // com.apollographql.cache.normalized/ApolloStore.writeFragment|writeFragment(com.apollographql.apollo.api.Fragment<0:0>;com.apollographql.cache.normalized.api.CacheKey;0:0;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§<com.apollographql.apollo.api.Fragment.Data>}[0]
abstract fun <#A1: com.apollographql.apollo.api/Fragment.Data> writeOptimisticUpdates(com.apollographql.apollo.api/Fragment<#A1>, com.apollographql.cache.normalized.api/CacheKey, #A1, com.benasher44.uuid/Uuid, com.apollographql.apollo.api/CustomScalarAdapters = ...): kotlin.collections/Set<kotlin/String> // com.apollographql.cache.normalized/ApolloStore.writeOptimisticUpdates|writeOptimisticUpdates(com.apollographql.apollo.api.Fragment<0:0>;com.apollographql.cache.normalized.api.CacheKey;0:0;com.benasher44.uuid.Uuid;com.apollographql.apollo.api.CustomScalarAdapters){0§<com.apollographql.apollo.api.Fragment.Data>}[0]
abstract fun <#A1: com.apollographql.apollo.api/Operation.Data> normalize(com.apollographql.apollo.api/Operation<#A1>, #A1, com.apollographql.apollo.api/CustomScalarAdapters): kotlin.collections/Map<kotlin/String, com.apollographql.cache.normalized.api/Record> // com.apollographql.cache.normalized/ApolloStore.normalize|normalize(com.apollographql.apollo.api.Operation<0:0>;0:0;com.apollographql.apollo.api.CustomScalarAdapters){0§<com.apollographql.apollo.api.Operation.Data>}[0]
abstract fun <#A1: com.apollographql.apollo.api/Operation.Data> readOperation(com.apollographql.apollo.api/Operation<#A1>, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): com.apollographql.cache.normalized/ApolloStore.ReadResult<#A1> // com.apollographql.cache.normalized/ApolloStore.readOperation|readOperation(com.apollographql.apollo.api.Operation<0:0>;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§<com.apollographql.apollo.api.Operation.Data>}[0]
abstract fun <#A1: com.apollographql.apollo.api/Operation.Data> writeOperation(com.apollographql.apollo.api/Operation<#A1>, #A1, com.apollographql.apollo.api/CustomScalarAdapters = ..., com.apollographql.cache.normalized.api/CacheHeaders = ...): kotlin.collections/Set<kotlin/String> // com.apollographql.cache.normalized/ApolloStore.writeOperation|writeOperation(com.apollographql.apollo.api.Operation<0:0>;0:0;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheHeaders){0§<com.apollographql.apollo.api.Operation.Data>}[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ interface ApolloStore {
val changedKeys: SharedFlow<Set<String>>

/**
* Read GraphQL operation from store.
* This is a synchronous operation that might block if the underlying cache is doing IO
* Reads an operation from the store.
*
* @param operation to be read
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param operation the operation to read
*
* @throws [com.apollographql.apollo.exception.CacheMissException] on cache miss
* @throws [com.apollographql.apollo.exception.ApolloException] on other cache read errors
Expand All @@ -58,11 +59,12 @@ interface ApolloStore {
): ReadResult<D>

/**
* Read a GraphQL fragment from the store.
* This is a synchronous operation that might block if the underlying cache is doing IO
* Reads a fragment from the store.
*
* @param fragment to be read
* @param cacheKey [CacheKey] to be used to find cache record for the fragment
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param fragment the fragment to read
* @param cacheKey the root where to read the fragment data from
*
* @throws [com.apollographql.apollo.exception.CacheMissException] on cache miss
* @throws [com.apollographql.apollo.exception.ApolloException] on other cache read errors
Expand All @@ -77,11 +79,12 @@ interface ApolloStore {
): ReadResult<D>

/**
* Write an operation data to the store.
* This is a synchronous operation that might block if the underlying cache is doing IO
* Write an operation to the store.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param operation [Operation] response data of which should be written to the store
* @param operationData [Operation.Data] operation response data to be written to the store
* @param operation the operation to write
* @param operationData the operation data to write
* @return the changed keys
*
* @see publish
Expand All @@ -94,12 +97,13 @@ interface ApolloStore {
): Set<String>

/**
* Write a fragment data to the store.
* This is a synchronous operation that might block if the underlying cache is doing IO
* Write a fragment to the store.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param fragment data to be written to the store
* @param cacheKey [CacheKey] to be used as root record key
* @param fragmentData [Fragment.Data] to be written to the store
* @param fragment the fragment to write
* @param cacheKey the root where to write the fragment data to
* @param fragmentData the fragment data to write
* @return the changed keys
*
* @see publish
Expand All @@ -113,12 +117,13 @@ interface ApolloStore {
): Set<String>

/**
* Write operation data to the optimistic store.
* Writes an operation to the optimistic store.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param operation [Operation] response data of which should be written to the store
* @param operationData [Operation.Data] operation response data to be written to the store
* @param mutationId mutation unique identifier
* @param operation the operation to write
* @param operationData the operation data to write
* @param mutationId a unique identifier for this optimistic update
* @return the changed keys
*
* @see publish
Expand All @@ -131,46 +136,74 @@ interface ApolloStore {
): Set<String>

/**
* Rollback operation data optimistic updates.
* Writes a fragment to the optimistic store.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param mutationId mutation unique identifier
* @param fragment the fragment to write
* @param cacheKey the root where to write the fragment data to
* @param fragmentData the fragment data to write
* @param mutationId a unique identifier for this optimistic update
* @return the changed keys
*
* @see publish
*/
fun <D : Fragment.Data> writeOptimisticUpdates(
fragment: Fragment<D>,
cacheKey: CacheKey,
fragmentData: D,
mutationId: Uuid,
customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty,
): Set<String>

/**
* Rollbacks optimistic updates.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param mutationId the unique identifier of the optimistic update to rollback
* @return the changed keys
*
* @see publish
*/
fun rollbackOptimisticUpdates(
mutationId: Uuid,
): Set<String>

/**
* Clear all records from this [ApolloStore].
* This is a synchronous operation that might block if the underlying cache is doing IO
* Clears all records.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @return `true` if all records were successfully removed, `false` otherwise
*/
fun clearAll(): Boolean

/**
* Remove cache record by the key
* This is a synchronous operation that might block if the underlying cache is doing IO
* Removes a record by its key.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param cacheKey of record to be removed
* @param cascade defines if remove operation is propagated to the referenced entities
* @param cacheKey the key of the record to remove
* @param cascade whether referenced records should also be removed
* @return `true` if the record was successfully removed, `false` otherwise
*/
fun remove(cacheKey: CacheKey, cascade: Boolean = true): Boolean

/**
* Remove a list of cache records
* This is an optimized version of [remove] for caches that can batch operations
* This is a synchronous operation that might block if the underlying cache is doing IO
* Removes a list of records by their keys.
* This is an optimized version of [remove] for caches that can batch operations.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param cacheKeys keys of records to be removed
* @param cacheKeys the keys of the records to remove
* @param cascade whether referenced records should also be removed
* @return the number of records that have been removed
*/
fun remove(cacheKeys: List<CacheKey>, cascade: Boolean = true): Int

/**
* Normalize [data] to a map of [Record] keyed by [Record.key].
* Normalizes operation data to a map of [Record] keyed by [Record.key].
*/
fun <D : Operation.Data> normalize(
operation: Operation<D>,
Expand All @@ -179,26 +212,32 @@ interface ApolloStore {
): Map<String, Record>

/**
* Publishes a set of keys that have changed. This will notify subscribers of [changedKeys].
*
* @see changedKeys
*
* @param keys A set of keys of [Record] which have changed.
*/
suspend fun publish(keys: Set<String>)

/**
* Direct access to the cache.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*
* @param block a function that can access the cache.
*/
fun <R> accessCache(block: (NormalizedCache) -> R): R

/**
* Dump the content of the store for debugging purposes.
* Dumps the content of the store for debugging purposes.
*
* This is a synchronous operation that might block if the underlying cache is doing IO.
*/
fun dump(): Map<KClass<*>, Map<String, Record>>

/**
* Release resources associated with this store.
* Releases resources associated with this store.
*/
fun dispose()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,31 @@ internal class DefaultApolloStore(
return cache.addOptimisticUpdates(records)
}

override fun <D : Fragment.Data> writeOptimisticUpdates(
fragment: Fragment<D>,
cacheKey: CacheKey,
fragmentData: D,
mutationId: Uuid,
customScalarAdapters: CustomScalarAdapters,
): Set<String> {
val records = fragment.normalize(
data = fragmentData,
customScalarAdapters = customScalarAdapters,
cacheKeyGenerator = cacheKeyGenerator,
metadataGenerator = metadataGenerator,
fieldKeyGenerator = fieldKeyGenerator,
embeddedFieldsProvider = embeddedFieldsProvider,
rootKey = cacheKey.key
).values.map { record ->
Record(
key = record.key,
fields = record.fields,
mutationId = mutationId
)
}
return cache.addOptimisticUpdates(records)
}

override fun rollbackOptimisticUpdates(
mutationId: Uuid,
): Set<String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ query HeroAndFriendsNames($episode: Episode) {
}
}
}

fragment HeroAndFriendsNamesFragment on Character {
name
friends {
name
}
}
Loading

0 comments on commit 5181d41

Please sign in to comment.