Skip to content
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Cache control - and most of invalidation - is still a [discussing issue](https:/

## How does it work

This project exposes *invalidateFields*: a generator for a [`mutate.options.update`](http://dev.apollodata.com/react/api.html#graphql-mutation-options.update) implementation specialized in invalidating cache based on field paths.
This project exposes *invalidateFields*: a generator for a mutation [`update function`](https://www.apollographql.com/docs/react/advanced/caching.html#after-mutations) implementation specialized in invalidating cache based on field paths.

In some cases after a mutation you want to invalidate cache on other queries that might have become outdated, but you can't really update their results from the data provided by the mutation. The *refetchQueries* is often the tool of choice, but it allows no deep field invalidation, meaning you'll have to invalidate the exact and very specific performed queries. *invalidateFields* is an alternative.

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@
"author": "Lucas Constantino Silva <lucasconstantinosilva@gmail.com>",
"license": "MIT",
"devDependencies": {
"apollo-client": "^1.0.0-rc.4",
"apollo-cache-inmemory": "^1.2.10",
"apollo-client": "^2.4.2",
"babel-cli": "^6.18.0",
"babel-core": "^6.18.2",
"babel-preset-taller": "^0.1.1",
"babel-register": "^6.18.0",
"eslint": "^3.10.2",
"eslint-config-taller": "^1.0.4",
"graphql-tag": "^1.3.1",
"graphql": "^14.0.2",
"graphql-tag": "^2.9.2",
"husky": "^0.13.2",
"jest": "^19.0.2"
},
Expand Down
18 changes: 14 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,19 @@ export const matchFinder = (data, paths) => {
* an array of invalidating field paths.
* @return {Function} update Update function such as expected by Apollo option.
*/
export const invalidateFields = generator => (proxy, result) =>
matchFinder(proxy.data, generator(proxy, result) || [])
export const invalidateFields = generator => (proxy, result) => {
// This relies on a couple of implementation details of apollo-cache-inmemory,
// namely that `proxy` will actually be the cache itself and its `data` property
// will be an instance of the internal class `ObjectCache`.
// These are not guaranteed by the public API and so invalidateFields could break without
// warning
const objectCacheData = proxy.data && proxy.data.data
if (!objectCacheData) {
throw new Error('`invalidateFields` is only known to work with apollo-cache-inmemory and the default `storeFactory`.')
}
matchFinder(objectCacheData, generator(proxy, result) || [])
.forEach(path => path.length === 1 && path[0] === ROOT
? Object.keys(proxy.data[ROOT]).forEach(key => del(proxy.data, [ROOT, key]))
: del(proxy.data, path)
? Object.keys(objectCacheData[ROOT]).forEach(key => del(objectCacheData, [ROOT, key]))
: del(objectCacheData, path)
)
}
16 changes: 15 additions & 1 deletion tests/integration.test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import ApolloClient from 'apollo-client'
import { ApolloLink, Observable } from 'apollo-link'
import { InMemoryCache } from 'apollo-cache-inmemory'
import gql from 'graphql-tag'

import { invalidateFields } from 'apollo-cache-invalidation'

const dataIdFromObject = ({ __typename, id }) =>
!id || !__typename ? null : __typename + id

const mockLink = resolver => new ApolloLink((operation, forward) => {
const resultPromise = resolver(operation)
return new Observable(observer => resultPromise.then(result => {
observer.next(result)
observer.complete()
return result
}, err => {
observer.error(err)
}))
})

const mockedClient = query => new ApolloClient({
ssrMode: true,
dataIdFromObject,
networkInterface: { query },
cache: new InMemoryCache(),
link: mockLink(query),
})

/*
Expand Down
56 changes: 29 additions & 27 deletions tests/invalidateFields.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import { invalidateFields } from 'apollo-cache-invalidation'

const getProxyObject = () => ({
data: {
'ROOT_QUERY': {
'refField1': { type: 'id', id: 'id1', generated: false },
'refField2': { type: 'id', id: 'id2', generated: false },
'refField3': { type: 'id', id: 'id3', generated: false }
},
'id1': { 'f1': 'id1 field one value', 'f2': 'id1 field two value' },
'id2': { 'f1': 'id2 field one value', 'f2': 'id2 field two value' },
'id3': { 'refField4': { type: 'id', id: 'id4', generated: false } },
'id4': { 'f1': 'id3 field one value', 'f2': 'id3 field two value' },
data: {
'ROOT_QUERY': {
'refField1': { type: 'id', id: 'id1', generated: false },
'refField2': { type: 'id', id: 'id2', generated: false },
'refField3': { type: 'id', id: 'id3', generated: false }
},
'id1': { 'f1': 'id1 field one value', 'f2': 'id1 field two value' },
'id2': { 'f1': 'id2 field one value', 'f2': 'id2 field two value' },
'id3': { 'refField4': { type: 'id', id: 'id4', generated: false } },
'id4': { 'f1': 'id3 field one value', 'f2': 'id3 field two value' },
}
}
})

Expand All @@ -19,52 +21,52 @@ describe('[method] invalidateFields', () => {
const proxy = getProxyObject()
const invalidator = invalidateFields(() => [['id1']])

expect(proxy).toHaveProperty('data.id1')
expect(proxy).toHaveProperty('data.data.id1')
invalidator(proxy, {})
expect(proxy).not.toHaveProperty('data.id1')
expect(proxy).not.toHaveProperty('data.data.id1')
})

it('should invalidate second level paths', () => {
const proxy = getProxyObject()
const invalidator = invalidateFields(() => [['id1', 'f1']])

expect(proxy).toHaveProperty('data.id1.f1')
expect(proxy).toHaveProperty('data.data.id1.f1')
invalidator(proxy, {})
expect(proxy).toHaveProperty('data.id1')
expect(proxy).not.toHaveProperty('data.id1.f1')
expect(proxy).toHaveProperty('data.data.id1')
expect(proxy).not.toHaveProperty('data.data.id1.f1')
})

it('should invalidate third level (via reference) paths', () => {
const proxy = getProxyObject()
const invalidator = invalidateFields(() => [['ROOT_QUERY', 'refField2', 'f1']])

expect(proxy).toHaveProperty('data.id2.f1')
expect(proxy).toHaveProperty('data.data.id2.f1')
invalidator(proxy, {})
expect(proxy).toHaveProperty('data.ROOT_QUERY.refField2')
expect(proxy).toHaveProperty('data.id2')
expect(proxy).not.toHaveProperty('data.id2.f1')
expect(proxy).toHaveProperty('data.data.ROOT_QUERY.refField2')
expect(proxy).toHaveProperty('data.data.id2')
expect(proxy).not.toHaveProperty('data.data.id2.f1')
})

it('should invalidate fourth level (via double reference) paths', () => {
const proxy = getProxyObject()
const invalidator = invalidateFields(() => [['ROOT_QUERY', 'refField3', 'refField4', 'f1']])

expect(proxy).toHaveProperty('data.id4.f1')
expect(proxy).toHaveProperty('data.data.id4.f1')
invalidator(proxy, {})
expect(proxy).toHaveProperty('data.ROOT_QUERY.refField3')
expect(proxy).toHaveProperty('data.id3.refField4')
expect(proxy).toHaveProperty('data.id4')
expect(proxy).not.toHaveProperty('data.id4.f1')
expect(proxy).toHaveProperty('data.data.ROOT_QUERY.refField3')
expect(proxy).toHaveProperty('data.data.id3.refField4')
expect(proxy).toHaveProperty('data.data.id4')
expect(proxy).not.toHaveProperty('data.data.id4.f1')
})

it('should invalidate paths paths', () => {
const proxy = getProxyObject()
const invalidator = invalidateFields(() => [['ROOT_QUERY'], ['id1', 'f1']])

expect(proxy).toHaveProperty('data.ROOT_QUERY')
expect(proxy).toHaveProperty('data.id1.f1')
expect(proxy).toHaveProperty('data.data.ROOT_QUERY')
expect(proxy).toHaveProperty('data.data.id1.f1')
invalidator(proxy, {})
expect(proxy).toHaveProperty('data.ROOT_QUERY', {})
expect(proxy).not.toHaveProperty('data.id1.f1')
expect(proxy).toHaveProperty('data.data.ROOT_QUERY', {})
expect(proxy).not.toHaveProperty('data.data.id1.f1')
})
})
133 changes: 88 additions & 45 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@
# yarn lockfile v1


"@types/async@^2.0.31":
version "2.0.39"
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.39.tgz#dd078f9ce8e9fff0c9410e0595f27a1f5a97db90"
"@types/async@2.0.49":
version "2.0.49"
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0"

"@types/graphql@^0.8.0":
version "0.8.6"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.8.6.tgz#b34fb880493ba835b0c067024ee70130d6f9bb68"

"@types/isomorphic-fetch@0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.33.tgz#3ea1b86f8b73e6a7430d01d4dbd5b1f63fd72718"
"@types/zen-observable@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"

abab@^1.0.3:
version "1.0.3"
Expand Down Expand Up @@ -90,19 +86,53 @@ anymatch@^1.3.0:
arrify "^1.0.0"
micromatch "^2.1.5"

apollo-client@^1.0.0-rc.4:
version "1.0.0-rc.4"
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.0-rc.4.tgz#2a663b947b2121fa551259cf86d0a429b8aae041"
apollo-cache-inmemory@^1.2.10:
version "1.2.10"
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.2.10.tgz#362d6c36cfd815a309b966f71e5d2b6c770c7989"
dependencies:
apollo-cache "^1.1.17"
apollo-utilities "^1.0.21"
graphql-anywhere "^4.1.19"

apollo-cache@1.1.17, apollo-cache@^1.1.17:
version "1.1.17"
resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.17.tgz#1fcca8423125223723b97fd72808be91a1a76490"
dependencies:
apollo-utilities "^1.0.21"

apollo-client@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.4.2.tgz#d2f044d8740723bf98a6d8d8b9684ee8c36150e6"
dependencies:
graphql-anywhere "^3.0.1"
graphql-tag "^1.3.1"
redux "^3.4.0"
"@types/zen-observable" "^0.8.0"
apollo-cache "1.1.17"
apollo-link "^1.0.0"
apollo-link-dedup "^1.0.0"
apollo-utilities "1.0.21"
symbol-observable "^1.0.2"
whatwg-fetch "^2.0.0"
zen-observable "^0.8.0"
optionalDependencies:
"@types/async" "^2.0.31"
"@types/graphql" "^0.8.0"
"@types/isomorphic-fetch" "0.0.33"
"@types/async" "2.0.49"

apollo-link-dedup@^1.0.0:
version "1.0.10"
resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.10.tgz#7b94589fe7f969777efd18a129043c78430800ae"
dependencies:
apollo-link "^1.2.3"

apollo-link@^1.0.0, apollo-link@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d"
dependencies:
apollo-utilities "^1.0.0"
zen-observable-ts "^0.8.10"

apollo-utilities@1.0.21, apollo-utilities@^1.0.0, apollo-utilities@^1.0.21:
version "1.0.21"
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.21.tgz#cb8b5779fe275850b16046ff8373f4af2de90765"
dependencies:
fast-json-stable-stringify "^2.0.0"
fclone "^1.0.11"

append-transform@^0.4.0:
version "0.4.0"
Expand Down Expand Up @@ -1508,6 +1538,10 @@ extsprintf@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"

fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"

fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
Expand All @@ -1524,6 +1558,10 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"

fclone@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/fclone/-/fclone-1.0.11.tgz#10e85da38bfea7fc599341c296ee1d77266ee640"

figures@^1.3.5:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
Expand Down Expand Up @@ -1727,13 +1765,21 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6:
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"

graphql-anywhere@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-3.0.1.tgz#73531db861174c8f212eafb9f8e84944b38b4e5a"
graphql-anywhere@^4.1.19:
version "4.1.19"
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.19.tgz#5f6ca3b58218e5449f4798e3c6d942fcd2fef082"
dependencies:
apollo-utilities "^1.0.21"

graphql-tag@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.3.1.tgz#16cdf13635f10bbc968c6f2c6265ffe883a906da"
graphql-tag@^2.9.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.9.2.tgz#2f60a5a981375f430bf1e6e95992427dc18af686"

graphql@^14.0.2:
version "14.0.2"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.0.2.tgz#7dded337a4c3fd2d075692323384034b357f5650"
dependencies:
iterall "^1.2.2"

growly@^1.3.0:
version "1.3.0"
Expand Down Expand Up @@ -2109,6 +2155,10 @@ istanbul-reports@^1.0.0:
dependencies:
handlebars "^4.0.3"

iterall@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7"

jest-changed-files@^19.0.2:
version "19.0.2"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-19.0.2.tgz#16c54c84c3270be408e06d2e8af3f3e37a885824"
Expand Down Expand Up @@ -2449,23 +2499,19 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"

lodash-es@^4.2.1:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"

lodash.pickby@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff"

lodash@^4.0.0, lodash@^4.14.0, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
lodash@^4.0.0, lodash@^4.14.0, lodash@^4.2.0, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"

longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"

loose-envify@^1.0.0, loose-envify@^1.1.0:
loose-envify@^1.0.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
dependencies:
Expand Down Expand Up @@ -2900,15 +2946,6 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"

redux@^3.4.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"
dependencies:
lodash "^4.2.1"
lodash-es "^4.2.1"
loose-envify "^1.1.0"
symbol-observable "^1.0.2"

regenerate@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
Expand Down Expand Up @@ -3414,10 +3451,6 @@ whatwg-encoding@^1.0.1:
dependencies:
iconv-lite "0.4.13"

whatwg-fetch@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"

whatwg-url@^4.3.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.6.0.tgz#ef98da442273be04cf9632e176f257d2395a1ae4"
Expand Down Expand Up @@ -3521,3 +3554,13 @@ yargs@~3.10.0:
cliui "^2.1.0"
decamelize "^1.0.0"
window-size "0.1.0"

zen-observable-ts@^0.8.10:
version "0.8.10"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz#18e2ce1c89fe026e9621fd83cc05168228fce829"
dependencies:
zen-observable "^0.8.0"

zen-observable@^0.8.0:
version "0.8.9"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.9.tgz#0475c760ff0eda046bbdfa4dc3f95d392807ac53"