1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.ApolloFederation = exports.removeFederation = exports.addFederationReferencesToSchema = exports.federationSpec = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const graphql_1 = require("graphql");
|
6 | const merge_js_1 = tslib_1.__importDefault(require("lodash/merge.js"));
|
7 | const utils_js_1 = require("./utils.js");
|
8 | const utils_1 = require("@graphql-tools/utils");
|
9 | const index_js_1 = require("./index.js");
|
10 |
|
11 |
|
12 |
|
13 | exports.federationSpec = (0, graphql_1.parse)( `
|
14 | scalar _FieldSet
|
15 |
|
16 | directive @external on FIELD_DEFINITION
|
17 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
|
18 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
|
19 | directive @key(fields: _FieldSet!) on OBJECT | INTERFACE
|
20 | `);
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | function addFederationReferencesToSchema(schema) {
|
26 | return (0, utils_1.mapSchema)(schema, {
|
27 | [utils_1.MapperKind.OBJECT_TYPE]: type => {
|
28 | if (isFederationObjectType(type, schema)) {
|
29 | const typeConfig = type.toConfig();
|
30 | typeConfig.fields = {
|
31 | [resolveReferenceFieldName]: {
|
32 | type,
|
33 | },
|
34 | ...typeConfig.fields,
|
35 | };
|
36 | return new graphql_1.GraphQLObjectType(typeConfig);
|
37 | }
|
38 | return type;
|
39 | },
|
40 | });
|
41 | }
|
42 | exports.addFederationReferencesToSchema = addFederationReferencesToSchema;
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | function removeFederation(schema) {
|
49 | return (0, utils_1.mapSchema)(schema, {
|
50 | [utils_1.MapperKind.QUERY]: queryType => {
|
51 | const queryTypeConfig = queryType.toConfig();
|
52 | delete queryTypeConfig.fields._entities;
|
53 | delete queryTypeConfig.fields._service;
|
54 | return new graphql_1.GraphQLObjectType(queryTypeConfig);
|
55 | },
|
56 | [utils_1.MapperKind.UNION_TYPE]: unionType => {
|
57 | const unionTypeName = unionType.name;
|
58 | if (unionTypeName === '_Entity' || unionTypeName === '_Any') {
|
59 | return null;
|
60 | }
|
61 | return unionType;
|
62 | },
|
63 | [utils_1.MapperKind.OBJECT_TYPE]: objectType => {
|
64 | if (objectType.name === '_Service') {
|
65 | return null;
|
66 | }
|
67 | return objectType;
|
68 | },
|
69 | });
|
70 | }
|
71 | exports.removeFederation = removeFederation;
|
72 | const resolveReferenceFieldName = '__resolveReference';
|
73 | class ApolloFederation {
|
74 | constructor({ enabled, schema }) {
|
75 | this.enabled = false;
|
76 | this.enabled = enabled;
|
77 | this.schema = schema;
|
78 | this.providesMap = this.createMapOfProvides();
|
79 | }
|
80 | |
81 |
|
82 |
|
83 |
|
84 | filterTypeNames(typeNames) {
|
85 | return this.enabled ? typeNames.filter(t => t !== '_FieldSet') : typeNames;
|
86 | }
|
87 | |
88 |
|
89 |
|
90 |
|
91 | filterFieldNames(fieldNames) {
|
92 | return this.enabled ? fieldNames.filter(t => t !== resolveReferenceFieldName) : fieldNames;
|
93 | }
|
94 | |
95 |
|
96 |
|
97 |
|
98 | skipDirective(name) {
|
99 | return this.enabled && ['external', 'requires', 'provides', 'key'].includes(name);
|
100 | }
|
101 | |
102 |
|
103 |
|
104 |
|
105 | skipScalar(name) {
|
106 | return this.enabled && name === '_FieldSet';
|
107 | }
|
108 | |
109 |
|
110 |
|
111 |
|
112 | skipField({ fieldNode, parentType }) {
|
113 | if (!this.enabled || !(0, graphql_1.isObjectType)(parentType) || !isFederationObjectType(parentType, this.schema)) {
|
114 | return false;
|
115 | }
|
116 | return this.isExternalAndNotProvided(fieldNode, parentType);
|
117 | }
|
118 | isResolveReferenceField(fieldNode) {
|
119 | const name = typeof fieldNode.name === 'string' ? fieldNode.name : fieldNode.name.value;
|
120 | return this.enabled && name === resolveReferenceFieldName;
|
121 | }
|
122 | |
123 |
|
124 |
|
125 |
|
126 | transformParentType({ fieldNode, parentType, parentTypeSignature, }) {
|
127 | if (this.enabled &&
|
128 | (0, graphql_1.isObjectType)(parentType) &&
|
129 | isFederationObjectType(parentType, this.schema) &&
|
130 | (isTypeExtension(parentType, this.schema) || fieldNode.name.value === resolveReferenceFieldName)) {
|
131 | const keys = getDirectivesByName('key', parentType);
|
132 | if (keys.length) {
|
133 | const outputs = [`{ __typename: '${parentType.name}' } &`];
|
134 |
|
135 | const requires = getDirectivesByName('requires', fieldNode).map(this.extractKeyOrRequiresFieldSet);
|
136 | const requiredFields = this.translateFieldSet((0, merge_js_1.default)({}, ...requires), parentTypeSignature);
|
137 |
|
138 | const primaryKeys = keys.map(def => {
|
139 | const fields = this.extractKeyOrRequiresFieldSet(def);
|
140 | return this.translateFieldSet(fields, parentTypeSignature);
|
141 | });
|
142 | const [open, close] = primaryKeys.length > 1 ? ['(', ')'] : ['', ''];
|
143 | outputs.push([open, primaryKeys.join(' | '), close].join(''));
|
144 |
|
145 | if (requires.length) {
|
146 | outputs.push(`& ${requiredFields}`);
|
147 | }
|
148 | return outputs.join(' ');
|
149 | }
|
150 | }
|
151 | return parentTypeSignature;
|
152 | }
|
153 | isExternalAndNotProvided(fieldNode, objectType) {
|
154 | return this.isExternal(fieldNode) && !this.hasProvides(objectType, fieldNode);
|
155 | }
|
156 | isExternal(node) {
|
157 | return getDirectivesByName('external', node).length > 0;
|
158 | }
|
159 | hasProvides(objectType, node) {
|
160 | const fields = this.providesMap[(0, graphql_1.isObjectType)(objectType) ? objectType.name : objectType.name.value];
|
161 | if (fields && fields.length) {
|
162 | return fields.includes(node.name.value);
|
163 | }
|
164 | return false;
|
165 | }
|
166 | translateFieldSet(fields, parentTypeRef) {
|
167 | return `GraphQLRecursivePick<${parentTypeRef}, ${JSON.stringify(fields)}>`;
|
168 | }
|
169 | extractKeyOrRequiresFieldSet(directive) {
|
170 | const arg = directive.arguments.find(arg => arg.name.value === 'fields');
|
171 | const { value } = arg.value;
|
172 | return (0, index_js_1.oldVisit)((0, graphql_1.parse)(`{${value}}`), {
|
173 | leave: {
|
174 | SelectionSet(node) {
|
175 | return node.selections.reduce((accum, field) => {
|
176 | accum[field.name] = field.selection;
|
177 | return accum;
|
178 | }, {});
|
179 | },
|
180 | Field(node) {
|
181 | return {
|
182 | name: node.name.value,
|
183 | selection: node.selectionSet ? node.selectionSet : true,
|
184 | };
|
185 | },
|
186 | Document(node) {
|
187 | return node.definitions.find((def) => def.kind === 'OperationDefinition' && def.operation === 'query').selectionSet;
|
188 | },
|
189 | },
|
190 | });
|
191 | }
|
192 | extractProvidesFieldSet(directive) {
|
193 | const arg = directive.arguments.find(arg => arg.name.value === 'fields');
|
194 | const { value } = arg.value;
|
195 | if (/[{}]/gi.test(value)) {
|
196 | throw new Error('Nested fields in _FieldSet is not supported in the @provides directive');
|
197 | }
|
198 | return value.split(/\s+/g);
|
199 | }
|
200 | createMapOfProvides() {
|
201 | const providesMap = {};
|
202 | Object.keys(this.schema.getTypeMap()).forEach(typename => {
|
203 | const objectType = this.schema.getType(typename);
|
204 | if ((0, graphql_1.isObjectType)(objectType)) {
|
205 | Object.values(objectType.getFields()).forEach(field => {
|
206 | const provides = getDirectivesByName('provides', field.astNode)
|
207 | .map(this.extractProvidesFieldSet)
|
208 | .reduce((prev, curr) => [...prev, ...curr], []);
|
209 | const ofType = (0, utils_js_1.getBaseType)(field.type);
|
210 | if (!providesMap[ofType.name]) {
|
211 | providesMap[ofType.name] = [];
|
212 | }
|
213 | providesMap[ofType.name].push(...provides);
|
214 | });
|
215 | }
|
216 | });
|
217 | return providesMap;
|
218 | }
|
219 | }
|
220 | exports.ApolloFederation = ApolloFederation;
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | function isFederationObjectType(node, schema) {
|
226 | const { name: { value: name }, directives, } = (0, graphql_1.isObjectType)(node) ? (0, utils_1.astFromObjectType)(node, schema) : node;
|
227 | const rootTypeNames = (0, utils_1.getRootTypeNames)(schema);
|
228 | const isNotRoot = !rootTypeNames.has(name);
|
229 | const isNotIntrospection = !name.startsWith('__');
|
230 | const hasKeyDirective = directives.some(d => d.name.value === 'key');
|
231 | return isNotRoot && isNotIntrospection && hasKeyDirective;
|
232 | }
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | function getDirectivesByName(name, node) {
|
239 | var _a;
|
240 | let astNode;
|
241 | if ((0, graphql_1.isObjectType)(node)) {
|
242 | astNode = node.astNode;
|
243 | }
|
244 | else {
|
245 | astNode = node;
|
246 | }
|
247 | return ((_a = astNode === null || astNode === void 0 ? void 0 : astNode.directives) === null || _a === void 0 ? void 0 : _a.filter(d => d.name.value === name)) || [];
|
248 | }
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | function isTypeExtension(node, schema) {
|
255 | var _a;
|
256 | const definition = (0, graphql_1.isObjectType)(node) ? node.astNode || (0, utils_1.astFromObjectType)(node, schema) : node;
|
257 | return (_a = definition.fields) === null || _a === void 0 ? void 0 : _a.some(field => getDirectivesByName('external', field).length);
|
258 | }
|