Skip to content

Commit 8447a90

Browse files
committed
Adjust dependency and definition logic to include federated links
1 parent a373ac7 commit 8447a90

File tree

6 files changed

+116
-7
lines changed

6 files changed

+116
-7
lines changed

.changeset/strong-melons-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-tools/import': patch
3+
---
4+
5+
Adjust dependency and definition logic to include federated links

packages/import/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
},
5353
"dependencies": {
5454
"@graphql-tools/utils": "^10.8.6",
55+
"@theguild/federation-composition": "^0.18.3",
5556
"resolve-from": "5.0.0",
5657
"tslib": "^2.4.0"
5758
},

packages/import/src/index.ts

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,21 @@ import {
3939
} from 'graphql';
4040
import resolveFrom from 'resolve-from';
4141
import { parseGraphQLSDL } from '@graphql-tools/utils';
42+
import { extractLinkImplementations } from '@theguild/federation-composition';
4243

4344
const builtinTypes = ['String', 'Float', 'Int', 'Boolean', 'ID', 'Upload'];
4445

46+
const federationV1Directives = ['key', 'provides', 'requires', 'external'];
47+
4548
const builtinDirectives = [
4649
'deprecated',
4750
'skip',
4851
'include',
4952
'cacheControl',
50-
'key',
51-
'external',
52-
'requires',
53-
'provides',
5453
'connection',
5554
'client',
5655
'specifiedBy',
57-
'link',
56+
...federationV1Directives,
5857
];
5958

6059
const IMPORT_FROM_REGEX = /^import\s+(\*|(.*))\s+from\s+('|")(.*)('|");?$/;
@@ -264,6 +263,66 @@ export function extractDependencies(
264263
};
265264
}
266265

266+
function importFederatedSchemaLinks(
267+
definition: DefinitionNode,
268+
definitionsByName: Map<string, Set<DefinitionNode>>,
269+
) {
270+
const addDefinition = (name: string) => {
271+
definitionsByName.set(name.replace(/^@/g, ''), new Set());
272+
};
273+
274+
// extract links from this definition
275+
const { links, matchesImplementation, resolveImportName } = extractLinkImplementations({
276+
kind: Kind.DOCUMENT,
277+
definitions: [definition],
278+
});
279+
280+
if (links.length) {
281+
const federationUrl = 'https://specs.apollo.dev/federation';
282+
const linkUrl = 'https://specs.apollo.dev/link';
283+
284+
/**
285+
* Official Federated imports are special because they can be referenced without specifyin the import.
286+
* To handle this case, we must prepare a list of all the possible valid usages to check against.
287+
* Note that this versioning is not technically correct, since some definitions are after v2.0.
288+
* But this is enough information to be comfortable not blocking the imports at this phase. It's
289+
* the job of the composer to validate the versions.
290+
* */
291+
if (matchesImplementation(federationUrl, 'v2.0')) {
292+
const federationImports = [
293+
'@composeDirective',
294+
'@extends',
295+
'@external',
296+
'@inaccessible',
297+
'@interfaceObject',
298+
'@key',
299+
'@override',
300+
'@provides',
301+
'@requires',
302+
'@shareable',
303+
'@tag',
304+
'FieldSet',
305+
];
306+
for (const i of federationImports) {
307+
addDefinition(resolveImportName(federationUrl, i));
308+
}
309+
}
310+
if (matchesImplementation(linkUrl, 'v1.0')) {
311+
const linkImports = ['Purpose', 'Import', '@link'];
312+
for (const i of linkImports) {
313+
addDefinition(resolveImportName(linkUrl, i));
314+
}
315+
}
316+
317+
const imported = links
318+
.filter(l => ![linkUrl, federationUrl].includes(l.identity))
319+
.flatMap(l => l.imports.map(i => i.as ?? i.name));
320+
for (const namedImport of imported) {
321+
addDefinition(namedImport);
322+
}
323+
}
324+
}
325+
267326
function visitDefinition(
268327
definition: DefinitionNode,
269328
definitionsByName: Map<string, Set<DefinitionNode>>,
@@ -288,6 +347,7 @@ function visitDefinition(
288347
dependencySet = new Set();
289348
dependenciesByDefinitionName.set(definitionName, dependencySet);
290349
}
350+
291351
switch (definition.kind) {
292352
case Kind.OPERATION_DEFINITION:
293353
visitOperationDefinitionNode(definition, dependencySet);
@@ -317,9 +377,11 @@ function visitDefinition(
317377
visitScalarDefinitionNode(definition, dependencySet);
318378
break;
319379
case Kind.SCHEMA_DEFINITION:
380+
importFederatedSchemaLinks(definition, definitionsByName);
320381
visitSchemaDefinitionNode(definition, dependencySet);
321382
break;
322383
case Kind.SCHEMA_EXTENSION:
384+
importFederatedSchemaLinks(definition, definitionsByName);
323385
visitSchemaExtensionDefinitionNode(definition, dependencySet);
324386
break;
325387
case Kind.OBJECT_TYPE_EXTENSION:
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
extend schema @link(url: "https://the-guild.dev/graphql/tools", import: ["@foo"])
1+
extend schema
2+
@link(url: "https://specs.apollo.dev/link/v1.0")
3+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
4+
@link(url: "https://the-guild.dev/graphql/tools", import: ["@foo"])
25

36
directive @foo on FIELD_DEFINITION
47

58
extend type User @key(fields: "id") {
69
id: ID!
710
email: String @foo
11+
ssn: String @federation__tag(name: "private")
812
}

packages/import/tests/schema/import-schema.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,17 @@ describe('importSchema', () => {
469469

470470
test('importSchema: link directive', () => {
471471
const expectedSDL = /* GraphQL */ `
472-
extend schema @link(url: "https://the-guild.dev/graphql/tools", import: ["@foo"])
472+
extend schema
473+
@link(url: "https://specs.apollo.dev/link/v1.0")
474+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
475+
@link(url: "https://the-guild.dev/graphql/tools", import: ["@foo"])
473476
474477
directive @foo on FIELD_DEFINITION
475478
476479
extend type User @key(fields: "id") {
477480
id: ID!
478481
email: String @foo
482+
ssn: String @federation__tag(name: "private")
479483
}
480484
`;
481485
expect(importSchema('./fixtures/directive/h.graphql')).toBeSimilarGqlDoc(expectedSDL);

yarn.lock

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3062,6 +3062,16 @@
30623062
tailwind-merge "^2.5.2"
30633063
unist-util-visit "5.0.0"
30643064

3065+
"@theguild/federation-composition@^0.18.3":
3066+
version "0.18.3"
3067+
resolved "https://registry.yarnpkg.com/@theguild/federation-composition/-/federation-composition-0.18.3.tgz#7e475b7834f4fd0b4e5139f22b31eeaf763fdce8"
3068+
integrity sha512-UGa3R3JMDHzoKi6k32kq/bB8SbZrkkxLrfinuEPcsv0sU8r1CW+4jwCBbQGC5vnoU9asg+MuCjMYpLZSOJS/dw==
3069+
dependencies:
3070+
constant-case "^3.0.4"
3071+
debug "4.4.0"
3072+
json5 "^2.2.3"
3073+
lodash.sortby "^4.7.0"
3074+
30653075
"@theguild/prettier-config@3.0.1":
30663076
version "3.0.1"
30673077
resolved "https://registry.yarnpkg.com/@theguild/prettier-config/-/prettier-config-3.0.1.tgz#83b662197277a0de8a211c09cd43fd4cc59be614"
@@ -4983,6 +4993,15 @@ consola@^3.0.0:
49834993
resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7"
49844994
integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==
49854995

4996+
constant-case@^3.0.4:
4997+
version "3.0.4"
4998+
resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1"
4999+
integrity sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==
5000+
dependencies:
5001+
no-case "^3.0.4"
5002+
tslib "^2.0.3"
5003+
upper-case "^2.0.2"
5004+
49865005
content-disposition@^1.0.0:
49875006
version "1.0.0"
49885007
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2"
@@ -5464,6 +5483,13 @@ debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, d
54645483
dependencies:
54655484
ms "^2.1.3"
54665485

5486+
debug@4.4.0:
5487+
version "4.4.0"
5488+
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
5489+
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
5490+
dependencies:
5491+
ms "^2.1.3"
5492+
54675493
debug@^3.2.7:
54685494
version "3.2.7"
54695495
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@@ -12325,6 +12351,13 @@ update-browserslist-db@^1.1.3:
1232512351
escalade "^3.2.0"
1232612352
picocolors "^1.1.1"
1232712353

12354+
upper-case@^2.0.2:
12355+
version "2.0.2"
12356+
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a"
12357+
integrity sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==
12358+
dependencies:
12359+
tslib "^2.0.3"
12360+
1232812361
uri-js@^4.2.2:
1232912362
version "4.4.1"
1233012363
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"

0 commit comments

Comments
 (0)