Skip to content

Fragment optimistic updates #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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