Skip to content

Commit

Permalink
Revert "Remove experimental cache.modify method. (#6289)"
Browse files Browse the repository at this point in the history
This reverts commit 61a799b.
  • Loading branch information
benjamn committed May 21, 2020
1 parent 3d48f9f commit 5c73453
Show file tree
Hide file tree
Showing 11 changed files with 614 additions and 194 deletions.
19 changes: 14 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
- **[BREAKING]** `InMemoryCache` now _throws_ when data with missing or undefined query fields is written into the cache, rather than just warning in development. <br/>
[@benjamn](https://github.com/benjamn) in [#6055](https://github.com/apollographql/apollo-client/pull/6055)

- **[BREAKING]** The `client|cache.writeData` methods have been fully removed, as `writeData` is one of the easiest ways to turn faulty assumptions about how the cache represents data internally into cache inconsistency and corruption. Instead, use `client|cache.writeQuery` or `client|cache.writeFragment` to update the cache. <br/>
- **[BREAKING]** `client|cache.writeData` have been fully removed. `writeData` usage is one of the easiest ways to turn faulty assumptions about how the cache represents data internally, into cache inconsistency and corruption. `client|cache.writeQuery`, `client|cache.writeFragment`, and/or `cache.modify` can be used to update the cache. <br/>
[@benjamn](https://github.com/benjamn) in [#5923](https://github.com/apollographql/apollo-client/pull/5923)

- **[BREAKING]** Apollo Client will no longer deliver "stale" results to `ObservableQuery` consumers, but will instead log more helpful errors about which cache fields were missing. <br/>
Expand All @@ -56,9 +56,6 @@
- **[BREAKING?]** Refactor `QueryManager` to make better use of observables and enforce `fetchPolicy` more reliably. <br/>
[@benjamn](https://github.com/benjamn) in [#6221](https://github.com/apollographql/apollo-client/pull/6221)

- **[beta-BREAKING]** The experimental `cache.modify` method, first introduced in [PR #5909](https://github.com/apollographql/apollo-client/pull/5909), has been removed. <br/>
[@benjamn](https://github.com/benjamn) in [#6289](https://github.com/apollographql/apollo-client/pull/6289)

- `InMemoryCache` now supports tracing garbage collection and eviction. Note that the signature of the `evict` method has been simplified in a potentially backwards-incompatible way. <br/>
[@benjamn](https://github.com/benjamn) in [#5310](https://github.com/apollographql/apollo-client/pull/5310)

Expand Down Expand Up @@ -89,6 +86,18 @@
- The result caching system (introduced in [#3394](https://github.com/apollographql/apollo-client/pull/3394)) now tracks dependencies at the field level, rather than at the level of whole entity objects, allowing the cache to return identical (`===`) results much more often than before. <br/>
[@benjamn](https://github.com/benjamn) in [#5617](https://github.com/apollographql/apollo-client/pull/5617)

- `InMemoryCache` now has a method called `modify` which can be used to update the value of a specific field within a specific entity object:
```ts
cache.modify({
comments(comments: Reference[], { readField }) {
return comments.filter(comment => idToRemove !== readField("id", comment));
},
}, cache.identify(post));
```
This API gracefully handles cases where multiple field values are associated with a single field name, and also removes the need for updating the cache by reading a query or fragment, modifying the result, and writing the modified result back into the cache. Behind the scenes, the `cache.evict` method is now implemented in terms of `cache.modify`. <br/>
[@benjamn](https://github.com/benjamn) in [#5909](https://github.com/apollographql/apollo-client/pull/5909)
and [#6178](https://github.com/apollographql/apollo-client/pull/6178)

- `InMemoryCache` provides a new API for storing client state that can be updated from anywhere:
```ts
const v = cache.makeVar(123)
Expand Down Expand Up @@ -141,7 +150,7 @@
- Make sure `ApolloContext` plays nicely with IE11 when storing the shared context. <br/>
[@ms](https://github.com/ms) in [#5840](https://github.com/apollographql/apollo-client/pull/5840)

- Expose `cache.identify` to the mutation `update` function. <br/>
- Expose cache `modify` and `identify` to the mutate `update` function. <br/>
[@hwillson](https://github.com/hwillson) in [#5956](https://github.com/apollographql/apollo-client/pull/5956)

- Add a default `gc` implementation to `ApolloCache`. <br/>
Expand Down
14 changes: 14 additions & 0 deletions src/__tests__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2942,6 +2942,19 @@ describe('@connection', () => {
checkLastResult(abResults, a456bOyez);
checkLastResult(cResults, { c: "see" });

cache.modify({
c(value) {
expect(value).toBe("see");
return "saw";
},
});
await wait();

checkLastResult(aResults, a456);
checkLastResult(bResults, bOyez);
checkLastResult(abResults, a456bOyez);
checkLastResult(cResults, { c: "saw" });

client.cache.evict("ROOT_QUERY", "c");
await wait();

Expand Down Expand Up @@ -2978,6 +2991,7 @@ describe('@connection', () => {
expect(cResults).toEqual([
{},
{ c: "see" },
{ c: "saw" },
{},
]);

Expand Down
59 changes: 22 additions & 37 deletions src/__tests__/local-state/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,22 +546,19 @@ describe('Writing cache data from resolvers', () => {
resolvers: {
Mutation: {
start() {
const obj = {
__typename: 'Object',
id: 'uniqueId',
field: 1,
};

cache.writeQuery({
query,
data: { obj },
data: {
obj: { field: 1, id: 'uniqueId', __typename: 'Object' },
},
});

cache.writeFragment({
id: cache.identify(obj)!,
fragment: gql`fragment Field on Object { field }`,
data: { field: 2 },
});
cache.modify({
field(value) {
expect(value).toBe(1);
return 2;
},
}, 'Object:uniqueId');

return { start: true };
},
Expand All @@ -577,7 +574,7 @@ describe('Writing cache data from resolvers', () => {
});
});

itAsync('should not overwrite __typename when writing to the cache with an id', (resolve, reject) => {
it('should not overwrite __typename when writing to the cache with an id', () => {
const query = gql`
{
obj @client {
Expand All @@ -603,35 +600,22 @@ describe('Writing cache data from resolvers', () => {
resolvers: {
Mutation: {
start() {
const obj = {
__typename: 'Object',
id: 'uniqueId',
field: {
__typename: 'Field',
field2: 1,
},
};

cache.writeQuery({
query,
data: { obj },
});

cache.writeFragment({
id: cache.identify(obj)!,
fragment: gql`fragment FieldField2 on Object {
field {
field2
}
}`,
data: {
field: {
__typename: 'Field',
field2: 2,
obj: {
field: { field2: 1, __typename: 'Field' },
id: 'uniqueId',
__typename: 'Object',
},
},
});

cache.modify({
field(value: { field2: number }) {
expect(value.field2).toBe(1);
return { ...value, field2: 2 };
},
}, 'Object:uniqueId');
return { start: true };
},
},
Expand All @@ -644,7 +628,8 @@ describe('Writing cache data from resolvers', () => {
.then(({ data }: any) => {
expect(data.obj.__typename).toEqual('Object');
expect(data.obj.field.__typename).toEqual('Field');
}).then(resolve, reject);
})
.catch(e => console.log(e));
});
});

Expand Down
9 changes: 9 additions & 0 deletions src/cache/core/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getFragmentQueryDocument } from '../../utilities/graphql/fragments';
import { StoreObject } from '../../utilities/graphql/storeUtils';
import { DataProxy } from './types/DataProxy';
import { Cache } from './types/Cache';
import { Modifier, Modifiers } from './types/common';

export type Transaction<T> = (c: ApolloCache<T>) => void;

Expand Down Expand Up @@ -77,6 +78,14 @@ export abstract class ApolloCache<TSerialized> implements DataProxy {
public identify(object: StoreObject): string | undefined {
return;
}

public modify(
modifiers: Modifier<any> | Modifiers,
dataId?: string,
optimistic?: boolean,
): boolean {
return false;
}

public gc(): string[] {
return [];
Expand Down
25 changes: 25 additions & 0 deletions src/cache/core/types/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { DocumentNode } from 'graphql';

import {
isReference,
StoreValue,
StoreObject,
Reference
} from '../../../utilities/graphql/storeUtils';

import { ToReferenceFunction } from '../../inmemory/entityStore';

// The Readonly<T> 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
Expand All @@ -9,6 +18,22 @@ import { DocumentNode } from 'graphql';
// Readonly<any>, somewhat surprisingly.
export type SafeReadonly<T> = T extends object ? Readonly<T> : T;

export type Modifier<T> = (value: T, details: {
DELETE: any;
fieldName: string;
storeFieldName: string;
isReference: typeof isReference;
toReference: ToReferenceFunction;
readField<V = StoreValue>(
fieldName: string,
objOrRef?: StoreObject | Reference,
): SafeReadonly<V>;
}) => T;

export type Modifiers = {
[fieldName: string]: Modifier<any>;
}

export class MissingFieldError {
constructor(
public readonly message: string,
Expand Down
Loading

0 comments on commit 5c73453

Please sign in to comment.