gatsby-source-wordpress
Version:
Source data from WordPress in an efficient and scalable way.
608 lines (597 loc) • 20.7 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.default = void 0;
exports.transformField = transformField;
exports.transformInlineFragments = void 0;
var _store = require("../../../store");
var _helpers = require("../../create-schema-customization/helpers");
var _isExcluded = require("../is-excluded");
var _transformFields = require("../../create-schema-customization/transform-fields");
const transformInlineFragments = ({
possibleTypes,
gatsbyNodesInfo,
typeMap,
maxDepth,
parentType,
mainType,
parentField,
fragments,
circularQueryLimit,
buildGatsbyNodeFields = false,
depth = 0,
buildingFragment = false,
ancestorTypeNames: parentAncestorTypeNames = []
}) => {
const state = (0, _store.getStore)().getState();
if (!typeMap) {
typeMap = state.remoteSchema.typeMap;
}
const {
pluginOptions
} = state.gatsbyApi;
if (!maxDepth) {
maxDepth = pluginOptions.schema.queryDepth;
}
if (!circularQueryLimit) {
circularQueryLimit = pluginOptions.circularQueryLimit;
}
if (!gatsbyNodesInfo) {
gatsbyNodesInfo = state.remoteSchema.gatsbyNodesInfo;
}
const ancestorTypeNames = [...parentAncestorTypeNames];
const transformedInlineFragments = possibleTypes.map(possibleType => {
possibleType = {
...possibleType
};
const type = typeMap.get(possibleType.name);
if (!type) {
return false;
}
const typeSettings = (0, _helpers.getTypeSettingsByType)(type);
if (typeSettings.exclude) {
return false;
}
if ((0, _isExcluded.typeIsExcluded)({
pluginOptions,
typeName: (0, _helpers.findNamedTypeName)(type)
})) {
return false;
}
possibleType.type = {
...type
};
// save this type so we can use it in schema customization
(0, _store.getStore)().dispatch.remoteSchema.addFetchedType(type);
const isAGatsbyNode = gatsbyNodesInfo.typeNames.includes(possibleType.name);
if (isAGatsbyNode && !buildGatsbyNodeFields) {
// we use the id to link to the top level Gatsby node
possibleType.fields = [`id`];
return possibleType;
}
const typeInfo = typeMap.get(possibleType.name);
let filteredFields = [...typeInfo.fields];
if ((parentType === null || parentType === void 0 ? void 0 : parentType.kind) === `INTERFACE`) {
// remove any fields from our fragment if the parent type already has them as shared fields
filteredFields = filteredFields.filter(filteredField => !parentType.fields.find(parentField => parentField.name === filteredField.name));
}
if (typeInfo) {
const fields = recursivelyTransformFields({
fields: filteredFields,
parentType: type,
depth,
ancestorTypeNames,
fragments,
buildingFragment,
circularQueryLimit,
mainType,
parentField
});
if (!fields || !fields.length) {
return false;
}
possibleType.fields = [...fields];
return possibleType;
}
return false;
}).filter(Boolean);
return possibleTypes && depth <= maxDepth ? transformedInlineFragments : null;
};
// since we're counting circular types that may be on fields many levels up, incarnation felt like it works here ;) the types are born again in later generations
exports.transformInlineFragments = transformInlineFragments;
const countIncarnations = ({
typeName,
ancestorTypeNames
}) => {
var _ancestorTypeNames$fi;
return ancestorTypeNames.length ? (_ancestorTypeNames$fi = ancestorTypeNames.filter(ancestorTypeName => ancestorTypeName === typeName)) === null || _ancestorTypeNames$fi === void 0 ? void 0 : _ancestorTypeNames$fi.length : 0;
};
function transformField({
field,
gatsbyNodesInfo,
typeMap,
maxDepth,
depth,
fieldBlacklist,
fieldAliases,
ancestorTypeNames: parentAncestorTypeNames,
circularQueryLimit,
fragments,
buildingFragment,
mainType
} = {}) {
var _field$type, _field$type$ofType, _fieldType$fields, _typeMap$get, _typeMap$get$possible;
const ancestorTypeNames = [...parentAncestorTypeNames];
// we're potentially infinitely recursing when fields are connected to other types that have fields that are connections to other types
// so we need a maximum limit for that
if (depth > maxDepth) {
return false;
}
depth++;
// if the field has no type we can't use it.
if (!field || !field.type) {
return false;
}
const typeSettings = (0, _helpers.getTypeSettingsByType)(field.type);
if (typeSettings.exclude) {
return false;
}
// count the number of times this type has appeared as an ancestor of itself
// somewhere up the tree
const typeName = (0, _helpers.findNamedTypeName)(field.type);
const typeKind = (0, _helpers.findTypeKind)(field.type);
const typeIncarnationCount = countIncarnations({
typeName,
ancestorTypeNames
});
if (typeIncarnationCount > 0) {
// this type is nested within itself atleast once
// create a fragment here that can be reused
createFragment({
fields: typeMap.get(typeName).fields,
type: field.type,
fragments,
field,
ancestorTypeNames: parentAncestorTypeNames,
depth,
fieldBlacklist,
fieldAliases,
typeMap,
gatsbyNodesInfo,
circularQueryLimit,
queryDepth: maxDepth,
buildingFragment,
mainType
});
}
if (typeIncarnationCount >= circularQueryLimit) {
return false;
}
// this is used to alias fields that conflict with Gatsby node fields
// for ex Gatsby and WPGQL both have a `parent` field
const fieldName = (0, _transformFields.returnAliasedFieldName)({
fieldAliases,
field
});
if (fieldBlacklist.includes(field.name) || fieldBlacklist.includes(fieldName)) {
return false;
}
// remove fields that have required args. They'll cause query errors if omitted
// and we can't determine how to use those args programatically.
if (field.args && field.args.length && field.args.find(arg => {
var _arg$type;
return (arg === null || arg === void 0 ? void 0 : (_arg$type = arg.type) === null || _arg$type === void 0 ? void 0 : _arg$type.kind) === `NON_NULL`;
})) {
return false;
}
const fieldType = typeMap.get((0, _helpers.findNamedTypeName)(field.type)) || {};
const ofType = typeMap.get((0, _helpers.findNamedTypeName)(fieldType.ofType)) || {};
if (fieldType.kind === `SCALAR` || fieldType.kind === `ENUM` || fieldType.kind === `NON_NULL` && ofType.kind === `SCALAR` || fieldType.kind === `LIST` && fieldType.ofType.kind === `SCALAR` ||
// a list of enums has no type name, so findNamedTypeName above finds the enum type
// instead of the field type. Need to explicitly check here
// instead of using helpers
field.type.kind === `LIST` && ((_field$type = field.type) === null || _field$type === void 0 ? void 0 : (_field$type$ofType = _field$type.ofType) === null || _field$type$ofType === void 0 ? void 0 : _field$type$ofType.kind) === `ENUM`) {
return {
fieldName,
fieldType
};
}
const isListOfGatsbyNodes = ofType && gatsbyNodesInfo.typeNames.includes(typeName);
const isListOfMediaItems = ofType && typeName === `MediaItem`;
const hasIdField = fieldType === null || fieldType === void 0 ? void 0 : (_fieldType$fields = fieldType.fields) === null || _fieldType$fields === void 0 ? void 0 : _fieldType$fields.find(({
name
}) => name === `id`);
if (fieldType.kind === `LIST` && isListOfGatsbyNodes && !isListOfMediaItems && hasIdField) {
return {
fieldName: fieldName,
fields: [`__typename`, `id`],
fieldType
};
} else if (fieldType.kind === `LIST` && isListOfMediaItems && hasIdField) {
return {
fieldName: fieldName,
fields: [`__typename`, `id`],
fieldType
};
} else if (fieldType.kind === `LIST`) {
const listOfType = typeMap.get((0, _helpers.findNamedTypeName)(fieldType));
const transformedFields = recursivelyTransformFields({
fields: listOfType.fields,
parentType: listOfType || fieldType,
depth,
ancestorTypeNames,
fragments,
circularQueryLimit,
buildingFragment,
mainType
});
const transformedInlineFragments = transformInlineFragments({
possibleTypes: listOfType.possibleTypes,
parentType: listOfType || fieldType,
parentField: field,
mainType,
gatsbyNodesInfo,
typeMap,
depth,
maxDepth,
ancestorTypeNames,
fragments,
circularQueryLimit,
buildingFragment
});
if (!(transformedFields !== null && transformedFields !== void 0 && transformedFields.length) && !(transformedInlineFragments !== null && transformedInlineFragments !== void 0 && transformedInlineFragments.length)) {
return false;
}
// if we have either inlineFragments or fields
return {
fieldName: fieldName,
fields: transformedFields,
inlineFragments: transformedInlineFragments,
fieldType
};
}
const isAGatsbyNode =
// if this is a gatsby node type
gatsbyNodesInfo.typeNames.includes(typeName) || ( // or all possible types on this type are Gatsby node types
(_typeMap$get = typeMap.get(typeName)) === null || _typeMap$get === void 0 ? void 0 : (_typeMap$get$possible = _typeMap$get.possibleTypes) === null || _typeMap$get$possible === void 0 ? void 0 : _typeMap$get$possible.every(possibleType => gatsbyNodesInfo.typeNames.includes(possibleType.name)));
if (isAGatsbyNode && hasIdField) {
return {
fieldName: fieldName,
fields: [`__typename`, `id`],
fieldType
};
}
const typeInfo = typeMap.get((0, _helpers.findNamedTypeName)(fieldType));
const {
fields
} = typeInfo || {};
let transformedInlineFragments;
if (typeInfo.possibleTypes) {
transformedInlineFragments = transformInlineFragments({
possibleTypes: typeInfo.possibleTypes,
parentType: typeInfo,
parentField: field,
mainType,
gatsbyNodesInfo,
typeMap,
depth,
maxDepth,
ancestorTypeNames,
fragments,
circularQueryLimit,
buildingFragment
});
}
if (fields || transformedInlineFragments) {
var _transformedInlineFra;
const transformedFields = recursivelyTransformFields({
parentType: typeInfo,
parentFieldName: field.name,
mainType,
fields,
depth,
ancestorTypeNames,
parentField: field,
fragments,
circularQueryLimit,
buildingFragment
});
if (!(transformedFields !== null && transformedFields !== void 0 && transformedFields.length) && !((_transformedInlineFra = transformedInlineFragments) !== null && _transformedInlineFra !== void 0 && _transformedInlineFra.length)) {
return false;
}
return {
fieldName: fieldName,
fields: transformedFields,
inlineFragments: transformedInlineFragments,
fieldType
};
}
if (fieldType.kind === `UNION`) {
const typeInfo = typeMap.get(fieldType.name);
const transformedFields = recursivelyTransformFields({
fields: typeInfo.fields,
parentType: fieldType,
mainType,
depth,
ancestorTypeNames,
fragments,
circularQueryLimit,
buildingFragment
});
const inlineFragments = transformInlineFragments({
possibleTypes: typeInfo.possibleTypes,
gatsbyNodesInfo,
typeMap,
mainType,
depth,
maxDepth,
ancestorTypeNames,
parentField: field,
fragments,
circularQueryLimit,
buildingFragment
});
return {
fieldName: fieldName,
fields: transformedFields,
inlineFragments,
fieldType
};
}
return false;
}
const createFragment = ({
fields,
field,
type,
fragments,
fieldBlacklist,
fieldAliases,
typeMap,
gatsbyNodesInfo,
queryDepth,
ancestorTypeNames,
mainType,
buildingFragment = false
}) => {
var _queryType$possibleTy;
const typeName = (0, _helpers.findNamedTypeName)(type);
if (buildingFragment) {
// this fragment is inside a fragment that's already being built so we should exit
return null;
}
const previouslyCreatedFragment = fragments === null || fragments === void 0 ? void 0 : fragments[typeName];
if (previouslyCreatedFragment && buildingFragment === typeName) {
return previouslyCreatedFragment;
}
const fragmentFields = fields.reduce((fragmentFields, field) => {
var _fieldType$fields2;
const fieldTypeName = (0, _helpers.findNamedTypeName)(field.type);
const fieldType = typeMap.get(fieldTypeName);
if (
// if this field is a different type than the fragment but has a field of the same type as the fragment,
// we need to skip this field in the fragment to prevent nesting this type in itself a level down
fieldType.name !== typeName && fieldType !== null && fieldType !== void 0 && (_fieldType$fields2 = fieldType.fields) !== null && _fieldType$fields2 !== void 0 && _fieldType$fields2.find(innerFieldField => (0, _helpers.findNamedTypeName)(innerFieldField.type) === typeName)) {
return fragmentFields;
}
const transformedField = transformField({
field,
gatsbyNodesInfo,
typeMap,
maxDepth: queryDepth,
depth: 0,
fieldBlacklist,
fieldAliases,
ancestorTypeNames,
mainType,
circularQueryLimit: 1,
fragments,
buildingFragment: typeName
});
if ((0, _helpers.findNamedTypeName)(field.type) !== typeName && !!transformedField) {
fragmentFields.push(transformedField);
}
return fragmentFields;
}, []);
const queryType = typeMap.get(typeName);
const transformedInlineFragments = queryType !== null && queryType !== void 0 && (_queryType$possibleTy = queryType.possibleTypes) !== null && _queryType$possibleTy !== void 0 && _queryType$possibleTy.length ? transformInlineFragments({
possibleTypes: queryType.possibleTypes,
parentType: queryType,
parentField: field,
mainType,
gatsbyNodesInfo,
typeMap,
depth: 0,
maxDepth: queryDepth,
circularQueryLimit: 1,
ancestorTypeNames,
fragments,
buildingFragment: typeName
}) : null;
if (fragments) {
fragments[typeName] = {
name: `${typeName}Fragment`,
type: typeName,
fields: fragmentFields,
inlineFragments: transformedInlineFragments
};
}
return fragmentFields;
};
const transformFields = ({
fields,
parentType,
mainType,
fragments,
parentField,
ancestorTypeNames,
depth,
fieldBlacklist,
fieldAliases,
typeMap,
gatsbyNodesInfo,
queryDepth,
circularQueryLimit,
pluginOptions,
buildingFragment
}) => fields === null || fields === void 0 ? void 0 : fields.filter(field => !(0, _isExcluded.fieldIsExcludedOnParentType)({
field,
parentType
}) && !(0, _isExcluded.fieldIsExcludedOnAll)({
pluginOptions,
field
}) && !(0, _isExcluded.typeIsExcluded)({
pluginOptions,
typeName: (0, _helpers.findNamedTypeName)(field.type)
})).map(field => {
const transformedField = transformField({
maxDepth: queryDepth,
gatsbyNodesInfo,
fieldBlacklist,
fieldAliases,
typeMap,
field,
depth,
ancestorTypeNames,
circularQueryLimit,
fragments,
buildingFragment,
mainType,
parentField
});
if (transformedField) {
// save this type so we know to use it in schema customization
(0, _store.getStore)().dispatch.remoteSchema.addFetchedType(field.type);
}
const typeName = (0, _helpers.findNamedTypeName)(field.type);
const fragment = fragments === null || fragments === void 0 ? void 0 : fragments[typeName];
// @todo add any adjacent fields and inline fragments directly to the stored fragment object so this logic can be changed to if (fragment) useTheFragment()
// once that's done it can be added above and below transformField() above ☝️
// and potentially short circuit expensive work that will be thrown away anyway
if (fragment && transformedField && buildingFragment !== typeName) {
var _transformedField$fie, _transformedField$inl;
// if (fragment && buildingFragment !== typeName && transformedField) {
// remove fields from this query that already exist in the fragment
if (transformedField !== null && transformedField !== void 0 && (_transformedField$fie = transformedField.fields) !== null && _transformedField$fie !== void 0 && _transformedField$fie.length) {
transformedField.fields = transformedField.fields.filter(field => !fragment.fields.find(fragmentField => fragmentField.fieldName === field.fieldName));
}
// if this field has no fields (because it has inline fragments only)
// we need to create an empty array since we treat reusable fragments as
// a field
if (!transformedField.fields) {
transformedField.fields = [];
}
transformedField.fields.push({
internalType: `Fragment`,
fragment
});
if (transformedField !== null && transformedField !== void 0 && (_transformedField$inl = transformedField.inlineFragments) !== null && _transformedField$inl !== void 0 && _transformedField$inl.length) {
transformedField.inlineFragments = transformedField.inlineFragments.filter(fieldInlineFragment =>
// yes this is a horrible use of .find(). @todo refactor this for better perf
!fragment.inlineFragments.find(fragmentInlineFragment => fragmentInlineFragment.name === fieldInlineFragment.name));
}
}
if (field.fields && !transformedField) {
return null;
}
const fieldTypeKind = (0, _helpers.findTypeKind)(field.type);
const fieldOfTypeKind = (0, _helpers.findTypeKind)(field.type.ofType);
const typeKindsRequiringSelectionSets = [`OBJECT`, `UNION`, `INTERFACE`];
const fieldNeedsSelectionSet = typeKindsRequiringSelectionSets.includes(fieldTypeKind) || typeKindsRequiringSelectionSets.includes(fieldOfTypeKind);
if (
// if our field needs a selectionset
fieldNeedsSelectionSet &&
// but we have no fields
!transformedField.fields &&
// and no inline fragments
!transformedField.inlineFragments) {
// we need to discard this field to prevent GraphQL errors
// we're likely at the very bottom of the query depth
// so that this fields children were omitted
return null;
}
return transformedField;
}).filter(Boolean);
const recursivelyTransformFields = ({
fields,
parentType,
mainType,
fragments,
parentField,
ancestorTypeNames: parentAncestorTypeNames,
depth = 0,
buildingFragment = false
}) => {
if (!fields || !fields.length) {
return null;
}
if (!parentAncestorTypeNames) {
parentAncestorTypeNames = [];
}
const ancestorTypeNames = [...parentAncestorTypeNames];
const {
gatsbyApi: {
pluginOptions
},
remoteSchema: {
fieldBlacklist,
fieldAliases,
typeMap,
gatsbyNodesInfo
}
} = (0, _store.getStore)().getState();
const {
schema: {
queryDepth,
circularQueryLimit
}
} = pluginOptions;
if (depth > queryDepth && ancestorTypeNames.length) {
return null;
}
const typeName = (0, _helpers.findNamedTypeName)(parentType);
const grandParentTypeName = ancestorTypeNames.length ? ancestorTypeNames[ancestorTypeNames.length - 1] : null;
if (grandParentTypeName && typeName !== grandParentTypeName) {
// if a field has fields of the same type as the field above it
// we shouldn't fetch them. 2 types that are circular between each other
// are dangerous as they will generate very large queries and fetch data we don't need
// these types should instead be proper connections so we can identify
// that only an id needs to be fetched.
// @todo maybe move this into transformFields() instead of here
fields = fields.filter(field => {
const fieldTypeName = (0, _helpers.findNamedTypeName)(field.type);
return fieldTypeName !== grandParentTypeName;
});
}
const typeIncarnationCount = countIncarnations({
typeName,
ancestorTypeNames
});
if (typeIncarnationCount >= circularQueryLimit) {
return null;
}
parentAncestorTypeNames.push(typeName);
const recursivelyTransformedFields = transformFields({
fields,
parentType,
mainType,
fragments,
parentField,
ancestorTypeNames: parentAncestorTypeNames,
depth,
fieldBlacklist,
fieldAliases,
typeMap,
gatsbyNodesInfo,
queryDepth,
circularQueryLimit,
pluginOptions,
buildingFragment
});
if (!recursivelyTransformedFields.length) {
return null;
}
return recursivelyTransformedFields;
};
var _default = recursivelyTransformFields;
exports.default = _default;
//# sourceMappingURL=recursively-transform-fields.js.map