UNPKG

@graphql-tools/federation

Version:

Useful tools to create and manipulate GraphQL schemas.

123 lines (117 loc) 4.3 kB
import { Kind, visit, } from 'graphql'; import { ValueOrPromise } from 'value-or-promise'; import { mergeResolvers, mergeTypeDefs } from '@graphql-tools/merge'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { printSchemaWithDirectives } from '@graphql-tools/utils'; export const SubgraphBaseSDL = /* GraphQL */ ` scalar _Any scalar _FieldSet scalar link__Import enum link__Purpose { SECURITY EXECUTION } type _Service { sdl: String! } type Query { _service: _Service! } directive @external on FIELD_DEFINITION | OBJECT directive @requires(fields: _FieldSet!) on FIELD_DEFINITION directive @provides(fields: _FieldSet!) on FIELD_DEFINITION directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE directive @link( url: String! as: String for: link__Purpose import: [link__Import] ) repeatable on SCHEMA directive @shareable repeatable on OBJECT | FIELD_DEFINITION directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION directive @override(from: String!) on FIELD_DEFINITION directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE `; export function buildSubgraphSchema(optsOrModules) { const opts = Array.isArray(optsOrModules) ? { typeDefs: optsOrModules.map(opt => opt.typeDefs), resolvers: optsOrModules.map(opt => opt.resolvers).flat(), } : optsOrModules; const entityTypeNames = []; function handleEntity(node) { if (node.directives?.some(directive => directive.name.value === 'key')) { entityTypeNames.push(node.name.value); } } const typeDefs = visit(mergeTypeDefs([SubgraphBaseSDL, opts.typeDefs]), { ObjectTypeDefinition: node => { handleEntity(node); }, ObjectTypeExtension: node => { handleEntity(node); return { ...node, kind: Kind.OBJECT_TYPE_DEFINITION, directives: [ ...(node.directives || []), { kind: 'Directive', name: { kind: 'Name', value: 'extends', }, }, ], }; }, }); const givenResolvers = mergeResolvers(opts.resolvers); const allTypeDefs = [typeDefs]; const allResolvers = [sdlResolvers, givenResolvers]; if (entityTypeNames.length > 0) { allTypeDefs.push(`union _Entity = ${entityTypeNames.join(' | ')}`); allTypeDefs.push(`extend type Query { _entities(representations: [_Any!]!): [_Entity]! }`); allResolvers.push({ _Entity: { __resolveType: entityTypeResolver, }, Query: { _entities: (_root, args, context, info) => ValueOrPromise.all(args.representations.map(representation => new ValueOrPromise(() => givenResolvers[representation.__typename]?.__resolveReference?.(representation, context, info)).then(resolvedEntity => { if (!resolvedEntity) { return representation; } if (!resolvedEntity.__typename) { resolvedEntity.__typename = representation.__typename; } return resolvedEntity; }))).resolve(), }, }); } return makeExecutableSchema({ assumeValid: true, assumeValidSDL: true, ...opts, typeDefs: allTypeDefs, resolvers: allResolvers, }); } function entityTypeResolver(obj) { return obj.__typename; } const sdlResolvers = { Query: { _service: () => ({}), }, _Service: { sdl: (_root, _args, _context, info) => printSchemaWithDirectives(info.schema), }, };