@theguild/federation-composition
Version:
Open Source Composition library for Apollo Federation
282 lines (281 loc) • 13.6 kB
JavaScript
import { GraphQLError, Kind, parse, specifiedDirectives, specifiedScalarTypes, } from 'graphql';
import { print } from '../graphql/printer.js';
import { directive as inaccessibleDirective } from './inaccessible.js';
import { parseLink } from './link.js';
import { directive as tagDirective } from './tag.js';
export function isFederationVersion(version) {
return version in federationSpecFactory;
}
export function createSpecSchema(version, imports) {
if (!isFederationVersion(version)) {
throw new 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([tagDirective, inaccessibleDirective]),
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),
};
function createTypeDefinitions(doc, prefix, imports) {
const shouldFilter = !!imports;
const toInclude = new Set(imports?.map(i => i.name.replace(/^@/, '')));
const docAST = 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 => !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 === 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 (!specifiedScalarTypes.some(t => t.name === nameNode.value)) {
nameNode.value = `${prefix}${nameNode.value}`;
}
});
}
return node;
}
function resolveNamedType(node) {
if (node.kind === Kind.LIST_TYPE) {
return resolveNamedType(node.type);
}
if (node.kind === Kind.NON_NULL_TYPE) {
return resolveNamedType(node.type);
}
return node.name;
}
export function isFederationLink(link) {
return link.identity === 'https://specs.apollo.dev/federation';
}
export function detectFederationVersion(typeDefs) {
for (const definition of typeDefs.definitions) {
if (definition.kind === Kind.SCHEMA_EXTENSION || definition.kind === 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 parseLink(url.value.value, importArg ? 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: [] };
}