@apollo/federation-internals 2.1.0-alpha.1 → 2.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +13 -3
- package/dist/buildSchema.js.map +1 -1
- package/dist/definitions.d.ts +47 -8
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +137 -23
- package/dist/definitions.js.map +1 -1
- package/dist/error.d.ts +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +2 -0
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +9 -0
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +5 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +13 -5
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +13 -9
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +27 -5
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.js +1 -2
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/operations.d.ts +48 -21
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +329 -48
- package/dist/operations.js.map +1 -1
- package/dist/print.d.ts +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +1 -1
- package/dist/print.js.map +1 -1
- package/dist/schemaUpgrader.js +2 -2
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +31 -1
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/definitions.test.ts +18 -0
- package/src/__tests__/operations.test.ts +217 -99
- package/src/__tests__/subgraphValidation.test.ts +2 -0
- package/src/buildSchema.ts +19 -5
- package/src/definitions.ts +217 -29
- package/src/error.ts +7 -0
- package/src/extractSubgraphsFromSupergraph.ts +20 -0
- package/src/federation.ts +16 -5
- package/src/federationSpec.ts +32 -5
- package/src/inaccessibleSpec.ts +2 -5
- package/src/operations.ts +520 -71
- package/src/print.ts +1 -1
- package/src/schemaUpgrader.ts +2 -2
- package/src/utils.ts +40 -0
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
defaultRootName,
|
|
2
3
|
Schema,
|
|
4
|
+
SchemaRootKind,
|
|
3
5
|
} from '../../dist/definitions';
|
|
4
6
|
import { buildSchema } from '../../dist/buildSchema';
|
|
5
7
|
import { Field, FieldSelection, parseOperation, SelectionSet } from '../../dist/operations';
|
|
6
8
|
import './matchers';
|
|
9
|
+
import { GraphQLError } from 'graphql';
|
|
7
10
|
|
|
8
11
|
function parseSchema(schema: string): Schema {
|
|
9
12
|
try {
|
|
@@ -13,133 +16,215 @@ function parseSchema(schema: string): Schema {
|
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
interface I {
|
|
23
|
-
b: Int
|
|
24
|
-
}
|
|
19
|
+
describe('fragments optimization', () => {
|
|
20
|
+
test('handles fragments using other fragments', () => {
|
|
21
|
+
const schema = parseSchema(`
|
|
22
|
+
type Query {
|
|
23
|
+
t: T1
|
|
24
|
+
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
u: U
|
|
30
|
-
}
|
|
26
|
+
interface I {
|
|
27
|
+
b: Int
|
|
28
|
+
}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
30
|
+
type T1 {
|
|
31
|
+
a: Int
|
|
32
|
+
b: Int
|
|
33
|
+
u: U
|
|
34
|
+
}
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
type T2 {
|
|
37
|
+
x: String
|
|
38
|
+
y: String
|
|
39
|
+
b: Int
|
|
40
|
+
u: U
|
|
41
|
+
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
a
|
|
45
|
-
b
|
|
46
|
-
}
|
|
43
|
+
union U = T1 | T2
|
|
44
|
+
`);
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
const operation = parseOperation(schema, `
|
|
47
|
+
fragment OnT1 on T1 {
|
|
48
|
+
a
|
|
49
|
+
b
|
|
50
|
+
}
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
fragment OnT2 on T2 {
|
|
53
|
+
x
|
|
54
|
+
y
|
|
55
|
+
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...OnT2
|
|
61
|
-
}
|
|
57
|
+
fragment OnI on I {
|
|
58
|
+
b
|
|
59
|
+
}
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
fragment OnU on U {
|
|
62
|
+
...OnI
|
|
65
63
|
...OnT1
|
|
66
64
|
...OnT2
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
query {
|
|
68
|
+
t {
|
|
69
|
+
...OnT1
|
|
70
|
+
...OnT2
|
|
71
|
+
...OnI
|
|
72
|
+
u {
|
|
73
|
+
...OnU
|
|
74
|
+
}
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
`);
|
|
78
|
+
|
|
79
|
+
const withoutFragments = parseOperation(schema, operation.toString(true, true));
|
|
80
|
+
expect(withoutFragments.toString()).toMatchString(`
|
|
81
|
+
{
|
|
82
|
+
t {
|
|
83
|
+
... on T1 {
|
|
84
|
+
a
|
|
85
|
+
b
|
|
86
|
+
}
|
|
87
|
+
... on T2 {
|
|
88
|
+
x
|
|
89
|
+
y
|
|
90
|
+
}
|
|
91
|
+
... on I {
|
|
92
|
+
b
|
|
93
|
+
}
|
|
94
|
+
u {
|
|
95
|
+
... on U {
|
|
96
|
+
... on I {
|
|
97
|
+
b
|
|
98
|
+
}
|
|
99
|
+
... on T1 {
|
|
100
|
+
a
|
|
101
|
+
b
|
|
102
|
+
}
|
|
103
|
+
... on T2 {
|
|
104
|
+
x
|
|
105
|
+
y
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
|
|
114
|
+
// Note that we expect onU to *not* be recreated because, by default, optimize only
|
|
115
|
+
// add add back a fragment if it is used at least twice (otherwise, the fragment just
|
|
116
|
+
// make the query bigger).
|
|
117
|
+
expect(optimized.toString()).toMatchString(`
|
|
118
|
+
fragment OnT1 on T1 {
|
|
119
|
+
a
|
|
120
|
+
b
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fragment OnT2 on T2 {
|
|
124
|
+
x
|
|
125
|
+
y
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fragment OnI on I {
|
|
129
|
+
b
|
|
130
|
+
}
|
|
74
131
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
132
|
+
{
|
|
133
|
+
t {
|
|
134
|
+
...OnT1
|
|
135
|
+
...OnT2
|
|
136
|
+
...OnI
|
|
137
|
+
u {
|
|
138
|
+
...OnI
|
|
139
|
+
...OnT1
|
|
140
|
+
...OnT2
|
|
141
|
+
}
|
|
82
142
|
}
|
|
83
|
-
|
|
143
|
+
}
|
|
144
|
+
`);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('handles fragments with nested selections', () => {
|
|
148
|
+
const schema = parseSchema(`
|
|
149
|
+
type Query {
|
|
150
|
+
t1a: T1
|
|
151
|
+
t2a: T1
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type T1 {
|
|
155
|
+
t2: T2
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
type T2 {
|
|
159
|
+
x: String
|
|
160
|
+
y: String
|
|
161
|
+
}
|
|
162
|
+
`);
|
|
163
|
+
|
|
164
|
+
const operation = parseOperation(schema, `
|
|
165
|
+
fragment OnT1 on T1 {
|
|
166
|
+
t2 {
|
|
84
167
|
x
|
|
85
|
-
y
|
|
86
168
|
}
|
|
87
|
-
|
|
88
|
-
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
query {
|
|
172
|
+
t1a {
|
|
173
|
+
...OnT1
|
|
174
|
+
t2 {
|
|
175
|
+
y
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
t2a {
|
|
179
|
+
...OnT1
|
|
89
180
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
181
|
+
}
|
|
182
|
+
`);
|
|
183
|
+
|
|
184
|
+
const withoutFragments = parseOperation(schema, operation.toString(true, true));
|
|
185
|
+
expect(withoutFragments.toString()).toMatchString(`
|
|
186
|
+
{
|
|
187
|
+
t1a {
|
|
188
|
+
... on T1 {
|
|
189
|
+
t2 {
|
|
190
|
+
x
|
|
98
191
|
}
|
|
99
|
-
|
|
192
|
+
}
|
|
193
|
+
t2 {
|
|
194
|
+
y
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
t2a {
|
|
198
|
+
... on T1 {
|
|
199
|
+
t2 {
|
|
100
200
|
x
|
|
101
|
-
y
|
|
102
201
|
}
|
|
103
202
|
}
|
|
104
203
|
}
|
|
105
204
|
}
|
|
106
|
-
|
|
107
|
-
`);
|
|
108
|
-
|
|
109
|
-
const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
|
|
110
|
-
// Note that we expect onU to *not* be recreated because, by default, optimize only
|
|
111
|
-
// add add back a fragment if it is used at least twice (otherwise, the fragment just
|
|
112
|
-
// make the query bigger).
|
|
113
|
-
expect(optimized.toString()).toMatchString(`
|
|
114
|
-
fragment OnT1 on T1 {
|
|
115
|
-
a
|
|
116
|
-
b
|
|
117
|
-
}
|
|
205
|
+
`);
|
|
118
206
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
207
|
+
const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
|
|
208
|
+
expect(optimized.toString()).toMatchString(`
|
|
209
|
+
fragment OnT1 on T1 {
|
|
210
|
+
t2 {
|
|
211
|
+
x
|
|
212
|
+
}
|
|
213
|
+
}
|
|
127
214
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
u {
|
|
134
|
-
... on U {
|
|
135
|
-
...OnI
|
|
136
|
-
...OnT1
|
|
137
|
-
...OnT2
|
|
215
|
+
{
|
|
216
|
+
t1a {
|
|
217
|
+
...OnT1
|
|
218
|
+
t2 {
|
|
219
|
+
y
|
|
138
220
|
}
|
|
139
221
|
}
|
|
222
|
+
t2a {
|
|
223
|
+
...OnT1
|
|
224
|
+
}
|
|
140
225
|
}
|
|
141
|
-
|
|
142
|
-
|
|
226
|
+
`);
|
|
227
|
+
});
|
|
143
228
|
});
|
|
144
229
|
|
|
145
230
|
describe('selection set freezing', () => {
|
|
@@ -259,3 +344,36 @@ describe('selection set freezing', () => {
|
|
|
259
344
|
expect(s2.toString()).toBe('{ t { b } }');
|
|
260
345
|
});
|
|
261
346
|
});
|
|
347
|
+
|
|
348
|
+
describe('validations', () => {
|
|
349
|
+
test.each([
|
|
350
|
+
{ directive: '@defer', rootKind: 'mutation' },
|
|
351
|
+
{ directive: '@defer', rootKind: 'subscription' },
|
|
352
|
+
{ directive: '@stream', rootKind: 'mutation' },
|
|
353
|
+
{ directive: '@stream', rootKind: 'subscription' },
|
|
354
|
+
])('reject $directive on $rootKind type', ({ directive, rootKind }) => {
|
|
355
|
+
const schema = parseSchema(`
|
|
356
|
+
type Query {
|
|
357
|
+
x: String
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
type Mutation {
|
|
361
|
+
x: String
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
type Subscription {
|
|
365
|
+
x: String
|
|
366
|
+
}
|
|
367
|
+
`);
|
|
368
|
+
|
|
369
|
+
expect(() => {
|
|
370
|
+
parseOperation(schema, `
|
|
371
|
+
${rootKind} {
|
|
372
|
+
... ${directive} {
|
|
373
|
+
x
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
`)
|
|
377
|
+
}).toThrowError(new GraphQLError(`The @defer and @stream directives cannot be used on ${rootKind} root type "${defaultRootName(rootKind as SchemaRootKind)}"`));
|
|
378
|
+
});
|
|
379
|
+
});
|
package/src/buildSchema.ts
CHANGED
|
@@ -100,7 +100,7 @@ export function buildSchemaFromAST(
|
|
|
100
100
|
// is that:
|
|
101
101
|
// 1. we can (enum values are self-contained and cannot reference anything that may need to be imported first; this
|
|
102
102
|
// is also why we skip directive applications at that point, as those _may_ reference something that hasn't been imported yet)
|
|
103
|
-
// 2. this allows the code to handle better the case where the `link__Purpose` enum is provided in the AST despite the `@link`
|
|
103
|
+
// 2. this allows the code to handle better the case where the `link__Purpose` enum is provided in the AST despite the `@link`
|
|
104
104
|
// _definition_ not being provided. And the reason that is true is that as we later _add_ the `@link` definition, we
|
|
105
105
|
// will need to check if `link_Purpose` needs to be added or not, but when it is already present, we check it's definition
|
|
106
106
|
// is the expected, but that check will unexpected fail if we haven't finished "building" said type definition.
|
|
@@ -215,7 +215,7 @@ function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema:
|
|
|
215
215
|
let type = schema.type(definitionNode.name.value);
|
|
216
216
|
// Note that the type may already exists due to an extension having been processed first, but we know we
|
|
217
217
|
// have seen 2 definitions (which is invalid) if the definition has `preserverEmptyDefnition` already set
|
|
218
|
-
// since it's only set for definitions, not extensions.
|
|
218
|
+
// since it's only set for definitions, not extensions.
|
|
219
219
|
// Also note that we allow to redefine built-ins.
|
|
220
220
|
if (!type || type.isBuiltIn) {
|
|
221
221
|
type = schema.addType(newNamedType(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value));
|
|
@@ -332,9 +332,23 @@ function buildAppliedDirectives(
|
|
|
332
332
|
for (const directive of elementNode.directives ?? []) {
|
|
333
333
|
withNodeAttachedToError(
|
|
334
334
|
() => {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
/**
|
|
336
|
+
* If we are at the schemaDefinition level of a federation schema, it's possible that some directives
|
|
337
|
+
* will not be added until after the federation calls completeSchema. In that case, we want to wait
|
|
338
|
+
* until after completeSchema is called before we try to apply those directives.
|
|
339
|
+
*/
|
|
340
|
+
if (element !== element.schema().schemaDefinition || directive.name.value === 'link' || !element.schema().blueprint.applyDirectivesAfterParsing()) {
|
|
341
|
+
const d = element.applyDirective(directive.name.value, buildArgs(directive));
|
|
342
|
+
d.setOfExtension(extension);
|
|
343
|
+
d.sourceAST = directive;
|
|
344
|
+
} else {
|
|
345
|
+
element.addUnappliedDirective({
|
|
346
|
+
extension,
|
|
347
|
+
directive,
|
|
348
|
+
args: buildArgs(directive),
|
|
349
|
+
nameOrDef: directive.name.value,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
338
352
|
},
|
|
339
353
|
directive,
|
|
340
354
|
errors,
|