@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/buildSchema.d.ts.map +1 -1
  3. package/dist/buildSchema.js +13 -3
  4. package/dist/buildSchema.js.map +1 -1
  5. package/dist/definitions.d.ts +47 -8
  6. package/dist/definitions.d.ts.map +1 -1
  7. package/dist/definitions.js +137 -23
  8. package/dist/definitions.js.map +1 -1
  9. package/dist/error.d.ts +1 -0
  10. package/dist/error.d.ts.map +1 -1
  11. package/dist/error.js +2 -0
  12. package/dist/error.js.map +1 -1
  13. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  14. package/dist/extractSubgraphsFromSupergraph.js +9 -0
  15. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  16. package/dist/federation.d.ts +5 -1
  17. package/dist/federation.d.ts.map +1 -1
  18. package/dist/federation.js +13 -5
  19. package/dist/federation.js.map +1 -1
  20. package/dist/federationSpec.d.ts +13 -9
  21. package/dist/federationSpec.d.ts.map +1 -1
  22. package/dist/federationSpec.js +27 -5
  23. package/dist/federationSpec.js.map +1 -1
  24. package/dist/inaccessibleSpec.js +1 -2
  25. package/dist/inaccessibleSpec.js.map +1 -1
  26. package/dist/operations.d.ts +48 -21
  27. package/dist/operations.d.ts.map +1 -1
  28. package/dist/operations.js +329 -48
  29. package/dist/operations.js.map +1 -1
  30. package/dist/print.d.ts +1 -1
  31. package/dist/print.d.ts.map +1 -1
  32. package/dist/print.js +1 -1
  33. package/dist/print.js.map +1 -1
  34. package/dist/schemaUpgrader.js +2 -2
  35. package/dist/schemaUpgrader.js.map +1 -1
  36. package/dist/utils.d.ts +9 -0
  37. package/dist/utils.d.ts.map +1 -1
  38. package/dist/utils.js +31 -1
  39. package/dist/utils.js.map +1 -1
  40. package/package.json +3 -3
  41. package/src/__tests__/definitions.test.ts +18 -0
  42. package/src/__tests__/operations.test.ts +217 -99
  43. package/src/__tests__/subgraphValidation.test.ts +2 -0
  44. package/src/buildSchema.ts +19 -5
  45. package/src/definitions.ts +217 -29
  46. package/src/error.ts +7 -0
  47. package/src/extractSubgraphsFromSupergraph.ts +20 -0
  48. package/src/federation.ts +16 -5
  49. package/src/federationSpec.ts +32 -5
  50. package/src/inaccessibleSpec.ts +2 -5
  51. package/src/operations.ts +520 -71
  52. package/src/print.ts +1 -1
  53. package/src/schemaUpgrader.ts +2 -2
  54. package/src/utils.ts +40 -0
  55. package/tsconfig.test.tsbuildinfo +1 -1
  56. 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
- test('fragments optimization of selection sets', () => {
17
- const schema = parseSchema(`
18
- type Query {
19
- t: T1
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
- type T1 {
27
- a: Int
28
- b: Int
29
- u: U
30
- }
26
+ interface I {
27
+ b: Int
28
+ }
31
29
 
32
- type T2 {
33
- x: String
34
- y: String
35
- b: Int
36
- u: U
37
- }
30
+ type T1 {
31
+ a: Int
32
+ b: Int
33
+ u: U
34
+ }
38
35
 
39
- union U = T1 | T2
40
- `);
36
+ type T2 {
37
+ x: String
38
+ y: String
39
+ b: Int
40
+ u: U
41
+ }
41
42
 
42
- const operation = parseOperation(schema, `
43
- fragment OnT1 on T1 {
44
- a
45
- b
46
- }
43
+ union U = T1 | T2
44
+ `);
47
45
 
48
- fragment OnT2 on T2 {
49
- x
50
- y
51
- }
46
+ const operation = parseOperation(schema, `
47
+ fragment OnT1 on T1 {
48
+ a
49
+ b
50
+ }
52
51
 
53
- fragment OnI on I {
54
- b
55
- }
52
+ fragment OnT2 on T2 {
53
+ x
54
+ y
55
+ }
56
56
 
57
- fragment OnU on U {
58
- ...OnI
59
- ...OnT1
60
- ...OnT2
61
- }
57
+ fragment OnI on I {
58
+ b
59
+ }
62
60
 
63
- query {
64
- t {
61
+ fragment OnU on U {
62
+ ...OnI
65
63
  ...OnT1
66
64
  ...OnT2
67
- ...OnI
68
- u {
69
- ...OnU
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
- const withoutFragments = parseOperation(schema, operation.toString(true, true));
76
- expect(withoutFragments.toString()).toMatchString(`
77
- {
78
- t {
79
- ... on T1 {
80
- a
81
- b
132
+ {
133
+ t {
134
+ ...OnT1
135
+ ...OnT2
136
+ ...OnI
137
+ u {
138
+ ...OnI
139
+ ...OnT1
140
+ ...OnT2
141
+ }
82
142
  }
83
- ... on T2 {
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
- ... on I {
88
- b
169
+ }
170
+
171
+ query {
172
+ t1a {
173
+ ...OnT1
174
+ t2 {
175
+ y
176
+ }
177
+ }
178
+ t2a {
179
+ ...OnT1
89
180
  }
90
- u {
91
- ... on U {
92
- ... on I {
93
- b
94
- }
95
- ... on T1 {
96
- a
97
- b
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
- ... on T2 {
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
- fragment OnT2 on T2 {
120
- x
121
- y
122
- }
123
-
124
- fragment OnI on I {
125
- b
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
- t {
130
- ...OnT1
131
- ...OnT2
132
- ...OnI
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
+ });
@@ -636,6 +636,8 @@ describe('@core/@link handling', () => {
636
636
 
637
637
  directive @federation__override(from: String!) on FIELD_DEFINITION
638
638
 
639
+ directive @federation__composeDirective(name: String) repeatable on SCHEMA
640
+
639
641
  type T
640
642
  @key(fields: "k")
641
643
  {
@@ -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
- const d = element.applyDirective(directive.name.value, buildArgs(directive));
336
- d.setOfExtension(extension);
337
- d.sourceAST = directive;
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,