UNPKG

5.7 kBJavaScriptView Raw
1// Taken from https://github.com/gmac/federation-to-stitching-sdl/blob/main/index.js
2import { print, Kind, parse, } from 'graphql';
3import { stitchingDirectives } from './stitchingDirectives.js';
4const extensionKind = /Extension$/;
5const entityKinds = [
6 Kind.OBJECT_TYPE_DEFINITION,
7 Kind.OBJECT_TYPE_EXTENSION,
8 Kind.INTERFACE_TYPE_DEFINITION,
9 Kind.INTERFACE_TYPE_EXTENSION,
10];
11function isEntityKind(def) {
12 return entityKinds.includes(def.kind);
13}
14function getQueryTypeDef(definitions) {
15 var _a;
16 const schemaDef = definitions.find(def => def.kind === Kind.SCHEMA_DEFINITION);
17 const typeName = schemaDef
18 ? (_a = schemaDef.operationTypes.find(({ operation }) => operation === 'query')) === null || _a === void 0 ? void 0 : _a.type.name.value
19 : 'Query';
20 return definitions.find(def => def.kind === Kind.OBJECT_TYPE_DEFINITION && def.name.value === typeName);
21}
22// Federation services are actually fairly complex,
23// as the `buildFederatedSchema` helper does a fair amount
24// of hidden work to setup the Federation schema specification:
25// https://www.apollographql.com/docs/federation/federation-spec/#federation-schema-specification
26export function federationToStitchingSDL(federationSDL, stitchingConfig = stitchingDirectives()) {
27 const doc = parse(federationSDL);
28 const entityTypes = [];
29 const baseTypeNames = doc.definitions.reduce((memo, typeDef) => {
30 if (!extensionKind.test(typeDef.kind) && 'name' in typeDef && typeDef.name) {
31 memo[typeDef.name.value] = true;
32 }
33 return memo;
34 }, {});
35 doc.definitions.forEach(typeDef => {
36 var _a, _b, _c;
37 // Un-extend all types (remove "extends" keywords)...
38 // extended types are invalid GraphQL without a local base type to extend from.
39 // Stitching merges flat types in lieu of hierarchical extensions.
40 if (extensionKind.test(typeDef.kind) && 'name' in typeDef && typeDef.name && !baseTypeNames[typeDef.name.value]) {
41 typeDef.kind = typeDef.kind.replace(extensionKind, 'Definition');
42 }
43 if (!isEntityKind(typeDef))
44 return;
45 // Find object definitions with "@key" directives;
46 // these are federated entities that get turned into merged types.
47 const keyDirs = [];
48 const otherDirs = [];
49 (_a = typeDef.directives) === null || _a === void 0 ? void 0 : _a.forEach(dir => {
50 if (dir.name.value === 'key') {
51 keyDirs.push(dir);
52 }
53 else {
54 otherDirs.push(dir);
55 }
56 });
57 if (!keyDirs.length)
58 return;
59 // Setup stitching MergedTypeConfig for all federated entities:
60 const selectionSet = `{ ${keyDirs.map((dir) => dir.arguments[0].value.value).join(' ')} }`;
61 const keyFields = parse(selectionSet).definitions[0].selectionSet.selections.map((sel) => sel.name.value);
62 const keyDir = keyDirs[0];
63 keyDir.name.value = stitchingConfig.keyDirective.name;
64 keyDir.arguments[0].name.value = 'selectionSet';
65 keyDir.arguments[0].value.value = selectionSet;
66 typeDef.directives = [keyDir, ...otherDirs];
67 // Remove non-key "@external" fields from the type...
68 // the stitching query planner expects services to only publish their own fields.
69 // This makes "@provides" moot because the query planner can automate the logic.
70 typeDef.fields = (_b = typeDef.fields) === null || _b === void 0 ? void 0 : _b.filter(fieldDef => {
71 var _a;
72 return (keyFields.includes(fieldDef.name.value) || !((_a = fieldDef.directives) === null || _a === void 0 ? void 0 : _a.find(dir => dir.name.value === 'external')));
73 });
74 // Discard remaining "@external" directives and any "@provides" directives
75 (_c = typeDef.fields) === null || _c === void 0 ? void 0 : _c.forEach((fieldDef) => {
76 fieldDef.directives = fieldDef.directives.filter((dir) => !/^(external|provides)$/.test(dir.name.value));
77 fieldDef.directives.forEach((dir) => {
78 if (dir.name.value === 'requires') {
79 dir.name.value = stitchingConfig.computedDirective.name;
80 dir.arguments[0].name.value = 'selectionSet';
81 dir.arguments[0].value.value = `{ ${dir.arguments[0].value.value} }`;
82 }
83 });
84 });
85 if (typeDef.kind === Kind.OBJECT_TYPE_DEFINITION || typeDef.kind === Kind.OBJECT_TYPE_EXTENSION) {
86 entityTypes.push(typeDef.name.value);
87 }
88 });
89 // Federation service SDLs are incomplete because they omit the federation spec itself...
90 // (https://www.apollographql.com/docs/federation/federation-spec/#federation-schema-specification)
91 // To make federation SDLs into valid and parsable GraphQL schemas,
92 // we must fill in the missing details from the specification.
93 if (entityTypes.length) {
94 const queryDef = getQueryTypeDef(doc.definitions);
95 const entitiesSchema = parse(/* GraphQL */ `
96 scalar _Any
97 union _Entity = ${entityTypes.filter((v, i, a) => a.indexOf(v) === i).join(' | ')}
98 type Query { _entities(representations: [_Any!]!): [_Entity]! @${stitchingConfig.mergeDirective.name} }
99 `).definitions;
100 doc.definitions.push(entitiesSchema[0]);
101 doc.definitions.push(entitiesSchema[1]);
102 if (queryDef) {
103 queryDef.fields.push(entitiesSchema[2].fields[0]);
104 }
105 else {
106 doc.definitions.push(entitiesSchema[2]);
107 }
108 }
109 return [stitchingConfig.stitchingDirectivesTypeDefs, print(doc)].join('\n');
110}