@graphql-tools/federation
Version:
Useful tools to create and manipulate GraphQL schemas.
128 lines (127 loc) • 4.75 kB
JavaScript
import { Kind } from 'graphql';
import { MapperKind, mapSchema, mergeDeep, parseSelectionSet } from '@graphql-tools/utils';
export function getArgsFromKeysForFederation(representations) {
return { representations };
}
export function getKeyForFederation(root) {
return root;
}
export function projectDataSelectionSet(data, selectionSet) {
if (data == null || selectionSet == null) {
return data;
}
if (Array.isArray(data)) {
return data.map(entry => projectDataSelectionSet(entry, selectionSet));
}
const projectedData = {
__typename: data.__typename,
};
for (const selection of selectionSet.selections) {
if (selection.kind === Kind.FIELD) {
const key = selection.name.value;
if (data.hasOwnProperty(key)) {
const projectedKeyData = projectDataSelectionSet(data[key], selection.selectionSet);
if (projectedData[key]) {
projectedData[key] = mergeDeep([projectedData[key], projectedKeyData]);
}
else {
projectedData[key] = projectDataSelectionSet(data[key], selection.selectionSet);
}
}
}
else if (selection.kind === Kind.INLINE_FRAGMENT) {
if (selection.typeCondition &&
projectedData['__typename'] != null &&
projectedData['__typename'] !== selection.typeCondition.name.value) {
continue;
}
Object.assign(projectedData, mergeDeep([projectedData, projectDataSelectionSet(data, selection.selectionSet)]));
}
}
return projectedData;
}
export function getKeyFnForFederation(typeName, keys) {
if (keys.some(key => key.includes('{'))) {
const parsedSelectionSet = parseSelectionSet(`{${keys.join(' ')}}`, { noLocation: true });
return function keyFn(root) {
root.__typename ||= typeName;
return projectDataSelectionSet(root, parsedSelectionSet);
};
}
const allKeyProps = keys.flatMap(key => key.split(' ')).map(key => key.trim());
if (allKeyProps.length > 1) {
return function keyFn(root) {
return allKeyProps.reduce((prev, key) => {
prev[key] = root[key];
return prev;
}, { __typename: root['__typename'] || typeName });
};
}
const keyProp = allKeyProps[0];
return function keyFn(root) {
return {
__typename: root['__typename'] || typeName,
[keyProp]: root[keyProp],
};
};
}
export function getCacheKeyFnFromKey(key) {
if (key.includes('{')) {
const parsedSelectionSet = parseSelectionSet(`{${key}}`, { noLocation: true });
return function cacheKeyFn(root) {
return JSON.stringify(projectDataSelectionSet(root, parsedSelectionSet));
};
}
const keyTrimmed = key.trim();
const keys = keyTrimmed.split(' ').map(key => key.trim());
if (keys.length > 1) {
return function cacheKeyFn(root) {
return keys.map(key => root[key]).join(' ');
};
}
return function cacheKeyFn(root) {
return root[keyTrimmed];
};
}
const internalTypeNames = ['_Entity', '_Any', '_FieldSet', '_Service'];
export function filterInternalFieldsAndTypes(finalSchema) {
return mapSchema(finalSchema, {
[MapperKind.TYPE]: type => {
if (internalTypeNames.includes(type.name) ||
type.name.startsWith('link__') ||
type.astNode?.directives?.some(d => d.name.value === 'inaccessible')) {
return null;
}
return type;
},
[MapperKind.COMPOSITE_FIELD]: fieldConfig => {
if (fieldConfig.astNode?.directives?.some(d => d.name.value === 'inaccessible')) {
return null;
}
return fieldConfig;
},
[MapperKind.QUERY_ROOT_FIELD]: (fieldConfig, fieldName) => {
if (fieldName === '_entities') {
return null;
}
return fieldConfig;
},
[MapperKind.ENUM_VALUE]: valueConfig => {
if (valueConfig.astNode?.directives?.some(d => d.name.value === 'inaccessible')) {
return null;
}
},
[MapperKind.ARGUMENT]: argConfig => {
if (argConfig.astNode?.directives?.some(d => d.name.value === 'inaccessible')) {
return null;
}
return argConfig;
},
});
}
export function getNamedTypeNode(typeNode) {
if (typeNode.kind !== Kind.NAMED_TYPE) {
return getNamedTypeNode(typeNode.type);
}
return typeNode;
}