diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d1263e2faf..fa6b5b705a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -119,6 +119,9 @@
- Make sure `ApolloContext` plays nicely with IE11 when storing the shared context.
[@ms](https://github.com/ms) in [#5840](https://github.com/apollographql/apollo-client/pull/5840)
+- Expose cache `modify` and `identify` to the mutate `update` function.
+ [@hwillson](https://github.com/hwillson) in [#5956](https://github.com/apollographql/apollo-client/pull/5956)
+
### Bug Fixes
- `useMutation` adjustments to help avoid an infinite loop / too many renders issue, caused by unintentionally modifying the `useState` based mutation result directly.
diff --git a/src/cache/core/cache.ts b/src/cache/core/cache.ts
index dceca187a21..9a510135ce7 100644
--- a/src/cache/core/cache.ts
+++ b/src/cache/core/cache.ts
@@ -2,9 +2,11 @@ import { DocumentNode } from 'graphql';
import { wrap } from 'optimism';
import { getFragmentQueryDocument } from '../../utilities/graphql/fragments';
+import { StoreObject } from '../../utilities/graphql/storeUtils';
import { DataProxy } from './types/DataProxy';
import { Cache } from './types/Cache';
import { queryFromPojo, fragmentFromPojo } from './utils';
+import { Modifier, Modifiers } from './types/common';
const justTypenameQuery: DocumentNode = {
kind: "Document",
@@ -61,23 +63,41 @@ export abstract class ApolloCache implements DataProxy {
*/
public abstract extract(optimistic?: boolean): TSerialized;
- // optimistic API
+ // Optimistic API
+
public abstract removeOptimistic(id: string): void;
- // transactional API
+ // Transactional API
+
public abstract performTransaction(
transaction: Transaction,
): void;
+
public abstract recordOptimisticTransaction(
transaction: Transaction,
id: string,
): void;
- // optional API
+ // Optional API
+
public transformDocument(document: DocumentNode): DocumentNode {
return document;
}
- // experimental
+
+ public identify(object: StoreObject): string | null {
+ return null;
+ }
+
+ public modify(
+ dataId: string,
+ modifiers: Modifier | Modifiers,
+ optimistic = false,
+ ): boolean {
+ return false;
+ }
+
+ // Experimental API
+
public transformForLink(document: DocumentNode): DocumentNode {
return document;
}
diff --git a/src/cache/core/types/common.ts b/src/cache/core/types/common.ts
new file mode 100644
index 00000000000..9ba9f4a1c06
--- /dev/null
+++ b/src/cache/core/types/common.ts
@@ -0,0 +1,38 @@
+import { SelectionSetNode } from 'graphql';
+
+import {
+ isReference,
+ StoreValue,
+ StoreObject,
+ Reference
+} from '../../../utilities/graphql/storeUtils';
+import { FragmentMap } from '../../../utilities/graphql/fragments';
+
+// The Readonly type only really works for object types, since it marks
+// all of the object's properties as readonly, but there are many cases when
+// a generic type parameter like TExisting might be a string or some other
+// primitive type, in which case we need to avoid wrapping it with Readonly.
+// SafeReadonly collapses to just string, which makes string
+// assignable to SafeReadonly, whereas string is not assignable to
+// Readonly, somewhat surprisingly.
+export type SafeReadonly = T extends object ? Readonly : T;
+
+export type Modifier = (value: T, details: {
+ DELETE: any;
+ fieldName: string;
+ storeFieldName: string;
+ isReference: typeof isReference;
+ toReference(
+ object: StoreObject,
+ selectionSet?: SelectionSetNode,
+ fragmentMap?: FragmentMap,
+ ): Reference;
+ readField(
+ fieldName: string,
+ objOrRef?: StoreObject | Reference,
+ ): SafeReadonly;
+}) => T;
+
+export type Modifiers = {
+ [fieldName: string]: Modifier;
+}
diff --git a/src/cache/inmemory/entityStore.ts b/src/cache/inmemory/entityStore.ts
index 3afefc3bf39..de184cec086 100644
--- a/src/cache/inmemory/entityStore.ts
+++ b/src/cache/inmemory/entityStore.ts
@@ -1,32 +1,23 @@
import { dep, OptimisticDependencyFunction, KeyTrie } from 'optimism';
import { equal } from '@wry/equality';
-import { isReference, StoreValue, Reference, makeReference } from '../../utilities/graphql/storeUtils';
+import {
+ isReference,
+ StoreValue,
+ StoreObject,
+ Reference,
+ makeReference
+} from '../../utilities/graphql/storeUtils';
import { DeepMerger } from '../../utilities/common/mergeDeep';
import { maybeDeepFreeze } from '../../utilities/common/maybeDeepFreeze';
import { canUseWeakMap } from '../../utilities/common/canUse';
-import { NormalizedCache, NormalizedCacheObject, StoreObject, SafeReadonly } from './types';
+import { NormalizedCache, NormalizedCacheObject } from './types';
import { fieldNameFromStoreName } from './helpers';
import { Policies } from './policies';
+import { Modifier, Modifiers, SafeReadonly } from '../core/types/common';
const hasOwn = Object.prototype.hasOwnProperty;
-export type Modifier = (value: T, details: {
- DELETE: typeof DELETE;
- fieldName: string;
- storeFieldName: string;
- isReference: typeof isReference;
- toReference: Policies["toReference"];
- readField(
- fieldName: string,
- objOrRef?: StoreObject | Reference,
- ): SafeReadonly;
-}) => T;
-
-export type Modifiers = {
- [fieldName: string]: Modifier;
-}
-
const DELETE: any = Object.create(null);
const delModifier: Modifier = () => DELETE;
diff --git a/src/cache/inmemory/helpers.ts b/src/cache/inmemory/helpers.ts
index 232e4c050a2..9f7ca769031 100644
--- a/src/cache/inmemory/helpers.ts
+++ b/src/cache/inmemory/helpers.ts
@@ -1,6 +1,13 @@
import { FieldNode } from 'graphql';
-import { NormalizedCache, StoreObject } from './types';
-import { Reference, isReference, StoreValue, isField } from '../../utilities/graphql/storeUtils';
+
+import { NormalizedCache } from './types';
+import {
+ Reference,
+ isReference,
+ StoreValue,
+ StoreObject,
+ isField
+} from '../../utilities/graphql/storeUtils';
import { DeepMerger, ReconcilerFunction } from '../../utilities/common/mergeDeep';
export function getTypenameFromStoreObject(
diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts
index e3dbb16eb70..d2a2f66e8b7 100644
--- a/src/cache/inmemory/inMemoryCache.ts
+++ b/src/cache/inmemory/inMemoryCache.ts
@@ -6,15 +6,16 @@ import { dep, wrap } from 'optimism';
import { ApolloCache, Transaction } from '../core/cache';
import { Cache } from '../core/types/Cache';
+import { Modifier, Modifiers } from '../core/types/common';
import { addTypenameToDocument } from '../../utilities/graphql/transform';
+import { StoreObject } from '../../utilities/graphql/storeUtils';
import {
ApolloReducerConfig,
NormalizedCacheObject,
- StoreObject,
} from './types';
import { StoreReader } from './readFromStore';
import { StoreWriter } from './writeToStore';
-import { EntityStore, supportsResultCaching, Modifiers, Modifier } from './entityStore';
+import { EntityStore, supportsResultCaching } from './entityStore';
import {
defaultDataIdFromObject,
PossibleTypesMap,
diff --git a/src/cache/inmemory/policies.ts b/src/cache/inmemory/policies.ts
index 514a97335f4..a46ac91308f 100644
--- a/src/cache/inmemory/policies.ts
+++ b/src/cache/inmemory/policies.ts
@@ -12,32 +12,26 @@ import {
FragmentMap,
getFragmentFromSelection,
} from '../../utilities/graphql/fragments';
-
import {
isField,
getTypenameFromResult,
storeKeyNameFromField,
StoreValue,
+ StoreObject,
argumentsObjectFromField,
Reference,
makeReference,
isReference,
} from '../../utilities/graphql/storeUtils';
-
import { canUseWeakMap } from '../../utilities/common/canUse';
-
-import {
- IdGetter,
- StoreObject,
- SafeReadonly,
-} from "./types";
-
+import { IdGetter } from "./types";
import {
fieldNameFromStoreName,
FieldValueToBeMerged,
isFieldValueToBeMerged,
} from './helpers';
import { FieldValueGetter } from './entityStore';
+import { SafeReadonly } from '../core/types/common';
const hasOwn = Object.prototype.hasOwnProperty;
diff --git a/src/cache/inmemory/readFromStore.ts b/src/cache/inmemory/readFromStore.ts
index 1688f4343e3..99941d81b18 100644
--- a/src/cache/inmemory/readFromStore.ts
+++ b/src/cache/inmemory/readFromStore.ts
@@ -15,6 +15,7 @@ import {
Reference,
isReference,
makeReference,
+ StoreObject,
} from '../../utilities/graphql/storeUtils';
import { createFragmentMap, FragmentMap } from '../../utilities/graphql/fragments';
import { shouldInclude } from '../../utilities/graphql/directives';
@@ -31,7 +32,6 @@ import { Cache } from '../core/types/Cache';
import {
DiffQueryAgainstStoreOptions,
ReadQueryOptions,
- StoreObject,
NormalizedCache,
} from './types';
import { supportsResultCaching } from './entityStore';
diff --git a/src/cache/inmemory/types.ts b/src/cache/inmemory/types.ts
index e66255c0c1a..06477429b8d 100644
--- a/src/cache/inmemory/types.ts
+++ b/src/cache/inmemory/types.ts
@@ -1,8 +1,9 @@
import { DocumentNode } from 'graphql';
import { Transaction } from '../core/cache';
-import { StoreValue } from '../../utilities/graphql/storeUtils';
-import { Modifiers, Modifier, FieldValueGetter } from './entityStore';
+import { Modifier, Modifiers } from '../core/types/common';
+import { StoreValue, StoreObject } from '../../utilities/graphql/storeUtils';
+import { FieldValueGetter } from './entityStore';
export { StoreValue }
export interface IdGetterObj extends Object {
@@ -59,20 +60,6 @@ export interface NormalizedCacheObject {
[dataId: string]: StoreObject | undefined;
}
-export interface StoreObject {
- __typename?: string;
- [storeFieldName: string]: StoreValue;
-}
-
-// The Readonly type only really works for object types, since it marks
-// all of the object's properties as readonly, but there are many cases when
-// a generic type parameter like TExisting might be a string or some other
-// primitive type, in which case we need to avoid wrapping it with Readonly.
-// SafeReadonly collapses to just string, which makes string
-// assignable to SafeReadonly, whereas string is not assignable to
-// Readonly, somewhat surprisingly.
-export type SafeReadonly = T extends object ? Readonly : T;
-
export type OptimisticStoreItem = {
id: string;
data: NormalizedCacheObject;
diff --git a/src/cache/inmemory/writeToStore.ts b/src/cache/inmemory/writeToStore.ts
index 6087b58068e..2587566f5b2 100644
--- a/src/cache/inmemory/writeToStore.ts
+++ b/src/cache/inmemory/writeToStore.ts
@@ -19,6 +19,7 @@ import {
isField,
resultKeyNameFromField,
StoreValue,
+ StoreObject,
} from '../../utilities/graphql/storeUtils';
import { shouldInclude } from '../../utilities/graphql/directives';
@@ -26,7 +27,7 @@ import { cloneDeep } from '../../utilities/common/cloneDeep';
import { Policies } from './policies';
import { defaultNormalizedCacheFactory } from './entityStore';
-import { NormalizedCache, StoreObject } from './types';
+import { NormalizedCache } from './types';
import { makeProcessedFieldsMerger, FieldValueToBeMerged } from './helpers';
export type WriteContext = {
diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts
index 50eb63fc881..4360af1e33c 100644
--- a/src/core/QueryManager.ts
+++ b/src/core/QueryManager.ts
@@ -5,7 +5,6 @@ import { ApolloLink } from '../link/core/ApolloLink';
import { execute } from '../link/core/execute';
import { FetchResult } from '../link/core/types';
import { Cache } from '../cache/core/types/Cache';
-import { DataProxy } from '../cache/core/types/DataProxy';
import {
getDefaultValues,
@@ -1452,7 +1451,9 @@ function markMutationResult(
document: DocumentNode;
variables: any;
queryUpdatersById: Record;
- update: ((proxy: DataProxy, mutationResult: Object) => void) | undefined;
+ update:
+ ((cache: ApolloCache, mutationResult: Object) => void) |
+ undefined;
},
cache: ApolloCache,
) {
diff --git a/src/core/watchQueryOptions.ts b/src/core/watchQueryOptions.ts
index f789dd93db8..934ab26f62a 100644
--- a/src/core/watchQueryOptions.ts
+++ b/src/core/watchQueryOptions.ts
@@ -1,7 +1,7 @@
import { DocumentNode, ExecutionResult } from 'graphql';
+import { ApolloCache } from '../cache/core/cache';
import { FetchResult } from '../link/core/types';
-import { DataProxy } from '../cache/core/types/DataProxy';
import { MutationQueryReducersMap } from './types';
import { PureQueryOptions, OperationVariables } from './types';
@@ -218,9 +218,9 @@ export interface MutationBaseOptions<
awaitRefetchQueries?: boolean;
/**
- * A function which provides a {@link DataProxy} and the result of the
- * mutation to allow the user to update the store based on the results of the
- * mutation.
+ * A function which provides an {@link ApolloCache} instance, and the result
+ * of the mutation, to allow the user to update the store based on the
+ * results of the mutation.
*
* This function will be called twice over the lifecycle of a mutation. Once
* at the very beginning if an `optimisticResponse` was provided. The writes
@@ -229,11 +229,6 @@ export interface MutationBaseOptions<
* resolved. At that point `update` will be called with the *actual* mutation
* result and those writes will not be rolled back.
*
- * The reason a {@link DataProxy} is provided instead of the user calling the
- * methods directly on {@link ApolloClient} is that all of the writes are
- * batched together at the end of the update, and it allows for writes
- * generated by optimistic data to be rolled back.
- *
* Note that since this function is intended to be used to update the
* store, it cannot be used with a `no-cache` fetch policy. If you're
* interested in performing some action after a mutation has completed,
@@ -284,6 +279,6 @@ export interface MutationOptions<
// Add a level of indirection for `typedoc`.
export type MutationUpdaterFn = (
- proxy: DataProxy,
+ cache: ApolloCache,
mutationResult: FetchResult,
) => void;
diff --git a/src/utilities/graphql/storeUtils.ts b/src/utilities/graphql/storeUtils.ts
index 5bf701f1a2c..9cb365d3a29 100644
--- a/src/utilities/graphql/storeUtils.ts
+++ b/src/utilities/graphql/storeUtils.ts
@@ -44,6 +44,11 @@ export type StoreValue =
| void
| Object;
+export interface StoreObject {
+ __typename?: string;
+ [storeFieldName: string]: StoreValue;
+}
+
function isStringValue(value: ValueNode): value is StringValueNode {
return value.kind === 'StringValue';
}