@graphql-tools/federation
Version:
Useful tools to create and manipulate GraphQL schemas.
184 lines (183 loc) • 8.95 kB
JavaScript
;
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;