diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 13:49:51 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 13:49:51 +0000 |
commit | 71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch) | |
tree | 6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /app/assets/javascripts/lib/apollo/persist_link.js | |
parent | a7253423e3403b8c08f8a161e5937e1488f5f407 (diff) | |
download | gitlab-ce-71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e.tar.gz |
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/lib/apollo/persist_link.js')
-rw-r--r-- | app/assets/javascripts/lib/apollo/persist_link.js | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/app/assets/javascripts/lib/apollo/persist_link.js b/app/assets/javascripts/lib/apollo/persist_link.js new file mode 100644 index 00000000000..9d95409d96c --- /dev/null +++ b/app/assets/javascripts/lib/apollo/persist_link.js @@ -0,0 +1,141 @@ +// this file is based on https://github.com/apollographql/apollo-cache-persist/blob/master/examples/react-native/src/utils/persistence/persistLink.ts +// with some heavy refactororing + +/* eslint-disable consistent-return */ +/* eslint-disable @gitlab/require-i18n-strings */ +/* eslint-disable no-param-reassign */ +import { visit } from 'graphql'; +import { ApolloLink } from '@apollo/client/core'; +import traverse from 'traverse'; + +const extractPersistDirectivePaths = (originalQuery, directive = 'persist') => { + const paths = []; + const fragmentPaths = {}; + const fragmentPersistPaths = {}; + + const query = visit(originalQuery, { + FragmentSpread: ({ name: { value: name } }, _key, _parent, _path, ancestors) => { + const root = ancestors.find( + ({ kind }) => kind === 'OperationDefinition' || kind === 'FragmentDefinition', + ); + + const rootKey = root.kind === 'FragmentDefinition' ? root.name.value : '$ROOT'; + + const fieldPath = ancestors + .filter(({ kind }) => kind === 'Field') + .map(({ name: { value } }) => value); + + fragmentPaths[name] = [rootKey].concat(fieldPath); + }, + Directive: ({ name: { value: name } }, _key, _parent, _path, ancestors) => { + if (name === directive) { + const fieldPath = ancestors + .filter(({ kind }) => kind === 'Field') + .map(({ name: { value } }) => value); + + const fragmentDefinition = ancestors.find(({ kind }) => kind === 'FragmentDefinition'); + + // If we are inside a fragment, we must save the reference. + if (fragmentDefinition) { + fragmentPersistPaths[fragmentDefinition.name.value] = fieldPath; + } else if (fieldPath.length) { + paths.push(fieldPath); + } + return null; + } + }, + }); + + // In case there are any FragmentDefinition items, we need to combine paths. + if (Object.keys(fragmentPersistPaths).length) { + visit(originalQuery, { + FragmentSpread: ({ name: { value: name } }, _key, _parent, _path, ancestors) => { + if (fragmentPersistPaths[name]) { + let fieldPath = ancestors + .filter(({ kind }) => kind === 'Field') + .map(({ name: { value } }) => value); + + fieldPath = fieldPath.concat(fragmentPersistPaths[name]); + + const fragment = name; + let parent = fragmentPaths[fragment][0]; + + while (parent && parent !== '$ROOT' && fragmentPaths[parent]) { + fieldPath = fragmentPaths[parent].slice(1).concat(fieldPath); + // eslint-disable-next-line prefer-destructuring + parent = fragmentPaths[parent][0]; + } + + paths.push(fieldPath); + } + }, + }); + } + + return { query, paths }; +}; + +/** + * Given a data result object path, return the equivalent query selection path. + * + * @param {Array} path The data result object path. i.e.: ["a", 0, "b"] + * @return {String} the query selection path. i.e.: "a.b" + */ +const toQueryPath = (path) => path.filter((key) => Number.isNaN(Number(key))).join('.'); + +const attachPersists = (paths, object) => { + const queryPaths = paths.map(toQueryPath); + function mapperFunction() { + if ( + !this.isRoot && + this.node && + typeof this.node === 'object' && + Object.keys(this.node).length && + !Array.isArray(this.node) + ) { + const path = toQueryPath(this.path); + + this.update({ + __persist: Boolean( + queryPaths.find( + (queryPath) => queryPath.indexOf(path) === 0 || path.indexOf(queryPath) === 0, + ), + ), + ...this.node, + }); + } + } + + return traverse(object).map(mapperFunction); +}; + +export const getPersistLink = () => { + return new ApolloLink((operation, forward) => { + const { query, paths } = extractPersistDirectivePaths(operation.query); + + // Noop if not a persist query + if (!paths.length) { + return forward(operation); + } + + // Replace query with one without @persist directives. + operation.query = query; + + // Remove requesting __persist fields. + operation.query = visit(operation.query, { + Field: ({ name: { value: name } }) => { + if (name === '__persist') { + return null; + } + }, + }); + + return forward(operation).map((result) => { + if (result.data) { + result.data = attachPersists(paths, result.data); + } + + return result; + }); + }); +}; |