debian-mirror-gitlab/app/assets/javascripts/lib/apollo/persist_link.js
2023-04-23 21:23:45 +05:30

141 lines
4.3 KiB
JavaScript

// 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;
});
});
};