···11+---
22+'@urql/exchange-graphcache': patch
33+---
44+55+Add `invariant` to data layer that prevents cache writes during cache query operations. This prevents `cache.writeFragment`, `cache.updateQuery`, and `cache.link` from being called in `resolvers` for instance.
+14
docs/graphcache/errors.md
···395395implemented type for these.
396396397397Check the type mentioned and change it to one of the specific types.
398398+399399+## (27) Invalid Cache write
400400+401401+> Invalid Cache write: You may not write to the cache during cache reads.
402402+> Accesses to `cache.writeFragment`, `cache.updateQuery`, and `cache.link` may
403403+> not be made inside `resolvers` for instance.
404404+405405+If you're using the `Cache` inside your `cacheExchange` config you receive it
406406+either inside callbacks that are called when the cache is queried (e.g.
407407+`resolvers`) or when data is written to the cache (e.g. `updates`). You may not
408408+write to the cache when it's being queried.
409409+410410+Please make sure that you're not calling `cache.updateQuery`,
411411+`cache.writeFragment`, or `cache.link` inside `resolvers`.
+23-28
docs/graphcache/local-resolvers.md
···6565to transform records, like dates in our previous example, or to imitate server-side logic to allow
6666Graphcache to retrieve more data from its cache without sending a query to our API.
67676868+Furthermore, while we see on this page that we get access to methods like `cache.resolve` and other
6969+methods to read from our cache, only ["Cache Updates"](./cache-updates.md) get to write and change
7070+the cache. If you call `cache.updateQuery`, `cache.writeFragment`, or `cache.link` in resolvers,
7171+you‘ll get an error, since it‘s not possible to update the cache while reading from it.
7272+6873## Transforming Records
69747075As we've explored in the ["Normalized Caching" page's section on
···101106operating on. Hence, we can create a reusable resolver like so:
102107103108```js
104104-const transformToDate = (parent, _args, _cache, info) =>
105105- new Date(parent[info.fieldName]);
109109+const transformToDate = (parent, _args, _cache, info) => new Date(parent[info.fieldName]);
106110107111cacheExchange({
108112 resolvers: {
···124128 resolvers: {
125129 Todo: {
126130 text: (parent, args) => {
127127- return args.capitalize && parent.text
128128- ? parent.text.toUpperCase()
129129- : parent.text;
131131+ return args.capitalize && parent.text ? parent.text.toUpperCase() : parent.text;
130132 },
131133 },
132134 },
···178180The `__typename` field is required. Graphcache will [use its keying
179181logic](./normalized-caching.md#custom-keys-and-non-keyable-entities), and your custom `keys`
180182configuration to generate a key for this entity and will then be able to look this entity up in its
181181-local cache. As with regular queries, the resolver is known to return a link since the `todo(id:
182182-$id) { id }` will be used with a selection set, querying fields on the entity.
183183+local cache. As with regular queries, the resolver is known to return a link since the `todo(id: $id) { id }` will be used with a selection set, querying fields on the entity.
183184184185### Resolving by keys
185186···198199cacheExchange({
199200 resolvers: {
200201 Query: {
201201- todo: (_, args, cache) =>
202202- cache.keyOfEntity({ __typename: 'Todo', id: args.id }),
202202+ todo: (_, args, cache) => cache.keyOfEntity({ __typename: 'Todo', id: args.id }),
203203 },
204204 },
205205});
···253253cacheExchange({
254254 resolvers: {
255255 Todo: {
256256- updatedAt: (parent, _args, cache) =>
257257- new Date(cache.resolve(parent, "updatedAt")),
256256+ updatedAt: (parent, _args, cache) => new Date(cache.resolve(parent, 'updatedAt')),
258257 },
259258 },
260259});
···279278cacheExchange({
280279 resolvers: {
281280 Todo: {
282282- updatedAt: (parent, _args, cache) =>
283283- parent.updatedAt || cache.resolve(parent, "createdAt")
281281+ updatedAt: (parent, _args, cache) => parent.updatedAt || cache.resolve(parent, 'createdAt'),
284282 },
285283 },
286284});
···299297 resolvers: {
300298 Todo: {
301299 createdAt: (parent, _args, cache) =>
302302- cache.resolve(
303303- cache.resolve(parent, "author"), /* "Author:1" */
304304- "createdAt"
305305- )
300300+ cache.resolve(cache.resolve(parent, 'author') /* "Author:1" */, 'createdAt'),
306301 },
307302 },
308303});
···387382388383```js
389384import { gql } from '@urql/core';
390390-import { cacheExchange } from '@urql/exchange-graphcache';
385385+import { cacheExchange } from '@urql/exchange-graphcache';
391386392387const cache = cacheExchange({
393388 updates: {
394389 Mutation: {
395390 addTodo: (result, args, cache) => {
396391 const data = cache.readQuery({ query: Todos, variables: { from: 0, limit: 10 } });
397397- }
398398- }
399399- }
400400-})
392392+ },
393393+ },
394394+ },
395395+});
401396```
402397403398This way we'll get the stored data for the `TodosQuery` for the given `variables`.
···411406412407```js
413408import { gql } from '@urql/core';
414414-import { cacheExchange } from '@urql/exchange-graphcache';
409409+import { cacheExchange } from '@urql/exchange-graphcache';
415410416411const cache = cacheExchange({
417412 resolvers: {
···426421 `,
427422 { id: 1 }
428423 );
429429- }
430430- }
431431- }
432432-})
424424+ },
425425+ },
426426+ },
427427+});
433428```
434429435430> **Note:** In the above example, we've used
···528523 // Or if the pagination happens in a nested field:
529524 User: {
530525 todos: relayPagination(),
531531- }
526526+ },
532527 },
533528});
534529```
+4
docs/graphcache/normalized-caching.md
···445445affect other parts of the cache (like `Query.todos` here) beyond the automatic updates that a
446446normalized cache is expected to perform.
447447448448+We get methods like `cache.updateQuery`, `cache.writeFragment`, and `cache.link` in our updater
449449+functions, which aren't available to us in local resolvers, and can only be used in these `updates`
450450+entries to change the data that the cache holds.
451451+448452[Read more about writing cache updates on the "Cache Updates" page.](./cache-updates.md)
449453450454## Deterministic Cache Updates
···255255 fieldKey: string,
256256 value: T
257257) => {
258258+ if (process.env.NODE_ENV !== 'production') {
259259+ invariant(
260260+ currentOperation !== 'read',
261261+ 'Invalid Cache write: You may not write to the cache during cache reads. ' +
262262+ ' Accesses to `cache.writeFragment`, `cache.updateQuery`, and `cache.link` may ' +
263263+ ' not be made inside `resolvers` for instance.',
264264+ 27
265265+ );
266266+ }
267267+258268 // Optimistic values are written to a map in the optimistic dict
259269 // All other values are written to the base map
260270 const keymap: KeyMap<Dict<T | undefined>> = currentOptimisticKey
···550560 // Hide current dependencies from squashing operations
551561 const previousDependencies = currentDependencies;
552562 currentDependencies = new Set();
563563+ currentOperation = 'write';
553564554565 const links = currentData!.links.optimistic.get(layerKey);
555566 if (links) {
+6-5
exchanges/graphcache/src/store/store.test.ts
···468468 );
469469 let { data } = query(store, { query: connection });
470470471471- InMemoryData.initDataState('read', store.data, null);
471471+ InMemoryData.initDataState('write', store.data, null);
472472 expect((data as any).exercisesConnection).toEqual(null);
473473 const fields = store.inspectFields({ __typename: 'Query' });
474474 fields.forEach(({ fieldName, arguments: args }) => {
···483483 });
484484485485 it('should be able to write a fragment', () => {
486486- InMemoryData.initDataState('read', store.data, null);
486486+ InMemoryData.initDataState('write', store.data, null);
487487488488 store.writeFragment(
489489 gql`
···520520 });
521521522522 it('should be able to write a fragment by name', () => {
523523- InMemoryData.initDataState('read', store.data, null);
523523+ InMemoryData.initDataState('write', store.data, null);
524524525525 store.writeFragment(
526526 gql`
···626626 });
627627628628 it('should be able to update a query', () => {
629629- InMemoryData.initDataState('read', store.data, null);
629629+ InMemoryData.initDataState('write', store.data, null);
630630 store.updateQuery({ query: Todos }, data => ({
631631 ...data,
632632 todos: [
···686686 }
687687 );
688688689689- InMemoryData.initDataState('read', store.data, null);
689689+ InMemoryData.initDataState('write', store.data, null);
690690 store.updateQuery({ query: Appointment, variables: { id: '1' } }, data => ({
691691 ...data,
692692 appointment: {
···827827828828 describe('Invalidating an entity', () => {
829829 it('removes an entity from a list.', () => {
830830+ InMemoryData.initDataState('write', store.data, null);
830831 store.invalidate(todosData.todos[1]);
831832 const { data } = query(store, { query: Todos });
832833 expect(data).toBe(null);