142 lines
4.3 KiB
JavaScript
142 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;
|
||
|
});
|
||
|
});
|
||
|
};
|