UNPKG

@graphql-tools/federation

Version:

Useful tools to create and manipulate GraphQL schemas.

184 lines (183 loc) • 8.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.federationSubschemaTransformer = exports.getStitchedSchemaWithUrls = exports.getSubschemaForFederationWithSchema = exports.getSubschemaForFederationWithExecutor = exports.getSubschemaForFederationWithTypeDefs = exports.getSubschemaForFederationWithURL = exports.SubgraphSDLQuery = void 0; const graphql_1 = require("graphql"); const delegate_1 = require("@graphql-tools/delegate"); const executor_http_1 = require("@graphql-tools/executor-http"); const stitch_1 = require("@graphql-tools/stitch"); const utils_1 = require("@graphql-tools/utils"); const subgraph_js_1 = require("./subgraph.js"); const utils_js_1 = require("./utils.js"); exports.SubgraphSDLQuery = ` query SubgraphSDL { _service { sdl } } `; async function getSubschemaForFederationWithURL(config) { const executor = (0, executor_http_1.buildHTTPExecutor)(config); const subschemaConfig = await getSubschemaForFederationWithExecutor(executor); return { batch: true, ...subschemaConfig, }; } exports.getSubschemaForFederationWithURL = getSubschemaForFederationWithURL; function getSubschemaForFederationWithTypeDefs(typeDefs) { const subschemaConfig = {}; const typeMergingConfig = (subschemaConfig.merge = subschemaConfig.merge || {}); const entityTypes = []; const visitor = (node) => { if (node.directives) { const typeName = node.name.value; const selections = []; for (const directive of node.directives) { const directiveArgs = directive.arguments || []; switch (directive.name.value) { case 'key': { if (directiveArgs.some(arg => arg.name.value === 'resolvable' && arg.value.kind === graphql_1.Kind.BOOLEAN && arg.value.value === false)) { continue; } const selectionValueNode = directiveArgs.find(arg => arg.name.value === 'fields') ?.value; if (selectionValueNode?.kind === graphql_1.Kind.STRING) { selections.push(selectionValueNode.value); } break; } case 'inaccessible': return null; } } // If it is not an entity, continue if (selections.length === 0) { return node; } const typeMergingTypeConfig = (typeMergingConfig[typeName] = typeMergingConfig[typeName] || {}); if (node.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && !node.directives?.some(d => d.name.value === 'extends')) { typeMergingTypeConfig.canonical = true; } entityTypes.push(typeName); const selectionsStr = selections.join(' '); typeMergingTypeConfig.selectionSet = `{ ${selectionsStr} }`; typeMergingTypeConfig.dataLoaderOptions = { cacheKeyFn: (0, utils_js_1.getCacheKeyFnFromKey)(selectionsStr), }; typeMergingTypeConfig.argsFromKeys = utils_js_1.getArgsFromKeysForFederation; typeMergingTypeConfig.fieldName = `_entities`; typeMergingTypeConfig.key = utils_js_1.getKeyForFederation; const fields = []; if (node.fields) { for (const fieldNode of node.fields) { let removed = false; if (fieldNode.directives) { const fieldName = fieldNode.name.value; for (const directive of fieldNode.directives) { const directiveArgs = directive.arguments || []; switch (directive.name.value) { case 'requires': { const typeMergingFieldsConfig = (typeMergingTypeConfig.fields = typeMergingTypeConfig.fields || {}); typeMergingFieldsConfig[fieldName] = typeMergingFieldsConfig[fieldName] || {}; if (directiveArgs.some(arg => arg.name.value === 'resolvable' && arg.value.kind === graphql_1.Kind.BOOLEAN && arg.value.value === false)) { continue; } const selectionValueNode = directiveArgs.find(arg => arg.name.value === 'fields') ?.value; if (selectionValueNode?.kind === graphql_1.Kind.STRING) { typeMergingFieldsConfig[fieldName].selectionSet = `{ ${selectionValueNode.value} }`; typeMergingFieldsConfig[fieldName].computed = true; } break; } case 'external': case 'inaccessible': { removed = !typeMergingTypeConfig.selectionSet?.includes(` ${fieldName} `); break; } case 'override': { const typeMergingFieldsConfig = (typeMergingTypeConfig.fields = typeMergingTypeConfig.fields || {}); typeMergingFieldsConfig[fieldName] = typeMergingFieldsConfig[fieldName] || {}; typeMergingFieldsConfig[fieldName].canonical = true; break; } } } } if (!removed) { fields.push(fieldNode); } } node.fields = fields; } } return { ...node, kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION, }; }; const parsedSDL = (0, graphql_1.visit)(typeDefs, { ObjectTypeExtension: visitor, ObjectTypeDefinition: visitor, }); subschemaConfig.schema = (0, graphql_1.buildASTSchema)((0, graphql_1.concatAST)([(0, graphql_1.parse)(`union _Entity = ${entityTypes.join(' | ')}` + subgraph_js_1.SubgraphBaseSDL), parsedSDL]), { assumeValidSDL: true, assumeValid: true, }); // subschemaConfig.batch = true; return subschemaConfig; } exports.getSubschemaForFederationWithTypeDefs = getSubschemaForFederationWithTypeDefs; async function getSubschemaForFederationWithExecutor(executor) { const sdlQueryResult = (await executor({ document: (0, graphql_1.parse)(exports.SubgraphSDLQuery), })); if (sdlQueryResult.errors?.length) { const error = sdlQueryResult.errors[0]; throw (0, utils_1.createGraphQLError)(error.message, error); } if (!sdlQueryResult.data?._service?.sdl) { throw new Error(`Unexpected result: ${(0, utils_1.inspect)(sdlQueryResult)}`); } const typeDefs = (0, graphql_1.parse)(sdlQueryResult.data._service.sdl); const subschemaConfig = getSubschemaForFederationWithTypeDefs(typeDefs); return { ...subschemaConfig, executor, }; } exports.getSubschemaForFederationWithExecutor = getSubschemaForFederationWithExecutor; async function getSubschemaForFederationWithSchema(schema) { const executor = (0, delegate_1.createDefaultExecutor)(schema); return getSubschemaForFederationWithExecutor(executor); } exports.getSubschemaForFederationWithSchema = getSubschemaForFederationWithSchema; async function getStitchedSchemaWithUrls(configs) { const subschemas = await Promise.all(configs.map(config => getSubschemaForFederationWithURL(config))); const schema = (0, stitch_1.stitchSchemas)({ subschemas, }); return (0, utils_js_1.filterInternalFieldsAndTypes)(schema); } exports.getStitchedSchemaWithUrls = getStitchedSchemaWithUrls; const federationSubschemaTransformer = function federationSubschemaTransformer(subschemaConfig) { const typeDefs = (0, utils_1.getDocumentNodeFromSchema)(subschemaConfig.schema); const newSubschema = getSubschemaForFederationWithTypeDefs(typeDefs); return { ...subschemaConfig, ...newSubschema, merge: { ...newSubschema.merge, ...subschemaConfig.merge, }, }; }; exports.federationSubschemaTransformer = federationSubschemaTransformer;