{"id":13530336,"url":"https://github.com/ecerroni/apollo-cache-updater","last_synced_at":"2025-10-05T02:49:38.965Z","repository":{"id":33544924,"uuid":"159340043","full_name":"ecerroni/apollo-cache-updater","owner":"ecerroni","description":"Helper for updating the apollo cache after a mutation","archived":false,"fork":false,"pushed_at":"2022-12-10T02:02:06.000Z","size":5630,"stargazers_count":160,"open_issues_count":27,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-01T11:35:22.921Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ecerroni.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-27T13:30:54.000Z","updated_at":"2024-06-04T10:55:31.000Z","dependencies_parsed_at":"2023-01-15T01:30:56.411Z","dependency_job_id":null,"html_url":"https://github.com/ecerroni/apollo-cache-updater","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecerroni%2Fapollo-cache-updater","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecerroni%2Fapollo-cache-updater/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecerroni%2Fapollo-cache-updater/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecerroni%2Fapollo-cache-updater/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ecerroni","download_url":"https://codeload.github.com/ecerroni/apollo-cache-updater/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246691577,"owners_count":20818535,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T07:00:48.242Z","updated_at":"2025-10-05T02:49:38.895Z","avatar_url":"https://github.com/ecerroni.png","language":"JavaScript","funding_links":[],"categories":["Developer Tools","Uncategorized"],"sub_categories":["Uncategorized"],"readme":"# apollo-cache-updater\n\n[![Generated with nod](https://img.shields.io/badge/generator-nod-2196F3.svg?style=flat-square)](https://github.com/diegohaz/nod)\n[![NPM version](https://img.shields.io/npm/v/apollo-cache-updater.svg?style=flat-square)](https://npmjs.org/package/apollo-cache-updater)\n[![Build Status](https://img.shields.io/travis/ecerroni/apollo-cache-updater/master.svg?style=flat-square)](https://travis-ci.org/ecerroni/apollo-cache-updater) [![Coverage Status](https://img.shields.io/codecov/c/github/ecerroni/apollo-cache-updater/master.svg?style=flat-square)](https://codecov.io/gh/ecerroni/apollo-cache-updater/branch/master)\n![Dependencies](https://img.shields.io/librariesio/dependents/npm/apollo-cache-updater.svg)\n![minified + gzip](https://img.shields.io/bundlephobia/minzip/apollo-cache-updater.svg)\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"200\" height=\"200\" src=\"./assets/images/apc_logo.png\"\u003e\n\u003c/p\u003e\n\n\n\u003ccenter\u003e\nZero-dependencies helper for updating the apollo cache after a mutation\n\u003c/center\u003e\n\n## Status\nUnder heavy development\n\n## Why?\nI wanted an updater that steals the magic of refetch queries while keeping the power of apollo local cache, but stripped of the boilerplate usually needed for each mutation update.\n\nUpdating the local cache becomes exponentially complicated when it needs to:\n- include multiple variables\n- include multiple queries\n- know which of our target queries has been already fired before our speficific mutation happend\n- cover scenarios** where apollo's in-place update may not be sufficient\n\n** Add/remove to list, move from one list to another, update filtered list, etc.\n\nThis solution tries to decouple the view from the caching layer by configuring the mutation's result caching behavior through the Apollo's `update` variable.\n\n## Demo\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./assets/images/apc_demo.gif\"\u003e\n\u003c/p\u003e\n\n\n## Install\n\n    $ npm install --save apollo-cache-updater\n\n    OR \n\n    $ yarn add apollo-cache-updater\n\n## Usage\n\nExample: Add an Article\n\nThe following block of code:\n- adds a new article to getArticles queries that contain the `published: true` variable\n- adds `1` to the articleCounts queries that contain the `published: true` variable\n```js\nimport ApolloCacheUpdater from \"apollo-cache-updater\";\nimport { getArticles, articlesCount } form '../../apollo/api'; // your apollo queries\n\ncreateArticleMutation({ // your mutation\n    variables: {\n        ...articleVariables // your mutation vars\n    },\n    update: (proxy, { data: { createArticle = {} } }) =\u003e { // your mutation response        \n        const mutationResult = createArticle; // mutation result to pass into the updater\n        const updates = ApolloCacheUpdater({\n            proxy, // apollo proxy\n            queriesToUpdate: [getArticles, articlesCount], // queries you want to automatically update\n            searchVariables: {\n                published: true, // update queries in the cache that have these vars\n            },\n            mutationResult,\n        })\n        if (updates) console.log(`Article added`) // if no errors\n    },\n})\n```\n\u003chr /\u003e\n\nExample: **Remove an Article**\n\nThe following block of code:\n- removes an article with a specific id from the getArticles queries that contain the `published: true` variable\n- subtract `1` from the articleCounts queries that contain the `published: true` variable\n```js\nremoveArticleMutation({ // your mutation\n    variables: {\n        id: article.id // your mutation vars\n    },\n    update: (proxy }) =\u003e {\n        const updates = ApolloCacheUpdater({\n            proxy, // mandatory\n            queriesToUpdate: [getArticles, articlesCount], // queries you want to automatically update\n            searchVariables: {\n                published: true, // update queries in the cache that have these vars\n            },\n            operation: 'REMOVE',\n            mutationResult: { id: article.id },\n        })\n        if (updates) console.log(`Article removed`) // if no errors\n    },\n})\n```\n\u003chr /\u003e\n\n### Query params\n\nThis package assumes an exact correspondence between operation's params and query's params. So the updates will not work if you're using queries like this:\n```\nquery getItems(\n  $limit: Int\n  $offset: Int\n  $sort: String\n) {\n  getItems(\n    options: {\n      limit: $limit\n      offset: $offset\n      sort: $sort\n    }\n  ) {\n...\n}\n```\n\nIt should be:\n```\nquery getItems(\n  $limit: Int\n  $offset: Int\n  $sort: String\n) {\n  getItems(\n    limit: $limit\n    offset: $offset\n    sort: $sort\n  ) {\n...\n}\n```\n\n\u003chr /\u003e\n\n### Advanced Usage\n\n**@client** directive: remote queries with mixed local state.\n\nIn case you have queries like this one:\n```\nconst GET_TODO = gql`\n  query todos {\n    todos {\n      id\n      type\n      local @client\n    }\n  }\n`;\n```\n\nIt's your duty to extend the mutation results with the local state fields that are going to be missing from the response:\n\n```\nupdate: (proxy, { data: { addTodo = {} } }) =\u003e {\n              // your mutation response\n              const mutationResult = addTodo; // mutation result to pass into the updater\n              const updates = ApolloCacheUpdater({\n                proxy, // apollo proxy\n                queriesToUpdate: [GET_TODO], // queries you want to automatically update\n                searchVariables: {},\n                mutationResult: {\n                  ...mutationResult,\n                  local: client.localState.resolvers[\n                    mutationResult.__typename\n                  ].local(mutationResult)\n                }\n              });\n              if (updates) console.log(`Todo added`); // if no errors\n            }\n```\nCheck this fully working example [here](https://codesandbox.io/s/apollo-mutations-m5fx0)\n\nExample: **Match any query**\n\nIf you need to go through all matching query names ignoring any variables you should use the `ANY` operator. This does not work with the `MOVE` operation.\n\nThe following code removes the article with the matching id from any getArticles cached items, despite all possibe variables combinations stored in the cache.\n\n```\nremoveArticleMutation({ // your mutation\n    variables: {\n        id: article.id // your mutation vars\n    },\n    update: (proxy }) =\u003e {\n        const updates = ApolloCacheUpdater({\n            proxy, // mandatory\n            operator: 'ANY',\n            queriesToUpdate: [getArticles], // queries you want to automatically update\n            searchVariables: {},\n            operation: 'REMOVE',\n            mutationResult: { id: article.id },\n        })\n        if (updates) console.log(`Article removed`) // if no errors\n    },\n})\n```\n\nExample: **Move an Article**\n\nThe following block of code:\n- removes an article from getArticles queries that contain the `published: true` variable and adds it to getArticles queries that contain the `published: false` variables\n- adds `1` to the articleCounts that contain the `published: true` variable and adds it to articleCounts queries that contain the `published: false` variables\n```js\nsetArticleStatus({\n    variables: {\n        _id: id,\n        published: false, // set the published status to false\n    },\n    update: (proxy, { data: { setArticleStatus = {} } }) =\u003e {    \n        const mutationResult = setArticleStatus;\n        const updates = ApolloCacheUpdater({\n            proxy, // mandatory\n            operation: 'MOVE',\n            queriesToUpdate: [getArticles, articlesCount],\n            searchVariables: {\n                published: true, // find the mutation result article that in the cache is still part of the queries with published = true and remove it\n            },\n            switchVars: {\n                published: false, // add the mutation result article to the queries that in the cache were invoked with published = false, if any\n            },\n            mutationResult,\n        })\n        if (updates) console.log(`Article moved`)\n    },\n})\n```\n\n**Complete configuration object**\n```js\n{\n    proxy, // mandatory\n    searchOperator: 'AND', // [AND, AND_EDGE, OR, OR_EDGE, ANY], default AND. If you need to match all searchVariable, just one at least or none (with the latter will ignore any variables)\n    searchVariables: {\n        ...vars // searchVariables cannot be nested objects\n    },\n    queriesToUpdate: [...queries],\n    operation: { // String || Object, default String ('ADD', 'REMOVE', 'MOVE', default: 'ADD')\n        type: 'MOVE', // 'ADD', 'REMOVE', 'MOVE', default: ADD\n        row: { // String || Object, default String ('TOP', 'BOTTOM', 'SORT', default: TOP)\n            type: 'SORT', // 'TOP', 'BOTTOM', 'SORT', [SORT is effective only for ADD and MOVE], default: TOP\n            field: 'createdAt', // if SORT, this indicates the field to be sorted\n        },\n    },\n    switchVars: {\n        ...otherVars, // switchVars cannot be nested objects\n    },\n    mutationResult, // mandatory\n    ID: '_id', // Set the id field returned by your queries, default: id\n}\n````\n\n#### Override default actions\n\nFor maximum flexibility you can also override the default actions of `ADD` and `REMOVE` operations.\n\nAdd 1 to all queries which data type is a number:\n\n```js\n    operation: {\n        type: 'ADD',\n        add: ({ query, type, data, variables }) =\u003e {\n            if (type === 'number') {\n                return data + 1;\n            }\n        }\n    }\n```\n\nPass a custom action for the query `articles`\n```js\n    operation: {\n        type: 'ADD',\n        add: ({ query, type, data, variables }) =\u003e {\n            if (query === 'articles') {\n                return [mutationResult, ...data]; // if you have mixed queries you need to extend the mutationResults with the missing local fields here too\n            }\n        }\n    }\n```\nWhen it's an array of objects `mutationResult` must contain the right `__typename` field too.\n\nUse the custom add/remove if you want to:\n- override the default behavior for arrays\n- override the default behavior for numbers\n- add a custom function to handle strings (not handled by default)\n- affect other variables depending on the query data\n- you have specific needs that default actions do not satisfy\n\n\nNote:\n- when using custom `add` and/or `remove` sorting is disabled and will be ignored even if you set it. It's up to you to do the sorting in the custom function\n- if you do not return the mutated data (or it is undefined) the custom add/remove function's result will be skipped and default actions'result will be used instead. However the logic inside the custom function will always be executed.\n- if the operation type is MOVE you need to pass both custom  `add` and `remove`. Passing just one of them will not work.\n\n#### EDGE cases\nThere are edge situations where the cache includes queries like:\n- articles({})\n- articles({\"sort\":null,\"limit\":null,\"start\":null,\"where\":null})\n\nThis typically happen when a query with params like\n```\nquery articles($sort: String, $limit: Int, $start: Int, $where: JSON) {\n    articles(sort: $sort, limit: $limit, start: $start, where: $where) {\n      _id\n      title\n      published\n      flagged\n    }\n  }\n```\ngets called with either no variables object at all (variables object is not present) or a variables empty object has been passed, such as `variables: {}`. This may happen when variables are built programmatically.\n\n`articles({})` and `articles({\"sort\":null,\"limit\":null,\"start\":null,\"where\":null})` are not handled by default and will be skipped, that is they will not be affected by the update.\n\nHowever EDGE cases can be handled passing one of the EDGE searchOperator(s) such as `AND_EDGE` and `OR_EDGE`.\n\nAs an example using searchOperator: 'AND_EDGE' the end result would be:\n\n|                                                                | ADD(to published) | REMOVE(from published) | MOVE(from published to flagged) |\n|----------------------------------------------------------------|-------------------|------------------------|---------------------------------|\n| articles({\"published\":true})                                   | +1                | -1                     | -1                              |\n| articles({\"flagged\":true})                                     | 0                 | 0                      | +1                              |\n| articles({})                                                   | +1                | -1                     | +1/-1 (=no-change)              |\n| articles({\"sort\":null,\"limit\":null,\"start\":null,\"where\":null}) | +1                | -1                     | +1/-1 (=no-change)              |\n\nOn the other hand queries with no variables included like:\n```\nquery articles {\n    articles {\n      _id\n      title\n      published\n      flagged\n    }\n  }\n```\nare not considered EDGE cases. If included in the `queriesToUpdate` array they will be always updated like the following despite searchOperator that is used:\n\n|                              | ADD(to published) | REMOVE(from published) | MOVE(from published to flagged) |\n|------------------------------|-------------------|------------------------|---------------------------------|\n| articles({\"published\":true}) | +1                | -1                     | -1                              |\n| articles({\"flagged\":true})   | 0                 | 0                      | +1                              |\n| articles                     | +1                | -1                     | +1/-1 (=no-change)              |\n\nIn the unlikely case that `queriesToUpdate` contains exclusively queries with no paramaters, the `searchVariables` should be an emptyObject:\n```\nupdate: (proxy, { data: { createArticle = {} } }) =\u003e { // your mutation response        \n        const mutationResult = createArticle;\n        const updates = ApolloCacheUpdater({\n            proxy,\n            queriesToUpdate: [getArticlesNoParams, articlesCountNoParams],\n            searchVariables: {},\n            mutationResult,\n        })\n        if (updates) console.log(`Article added`)\n    },\n```\n## License\n\nMIT © [ric0](https://github.com/ecerroni)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecerroni%2Fapollo-cache-updater","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fecerroni%2Fapollo-cache-updater","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecerroni%2Fapollo-cache-updater/lists"}