UNPKG

@theguild/federation-composition

Version:
400 lines (399 loc) 19.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isFederationVersion = isFederationVersion; exports.createSpecSchema = createSpecSchema; exports.getLatestFederationVersion = getLatestFederationVersion; exports.isFederationLink = isFederationLink; exports.detectFederationVersion = detectFederationVersion; const graphql_1 = require("graphql"); const printer_js_1 = require("../graphql/printer.js"); const inaccessible_js_1 = require("./inaccessible.js"); const link_js_1 = require("./link.js"); const tag_js_1 = require("./tag.js"); function isFederationVersion(version) { return version in federationSpecFactory; } function createSpecSchema(version, imports) { if (!isFederationVersion(version)) { throw new graphql_1.GraphQLError(`Invalid version ${version} for the federation feature in @link directive on schema`, { extensions: { code: 'UNKNOWN_FEDERATION_LINK_VERSION', }, }); } if (version !== 'v1.0') { const spec = federationSpecFactory[version]('', imports); const namespacedSpec = federationSpecFactory[version]('federation__'); return { directives: spec.directives.concat(namespacedSpec.directives), types: spec.types.concat(namespacedSpec.types), }; } const spec = federationSpecFactory[version](''); return { directives: spec.directives.concat([tag_js_1.directive, inaccessible_js_1.directive]), types: spec.types, }; } const federationSpecFactory = { 'v1.0': (prefix) => createTypeDefinitions(` directive @key( fields: _FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @requires(fields: _FieldSet!) on FIELD_DEFINITION directive @provides(fields: _FieldSet!) on FIELD_DEFINITION directive @external on OBJECT | FIELD_DEFINITION directive @extends on OBJECT | INTERFACE # @override is not supported in v1 but somehow it's supported by Apollo Composition directive @override(from: String!) on FIELD_DEFINITION scalar _FieldSet `, prefix), 'v2.0': (prefix, imports) => createTypeDefinitions(` directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @external on OBJECT | FIELD_DEFINITION directive @shareable on FIELD_DEFINITION | OBJECT directive @extends on OBJECT | INTERFACE directive @override(from: String!) on FIELD_DEFINITION directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet `, prefix, imports), 'v2.1': (prefix, imports) => createTypeDefinitions(` directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet `, prefix, imports), 'v2.2': (prefix, imports) => createTypeDefinitions(` directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet `, prefix, imports), 'v2.3': (prefix, imports) => createTypeDefinitions(` directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @interfaceObject on OBJECT directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet `, prefix, imports), 'v2.4': (prefix, imports) => createTypeDefinitions(` directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @interfaceObject on OBJECT directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet `, prefix, imports), 'v2.5': (prefix, imports) => createTypeDefinitions(` directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @requiresScopes( scopes: [[Scope!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @interfaceObject on OBJECT directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet scalar Scope `, prefix, imports), 'v2.6': (prefix, imports) => createTypeDefinitions(` directive @policy( policies: [[federation__Policy!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @requiresScopes( scopes: [[federation__Scope!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @interfaceObject on OBJECT directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet scalar federation__Policy scalar federation__Scope `, prefix, imports), 'v2.7': (prefix, imports) => createTypeDefinitions(` directive @policy( policies: [[federation__Policy!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @requiresScopes( scopes: [[federation__Scope!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @interfaceObject on OBJECT directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION scalar FieldSet scalar federation__Policy scalar federation__Scope `, prefix, imports), 'v2.8': (prefix, imports) => createTypeDefinitions(` directive @policy( policies: [[federation__Policy!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @requiresScopes( scopes: [[federation__Scope!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @interfaceObject on OBJECT directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION directive @cost( weight: Int! ) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR directive @listSize( assumedSize: Int slicingArguments: [String!] sizedFields: [String!] requireOneSlicingArgument: Boolean = true ) on FIELD_DEFINITION directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION directive @fromContext(field: federation__ContextFieldValue) on ARGUMENT_DEFINITION scalar FieldSet scalar federation__Policy scalar federation__Scope scalar federation__ContextFieldValue `, prefix, imports), 'v2.9': (prefix, imports) => createTypeDefinitions(` directive @policy( policies: [[federation__Policy!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @requiresScopes( scopes: [[federation__Scope!]!]! ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE directive @external on OBJECT | FIELD_DEFINITION directive @key( fields: FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION directive @interfaceObject on OBJECT directive @override(from: String!) on FIELD_DEFINITION directive @provides(fields: FieldSet!) on FIELD_DEFINITION directive @requires(fields: FieldSet!) on FIELD_DEFINITION directive @shareable repeatable on FIELD_DEFINITION | OBJECT directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION directive @cost( weight: Int! ) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR directive @listSize( assumedSize: Int slicingArguments: [String!] sizedFields: [String!] requireOneSlicingArgument: Boolean = true ) on FIELD_DEFINITION directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION directive @fromContext(field: federation__ContextFieldValue) on ARGUMENT_DEFINITION scalar FieldSet scalar federation__Policy scalar federation__Scope scalar federation__ContextFieldValue `, prefix, imports), }; function getLatestFederationVersion() { return Object.keys(federationSpecFactory).sort().pop(); } function createTypeDefinitions(doc, prefix, imports) { const shouldFilter = !!imports; const toInclude = new Set(imports?.map(i => i.name.replace(/^@/, ''))); const docAST = (0, graphql_1.parse)(doc, { noLocation: true, }); if (!imports?.length || toInclude.has('key') || toInclude.has('requires') || toInclude.has('provides')) { toInclude.add('FieldSet'); toInclude.add('federation__FieldSet'); } if (toInclude.has('requiresScopes')) { toInclude.add('federation__Scope'); } if (toInclude.has('policy')) { toInclude.add('federation__Policy'); } const directives = []; const types = []; for (const node of docAST.definitions) { if (isDirectiveDefinitionNode(node)) { directives.push(applyPrefix(node, prefix)); } else { types.push(applyPrefix(node, prefix)); } } return { directives: directives.filter(d => !graphql_1.specifiedDirectives.some(sd => sd.name === d.name.value) && (!shouldFilter || toInclude.has(d.name.value))), types: types.filter(t => toInclude.has(t.name.value)), }; } function isDirectiveDefinitionNode(node) { return node.kind === graphql_1.Kind.DIRECTIVE_DEFINITION; } function applyPrefix(node, prefix) { if (prefix.length === 0) { return node; } node.name.value = `${prefix}${node.name.value}`; if (isDirectiveDefinitionNode(node)) { node.arguments?.forEach(arg => { const nameNode = resolveNamedType(arg.type); if (!graphql_1.specifiedScalarTypes.some(t => t.name === nameNode.value)) { nameNode.value = `${prefix}${nameNode.value}`; } }); } return node; } function resolveNamedType(node) { if (node.kind === graphql_1.Kind.LIST_TYPE) { return resolveNamedType(node.type); } if (node.kind === graphql_1.Kind.NON_NULL_TYPE) { return resolveNamedType(node.type); } return node.name; } function isFederationLink(link) { return link.identity === 'https://specs.apollo.dev/federation'; } function detectFederationVersion(typeDefs) { for (const definition of typeDefs.definitions) { if (definition.kind === graphql_1.Kind.SCHEMA_EXTENSION || definition.kind === graphql_1.Kind.SCHEMA_DEFINITION) { const links = definition.directives?.filter(directive => directive.name.value === 'link'); if (links?.length) { const parsedLinks = links.map(l => { const url = l.arguments?.find(a => a.name.value === 'url'); const importArg = l.arguments?.find(a => a.name.value === 'import'); if (!url) { throw new Error('Invalid @link directive'); } return (0, link_js_1.parseLink)(url.value.value, importArg ? (0, printer_js_1.print)(importArg.value) : '[]'); }); const fedLink = parsedLinks.find(l => l.identity === 'https://specs.apollo.dev/federation'); if (fedLink?.version) { if (!isFederationVersion(fedLink.version)) { throw new Error(`Unsupported federation version: ${fedLink.version}`); } return { version: fedLink.version, imports: fedLink.imports, }; } } } } return { version: 'v1.0', imports: [] }; }