UNPKG

@netlify/content-engine

Version:
1,110 lines 54.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildSchema = void 0; const lodash_camelcase_1 = __importDefault(require("lodash.camelcase")); const lodash_isempty_1 = __importDefault(require("lodash.isempty")); const invariant_1 = __importDefault(require("invariant")); const graphql_1 = require("graphql"); const graphql_compose_1 = require("graphql-compose"); const datastore_1 = require("../datastore"); const api_runner_node_1 = __importDefault(require("../utils/api-runner-node")); const reporter_1 = __importDefault(require("../reporter")); const node_interface_1 = require("./types/node-interface"); const built_in_types_1 = require("./types/built-in-types"); const index_1 = require("./infer/index"); const remote_file_interface_1 = require("./types/remote-file-interface"); const resolvers_1 = require("./resolvers"); const extensions_1 = require("./extensions"); const pagination_1 = require("./types/pagination"); const sort_1 = require("./types/sort"); const filter_1 = require("./types/filter"); const type_builders_1 = require("./types/type-builders"); const type_defs_1 = require("./types/type-defs"); const print_1 = 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 (0, datastore_1.getDataStore)().ready(); await updateSchemaComposer({ schemaComposer, types, typeMapping, fieldExtensions, thirdPartySchemas, printConfig, enginePrintConfig, typeConflictReporter, inferenceMetadata, parentSpan, }); const schema = schemaComposer.buildSchema(); freezeTypeComposers(schemaComposer); // const { printSchema } = require(`graphql`); // console.log(`SCHEMA_PRINTED`, printSchema(schema)); return schema; }; exports.buildSchema = 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 graphql_compose_1.ObjectTypeComposer || tc instanceof graphql_compose_1.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(); // @ts-ignore tc.getType = () => type; } }); }; const updateSchemaComposer = async ({ schemaComposer, types, typeMapping, fieldExtensions, thirdPartySchemas, printConfig, enginePrintConfig, typeConflictReporter, inferenceMetadata, parentSpan, }) => { let activity = reporter_1.default.phantomActivity(`Add explicit types`, { parentSpan: parentSpan, }); activity.start(); await addTypes({ schemaComposer, parentSpan: activity.span, types }); activity.end(); activity = reporter_1.default.phantomActivity(`Add inferred types`, { parentSpan: parentSpan, }); activity.start(); (0, index_1.addInferredTypes)({ schemaComposer, typeConflictReporter, typeMapping, inferenceMetadata, // parentSpan: activity.span, }); addInferredChildOfExtensions({ schemaComposer, }); activity.end(); activity = reporter_1.default.phantomActivity(`Processing types`, { parentSpan: parentSpan, }); activity.start(); if (!process.env.GATSBY_SKIP_WRITING_SCHEMA_TO_FILE) { await (0, print_1.printTypeDefinitions)({ config: printConfig, schemaComposer, // parentSpan: activity.span, }); if (enginePrintConfig) { // make sure to print schema that will be used when bundling graphql-engine await (0, print_1.printTypeDefinitions)({ config: enginePrintConfig, schemaComposer, // parentSpan: activity.span, }); } } await addSetFieldsOnGraphQLNodeTypeFields({ schemaComposer, parentSpan: activity.span, }); await addConvenienceChildrenFields({ schemaComposer, // parentSpan: activity.span, }); await Promise.all( // @ts-ignore Array.from(new Set(schemaComposer.values())).map((typeComposer) => processTypeComposer({ schemaComposer, typeComposer, fieldExtensions, parentSpan: activity.span, }))); await checkQueryableInterfaces({ schemaComposer, // parentSpan: activity.span }); await addThirdPartySchemas({ schemaComposer, thirdPartySchemas, // @ts-ignore parentSpan: activity.span, }); await addCustomResolveFunctions({ schemaComposer, parentSpan: activity.span, }); attachTracingResolver({ schemaComposer, // parentSpan: activity.span }); // TODO: instead of patching the schema like this we should prevent these types from being added to the schema. // I haven't done that because these node types seem to be inferred, the node data is needed for caching/replication, and I don't want to break things and have no time right now. // // these fields and types are a security risk so they're removed schemaComposer.Query.removeField([ `sitePlugin`, `allSitePlugin`, `site`, `allSite`, `allSiteBuildMetadata`, ]); schemaComposer.delete(`SitePlugin`); schemaComposer.delete(`Site`); schemaComposer.delete(`SiteBuildMetadata`); // TODO: end activity.end(); }; const processTypeComposer = async ({ schemaComposer, typeComposer, fieldExtensions, parentSpan, }) => { if (typeComposer instanceof graphql_compose_1.ObjectTypeComposer) { await (0, extensions_1.processFieldExtensions)({ schemaComposer, typeComposer, fieldExtensions, parentSpan, }); if (typeComposer.hasInterface(`Node`)) { (0, node_interface_1.addNodeInterfaceFields)({ schemaComposer, typeComposer }); } if (typeComposer.hasInterface(`RemoteFile`)) { (0, remote_file_interface_1.addRemoteFileInterfaceFields)(schemaComposer, typeComposer); } determineSearchableFields({ schemaComposer, typeComposer, // parentSpan, }); if (typeComposer.hasInterface(`Node`)) { addTypeToRootQuery({ schemaComposer, typeComposer, // parentSpan }); } } else if (typeComposer instanceof graphql_compose_1.InterfaceTypeComposer) { if (isNodeInterface(typeComposer)) { (0, node_interface_1.addNodeInterfaceFields)({ schemaComposer, // @ts-ignore 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` (0, extensions_1.processFieldExtensions)({ schemaComposer, typeComposer, fieldExtensions, parentSpan, }); determineSearchableFields({ schemaComposer, typeComposer, // parentSpan, }); addTypeToRootQuery({ schemaComposer, typeComposer, // parentSpan }); } } }; const fieldNames = { query: (typeName) => (0, lodash_camelcase_1.default)(typeName), queryAll: (typeName) => (0, lodash_camelcase_1.default)(`all ${typeName}`), convenienceChild: (typeName) => (0, lodash_camelcase_1.default)(`child ${typeName}`), convenienceChildren: (typeName) => (0, lodash_camelcase_1.default)(`children ${typeName}`), }; const addTypes = ({ schemaComposer, types, parentSpan }) => { types.forEach(({ typeOrTypeDef, plugin }) => { if (typeof typeOrTypeDef === `string`) { typeOrTypeDef = (0, type_defs_1.parseTypeDef)(typeOrTypeDef); } if ((0, type_defs_1.isASTDocument)(typeOrTypeDef)) { let parsedTypes; const createdFrom = `sdl`; try { parsedTypes = parseTypes({ doc: typeOrTypeDef, plugin, createdFrom, schemaComposer, parentSpan, }); } catch (error) { (0, type_defs_1.reportParsingError)(error); return; } parsedTypes.forEach((type) => { processAddedType({ schemaComposer, type, // parentSpan, createdFrom, plugin, }); }); } else if ((0, type_builders_1.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 isSafeMerge = !plugin || plugin.name === `default-site-plugin` || plugin.name === typeOwner; if (!isSafeMerge) { if (typeOwner) { reporter_1.default.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 { reporter_1.default.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 graphql_compose_1.ObjectTypeComposer || type instanceof graphql_compose_1.InterfaceTypeComposer || type instanceof graphql_1.GraphQLObjectType || type instanceof graphql_1.GraphQLInterfaceType) { mergeFields({ typeComposer, fields: type.getFields() }); type.getInterfaces().forEach((iface) => typeComposer.addInterface(iface)); } if (type instanceof graphql_1.GraphQLInterfaceType || type instanceof graphql_compose_1.InterfaceTypeComposer || type instanceof graphql_1.GraphQLUnionType || type instanceof graphql_compose_1.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 graphql_compose_1.InterfaceTypeComposer || typeComposer instanceof graphql_compose_1.UnionTypeComposer) { if (!typeComposer.getResolveType()) { typeComposer.setResolveType((node) => { return node?.internal?.type || node?.__typename; }); } } 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 graphql_compose_1.InterfaceTypeComposer) { extensions[`nodeInterface`] = true; } break; case `authorization`: extensions[`authorization`] = args; break; case `runtime`: extensions[`runtime`] = 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 graphql_compose_1.InterfaceTypeComposer && isNodeInterface(typeComposer)) { const hasCorrectIdField = typeComposer.hasField(`id`) && typeComposer.getFieldType(`id`).toString() === `ID!`; if (!hasCorrectIdField) { reporter_1.default.panic(`Interfaces with the \`nodeInterface\` extension must have a field ` + `\`id\` of type \`ID!\`. Check the type definition of ` + `\`${typeComposer.getTypeName()}\`.`); } } if (typeComposer instanceof graphql_compose_1.ObjectTypeComposer || typeComposer instanceof graphql_compose_1.InterfaceTypeComposer || typeComposer instanceof graphql_compose_1.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) => !extensions_1.internalExtensionNames.includes(name)) .forEach((name) => { const args = fieldExtensions[name]; if (!args || typeof args !== `object`) { reporter_1.default.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) { reporter_1.default.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `has invalid argument \`${arg}\`.`); return; } const value = args[arg]; try { validate(argumentDef.type, value); } catch (error) { reporter_1.default.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `has argument \`${arg}\` with invalid value "${value}". ` + error.message); } }); } catch (error) { reporter_1.default.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `is not available.`); } }); }); } return typeComposer; }; const checkIsAllowedTypeName = (name) => { (0, invariant_1.default)(name !== `Node`, `The GraphQL type \`Node\` is reserved for internal use.`); (0, invariant_1.default)(!name.endsWith(`FilterInput`) && !name.endsWith(`SortInput`), `GraphQL type names ending with "FilterInput" or "SortInput" are ` + `reserved for internal use. Please rename \`${name}\`.`); (0, invariant_1.default)(!built_in_types_1.builtInScalarTypeNames.includes(name), `The GraphQL type \`${name}\` is reserved for internal use by ` + `built-in scalar types.`); (0, graphql_1.assertValidName)(name); }; const createTypeComposerFromGatsbyType = ({ schemaComposer, type }) => { let typeComposer; switch (type.kind) { case type_builders_1.GatsbyGraphQLTypeKind.OBJECT: { typeComposer = graphql_compose_1.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 type_builders_1.GatsbyGraphQLTypeKind.INPUT_OBJECT: { typeComposer = graphql_compose_1.InputTypeComposer.createTemp({ ...type.config, fields: schemaComposer.typeMapper.convertInputFieldConfigMap(type.config.fields), }); break; } case type_builders_1.GatsbyGraphQLTypeKind.UNION: { typeComposer = graphql_compose_1.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 type_builders_1.GatsbyGraphQLTypeKind.INTERFACE: { typeComposer = graphql_compose_1.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 type_builders_1.GatsbyGraphQLTypeKind.ENUM: { typeComposer = graphql_compose_1.EnumTypeComposer.createTemp(type.config); break; } case type_builders_1.GatsbyGraphQLTypeKind.SCALAR: { typeComposer = graphql_compose_1.ScalarTypeComposer.createTemp(type.config); break; } default: { reporter_1.default.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 graphql_compose_1.ObjectTypeComposer && tc.hasInterface(`Node`)) { const typeName = tc.getTypeName(); const result = await (0, api_runner_node_1.default)(`setFieldsOnGraphQLNodeType`, { type: { name: typeName, get nodes() { // TODO STRICT_MODE: return iterator instead of array return (0, datastore_1.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' }}` // @ts-ignore result.forEach((fields) => tc.addNestedFields(fields)); } } })); const addThirdPartySchemas = ({ schemaComposer, thirdPartySchemas, // parentSpan, }) => { thirdPartySchemas.forEach((schema) => { const schemaQueryType = schema.getQueryType(); const schemaMutationType = schema.getMutationType(); if (schemaMutationType) { const mutationTC = schemaComposer.createTempTC(schemaMutationType); schemaComposer.Mutation.addFields(mutationTC.getFields()); } 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 !== schemaMutationType && type !== schemaQueryType && !(0, graphql_1.isSpecifiedScalarType)(type) && !(0, graphql_1.isIntrospectionType)(type) && type.name !== `Date` && type.name !== `JSON`) { const typeHasFields = type instanceof graphql_1.GraphQLObjectType || type instanceof graphql_1.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): // @ts-ignore if (typeHasFields && typeof type._fields === `object`) { // @ts-ignore const fields = type._fields; // @ts-ignore 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) { // @ts-ignore newConfig.type = fieldConfig.type; } if (fieldConfig.args) { // @ts-ignore newConfig.args = fieldConfig.args; } if (fieldConfig.resolve) { // @ts-ignore 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) { reporter_1.default.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) { reporter_1.default.warn(`\`createResolvers\` passed resolvers for type \`${typeName}\` that ` + `doesn't exist in the schema. Use \`createTypes\` to add the type ` + `before adding resolvers.`); } }); }; await (0, api_runner_node_1.default)(`createResolvers`, { intermediateSchema, createResolvers, traceId: `initial-createResolvers`, parentSpan, }); }; function attachTracingResolver({ schemaComposer }) { schemaComposer.forEach((typeComposer) => { if (typeComposer instanceof graphql_compose_1.ObjectTypeComposer || typeComposer instanceof graphql_compose_1.InterfaceTypeComposer) { typeComposer.getFieldNames().forEach((fieldName) => { const field = typeComposer.getField(fieldName); const resolver = (0, resolvers_1.wrappingResolver)(field.resolve || resolvers_1.defaultResolver); typeComposer.extendField(fieldName, { resolve: resolver, }); }); } }); } const determineSearchableFields = ({ schemaComposer: _schemaComposer, typeComposer, }) => { const isRuntime = typeComposer.hasExtension(`runtime`); 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_1.SEARCHABLE_ENUM.SEARCHABLE, sortable: sort_1.SORTABLE_ENUM.SORTABLE, needsResolve: extensions.proxy ? true : false, runtime: !!isRuntime, }); } else if (!(0, lodash_isempty_1.default)(field.args)) { typeComposer.extendFieldExtensions(fieldName, { searchable: filter_1.SEARCHABLE_ENUM.DEPRECATED_SEARCHABLE, sortable: sort_1.SORTABLE_ENUM.DEPRECATED_SORTABLE, needsResolve: true, runtime: !!isRuntime, }); } else { typeComposer.extendFieldExtensions(fieldName, { searchable: filter_1.SEARCHABLE_ENUM.SEARCHABLE, sortable: sort_1.SORTABLE_ENUM.SORTABLE, needsResolve: true, runtime: !!isRuntime, }); } } else { typeComposer.extendFieldExtensions(fieldName, { searchable: filter_1.SEARCHABLE_ENUM.SEARCHABLE, sortable: sort_1.SORTABLE_ENUM.SORTABLE, needsResolve: false, runtime: !!isRuntime, }); } }); }; const addConvenienceChildrenFields = ({ schemaComposer }) => { const parentTypesToChildren = new Map(); const mimeTypesToChildren = new Map(); const typesHandlingMimeTypes = new Map(); schemaComposer.forEach((type) => { if ((type instanceof graphql_compose_1.ObjectTypeComposer || type instanceof graphql_compose_1.InterfaceTypeComposer) && type.hasExtension(`mimeTypes`)) { // @ts-ignore 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 graphql_compose_1.ObjectTypeComposer || type instanceof graphql_compose_1.InterfaceTypeComposer) && type.hasExtension(`childOf`)) { if (type instanceof graphql_compose_1.ObjectTypeComposer && !type.hasInterface(`Node`)) { reporter_1.default.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 graphql_compose_1.InterfaceTypeComposer && !isNodeInterface(type)) { reporter_1.default.error(`The \`childOf\` extension can only be used on types that implement the \`Node\` interface.\n` + `Check the type definition of \`${type.getTypeName()}\`.`); return; } // @ts-ignore 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 graphql_compose_1.InterfaceTypeComposer && !isNodeInterface(typeComposer)) { reporter_1.default.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 graphql_compose_1.InterfaceTypeComposer && !isNodeInterface(typeComposer)) { reporter_1.default.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 }) => { if (!childTypeComposer.hasExtension(`childOf`)) { return false; } const childOfExtension = childTypeComposer.getExtension(`childOf`); const { types: parentMimeTypes = [] } = typeComposer.getExtension(`mimeTypes`) ?? {}; return (childOfExtension?.types?.includes(typeComposer.getTypeName()) || childOfExtension?.mimeTypes?.some((mimeType) => parentMimeTypes.includes(mimeType))); }; const addInferredChildOfExtensions = ({ schemaComposer }) => { schemaComposer.forEach((typeComposer) => { if (typeComposer instanceof graphql_compose_1.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 (0, datastore_1.getDataStore)().iterateNodesByType(parentTypeName)) { const children = (node.children || []).map(datastore_1.getNode); for (const childNode of children) { if (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_1.getFilterInput)({ schemaComposer, typeComposer, }); const paginationTC = (0, pagination_1.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: (0, resolvers_1.findOne)(typeName), }, [queryNamePlural]: { type: paginationTC, args: { filter: filterInputTC, sort: (0, sort_1.getSortInputNestedObjects)({ schemaComposer, typeComposer }), skip: `Int`, limit: `Int`, }, resolve: (0, resolvers_1.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 =