@graphql-tools/delegate
Version:
A set of utils for faster development of GraphQL tools
312 lines (311 loc) • 14.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.prepareGatewayDocument = void 0;
const graphql_1 = require("graphql");
const utils_1 = require("@graphql-tools/utils");
const getDocumentMetadata_js_1 = require("./getDocumentMetadata.js");
function prepareGatewayDocument(originalDocument, transformedSchema, returnType, infoSchema) {
const wrappedConcreteTypesDocument = wrapConcreteTypes(returnType, transformedSchema, originalDocument);
if (infoSchema == null) {
return wrappedConcreteTypesDocument;
}
const { possibleTypesMap, reversePossibleTypesMap, interfaceExtensionsMap, fieldNodesByType, fieldNodesByField, dynamicSelectionSetsByField, } = getSchemaMetaData(infoSchema, transformedSchema);
const { operations, fragments, fragmentNames } = (0, getDocumentMetadata_js_1.getDocumentMetadata)(wrappedConcreteTypesDocument);
const { expandedFragments, fragmentReplacements } = getExpandedFragments(fragments, fragmentNames, possibleTypesMap);
const typeInfo = new graphql_1.TypeInfo(transformedSchema);
const expandedDocument = {
kind: graphql_1.Kind.DOCUMENT,
definitions: [...operations, ...fragments, ...expandedFragments],
};
const visitorKeyMap = {
Document: ['definitions'],
OperationDefinition: ['selectionSet'],
SelectionSet: ['selections'],
Field: ['selectionSet'],
InlineFragment: ['selectionSet'],
FragmentDefinition: ['selectionSet'],
};
return (0, graphql_1.visit)(expandedDocument, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
[graphql_1.Kind.SELECTION_SET]: node => visitSelectionSet(node, fragmentReplacements, transformedSchema, typeInfo, possibleTypesMap, reversePossibleTypesMap, interfaceExtensionsMap, fieldNodesByType, fieldNodesByField, dynamicSelectionSetsByField),
}),
// visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
// empty keys cannot be removed only because of typescript errors
// will hopefully be fixed in future version of graphql-js to be optional
visitorKeyMap);
}
exports.prepareGatewayDocument = prepareGatewayDocument;
function visitSelectionSet(node, fragmentReplacements, schema, typeInfo, possibleTypesMap, reversePossibleTypesMap, interfaceExtensionsMap, fieldNodesByType, fieldNodesByField, dynamicSelectionSetsByField) {
var _a, _b;
const newSelections = new Set();
const maybeType = typeInfo.getParentType();
if (maybeType != null) {
const parentType = (0, graphql_1.getNamedType)(maybeType);
const parentTypeName = parentType.name;
const fieldNodes = fieldNodesByType[parentTypeName];
if (fieldNodes) {
for (const fieldNode of fieldNodes) {
newSelections.add(fieldNode);
}
}
const interfaceExtensions = interfaceExtensionsMap[parentType.name];
const interfaceExtensionFields = [];
for (const selection of node.selections) {
if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) {
if (selection.typeCondition != null) {
const possibleTypes = possibleTypesMap[selection.typeCondition.name.value];
if (possibleTypes == null) {
newSelections.add(selection);
continue;
}
for (const possibleTypeName of possibleTypes) {
const maybePossibleType = schema.getType(possibleTypeName);
if (maybePossibleType != null && (0, utils_1.implementsAbstractType)(schema, parentType, maybePossibleType)) {
newSelections.add(generateInlineFragment(possibleTypeName, selection.selectionSet));
}
}
}
}
else if (selection.kind === graphql_1.Kind.FRAGMENT_SPREAD) {
const fragmentName = selection.name.value;
if (!fragmentReplacements[fragmentName]) {
newSelections.add(selection);
continue;
}
for (const replacement of fragmentReplacements[fragmentName]) {
const typeName = replacement.typeName;
const maybeReplacementType = schema.getType(typeName);
if (maybeReplacementType != null && (0, utils_1.implementsAbstractType)(schema, parentType, maybeType)) {
newSelections.add({
kind: graphql_1.Kind.FRAGMENT_SPREAD,
name: {
kind: graphql_1.Kind.NAME,
value: replacement.fragmentName,
},
});
}
}
}
else {
const fieldName = selection.name.value;
const fieldNodes = (_a = fieldNodesByField[parentTypeName]) === null || _a === void 0 ? void 0 : _a[fieldName];
if (fieldNodes != null) {
for (const fieldNode of fieldNodes) {
newSelections.add(fieldNode);
}
}
const dynamicSelectionSets = (_b = dynamicSelectionSetsByField[parentTypeName]) === null || _b === void 0 ? void 0 : _b[fieldName];
if (dynamicSelectionSets != null) {
for (const selectionSetFn of dynamicSelectionSets) {
const selectionSet = selectionSetFn(selection);
if (selectionSet != null) {
for (const selection of selectionSet.selections) {
newSelections.add(selection);
}
}
}
}
if (interfaceExtensions === null || interfaceExtensions === void 0 ? void 0 : interfaceExtensions[fieldName]) {
interfaceExtensionFields.push(selection);
}
else {
newSelections.add(selection);
}
}
}
if (reversePossibleTypesMap[parentType.name]) {
newSelections.add({
kind: graphql_1.Kind.FIELD,
name: {
kind: graphql_1.Kind.NAME,
value: '__typename',
},
});
}
if (interfaceExtensionFields.length) {
const possibleTypes = possibleTypesMap[parentType.name];
if (possibleTypes != null) {
for (const possibleType of possibleTypes) {
newSelections.add(generateInlineFragment(possibleType, {
kind: graphql_1.Kind.SELECTION_SET,
selections: interfaceExtensionFields,
}));
}
}
}
return {
...node,
selections: Array.from(newSelections),
};
}
return node;
}
function generateInlineFragment(typeName, selectionSet) {
return {
kind: graphql_1.Kind.INLINE_FRAGMENT,
typeCondition: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: typeName,
},
},
selectionSet,
};
}
const getSchemaMetaData = (0, utils_1.memoize2)((sourceSchema, targetSchema) => {
var _a, _b, _c, _d;
const typeMap = sourceSchema.getTypeMap();
const targetTypeMap = targetSchema.getTypeMap();
const possibleTypesMap = Object.create(null);
const interfaceExtensionsMap = Object.create(null);
for (const typeName in typeMap) {
const type = typeMap[typeName];
if ((0, graphql_1.isAbstractType)(type)) {
const targetType = targetTypeMap[typeName];
if ((0, graphql_1.isInterfaceType)(type) && (0, graphql_1.isInterfaceType)(targetType)) {
const targetTypeFields = targetType.getFields();
const sourceTypeFields = type.getFields();
const extensionFields = Object.create(null);
let isExtensionFieldsEmpty = true;
for (const fieldName in sourceTypeFields) {
if (!targetTypeFields[fieldName]) {
extensionFields[fieldName] = true;
isExtensionFieldsEmpty = false;
}
}
if (!isExtensionFieldsEmpty) {
interfaceExtensionsMap[typeName] = extensionFields;
}
}
if (interfaceExtensionsMap[typeName] || !(0, graphql_1.isAbstractType)(targetType)) {
const implementations = sourceSchema.getPossibleTypes(type);
possibleTypesMap[typeName] = [];
for (const impl of implementations) {
if (targetTypeMap[impl.name]) {
possibleTypesMap[typeName].push(impl.name);
}
}
}
}
}
const stitchingInfo = (_a = sourceSchema.extensions) === null || _a === void 0 ? void 0 : _a['stitchingInfo'];
return {
possibleTypesMap,
reversePossibleTypesMap: reversePossibleTypesMap(possibleTypesMap),
interfaceExtensionsMap,
fieldNodesByType: (_b = stitchingInfo === null || stitchingInfo === void 0 ? void 0 : stitchingInfo.fieldNodesByType) !== null && _b !== void 0 ? _b : {},
fieldNodesByField: (_c = stitchingInfo === null || stitchingInfo === void 0 ? void 0 : stitchingInfo.fieldNodesByField) !== null && _c !== void 0 ? _c : {},
dynamicSelectionSetsByField: (_d = stitchingInfo === null || stitchingInfo === void 0 ? void 0 : stitchingInfo.dynamicSelectionSetsByField) !== null && _d !== void 0 ? _d : {},
};
});
function reversePossibleTypesMap(possibleTypesMap) {
const result = Object.create(null);
for (const typeName in possibleTypesMap) {
const toTypeNames = possibleTypesMap[typeName];
for (const toTypeName of toTypeNames) {
if (!result[toTypeName]) {
result[toTypeName] = [];
}
result[toTypeName].push(typeName);
}
}
return result;
}
function getExpandedFragments(fragments, fragmentNames, possibleTypesMap) {
let fragmentCounter = 0;
function generateFragmentName(typeName) {
let fragmentName;
do {
fragmentName = `_${typeName}_Fragment${fragmentCounter.toString()}`;
fragmentCounter++;
} while (fragmentNames.has(fragmentName));
return fragmentName;
}
const expandedFragments = [];
const fragmentReplacements = Object.create(null);
for (const fragment of fragments) {
const possibleTypes = possibleTypesMap[fragment.typeCondition.name.value];
if (possibleTypes != null) {
const fragmentName = fragment.name.value;
fragmentReplacements[fragmentName] = [];
for (const possibleTypeName of possibleTypes) {
const name = generateFragmentName(possibleTypeName);
fragmentNames.add(name);
expandedFragments.push({
kind: graphql_1.Kind.FRAGMENT_DEFINITION,
name: {
kind: graphql_1.Kind.NAME,
value: name,
},
typeCondition: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: possibleTypeName,
},
},
selectionSet: fragment.selectionSet,
});
fragmentReplacements[fragmentName].push({
fragmentName: name,
typeName: possibleTypeName,
});
}
}
}
return {
expandedFragments,
fragmentReplacements,
};
}
function wrapConcreteTypes(returnType, targetSchema, document) {
const namedType = (0, graphql_1.getNamedType)(returnType);
if (!(0, graphql_1.isCompositeType)(namedType)) {
return document;
}
const rootTypeNames = (0, utils_1.getRootTypeNames)(targetSchema);
const typeInfo = new graphql_1.TypeInfo(targetSchema);
const visitorKeys = {
Document: ['definitions'],
OperationDefinition: ['selectionSet'],
SelectionSet: ['selections'],
InlineFragment: ['selectionSet'],
FragmentDefinition: ['selectionSet'],
};
return (0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
[graphql_1.Kind.FRAGMENT_DEFINITION]: (node) => {
const typeName = node.typeCondition.name.value;
if (!rootTypeNames.has(typeName)) {
return false;
}
},
[graphql_1.Kind.FIELD]: (node) => {
const type = typeInfo.getType();
if (type != null && (0, graphql_1.isAbstractType)((0, graphql_1.getNamedType)(type))) {
return {
...node,
selectionSet: {
kind: graphql_1.Kind.SELECTION_SET,
selections: [
{
kind: graphql_1.Kind.INLINE_FRAGMENT,
typeCondition: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: namedType.name,
},
},
selectionSet: node.selectionSet,
},
],
},
};
}
},
}),
// visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js
// empty keys cannot be removed only because of typescript errors
// will hopefully be fixed in future version of graphql-js to be optional
visitorKeys);
}