gatsby
Version:
Blazing fast modern site generator for React
1,295 lines (1,278 loc) • 45.3 kB
JavaScript
var _pagination = require("./types/pagination");
var _sort = require("./types/sort");
var _filter = require("./types/filter");
var _typeBuilders = require("./types/type-builders");
const _ = require(`lodash`);
const invariant = require(`invariant`);
const {
isSpecifiedScalarType,
isIntrospectionType,
assertValidName,
GraphQLNonNull,
GraphQLList,
GraphQLObjectType,
GraphQLInterfaceType,
GraphQLUnionType
} = require(`graphql`);
const {
ObjectTypeComposer,
InterfaceTypeComposer,
UnionTypeComposer,
InputTypeComposer,
ScalarTypeComposer,
EnumTypeComposer
} = require(`graphql-compose`);
const {
getDataStore,
getNode,
getNodesByType
} = require(`../datastore`);
const apiRunner = require(`../utils/api-runner-node`);
const report = require(`gatsby-cli/lib/reporter`);
const {
addNodeInterfaceFields
} = require(`./types/node-interface`);
const {
overridableBuiltInTypeNames,
builtInScalarTypeNames
} = require(`./types/built-in-types`);
const {
addInferredTypes
} = require(`./infer`);
const {
addRemoteFileInterfaceFields
} = require(`./types/remote-file-interface`);
const {
findOne,
findManyPaginated,
wrappingResolver,
defaultResolver
} = require(`./resolvers`);
const {
processFieldExtensions,
internalExtensionNames
} = require(`./extensions`);
const {
isASTDocument,
parseTypeDef,
reportParsingError
} = require(`./types/type-defs`);
const {
printTypeDefinitions
} = require(`./print`);
const buildSchema = async ({
schemaComposer,
types,
typeMapping,
fieldExtensions,
thirdPartySchemas,
printConfig,
enginePrintConfig,
typeConflictReporter,
inferenceMetadata,
parentSpan
}) => {
// FIXME: consider removing .ready here - it is needed for various tests to pass (although probably harmless)
await getDataStore().ready();
await updateSchemaComposer({
schemaComposer,
types,
typeMapping,
fieldExtensions,
thirdPartySchemas,
printConfig,
enginePrintConfig,
typeConflictReporter,
inferenceMetadata,
parentSpan
});
// const { printSchema } = require(`graphql`)
const schema = schemaComposer.buildSchema();
freezeTypeComposers(schemaComposer);
// console.log(printSchema(schema))
return schema;
};
module.exports = {
buildSchema
};
// Workaround for https://github.com/graphql-compose/graphql-compose/issues/319
// FIXME: remove this when fixed in graphql-compose
const freezeTypeComposers = (schemaComposer, excluded = new Set()) => {
Array.from(schemaComposer.values()).forEach(tc => {
const isCompositeTC = tc instanceof ObjectTypeComposer || tc instanceof InterfaceTypeComposer;
if (isCompositeTC && !excluded.has(tc.getTypeName())) {
// typeComposer.getType() actually mutates the underlying GraphQL type
// and always re-assigns type._fields with a thunk.
// It causes continuous redundant field re-definitions when running queries
// (affects performance significantly).
// Prevent the mutation and "freeze" the type:
const type = tc.getType();
tc.getType = () => type;
}
});
};
const updateSchemaComposer = async ({
schemaComposer,
types,
typeMapping,
fieldExtensions,
thirdPartySchemas,
printConfig,
enginePrintConfig,
typeConflictReporter,
inferenceMetadata,
parentSpan
}) => {
let activity = report.phantomActivity(`Add explicit types`, {
parentSpan: parentSpan
});
activity.start();
await addTypes({
schemaComposer,
parentSpan: activity.span,
types
});
activity.end();
activity = report.phantomActivity(`Add inferred types`, {
parentSpan: parentSpan
});
activity.start();
await addInferredTypes({
schemaComposer,
typeConflictReporter,
typeMapping,
inferenceMetadata,
parentSpan: activity.span
});
addInferredChildOfExtensions({
schemaComposer
});
activity.end();
activity = report.phantomActivity(`Processing types`, {
parentSpan: parentSpan
});
activity.start();
if (!process.env.GATSBY_SKIP_WRITING_SCHEMA_TO_FILE) {
await printTypeDefinitions({
config: printConfig,
schemaComposer,
parentSpan: activity.span
});
if (enginePrintConfig) {
// make sure to print schema that will be used when bundling graphql-engine
await printTypeDefinitions({
config: enginePrintConfig,
schemaComposer,
parentSpan: activity.span
});
}
}
await addSetFieldsOnGraphQLNodeTypeFields({
schemaComposer,
parentSpan: activity.span
});
await addConvenienceChildrenFields({
schemaComposer,
parentSpan: activity.span
});
await Promise.all(Array.from(new Set(schemaComposer.values())).map(typeComposer => processTypeComposer({
schemaComposer,
typeComposer,
fieldExtensions,
parentSpan: activity.span
})));
checkQueryableInterfaces({
schemaComposer,
parentSpan: activity.span
});
await addThirdPartySchemas({
schemaComposer,
thirdPartySchemas,
parentSpan: activity.span
});
await addCustomResolveFunctions({
schemaComposer,
parentSpan: activity.span
});
attachTracingResolver({
schemaComposer,
parentSpan: activity.span
});
activity.end();
};
const processTypeComposer = async ({
schemaComposer,
typeComposer,
fieldExtensions,
parentSpan
}) => {
if (typeComposer instanceof ObjectTypeComposer) {
await processFieldExtensions({
schemaComposer,
typeComposer,
fieldExtensions,
parentSpan
});
if (typeComposer.hasInterface(`Node`)) {
await addNodeInterfaceFields({
schemaComposer,
typeComposer
});
}
if (typeComposer.hasInterface(`RemoteFile`)) {
addRemoteFileInterfaceFields(schemaComposer, typeComposer);
}
await determineSearchableFields({
schemaComposer,
typeComposer,
parentSpan
});
if (typeComposer.hasInterface(`Node`)) {
await addTypeToRootQuery({
schemaComposer,
typeComposer,
parentSpan
});
}
} else if (typeComposer instanceof InterfaceTypeComposer) {
if (isNodeInterface(typeComposer)) {
await addNodeInterfaceFields({
schemaComposer,
typeComposer,
parentSpan
});
// We only process field extensions for queryable Node interfaces, so we get
// the input args on the root query type, e.g. `formatString` etc. for `dateformat`
await processFieldExtensions({
schemaComposer,
typeComposer,
fieldExtensions,
parentSpan
});
await determineSearchableFields({
schemaComposer,
typeComposer,
parentSpan
});
await addTypeToRootQuery({
schemaComposer,
typeComposer,
parentSpan
});
}
}
};
const fieldNames = {
query: typeName => _.camelCase(typeName),
queryAll: typeName => _.camelCase(`all ${typeName}`),
convenienceChild: typeName => _.camelCase(`child ${typeName}`),
convenienceChildren: typeName => _.camelCase(`children ${typeName}`)
};
const addTypes = ({
schemaComposer,
types,
parentSpan
}) => {
types.forEach(({
typeOrTypeDef,
plugin
}) => {
if (typeof typeOrTypeDef === `string`) {
typeOrTypeDef = parseTypeDef(typeOrTypeDef);
}
if (isASTDocument(typeOrTypeDef)) {
let parsedTypes;
const createdFrom = `sdl`;
try {
parsedTypes = parseTypes({
doc: typeOrTypeDef,
plugin,
createdFrom,
schemaComposer,
parentSpan
});
} catch (error) {
reportParsingError(error);
return;
}
parsedTypes.forEach(type => {
processAddedType({
schemaComposer,
type,
parentSpan,
createdFrom,
plugin
});
});
} else if ((0, _typeBuilders.isGatsbyType)(typeOrTypeDef)) {
const type = createTypeComposerFromGatsbyType({
schemaComposer,
type: typeOrTypeDef,
parentSpan
});
if (type) {
const typeName = type.getTypeName();
const createdFrom = `typeBuilder`;
checkIsAllowedTypeName(typeName);
if (schemaComposer.has(typeName)) {
const typeComposer = schemaComposer.get(typeName);
mergeTypes({
schemaComposer,
typeComposer,
type,
plugin,
createdFrom,
parentSpan
});
} else {
processAddedType({
schemaComposer,
type,
parentSpan,
createdFrom,
plugin
});
}
}
} else {
const typeName = typeOrTypeDef.name;
const createdFrom = `graphql-js`;
checkIsAllowedTypeName(typeName);
if (schemaComposer.has(typeName)) {
const typeComposer = schemaComposer.get(typeName);
mergeTypes({
schemaComposer,
typeComposer,
type: typeOrTypeDef,
plugin,
createdFrom,
parentSpan
});
} else {
processAddedType({
schemaComposer,
type: typeOrTypeDef,
parentSpan,
createdFrom,
plugin
});
}
}
});
};
const mergeTypes = ({
schemaComposer,
typeComposer,
type,
plugin,
createdFrom,
parentSpan
}) => {
// The merge is considered safe when a user or a plugin owning the type extend this type
// TODO: add proper conflicts detection and reporting (on the field level)
const typeOwner = typeComposer.getExtension(`plugin`);
const isOverridableBuiltInType = !typeOwner && overridableBuiltInTypeNames.has(typeComposer.getTypeName());
const isSafeMerge = !plugin || plugin.name === `default-site-plugin` || plugin.name === typeOwner || typeComposer.hasExtension(`isPlaceholder`) || isOverridableBuiltInType;
if (!isSafeMerge) {
if (typeOwner) {
report.warn(`Plugin \`${plugin.name}\` has customized the GraphQL type ` + `\`${typeComposer.getTypeName()}\`, which has already been defined ` + `by the plugin \`${typeOwner}\`. ` + `This could potentially cause conflicts.`);
} else {
report.warn(`Plugin \`${plugin.name}\` has customized the built-in Gatsby GraphQL type ` + `\`${typeComposer.getTypeName()}\`. ` + `This is allowed, but could potentially cause conflicts.`);
}
}
if (type instanceof ObjectTypeComposer || type instanceof InterfaceTypeComposer || type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) {
mergeFields({
typeComposer,
fields: type.getFields()
});
type.getInterfaces().forEach(iface => typeComposer.addInterface(iface));
}
if (type instanceof GraphQLInterfaceType || type instanceof InterfaceTypeComposer || type instanceof GraphQLUnionType || type instanceof UnionTypeComposer) {
mergeResolveType({
typeComposer,
type
});
}
let extensions = {};
if (isNamedTypeComposer(type)) {
if (createdFrom === `sdl`) {
extensions = convertDirectivesToExtensions(type, type.getDirectives());
} else {
typeComposer.extendExtensions(type.getExtensions());
}
}
addExtensions({
schemaComposer,
typeComposer,
extensions,
plugin,
createdFrom
});
return true;
};
const processAddedType = ({
schemaComposer,
type,
parentSpan,
createdFrom,
plugin
}) => {
const typeName = schemaComposer.add(type);
const typeComposer = schemaComposer.get(typeName);
if (typeComposer instanceof InterfaceTypeComposer || typeComposer instanceof UnionTypeComposer) {
if (!typeComposer.getResolveType()) {
typeComposer.setResolveType(node => node.internal.type);
}
}
schemaComposer.addSchemaMustHaveType(typeComposer);
let extensions = {};
if (createdFrom === `sdl`) {
extensions = convertDirectivesToExtensions(typeComposer, typeComposer.getDirectives());
}
addExtensions({
schemaComposer,
typeComposer,
extensions,
plugin,
createdFrom
});
return typeComposer;
};
/**
* @param {import("graphql-compose").AnyTypeComposer} typeComposer
* @param {Array<import("graphql-compose").Directive>} directives
* @return {{infer?: boolean, mimeTypes?: { types: Array<string> }, childOf?: { types: Array<string> }, nodeInterface?: boolean}}
*/
const convertDirectivesToExtensions = (typeComposer, directives) => {
const extensions = {};
directives.forEach(({
name,
args
}) => {
switch (name) {
case `infer`:
case `dontInfer`:
{
extensions[`infer`] = name === `infer`;
break;
}
case `mimeTypes`:
extensions[`mimeTypes`] = args;
break;
case `childOf`:
extensions[`childOf`] = args;
break;
case `nodeInterface`:
if (typeComposer instanceof InterfaceTypeComposer) {
extensions[`nodeInterface`] = true;
}
break;
default:
}
});
return extensions;
};
const addExtensions = ({
schemaComposer,
typeComposer,
extensions = {},
plugin,
createdFrom
}) => {
typeComposer.setExtension(`createdFrom`, createdFrom);
typeComposer.setExtension(`plugin`, plugin ? plugin.name : null);
typeComposer.extendExtensions(extensions);
if (typeComposer instanceof InterfaceTypeComposer && isNodeInterface(typeComposer)) {
const hasCorrectIdField = typeComposer.hasField(`id`) && typeComposer.getFieldType(`id`).toString() === `ID!`;
if (!hasCorrectIdField) {
report.panic(`Interfaces with the \`nodeInterface\` extension must have a field ` + `\`id\` of type \`ID!\`. Check the type definition of ` + `\`${typeComposer.getTypeName()}\`.`);
}
}
if (typeComposer instanceof ObjectTypeComposer || typeComposer instanceof InterfaceTypeComposer || typeComposer instanceof InputTypeComposer) {
typeComposer.getFieldNames().forEach(fieldName => {
typeComposer.setFieldExtension(fieldName, `createdFrom`, createdFrom);
typeComposer.setFieldExtension(fieldName, `plugin`, plugin ? plugin.name : null);
if (createdFrom === `sdl`) {
const directives = typeComposer.getFieldDirectives(fieldName);
directives.forEach(({
name,
args
}) => {
typeComposer.setFieldExtension(fieldName, name, args);
});
}
// Validate field extension args. `graphql-compose` already checks the
// type of directive args in `parseDirectives`, but we want to check
// extensions provided with type builders as well. Also, we warn if an
// extension option was provided which does not exist in the field
// extension definition.
const fieldExtensions = typeComposer.getFieldExtensions(fieldName);
const typeName = typeComposer.getTypeName();
Object.keys(fieldExtensions).filter(name => !internalExtensionNames.includes(name)).forEach(name => {
const args = fieldExtensions[name];
if (!args || typeof args !== `object`) {
report.error(`Field extension arguments must be provided as an object. ` + `Received "${args}" on \`${typeName}.${fieldName}\`.`);
return;
}
try {
const definition = schemaComposer.getDirective(name);
// Handle `defaultValue` when not provided as directive
definition.args.forEach(({
name,
defaultValue
}) => {
if (args[name] === undefined && defaultValue !== undefined) {
args[name] = defaultValue;
}
});
Object.keys(args).forEach(arg => {
const argumentDef = definition.args.find(({
name
}) => name === arg);
if (!argumentDef) {
report.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `has invalid argument \`${arg}\`.`);
return;
}
const value = args[arg];
try {
validate(argumentDef.type, value);
} catch (error) {
report.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `has argument \`${arg}\` with invalid value "${value}". ` + error.message);
}
});
} catch (error) {
report.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `is not available.`);
}
});
});
}
return typeComposer;
};
const checkIsAllowedTypeName = name => {
invariant(name !== `Node`, `The GraphQL type \`Node\` is reserved for internal use.`);
invariant(!name.endsWith(`FilterInput`) && !name.endsWith(`SortInput`), `GraphQL type names ending with "FilterInput" or "SortInput" are ` + `reserved for internal use. Please rename \`${name}\`.`);
invariant(!builtInScalarTypeNames.includes(name), `The GraphQL type \`${name}\` is reserved for internal use by ` + `built-in scalar types.`);
assertValidName(name);
};
const createTypeComposerFromGatsbyType = ({
schemaComposer,
type,
parentSpan
}) => {
let typeComposer;
switch (type.kind) {
case _typeBuilders.GatsbyGraphQLTypeKind.OBJECT:
{
typeComposer = ObjectTypeComposer.createTemp({
...type.config,
fields: () => schemaComposer.typeMapper.convertOutputFieldConfigMap(type.config.fields),
interfaces: () => {
if (type.config.interfaces) {
return type.config.interfaces.map(iface => {
if (typeof iface === `string`) {
// Sadly, graphql-compose runs this function too early - before we have
// all of those interfaces actually created in the schema, so have to create
// a temporary placeholder composer :/
if (!schemaComposer.has(iface)) {
const tmpComposer = schemaComposer.createInterfaceTC(iface);
tmpComposer.setExtension(`isPlaceholder`, true);
return tmpComposer;
}
return schemaComposer.getIFTC(iface);
} else {
return iface;
}
});
} else {
return [];
}
}
});
break;
}
case _typeBuilders.GatsbyGraphQLTypeKind.INPUT_OBJECT:
{
typeComposer = InputTypeComposer.createTemp({
...type.config,
fields: schemaComposer.typeMapper.convertInputFieldConfigMap(type.config.fields)
});
break;
}
case _typeBuilders.GatsbyGraphQLTypeKind.UNION:
{
typeComposer = UnionTypeComposer.createTemp({
...type.config,
types: () => {
if (type.config.types) {
return type.config.types.map(typeName => {
if (!schemaComposer.has(typeName)) {
// Sadly, graphql-compose runs this function too early - before we have
// all of those types actually created in the schema, so have to create
// a temporary placeholder composer :/
const tmpComposer = schemaComposer.createObjectTC(typeName);
tmpComposer.setExtension(`isPlaceholder`, true);
return tmpComposer;
}
return schemaComposer.getOTC(typeName);
});
} else {
return [];
}
}
});
break;
}
case _typeBuilders.GatsbyGraphQLTypeKind.INTERFACE:
{
typeComposer = InterfaceTypeComposer.createTemp({
...type.config,
fields: () => schemaComposer.typeMapper.convertOutputFieldConfigMap(type.config.fields),
interfaces: () => {
if (type.config.interfaces) {
return type.config.interfaces.map(iface => {
if (typeof iface === `string`) {
// Sadly, graphql-compose runs this function too early - before we have
// all of those interfaces actually created in the schema, so have to create
// a temporary placeholder composer :/
if (!schemaComposer.has(iface)) {
const tmpComposer = schemaComposer.createInterfaceTC(iface);
tmpComposer.setExtension(`isPlaceholder`, true);
return tmpComposer;
}
return schemaComposer.getIFTC(iface);
} else {
return iface;
}
});
} else {
return [];
}
}
});
break;
}
case _typeBuilders.GatsbyGraphQLTypeKind.ENUM:
{
typeComposer = EnumTypeComposer.createTemp(type.config);
break;
}
case _typeBuilders.GatsbyGraphQLTypeKind.SCALAR:
{
typeComposer = ScalarTypeComposer.createTemp(type.config);
break;
}
default:
{
report.warn(`Illegal type definition: ${JSON.stringify(type.config)}`);
typeComposer = null;
}
}
if (typeComposer) {
// Workaround for https://github.com/graphql-compose/graphql-compose/issues/311
typeComposer.schemaComposer = schemaComposer;
}
return typeComposer;
};
const addSetFieldsOnGraphQLNodeTypeFields = ({
schemaComposer,
parentSpan
}) => Promise.all(Array.from(schemaComposer.values()).map(async tc => {
if (tc instanceof ObjectTypeComposer && tc.hasInterface(`Node`)) {
const typeName = tc.getTypeName();
const result = await apiRunner(`setFieldsOnGraphQLNodeType`, {
type: {
name: typeName,
get nodes() {
// TODO STRICT_MODE: return iterator instead of array
return getNodesByType(typeName);
}
},
traceId: `initial-setFieldsOnGraphQLNodeType`,
parentSpan
});
if (result) {
// NOTE: `setFieldsOnGraphQLNodeType` only allows setting
// nested fields with a path as property name, i.e.
// `{ 'frontmatter.published': 'Boolean' }`, but not in the form
// `{ frontmatter: { published: 'Boolean' }}`
result.forEach(fields => tc.addNestedFields(fields));
}
}
}));
const addThirdPartySchemas = ({
schemaComposer,
thirdPartySchemas,
parentSpan
}) => {
thirdPartySchemas.forEach(schema => {
const schemaQueryType = schema.getQueryType();
const queryTC = schemaComposer.createTempTC(schemaQueryType);
processThirdPartyTypeFields({
typeComposer: queryTC,
type: schemaQueryType,
schemaQueryType
});
schemaComposer.Query.addFields(queryTC.getFields());
// Explicitly add the third-party schema's types, so they can be targeted
// in `createResolvers` API.
const types = schema.getTypeMap();
Object.keys(types).forEach(typeName => {
const type = types[typeName];
if (type !== schemaQueryType && !isSpecifiedScalarType(type) && !isIntrospectionType(type) && type.name !== `Date` && type.name !== `JSON`) {
const typeHasFields = type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType;
// Workaround for an edge case typical for Relay Classic-compatible schemas.
// For example, GitHub API contains this piece:
// type Query { relay: Query }
// And gatsby-source-graphql transforms it to:
// type Query { github: GitHub }
// type GitHub { relay: Query }
// The problem:
// schemaComposer.createTC(type) for type `GitHub` will eagerly create type composers
// for all fields (including `relay` and it's type: `Query` of the third-party schema)
// This unexpected `Query` composer messes up with our own Query type composer and produces duplicate types.
// The workaround is to make sure fields of the GitHub type are lazy and are evaluated only when
// this Query type is already replaced with our own root `Query` type (see processThirdPartyTypeFields):
if (typeHasFields && typeof type._fields === `object`) {
const fields = type._fields;
type._fields = () => fields;
}
// ^^^ workaround done
const typeComposer = schemaComposer.createTC(type);
if (typeHasFields) {
processThirdPartyTypeFields({
typeComposer,
type,
schemaQueryType
});
}
typeComposer.setExtension(`createdFrom`, `thirdPartySchema`);
schemaComposer.addSchemaMustHaveType(typeComposer);
}
});
});
};
const resetOverriddenThirdPartyTypeFields = ({
typeComposer
}) => {
// The problem: createResolvers API mutates third party schema instance.
// For example it can add a new field referencing a type from our main schema
// Then if we rebuild the schema this old type instance will sneak into
// the new schema and produce the famous error:
// "Schema must contain uniquely named types but contains multiple types named X"
// This function only affects schema rebuilding pathway.
// It cleans up artifacts created by the `createResolvers` API of the previous build
// so that we return the third party schema to its initial state (hence can safely re-add)
// TODO: the right way to fix this would be not to mutate the third party schema in
// the first place. But unfortunately mutation happens in the `graphql-compose`
// and we don't have an easy way to avoid it without major rework
typeComposer.getFieldNames().forEach(fieldName => {
const createdFrom = typeComposer.getFieldExtension(fieldName, `createdFrom`);
if (createdFrom === `createResolvers`) {
typeComposer.removeField(fieldName);
return;
}
const config = typeComposer.getFieldExtension(fieldName, `originalFieldConfig`);
if (config) {
typeComposer.removeField(fieldName);
typeComposer.addFields({
[fieldName]: config
});
}
});
};
const processThirdPartyTypeFields = ({
typeComposer,
type,
schemaQueryType
}) => {
// Fix for types that refer to Query. Thanks Relay Classic!
const fields = type.getFields();
Object.keys(fields).forEach(fieldName => {
// Remove customization that we could have added via `createResolvers`
// to make it work with schema rebuilding
const fieldType = String(fields[fieldName].type);
if (fieldType.replace(/[[\]!]/g, ``) === schemaQueryType.name) {
typeComposer.extendField(fieldName, {
type: fieldType.replace(schemaQueryType.name, `Query`)
});
}
});
resetOverriddenThirdPartyTypeFields({
typeComposer
});
};
const addCustomResolveFunctions = async ({
schemaComposer,
parentSpan
}) => {
const intermediateSchema = schemaComposer.buildSchema();
const createResolvers = (resolvers, {
ignoreNonexistentTypes = false
} = {}) => {
Object.keys(resolvers).forEach(typeName => {
const fields = resolvers[typeName];
if (schemaComposer.has(typeName)) {
const tc = schemaComposer.getOTC(typeName);
Object.keys(fields).forEach(fieldName => {
const fieldConfig = fields[fieldName];
if (tc.hasField(fieldName)) {
const originalFieldConfig = tc.getFieldConfig(fieldName);
const originalTypeName = originalFieldConfig.type.toString();
const originalResolver = originalFieldConfig.resolve;
let fieldTypeName;
if (fieldConfig.type) {
fieldTypeName = Array.isArray(fieldConfig.type) ? stringifyArray(fieldConfig.type) : fieldConfig.type.toString();
}
if (!fieldTypeName || fieldTypeName.replace(/!/g, ``) === originalTypeName.replace(/!/g, ``) || tc.getExtension(`createdFrom`) === `thirdPartySchema`) {
const newConfig = {};
if (fieldConfig.type) {
newConfig.type = fieldConfig.type;
}
if (fieldConfig.args) {
newConfig.args = fieldConfig.args;
}
if (fieldConfig.resolve) {
newConfig.resolve = (source, args, context, info) => fieldConfig.resolve(source, args, context, {
...info,
originalResolver: originalResolver || context.defaultFieldResolver
});
tc.extendFieldExtensions(fieldName, {
needsResolve: true
});
}
tc.extendField(fieldName, newConfig);
// See resetOverriddenThirdPartyTypeFields for explanation
if (tc.getExtension(`createdFrom`) === `thirdPartySchema`) {
tc.setFieldExtension(fieldName, `originalFieldConfig`, originalFieldConfig);
}
} else if (fieldTypeName) {
report.warn(`\`createResolvers\` passed resolvers for field ` + `\`${typeName}.${fieldName}\` with type \`${fieldTypeName}\`. ` + `Such a field with type \`${originalTypeName}\` already exists ` + `on the type. Use \`createTypes\` to override type fields.`);
}
} else {
tc.addFields({
[fieldName]: fieldConfig
});
// See resetOverriddenThirdPartyTypeFields for explanation
tc.setFieldExtension(fieldName, `createdFrom`, `createResolvers`);
}
});
} else if (!ignoreNonexistentTypes) {
report.warn(`\`createResolvers\` passed resolvers for type \`${typeName}\` that ` + `doesn't exist in the schema. Use \`createTypes\` to add the type ` + `before adding resolvers.`);
}
});
};
await apiRunner(`createResolvers`, {
intermediateSchema,
createResolvers,
traceId: `initial-createResolvers`,
parentSpan
});
};
function attachTracingResolver({
schemaComposer
}) {
schemaComposer.forEach(typeComposer => {
if (typeComposer instanceof ObjectTypeComposer || typeComposer instanceof InterfaceTypeComposer) {
typeComposer.getFieldNames().forEach(fieldName => {
const field = typeComposer.getField(fieldName);
const resolver = wrappingResolver(field.resolve || defaultResolver);
typeComposer.extendField(fieldName, {
resolve: resolver
});
});
}
});
}
const determineSearchableFields = ({
schemaComposer,
typeComposer
}) => {
typeComposer.getFieldNames().forEach(fieldName => {
const field = typeComposer.getField(fieldName);
const extensions = typeComposer.getFieldExtensions(fieldName);
if (field.resolve) {
if (extensions.dateformat) {
typeComposer.extendFieldExtensions(fieldName, {
searchable: _filter.SEARCHABLE_ENUM.SEARCHABLE,
sortable: _sort.SORTABLE_ENUM.SORTABLE,
needsResolve: extensions.proxy ? true : false
});
} else if (!_.isEmpty(field.args)) {
typeComposer.extendFieldExtensions(fieldName, {
searchable: _filter.SEARCHABLE_ENUM.DEPRECATED_SEARCHABLE,
sortable: _sort.SORTABLE_ENUM.DEPRECATED_SORTABLE,
needsResolve: true
});
} else {
typeComposer.extendFieldExtensions(fieldName, {
searchable: _filter.SEARCHABLE_ENUM.SEARCHABLE,
sortable: _sort.SORTABLE_ENUM.SORTABLE,
needsResolve: true
});
}
} else {
typeComposer.extendFieldExtensions(fieldName, {
searchable: _filter.SEARCHABLE_ENUM.SEARCHABLE,
sortable: _sort.SORTABLE_ENUM.SORTABLE,
needsResolve: false
});
}
});
};
const addConvenienceChildrenFields = ({
schemaComposer
}) => {
const parentTypesToChildren = new Map();
const mimeTypesToChildren = new Map();
const typesHandlingMimeTypes = new Map();
schemaComposer.forEach(type => {
if ((type instanceof ObjectTypeComposer || type instanceof InterfaceTypeComposer) && type.hasExtension(`mimeTypes`)) {
const {
types
} = type.getExtension(`mimeTypes`);
new Set(types).forEach(mimeType => {
if (!typesHandlingMimeTypes.has(mimeType)) {
typesHandlingMimeTypes.set(mimeType, new Set());
}
typesHandlingMimeTypes.get(mimeType).add(type);
});
}
if ((type instanceof ObjectTypeComposer || type instanceof InterfaceTypeComposer) && type.hasExtension(`childOf`)) {
if (type instanceof ObjectTypeComposer && !type.hasInterface(`Node`)) {
report.error(`The \`childOf\` extension can only be used on types that implement the \`Node\` interface.\n` + `Check the type definition of \`${type.getTypeName()}\`.`);
return;
}
if (type instanceof InterfaceTypeComposer && !isNodeInterface(type)) {
report.error(`The \`childOf\` extension can only be used on types that implement the \`Node\` interface.\n` + `Check the type definition of \`${type.getTypeName()}\`.`);
return;
}
const {
types,
mimeTypes
} = type.getExtension(`childOf`);
new Set(types).forEach(parentType => {
if (!parentTypesToChildren.has(parentType)) {
parentTypesToChildren.set(parentType, new Set());
}
parentTypesToChildren.get(parentType).add(type);
});
new Set(mimeTypes).forEach(mimeType => {
if (!mimeTypesToChildren.has(mimeType)) {
mimeTypesToChildren.set(mimeType, new Set());
}
mimeTypesToChildren.get(mimeType).add(type);
});
}
});
parentTypesToChildren.forEach((children, parent) => {
if (!schemaComposer.has(parent)) return;
const typeComposer = schemaComposer.getAnyTC(parent);
if (typeComposer instanceof InterfaceTypeComposer && !isNodeInterface(typeComposer)) {
report.error(`With the \`childOf\` extension, children fields can only be added to ` + `interfaces which implement the \`Node\` interface.\n` + `Check the type definition of \`${typeComposer.getTypeName()}\`.`);
return;
}
children.forEach(child => {
typeComposer.addFields(createChildrenField(child.getTypeName()));
typeComposer.addFields(createChildField(child.getTypeName()));
});
});
mimeTypesToChildren.forEach((children, mimeType) => {
const parentTypes = typesHandlingMimeTypes.get(mimeType);
if (parentTypes) {
parentTypes.forEach(typeComposer => {
if (typeComposer instanceof InterfaceTypeComposer && !isNodeInterface(typeComposer)) {
report.error(`With the \`childOf\` extension, children fields can only be added to ` + `interfaces which implement the \`Node\` interface.\n` + `Check the type definition of \`${typeComposer.getTypeName()}\`.`);
return;
}
children.forEach(child => {
typeComposer.addFields(createChildrenField(child.getTypeName()));
typeComposer.addFields(createChildField(child.getTypeName()));
});
});
}
});
};
const isExplicitChild = ({
typeComposer,
childTypeComposer
}) => {
var _typeComposer$getExte, _childOfExtension$typ, _childOfExtension$mim;
if (!childTypeComposer.hasExtension(`childOf`)) {
return false;
}
const childOfExtension = childTypeComposer.getExtension(`childOf`);
const {
types: parentMimeTypes = []
} = (_typeComposer$getExte = typeComposer.getExtension(`mimeTypes`)) !== null && _typeComposer$getExte !== void 0 ? _typeComposer$getExte : {};
return (childOfExtension === null || childOfExtension === void 0 ? void 0 : (_childOfExtension$typ = childOfExtension.types) === null || _childOfExtension$typ === void 0 ? void 0 : _childOfExtension$typ.includes(typeComposer.getTypeName())) || (childOfExtension === null || childOfExtension === void 0 ? void 0 : (_childOfExtension$mim = childOfExtension.mimeTypes) === null || _childOfExtension$mim === void 0 ? void 0 : _childOfExtension$mim.some(mimeType => parentMimeTypes.includes(mimeType)));
};
const addInferredChildOfExtensions = ({
schemaComposer
}) => {
schemaComposer.forEach(typeComposer => {
if (typeComposer instanceof ObjectTypeComposer && typeComposer.hasInterface(`Node`)) {
addInferredChildOfExtension({
schemaComposer,
typeComposer
});
}
});
};
const addInferredChildOfExtension = ({
schemaComposer,
typeComposer
}) => {
const shouldInfer = typeComposer.getExtension(`infer`);
// With `@dontInfer`, only parent-child
// relations explicitly set with the `@childOf` extension are added.
if (shouldInfer === false) return;
const parentTypeName = typeComposer.getTypeName();
// This is expensive.
// TODO: We should probably collect this info during inference metadata pass
const childNodeTypes = new Set();
for (const node of getDataStore().iterateNodesByType(parentTypeName)) {
const children = (node.children || []).map(getNode);
for (const childNode of children) {
var _childNode$internal;
if (childNode !== null && childNode !== void 0 && (_childNode$internal = childNode.internal) !== null && _childNode$internal !== void 0 && _childNode$internal.type) {
childNodeTypes.add(childNode.internal.type);
}
}
}
childNodeTypes.forEach(typeName => {
const childTypeComposer = schemaComposer.getAnyTC(typeName);
let childOfExtension = childTypeComposer.getExtension(`childOf`);
if (isExplicitChild({
typeComposer,
childTypeComposer
})) {
return;
}
// Set `@childOf` extension automatically
// This will cause convenience children fields like `childImageSharp`
// to be added in `addConvenienceChildrenFields` method.
// Also required for proper printing of the `@childOf` directive in the snapshot plugin
if (!childOfExtension) {
childOfExtension = {};
}
if (!childOfExtension.types) {
childOfExtension.types = [];
}
childOfExtension.types.push(parentTypeName);
childTypeComposer.setExtension(`childOf`, childOfExtension);
});
};
const createChildrenField = typeName => {
return {
[fieldNames.convenienceChildren(typeName)]: {
type: () => [typeName],
description: `Returns all children nodes filtered by type ${typeName}`,
resolve(source, args, context) {
const {
path
} = context;
return context.nodeModel.getNodesByIds({
ids: source.children,
type: typeName
}, {
path
});
}
}
};
};
const createChildField = typeName => {
return {
[fieldNames.convenienceChild(typeName)]: {
type: () => typeName,
description: `Returns the first child node of type ${typeName} ` + `or null if there are no children of given type on this node`,
resolve(source, args, context) {
const {
path
} = context;
const result = context.nodeModel.getNodesByIds({
ids: source.children,
type: typeName
}, {
path
});
if (result && result.length > 0) {
return result[0];
} else {
return null;
}
}
}
};
};
const addTypeToRootQuery = ({
schemaComposer,
typeComposer
}) => {
const filterInputTC = (0, _filter.getFilterInput)({
schemaComposer,
typeComposer
});
const paginationTC = (0, _pagination.getPagination)({
schemaComposer,
typeComposer
});
const typeName = typeComposer.getTypeName();
// not strictly correctly, result is `npmPackage` and `allNpmPackage` from type `NPMPackage`
const queryName = fieldNames.query(typeName);
const queryNamePlural = fieldNames.queryAll(typeName);
schemaComposer.Query.addFields({
[queryName]: {
type: typeComposer,
args: {
...filterInputTC.getFields()
},
resolve: findOne(typeName)
},
[queryNamePlural]: {
type: paginationTC,
args: {
filter: filterInputTC,
sort: "5" === `5` ? (0, _sort.getSortInputNestedObjects)({
schemaComposer,
typeComposer
}) : (0, _sort.getSortInput)({
schemaComposer,
typeComposer
}),
skip: `Int`,
limit: `Int`
},
resolve: findManyPaginated(typeName)
}
}).makeFieldNonNull(queryNamePlural);
};
const parseTypes = ({
doc,
plugin,
createdFrom,
schemaComposer,
parentSpan
}) => {
const types = [];
doc.definitions.forEach(def => {
const name = def.name.value;
checkIsAllowedTypeName(name);
if (schemaComposer.has(name)) {
// We don't check if ast.kind matches composer type, but rely
// that this will throw when something is wrong and get
// reported by `reportParsingError`.
// Keep the original type composer around
const typeComposer = schemaComposer.get(name);
// After this, the parsed type composer will be registered as the composer
// handling the type name (requires cleanup after merging, see below)
const parsedType = schemaComposer.typeMapper.makeSchemaDef(def);
// Merging types require implemented interfaces to already exist.
// Depending on type creation order, interface might have not been
// processed yet. We check if interface already exist and create
// placeholder for it, if it doesn't exist yet.
if (parsedType.getInterfaces) {
parsedType.getInterfaces().forEach(iface => {
const ifaceName = iface.getTypeName();
if (!schemaComposer.has(ifaceName)) {
const tmpComposer = schemaComposer.createInterfaceTC(ifaceName);
tmpComposer.setExtension(`isPlaceholder`, true);
}
});
}
// Merge the parsed type with the original
mergeTypes({
schemaComposer,
typeComposer,
type: parsedType,
plugin,
createdFrom,
parentSpan
});
// Cleanup:
// Set the original type composer (with the merged fields added)
// as the correct composer for the type name and remove the temporary one
// `graphql-compose` doesn't make that easy 🤯
// TODO: clean this up when this issue is fixed:
// https://github.com/graphql-compose/graphql-compose/issues/311
schemaComposer.set(typeComposer.getTypeName(), typeComposer);
schemaComposer.set(typeComposer._gqType, typeComposer);
schemaComposer.delete(parsedType._gqType);
schemaComposer.delete(parsedType);
} else {
const parsedType = schemaComposer.typeMapper.makeSchemaDef(def);
types.push(parsedType);
}
});
return types;
};
const stringifyArray = arr => `[${arr.map(item => Array.isArray(item) ? stringifyArray(item) : item.toString())}]`;
// TODO: Import this directly from graphql-compose once we update to v7
const isNamedTypeComposer = type => type instanceof ObjectTypeComposer || type instanceof InputTypeComposer || type instanceof ScalarTypeComposer || type instanceof EnumTypeComposer || type instanceof InterfaceTypeComposer || type instanceof UnionTypeComposer;
const validate = (type, value) => {
if (type instanceof GraphQLNonNull) {
if (value == null) {
throw new Error(`Expected non-null field value.`);
}
return validate(type.ofType, value);
} else if (type instanceof GraphQLList) {
if (!Array.isArray(value)) {
throw new Error(`Expected array field value.`);
}
return value.map(v => validate(type.ofType, v));
} else {
return type.parseValue(value);
}
};
const isNodeInterface = interfaceTypeComposer => interfaceTypeComposer.hasInterface(`Node`);
const checkQueryableInterfaces = ({
schemaComposer
}) => {
const queryableInterfaces = new Set();
schemaComposer.forEach(type => {
if (type instanceof InterfaceTypeComposer && isNodeInterface(type)) {
queryableInterfaces.add(type.getTypeName());
}
});
const incorrectTypes = new Set();
schemaComposer.forEach(type => {
if (type instanceof ObjectTypeComposer) {
const interfaces = type.getInterfaces();
if (interfaces.some(iface => queryableInterfaces.has(iface.getTypeName())) && !type.hasInterface(`Node`)) {
incorrectTypes.add(type.getTypeName());
}
}
});
if (incorrectTypes.size) {
report.panic(`Types implementing queryable interfaces must also implement the \`Node\` ` + `interface. Check the type definition of ` + `${Array.from(incorrectTypes).map(t => `\`${t}\``).join(`, `)}.`);
}
};
const mergeFields = ({
typeComposer,
fields
}) => Object.entries(fields).forEach(([fieldName, fieldConfig]) => {
if (typeComposer.hasField(fieldName)) {
typeComposer.extendField(fieldName, fieldConfig);
} else {
typeComposer.setField(fieldName, fieldConfig);
}
});
const mergeResolveType = ({
typeComposer,
type
}) => {
if ((type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) && type.resolveType) {
typeComposer.setResolveType(type.resolveType);
}
if ((type instanceof InterfaceTypeComposer || type instanceof UnionTypeComposer) && type.getResolveType()) {
typeComposer.setResolveType(type.getResolveType());
}
if (!typeComposer.getResolveType()) {
typeComposer.setResolveType(node => {
var _node$internal;
return node === null || node === void 0 ? void 0 : (_node$internal = node.internal) === null || _node$internal === void 0 ? void 0 : _node$internal.type;
});
}
};
//# sourceMappingURL=schema.js.map
;