@apollo/gateway 0.43.1 → 2.0.0-alpha.1

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 (39) hide show
  1. package/CHANGELOG.md +2 -4
  2. package/LICENSE +95 -0
  3. package/dist/executeQueryPlan.d.ts +2 -3
  4. package/dist/executeQueryPlan.d.ts.map +1 -1
  5. package/dist/executeQueryPlan.js +2 -27
  6. package/dist/executeQueryPlan.js.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +22 -41
  10. package/dist/index.js.map +1 -1
  11. package/package.json +7 -5
  12. package/src/__tests__/build-query-plan.feature +34 -24
  13. package/src/__tests__/buildQueryPlan.test.ts +76 -840
  14. package/src/__tests__/executeQueryPlan.test.ts +543 -537
  15. package/src/__tests__/execution-utils.ts +20 -26
  16. package/src/__tests__/gateway/lifecycle-hooks.test.ts +26 -4
  17. package/src/__tests__/gateway/reporting.test.ts +0 -14
  18. package/src/__tests__/integration/abstract-types.test.ts +40 -52
  19. package/src/__tests__/integration/boolean.test.ts +3 -1
  20. package/src/__tests__/integration/complex-key.test.ts +40 -55
  21. package/src/__tests__/integration/configuration.test.ts +5 -5
  22. package/src/__tests__/integration/custom-directives.test.ts +8 -2
  23. package/src/__tests__/integration/multiple-key.test.ts +10 -11
  24. package/src/__tests__/integration/requires.test.ts +2 -2
  25. package/src/__tests__/integration/scope.test.ts +19 -30
  26. package/src/__tests__/integration/value-types.test.ts +31 -31
  27. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +217 -142
  28. package/src/__tests__/queryPlanCucumber.test.ts +4 -18
  29. package/src/core/__tests__/core.test.ts +1 -0
  30. package/src/executeQueryPlan.ts +1 -32
  31. package/src/index.ts +39 -80
  32. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +4 -4
  33. package/LICENSE.md +0 -21
  34. package/dist/core/index.d.ts +0 -13
  35. package/dist/core/index.d.ts.map +0 -1
  36. package/dist/core/index.js +0 -43
  37. package/dist/core/index.js.map +0 -1
  38. package/src/__tests__/build-query-plan-fragmentization.feature +0 -282
  39. package/src/core/index.ts +0 -51
@@ -2,12 +2,11 @@ import {
2
2
  buildClientSchema,
3
3
  getIntrospectionQuery,
4
4
  GraphQLObjectType,
5
- GraphQLSchema,
6
5
  print,
7
6
  } from 'graphql';
8
7
  import { addResolversToSchema, GraphQLResolverMap } from 'apollo-graphql';
9
8
  import gql from 'graphql-tag';
10
- import { GraphQLRequestContext, VariableValues } from 'apollo-server-types';
9
+ import { GraphQLExecutionResult, GraphQLRequestContext } from 'apollo-server-types';
11
10
  import { AuthenticationError } from 'apollo-server-core';
12
11
  import { buildOperationContext } from '../operationContext';
13
12
  import { executeQueryPlan } from '../executeQueryPlan';
@@ -17,15 +16,54 @@ import {
17
16
  queryPlanSerializer,
18
17
  superGraphWithInaccessible,
19
18
  } from 'apollo-federation-integration-testsuite';
20
- import { buildComposedSchema, QueryPlanner } from '@apollo/query-planner';
19
+ import { QueryPlan, QueryPlanner } from '@apollo/query-planner';
21
20
  import { ApolloGateway } from '..';
22
21
  import { ApolloServerBase as ApolloServer } from 'apollo-server-core';
23
22
  import { getFederatedTestingSchema } from './execution-utils';
23
+ import { Schema, Operation, parseOperation, buildSchemaFromAST } from '@apollo/federation-internals';
24
24
 
25
25
  expect.addSnapshotSerializer(astSerializer);
26
26
  expect.addSnapshotSerializer(queryPlanSerializer);
27
27
 
28
28
  describe('executeQueryPlan', () => {
29
+ let serviceMap: {
30
+ [serviceName: string]: LocalGraphQLDataSource;
31
+ };
32
+
33
+ let parseOp = (operation: string, operationSchema?: Schema): Operation => {
34
+ return parseOperation((operationSchema ?? schema), operation);
35
+ }
36
+
37
+ let buildPlan = (operation: string | Operation, operationQueryPlanner?: QueryPlanner, operationSchema?: Schema): QueryPlan => {
38
+ const op = typeof operation === 'string' ? parseOp(operation, operationSchema): operation;
39
+ return (operationQueryPlanner ?? queryPlanner).buildQueryPlan(op);
40
+ }
41
+
42
+ async function executePlan(
43
+ queryPlan: QueryPlan,
44
+ operation: Operation,
45
+ executeRequestContext?: GraphQLRequestContext,
46
+ executeSchema?: Schema,
47
+ executeServiceMap?: { [serviceName: string]: LocalGraphQLDataSource }
48
+ ): Promise<GraphQLExecutionResult> {
49
+ const operationContext = buildOperationContext({
50
+ schema: (executeSchema ?? schema).toAPISchema().toGraphQLJSSchema(),
51
+ operationDocument: gql`${operation.toString()}`,
52
+ });
53
+ return executeQueryPlan(
54
+ queryPlan,
55
+ executeServiceMap ?? serviceMap,
56
+ executeRequestContext ?? buildRequestContext(),
57
+ operationContext,
58
+ );
59
+ }
60
+
61
+ async function executeOperation(operationString: string, requestContext?: GraphQLRequestContext): Promise<GraphQLExecutionResult> {
62
+ const operation = parseOp(operationString);
63
+ const queryPlan = buildPlan(operation);
64
+ return executePlan(queryPlan, operation, requestContext);
65
+ }
66
+
29
67
  function overrideResolversInService(
30
68
  serviceName: string,
31
69
  resolvers: GraphQLResolverMap,
@@ -40,10 +78,7 @@ describe('executeQueryPlan', () => {
40
78
  return jest.spyOn(entitiesField, 'resolve');
41
79
  }
42
80
 
43
- let serviceMap: {
44
- [serviceName: string]: LocalGraphQLDataSource;
45
- };
46
- let schema: GraphQLSchema;
81
+ let schema: Schema;
47
82
  let queryPlanner: QueryPlanner;
48
83
  beforeEach(() => {
49
84
  expect(
@@ -52,15 +87,13 @@ describe('executeQueryPlan', () => {
52
87
  ).not.toThrow();
53
88
  });
54
89
 
55
- function buildRequestContext(
56
- variables: VariableValues = {},
57
- ): GraphQLRequestContext {
90
+ function buildRequestContext(): GraphQLRequestContext {
58
91
  // @ts-ignore
59
92
  return {
60
93
  cache: undefined as any,
61
94
  context: {},
62
95
  request: {
63
- variables,
96
+ variables: {},
64
97
  },
65
98
  };
66
99
  }
@@ -78,21 +111,7 @@ describe('executeQueryPlan', () => {
78
111
  }
79
112
  `;
80
113
 
81
- const operationDocument = gql(operationString);
82
-
83
- const operationContext = buildOperationContext({
84
- schema,
85
- operationDocument,
86
- });
87
-
88
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
89
-
90
- const response = await executeQueryPlan(
91
- queryPlan,
92
- serviceMap,
93
- buildRequestContext(),
94
- operationContext,
95
- );
114
+ const response = await executeOperation(operationString);
96
115
 
97
116
  expect(response).not.toHaveProperty('errors');
98
117
  });
@@ -117,21 +136,7 @@ describe('executeQueryPlan', () => {
117
136
  }
118
137
  `;
119
138
 
120
- const operationDocument = gql(operationString);
121
-
122
- const operationContext = buildOperationContext({
123
- schema,
124
- operationDocument,
125
- });
126
-
127
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
128
-
129
- const response = await executeQueryPlan(
130
- queryPlan,
131
- serviceMap,
132
- buildRequestContext(),
133
- operationContext,
134
- );
139
+ const response = await executeOperation(operationString);
135
140
 
136
141
  expect(response).toHaveProperty('data.me', null);
137
142
  expect(response).toHaveProperty(
@@ -176,21 +181,7 @@ describe('executeQueryPlan', () => {
176
181
  }
177
182
  `;
178
183
 
179
- const operationDocument = gql(operationString);
180
-
181
- const operationContext = buildOperationContext({
182
- schema,
183
- operationDocument,
184
- });
185
-
186
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
187
-
188
- const response = await executeQueryPlan(
189
- queryPlan,
190
- serviceMap,
191
- buildRequestContext(),
192
- operationContext,
193
- );
184
+ const response = await executeOperation(operationString);
194
185
 
195
186
  expect(accountsEntitiesResolverSpy).not.toHaveBeenCalled();
196
187
 
@@ -253,21 +244,7 @@ describe('executeQueryPlan', () => {
253
244
  }
254
245
  `;
255
246
 
256
- const operationDocument = gql(operationString);
257
-
258
- const operationContext = buildOperationContext({
259
- schema,
260
- operationDocument,
261
- });
262
-
263
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
264
-
265
- const response = await executeQueryPlan(
266
- queryPlan,
267
- serviceMap,
268
- buildRequestContext(),
269
- operationContext,
270
- );
247
+ const response = await executeOperation(operationString);
271
248
 
272
249
  expect(accountsEntitiesResolverSpy).toHaveBeenCalledTimes(1);
273
250
  expect(accountsEntitiesResolverSpy.mock.calls[0][1]).toEqual({
@@ -354,21 +331,7 @@ describe('executeQueryPlan', () => {
354
331
  }
355
332
  `;
356
333
 
357
- const operationDocument = gql(operationString);
358
-
359
- const operationContext = buildOperationContext({
360
- schema,
361
- operationDocument,
362
- });
363
-
364
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
365
-
366
- const response = await executeQueryPlan(
367
- queryPlan,
368
- serviceMap,
369
- buildRequestContext(),
370
- operationContext,
371
- );
334
+ const response = await executeOperation(operationString);
372
335
 
373
336
  expect(reviewsEntitiesResolverSpy).not.toHaveBeenCalled();
374
337
 
@@ -402,21 +365,7 @@ describe('executeQueryPlan', () => {
402
365
  }
403
366
  `;
404
367
 
405
- const operationDocument = gql(operationString);
406
-
407
- const operationContext = buildOperationContext({
408
- schema,
409
- operationDocument,
410
- });
411
-
412
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
413
-
414
- const response = await executeQueryPlan(
415
- queryPlan,
416
- serviceMap,
417
- buildRequestContext(),
418
- operationContext,
419
- );
368
+ const response = await executeOperation(operationString);
420
369
 
421
370
  expect(reviewsEntitiesResolverSpy).toHaveBeenCalledTimes(1);
422
371
  expect(reviewsEntitiesResolverSpy.mock.calls[0][1]).toEqual({
@@ -476,21 +425,7 @@ describe('executeQueryPlan', () => {
476
425
  }
477
426
  `;
478
427
 
479
- const operationDocument = gql(operationString);
480
-
481
- const operationContext = buildOperationContext({
482
- schema,
483
- operationDocument,
484
- });
485
-
486
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
487
-
488
- const response = await executeQueryPlan(
489
- queryPlan,
490
- serviceMap,
491
- buildRequestContext(),
492
- operationContext,
493
- );
428
+ const response = await executeOperation(operationString);
494
429
 
495
430
  expect(response).toHaveProperty('data.me', null);
496
431
  expect(response).toHaveProperty('data.topReviews', expect.any(Array));
@@ -513,21 +448,7 @@ describe('executeQueryPlan', () => {
513
448
  }
514
449
  `;
515
450
 
516
- const operationDocument = gql(operationString);
517
-
518
- const operationContext = buildOperationContext({
519
- schema,
520
- operationDocument,
521
- });
522
-
523
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
524
-
525
- const response = await executeQueryPlan(
526
- queryPlan,
527
- serviceMap,
528
- buildRequestContext(),
529
- operationContext,
530
- );
451
+ const response = await executeOperation(operationString);
531
452
 
532
453
  expect(response).toHaveProperty('data.me', null);
533
454
  expect(response).toHaveProperty('data.topReviews', expect.any(Array));
@@ -549,21 +470,7 @@ describe('executeQueryPlan', () => {
549
470
  }
550
471
  `;
551
472
 
552
- const operationDocument = gql(operationString);
553
-
554
- const operationContext = buildOperationContext({
555
- schema,
556
- operationDocument,
557
- });
558
-
559
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
560
-
561
- const response = await executeQueryPlan(
562
- queryPlan,
563
- serviceMap,
564
- buildRequestContext(),
565
- operationContext,
566
- );
473
+ const response = await executeOperation(operationString);
567
474
 
568
475
  expect(response.data).toMatchInlineSnapshot(`
569
476
  Object {
@@ -642,24 +549,9 @@ describe('executeQueryPlan', () => {
642
549
  }
643
550
  `;
644
551
 
645
- const operationDocument = gql(operationString);
646
-
647
- const operationContext = buildOperationContext({
648
- schema,
649
- operationDocument,
650
- });
651
-
652
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
653
-
654
552
  const requestContext = buildRequestContext();
655
553
  requestContext.request.variables = { first: 3 };
656
-
657
- const response = await executeQueryPlan(
658
- queryPlan,
659
- serviceMap,
660
- requestContext,
661
- operationContext,
662
- );
554
+ const response = await executeOperation(operationString, requestContext);
663
555
 
664
556
  expect(response.data).toMatchInlineSnapshot(`
665
557
  Object {
@@ -741,24 +633,9 @@ describe('executeQueryPlan', () => {
741
633
  }
742
634
  `;
743
635
 
744
- const operationDocument = gql(operationString);
745
-
746
- const operationContext = buildOperationContext({
747
- schema,
748
- operationDocument,
749
- });
750
-
751
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
752
-
753
636
  const requestContext = buildRequestContext();
754
637
  requestContext.request.variables = { locale: 'en-US' };
755
-
756
- const response = await executeQueryPlan(
757
- queryPlan,
758
- serviceMap,
759
- requestContext,
760
- operationContext,
761
- );
638
+ const response = await executeOperation(operationString, requestContext);
762
639
 
763
640
  expect(response.data).toMatchInlineSnapshot(`
764
641
  Object {
@@ -819,20 +696,7 @@ describe('executeQueryPlan', () => {
819
696
  });
820
697
 
821
698
  it('can execute an introspection query', async () => {
822
- const operationContext = buildOperationContext({
823
- schema,
824
- operationDocument: gql`
825
- ${getIntrospectionQuery()}
826
- `,
827
- });
828
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
829
-
830
- const response = await executeQueryPlan(
831
- queryPlan,
832
- serviceMap,
833
- buildRequestContext(),
834
- operationContext,
835
- );
699
+ const response = await executeOperation(`${getIntrospectionQuery()}`);
836
700
 
837
701
  expect(response.data).toHaveProperty('__schema');
838
702
  expect(response.errors).toBeUndefined();
@@ -849,21 +713,7 @@ describe('executeQueryPlan', () => {
849
713
  }
850
714
  `;
851
715
 
852
- const operationDocument = gql(operationString);
853
-
854
- const operationContext = buildOperationContext({
855
- schema,
856
- operationDocument,
857
- });
858
-
859
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
860
-
861
- const response = await executeQueryPlan(
862
- queryPlan,
863
- serviceMap,
864
- buildRequestContext(),
865
- operationContext,
866
- );
716
+ const response = await executeOperation(operationString);
867
717
 
868
718
  expect(response.data).toMatchInlineSnapshot(`
869
719
  Object {
@@ -893,21 +743,7 @@ describe('executeQueryPlan', () => {
893
743
  }
894
744
  `;
895
745
 
896
- const operationDocument = gql(operationString);
897
-
898
- const operationContext = buildOperationContext({
899
- schema,
900
- operationDocument,
901
- });
902
-
903
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
904
-
905
- const response = await executeQueryPlan(
906
- queryPlan,
907
- serviceMap,
908
- buildRequestContext(),
909
- operationContext,
910
- );
746
+ const response = await executeOperation(operationString);
911
747
 
912
748
  expect(response.data).toMatchInlineSnapshot(`
913
749
  Object {
@@ -948,21 +784,7 @@ describe('executeQueryPlan', () => {
948
784
  }
949
785
  `;
950
786
 
951
- const operationDocument = gql(operationString);
952
-
953
- const operationContext = buildOperationContext({
954
- schema,
955
- operationDocument,
956
- });
957
-
958
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
959
-
960
- const response = await executeQueryPlan(
961
- queryPlan,
962
- serviceMap,
963
- buildRequestContext(),
964
- operationContext,
965
- );
787
+ const response = await executeOperation(operationString);
966
788
 
967
789
  expect(response.data).toMatchInlineSnapshot(`
968
790
  Object {
@@ -990,21 +812,7 @@ describe('executeQueryPlan', () => {
990
812
  }
991
813
  `;
992
814
 
993
- const operationDocument = gql(operationString);
994
-
995
- const operationContext = buildOperationContext({
996
- schema,
997
- operationDocument,
998
- });
999
-
1000
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1001
-
1002
- const response = await executeQueryPlan(
1003
- queryPlan,
1004
- serviceMap,
1005
- buildRequestContext(),
1006
- operationContext,
1007
- );
815
+ const response = await executeOperation(operationString);
1008
816
 
1009
817
  expect(response.data).toMatchInlineSnapshot(`
1010
818
  Object {
@@ -1045,21 +853,7 @@ describe('executeQueryPlan', () => {
1045
853
  }
1046
854
  `;
1047
855
 
1048
- const operationDocument = gql(operationString);
1049
-
1050
- const operationContext = buildOperationContext({
1051
- schema,
1052
- operationDocument,
1053
- });
1054
-
1055
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1056
-
1057
- const response = await executeQueryPlan(
1058
- queryPlan,
1059
- serviceMap,
1060
- buildRequestContext(),
1061
- operationContext,
1062
- );
856
+ const response = await executeOperation(operationString);
1063
857
 
1064
858
  expect(response.errors).toMatchInlineSnapshot(`undefined`);
1065
859
 
@@ -1094,21 +888,7 @@ describe('executeQueryPlan', () => {
1094
888
  }
1095
889
  `;
1096
890
 
1097
- const operationDocument = gql(operationString);
1098
-
1099
- const operationContext = buildOperationContext({
1100
- schema,
1101
- operationDocument,
1102
- });
1103
-
1104
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1105
-
1106
- const response = await executeQueryPlan(
1107
- queryPlan,
1108
- serviceMap,
1109
- buildRequestContext(),
1110
- operationContext,
1111
- );
891
+ const response = await executeOperation(operationString);
1112
892
 
1113
893
  expect(response.errors).toBeUndefined();
1114
894
 
@@ -1137,21 +917,7 @@ describe('executeQueryPlan', () => {
1137
917
  }
1138
918
  `;
1139
919
 
1140
- const operationDocument = gql(operationString);
1141
-
1142
- const operationContext = buildOperationContext({
1143
- schema,
1144
- operationDocument,
1145
- });
1146
-
1147
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1148
-
1149
- const response = await executeQueryPlan(
1150
- queryPlan,
1151
- serviceMap,
1152
- buildRequestContext(),
1153
- operationContext,
1154
- );
920
+ const response = await executeOperation(operationString);
1155
921
 
1156
922
  expect(response.data).toMatchInlineSnapshot(`
1157
923
  Object {
@@ -1183,23 +949,12 @@ describe('executeQueryPlan', () => {
1183
949
 
1184
950
  describe('@inaccessible', () => {
1185
951
  it(`should not include @inaccessible fields in introspection`, async () => {
1186
- schema = buildComposedSchema(superGraphWithInaccessible);
1187
- queryPlanner = new QueryPlanner(schema);
952
+ schema = buildSchemaFromAST(superGraphWithInaccessible);
1188
953
 
1189
- const operationContext = buildOperationContext({
1190
- schema,
1191
- operationDocument: gql`
1192
- ${getIntrospectionQuery()}
1193
- `,
1194
- });
1195
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1196
-
1197
- const response = await executeQueryPlan(
1198
- queryPlan,
1199
- serviceMap,
1200
- buildRequestContext(),
1201
- operationContext,
1202
- );
954
+ const operation = parseOp(`${getIntrospectionQuery()}`, schema);
955
+ queryPlanner = new QueryPlanner(schema);
956
+ const queryPlan = queryPlanner.buildQueryPlan(operation);
957
+ const response = await executePlan(queryPlan, operation, undefined, schema);
1203
958
 
1204
959
  expect(response.data).toHaveProperty('__schema');
1205
960
  expect(response.errors).toBeUndefined();
@@ -1225,24 +980,14 @@ describe('executeQueryPlan', () => {
1225
980
  }
1226
981
  `;
1227
982
 
1228
- const operationDocument = gql(operationString);
1229
-
1230
- schema = buildComposedSchema(superGraphWithInaccessible);
983
+ const operation = parseOp(operationString);
1231
984
 
1232
- const operationContext = buildOperationContext({
1233
- schema,
1234
- operationDocument,
1235
- });
985
+ schema = buildSchemaFromAST(superGraphWithInaccessible);
1236
986
 
1237
987
  queryPlanner = new QueryPlanner(schema);
1238
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
988
+ const queryPlan = queryPlanner.buildQueryPlan(operation);
1239
989
 
1240
- const response = await executeQueryPlan(
1241
- queryPlan,
1242
- serviceMap,
1243
- buildRequestContext(),
1244
- operationContext,
1245
- );
990
+ const response = await executePlan(queryPlan, operation, undefined, schema);
1246
991
 
1247
992
  expect(response.data).toMatchInlineSnapshot(`
1248
993
  Object {
@@ -1349,26 +1094,16 @@ describe('executeQueryPlan', () => {
1349
1094
  }
1350
1095
  `;
1351
1096
 
1352
- const operationDocument = gql(operationString);
1097
+ const operation = parseOp(operationString);
1353
1098
 
1354
1099
  // Vehicle ID #1 is a "Car" type.
1355
1100
  // This supergraph marks the "Car" type as inaccessible.
1356
- schema = buildComposedSchema(superGraphWithInaccessible);
1357
-
1358
- const operationContext = buildOperationContext({
1359
- schema,
1360
- operationDocument,
1361
- });
1101
+ schema = buildSchemaFromAST(superGraphWithInaccessible);
1362
1102
 
1363
1103
  queryPlanner = new QueryPlanner(schema);
1364
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1104
+ const queryPlan = queryPlanner.buildQueryPlan(operation);
1365
1105
 
1366
- const response = await executeQueryPlan(
1367
- queryPlan,
1368
- serviceMap,
1369
- buildRequestContext(),
1370
- operationContext,
1371
- );
1106
+ const response = await executePlan(queryPlan, operation, undefined, schema);
1372
1107
 
1373
1108
  expect(response.data?.vehicle).toEqual(null);
1374
1109
  expect(response.errors).toBeUndefined();
@@ -1381,259 +1116,530 @@ describe('executeQueryPlan', () => {
1381
1116
  });
1382
1117
  });
1383
1118
 
1384
- describe('top-level @skip / @include usages', () => {
1385
- it(`top-level skip never calls reviews service (using literals)`, async () => {
1386
- const operationDocument = gql`
1387
- query {
1388
- topReviews @skip(if: true) {
1389
- body
1390
- }
1391
- }
1392
- `;
1119
+ it('can query other subgraphs when the Query type is the type of a field', async () => {
1120
+ const s1 = gql`
1121
+ type Query {
1122
+ getA: A
1123
+ }
1393
1124
 
1394
- const operationContext = buildOperationContext({
1395
- schema,
1396
- operationDocument,
1397
- });
1125
+ type A {
1126
+ q: Query
1127
+ }
1128
+ `;
1398
1129
 
1399
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1130
+ const s2 = gql`
1131
+ type Query {
1132
+ one: Int
1133
+ }
1134
+ `;
1400
1135
 
1401
- const response = await executeQueryPlan(
1402
- queryPlan,
1403
- serviceMap,
1404
- buildRequestContext(),
1405
- operationContext,
1406
- );
1136
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([
1137
+ { name: 'S1', typeDefs: s1 },
1138
+ { name: 'S2', typeDefs: s2 }
1139
+ ]);
1140
+
1141
+ addResolversToSchema(serviceMap['S1'].schema, {
1142
+ Query: {
1143
+ getA() {
1144
+ return {
1145
+ getA: {
1146
+ q: null
1147
+ }
1148
+ };
1149
+ },
1150
+ },
1151
+ A: {
1152
+ q() {
1153
+ return Object.create(null);
1154
+ }
1155
+ }
1156
+ });
1407
1157
 
1408
- expect(response.data).toMatchInlineSnapshot(`Object {}`);
1409
- expect(queryPlan).not.toCallService('reviews');
1158
+ addResolversToSchema(serviceMap['S2'].schema, {
1159
+ Query: {
1160
+ one() {
1161
+ return 1;
1162
+ },
1163
+ },
1410
1164
  });
1411
1165
 
1412
- it(`top-level skip never calls reviews service (using variables)`, async () => {
1413
- const operationDocument = gql`
1414
- query TopReviews($shouldSkip: Boolean!) {
1415
- topReviews @skip(if: $shouldSkip) {
1416
- body
1166
+ const operation = parseOp(`
1167
+ query {
1168
+ getA {
1169
+ q {
1170
+ one
1417
1171
  }
1418
1172
  }
1419
- `;
1173
+ }
1174
+ `, schema);
1420
1175
 
1421
- const operationContext = buildOperationContext({
1422
- schema,
1423
- operationDocument,
1424
- });
1176
+ const queryPlan = buildPlan(operation, queryPlanner);
1425
1177
 
1426
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1178
+ expect(queryPlan).toMatchInlineSnapshot(`
1179
+ QueryPlan {
1180
+ Sequence {
1181
+ Fetch(service: "S1") {
1182
+ {
1183
+ getA {
1184
+ q {
1185
+ __typename
1186
+ }
1187
+ }
1188
+ }
1189
+ },
1190
+ Flatten(path: "getA.q") {
1191
+ Fetch(service: "S2") {
1192
+ {
1193
+ ... on Query {
1194
+ one
1195
+ }
1196
+ }
1197
+ },
1198
+ },
1199
+ },
1200
+ }
1201
+ `);
1427
1202
 
1428
- const variables = { shouldSkip: true };
1429
- const response = await executeQueryPlan(
1430
- queryPlan,
1431
- serviceMap,
1432
- buildRequestContext(variables),
1433
- operationContext,
1434
- );
1203
+ const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
1435
1204
 
1436
- expect(response.data).toMatchInlineSnapshot(`Object {}`);
1437
- expect({ queryPlan, variables }).not.toCallService('reviews');
1438
- });
1205
+ expect(response.data).toMatchInlineSnapshot(`
1206
+ Object {
1207
+ "getA": Object {
1208
+ "q": Object {
1209
+ "one": 1,
1210
+ },
1211
+ },
1212
+ }
1213
+ `);
1214
+ })
1439
1215
 
1440
- it(`call to service isn't skipped unless all top-level fields are skipped`, async () => {
1441
- const operationDocument = gql`
1442
- query {
1443
- user(id: "1") @skip(if: true) {
1444
- username
1445
- }
1446
- me @include(if: true) {
1447
- username
1448
- }
1216
+ describe('interfaces on interfaces', () => {
1217
+ it('can execute queries on an interface only implemented by other interfaces', async () => {
1218
+ const s1 = gql`
1219
+ type Query {
1220
+ allValues: [TopInterface!]!
1221
+ }
1222
+
1223
+ interface TopInterface {
1224
+ a: Int
1225
+ }
1226
+
1227
+ interface SubInterface1 implements TopInterface {
1228
+ a: Int
1229
+ b: String
1230
+ }
1231
+
1232
+ interface SubInterface2 implements TopInterface {
1233
+ a: Int
1234
+ c: String
1235
+ }
1236
+
1237
+ type T1 implements SubInterface1 & TopInterface {
1238
+ a: Int
1239
+ b: String
1240
+ }
1241
+
1242
+ type T2 implements SubInterface1 & TopInterface @key(fields: "b") {
1243
+ a: Int @external
1244
+ b: String
1245
+ }
1246
+
1247
+ type T3 implements SubInterface2 & TopInterface {
1248
+ a: Int
1249
+ c: String
1250
+ }
1251
+
1252
+ type T4 implements SubInterface1 & SubInterface2 & TopInterface @key(fields: "a") {
1253
+ a: Int
1254
+ b: String @external
1255
+ c: String @external
1449
1256
  }
1450
1257
  `;
1451
1258
 
1452
- const operationContext = buildOperationContext({
1453
- schema,
1454
- operationDocument,
1455
- });
1259
+ const s2 = gql`
1260
+ type T2 @key(fields: "b") {
1261
+ a: Int
1262
+ b: String
1263
+ }
1456
1264
 
1457
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1265
+ type T4 @key(fields: "a") {
1266
+ a: Int
1267
+ b: String
1268
+ c: String
1269
+ }
1270
+ `;
1458
1271
 
1459
- const response = await executeQueryPlan(
1460
- queryPlan,
1461
- serviceMap,
1462
- buildRequestContext(),
1463
- operationContext,
1464
- );
1272
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([
1273
+ { name: 'S1', typeDefs: s1 },
1274
+ { name: 'S2', typeDefs: s2 }
1275
+ ]);
1276
+
1277
+ const t1s_s1: any[] = [{ __typename: 'T1', a: 1, b: 'T1_v1'}, {__typename: 'T1', a: 2, b: 'T1_v2'}];
1278
+ const t2s_s1: any[] = [{__typename: 'T2', b: 'k1'}, {__typename: 'T2', b: 'k2'}];
1279
+ const t3s_s1: any[] = [{__typename: 'T3', a: 42, c: 'T3_v1'}];
1280
+ const t4s_s1: any[] = [{__typename: 'T4', a: 0}, {__typename: 'T4', a: 10}, {__typename: 'T4', a: 20}];
1281
+
1282
+ const t2s_s2 = new Map<string, {a: number, b: string}>();
1283
+ t2s_s2.set('k1', {a: 12 , b: 'k1'});
1284
+ t2s_s2.set('k2', {a: 24 , b: 'k2'});
1285
+
1286
+ const t4s_s2 = new Map<number, {a: number, b: string, c: string}>();
1287
+ t4s_s2.set(0, {a: 0, b: 'b_0', c: 'c_0'});
1288
+ t4s_s2.set(10, {a: 10, b: 'b_10', c: 'c_10'});
1289
+ t4s_s2.set(20, {a: 20, b: 'b_20', c: 'c_20'});
1290
+
1291
+ addResolversToSchema(serviceMap['S1'].schema, {
1292
+ Query: {
1293
+ allValues() {
1294
+ return t1s_s1.concat(t2s_s1).concat(t3s_s1).concat(t4s_s1);
1295
+ },
1296
+ },
1297
+ });
1465
1298
 
1466
- expect(response.data).toMatchObject({
1467
- me: {
1468
- username: '@ada',
1299
+ addResolversToSchema(serviceMap['S2'].schema, {
1300
+ T2: {
1301
+ __resolveReference(ref) {
1302
+ return t2s_s2.get(ref.b);
1303
+ }
1304
+ },
1305
+ T4: {
1306
+ __resolveReference(ref) {
1307
+ return t4s_s2.get(ref.a);
1308
+ }
1469
1309
  },
1470
1310
  });
1471
- expect(queryPlan).toCallService('accounts');
1472
- });
1473
1311
 
1474
- it(`call to service is skipped when all top-level fields are skipped`, async () => {
1475
- const operationDocument = gql`
1312
+ let operation = parseOp(`
1476
1313
  query {
1477
- user(id: "1") @skip(if: true) {
1478
- username
1479
- }
1480
- me @include(if: false) {
1481
- username
1314
+ allValues {
1315
+ a
1316
+ ... on SubInterface1 {
1317
+ b
1318
+ }
1319
+ ... on SubInterface2 {
1320
+ c
1321
+ }
1482
1322
  }
1483
1323
  }
1484
- `;
1324
+ `, schema);
1485
1325
 
1486
- const operationContext = buildOperationContext({
1487
- schema,
1488
- operationDocument,
1489
- });
1326
+ let queryPlan = buildPlan(operation, queryPlanner);
1490
1327
 
1491
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1328
+ expect(queryPlan).toMatchInlineSnapshot(`
1329
+ QueryPlan {
1330
+ Sequence {
1331
+ Fetch(service: "S1") {
1332
+ {
1333
+ allValues {
1334
+ __typename
1335
+ ... on T1 {
1336
+ a
1337
+ b
1338
+ }
1339
+ ... on T2 {
1340
+ __typename
1341
+ b
1342
+ }
1343
+ ... on T3 {
1344
+ a
1345
+ c
1346
+ }
1347
+ ... on T4 {
1348
+ __typename
1349
+ a
1350
+ }
1351
+ }
1352
+ }
1353
+ },
1354
+ Flatten(path: "allValues.@") {
1355
+ Fetch(service: "S2") {
1356
+ {
1357
+ ... on T2 {
1358
+ __typename
1359
+ b
1360
+ }
1361
+ ... on T4 {
1362
+ __typename
1363
+ a
1364
+ }
1365
+ } =>
1366
+ {
1367
+ ... on T2 {
1368
+ a
1369
+ }
1370
+ ... on T4 {
1371
+ b
1372
+ c
1373
+ }
1374
+ }
1375
+ },
1376
+ },
1377
+ },
1378
+ }
1379
+ `);
1492
1380
 
1493
- const response = await executeQueryPlan(
1494
- queryPlan,
1495
- serviceMap,
1496
- buildRequestContext(),
1497
- operationContext,
1498
- );
1381
+ let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
1499
1382
 
1500
- expect(response.data).toMatchObject({});
1501
- expect(queryPlan).not.toCallService('accounts');
1383
+ expect(response.data).toMatchInlineSnapshot(`
1384
+ Object {
1385
+ "allValues": Array [
1386
+ Object {
1387
+ "a": 1,
1388
+ "b": "T1_v1",
1389
+ },
1390
+ Object {
1391
+ "a": 2,
1392
+ "b": "T1_v2",
1393
+ },
1394
+ Object {
1395
+ "a": 12,
1396
+ "b": "k1",
1397
+ },
1398
+ Object {
1399
+ "a": 24,
1400
+ "b": "k2",
1401
+ },
1402
+ Object {
1403
+ "a": 42,
1404
+ "c": "T3_v1",
1405
+ },
1406
+ Object {
1407
+ "a": 0,
1408
+ "b": "b_0",
1409
+ "c": "c_0",
1410
+ },
1411
+ Object {
1412
+ "a": 10,
1413
+ "b": "b_10",
1414
+ "c": "c_10",
1415
+ },
1416
+ Object {
1417
+ "a": 20,
1418
+ "b": "b_20",
1419
+ "c": "c_20",
1420
+ },
1421
+ ],
1422
+ }
1423
+ `);
1502
1424
  });
1503
1425
 
1504
- describe('@skip and @include combinations', () => {
1505
- it(`include: false, skip: false`, async () => {
1506
- const operationDocument = gql`
1507
- query TopReviews($shouldInclude: Boolean!, $shouldSkip: Boolean!) {
1508
- topReviews @include(if: $shouldInclude) @skip(if: $shouldSkip) {
1509
- body
1510
- }
1511
- }
1512
- `;
1426
+ it('does not type explode when it does not need to', async () => {
1427
+ // Fairly similar example than the previous one, but ensure field `a` don't need
1428
+ // type explosion and unsure it isn't type-exploded.
1429
+ const s1 = gql`
1430
+ type Query {
1431
+ allValues: [TopInterface!]!
1432
+ }
1513
1433
 
1514
- const operationContext = buildOperationContext({
1515
- schema,
1516
- operationDocument,
1517
- });
1434
+ interface TopInterface {
1435
+ a: Int
1436
+ }
1518
1437
 
1519
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1438
+ interface SubInterface1 implements TopInterface {
1439
+ a: Int
1440
+ b: String
1441
+ }
1520
1442
 
1521
- const variables = { shouldInclude: false, shouldSkip: false };
1522
- const response = await executeQueryPlan(
1523
- queryPlan,
1524
- serviceMap,
1525
- buildRequestContext(variables),
1526
- operationContext,
1527
- );
1443
+ interface SubInterface2 implements TopInterface {
1444
+ a: Int
1445
+ c: String
1446
+ }
1528
1447
 
1529
- expect(response.data).toMatchObject({});
1530
- expect({ queryPlan, variables }).not.toCallService('reviews');
1531
- });
1448
+ type T1 implements SubInterface1 & TopInterface {
1449
+ a: Int
1450
+ b: String
1451
+ }
1532
1452
 
1533
- it(`include: false, skip: true`, async () => {
1534
- const operationDocument = gql`
1535
- query TopReviews($shouldInclude: Boolean!, $shouldSkip: Boolean!) {
1536
- topReviews @include(if: $shouldInclude) @skip(if: $shouldSkip) {
1537
- body
1538
- }
1539
- }
1540
- `;
1453
+ type T2 implements SubInterface1 & TopInterface @key(fields: "a") {
1454
+ a: Int
1455
+ b: String @external
1456
+ }
1541
1457
 
1542
- const operationContext = buildOperationContext({
1543
- schema,
1544
- operationDocument,
1545
- });
1458
+ type T3 implements SubInterface2 & TopInterface {
1459
+ a: Int
1460
+ c: String
1461
+ }
1546
1462
 
1547
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1463
+ type T4 implements SubInterface1 & SubInterface2 & TopInterface @key(fields: "a") {
1464
+ a: Int
1465
+ b: String @external
1466
+ c: String @external
1467
+ }
1468
+ `;
1548
1469
 
1549
- const variables = { shouldInclude: false, shouldSkip: true };
1550
- const response = await executeQueryPlan(
1551
- queryPlan,
1552
- serviceMap,
1553
- buildRequestContext(variables),
1554
- operationContext,
1555
- );
1470
+ const s2 = gql`
1471
+ type T2 @key(fields: "a") {
1472
+ a: Int
1473
+ b: String
1474
+ }
1556
1475
 
1557
- expect(response.data).toMatchObject({});
1558
- expect({ queryPlan, variables }).not.toCallService('reviews');
1476
+ type T4 @key(fields: "a") {
1477
+ a: Int
1478
+ b: String
1479
+ c: String
1480
+ }
1481
+ `;
1482
+
1483
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([
1484
+ { name: 'S1', typeDefs: s1 },
1485
+ { name: 'S2', typeDefs: s2 }
1486
+ ]);
1487
+
1488
+ const t1s_s1: any[] = [{ __typename: 'T1', a: 1, b: 'T1_v1'}, {__typename: 'T1', a: 2, b: 'T1_v2'}];
1489
+ const t2s_s1: any[] = [{__typename: 'T2', a: 12}, {__typename: 'T2', a: 24}];
1490
+ const t3s_s1: any[] = [{__typename: 'T3', a: 42, c: 'T3_v1'}];
1491
+ const t4s_s1: any[] = [{__typename: 'T4', a: 0}, {__typename: 'T4', a: 10}, {__typename: 'T4', a: 20}];
1492
+
1493
+ const t2s_s2 = new Map<number, {a: number, b: string}>();
1494
+ t2s_s2.set(12, {a: 12 , b: 'k1'});
1495
+ t2s_s2.set(24, {a: 24 , b: 'k2'});
1496
+
1497
+ const t4s_s2 = new Map<number, {a: number, b: string, c: string}>();
1498
+ t4s_s2.set(0, {a: 0, b: 'b_0', c: 'c_0'});
1499
+ t4s_s2.set(10, {a: 10, b: 'b_10', c: 'c_10'});
1500
+ t4s_s2.set(20, {a: 20, b: 'b_20', c: 'c_20'});
1501
+
1502
+ addResolversToSchema(serviceMap['S1'].schema, {
1503
+ Query: {
1504
+ allValues() {
1505
+ return t1s_s1.concat(t2s_s1).concat(t3s_s1).concat(t4s_s1);
1506
+ },
1507
+ },
1559
1508
  });
1560
1509
 
1561
- it(`include: true, skip: false`, async () => {
1562
- const operationDocument = gql`
1563
- query TopReviews($shouldInclude: Boolean!, $shouldSkip: Boolean!) {
1564
- topReviews(first: 2) @include(if: $shouldInclude) @skip(if: $shouldSkip) {
1565
- body
1566
- }
1510
+ addResolversToSchema(serviceMap['S2'].schema, {
1511
+ T2: {
1512
+ __resolveReference(ref) {
1513
+ return t2s_s2.get(ref.b);
1567
1514
  }
1568
- `;
1569
-
1570
- const operationContext = buildOperationContext({
1571
- schema,
1572
- operationDocument,
1573
- });
1515
+ },
1516
+ T4: {
1517
+ __resolveReference(ref) {
1518
+ return t4s_s2.get(ref.a);
1519
+ }
1520
+ },
1521
+ });
1574
1522
 
1575
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1523
+ let operation = parseOp(`
1524
+ query {
1525
+ allValues {
1526
+ a
1527
+ }
1528
+ }
1529
+ `, schema);
1576
1530
 
1577
- const variables = { shouldInclude: true, shouldSkip: false };
1578
- const response = await executeQueryPlan(
1579
- queryPlan,
1580
- serviceMap,
1581
- buildRequestContext(variables),
1582
- operationContext,
1583
- );
1531
+ let queryPlan = buildPlan(operation, queryPlanner);
1584
1532
 
1585
- expect(response.data).toMatchObject({
1586
- topReviews: [
1533
+ expect(queryPlan).toMatchInlineSnapshot(`
1534
+ QueryPlan {
1535
+ Fetch(service: "S1") {
1587
1536
  {
1588
- body: 'Love it!',
1537
+ allValues {
1538
+ __typename
1539
+ a
1540
+ }
1541
+ }
1542
+ },
1543
+ }
1544
+ `);
1545
+
1546
+ let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
1547
+ expect(response.data).toMatchInlineSnapshot(`
1548
+ Object {
1549
+ "allValues": Array [
1550
+ Object {
1551
+ "a": 1,
1589
1552
  },
1590
- {
1591
- body: 'Too expensive.',
1553
+ Object {
1554
+ "a": 2,
1555
+ },
1556
+ Object {
1557
+ "a": 12,
1558
+ },
1559
+ Object {
1560
+ "a": 24,
1561
+ },
1562
+ Object {
1563
+ "a": 42,
1564
+ },
1565
+ Object {
1566
+ "a": 0,
1567
+ },
1568
+ Object {
1569
+ "a": 10,
1570
+ },
1571
+ Object {
1572
+ "a": 20,
1592
1573
  },
1593
1574
  ],
1594
- });
1595
- expect({ queryPlan, variables }).toCallService('reviews');
1596
- });
1575
+ }
1576
+ `);
1597
1577
 
1598
- it(`include: true, skip: true`, async () => {
1599
- const operationDocument = gql`
1600
- query TopReviews($shouldInclude: Boolean!, $shouldSkip: Boolean!) {
1601
- topReviews @include(if: $shouldInclude) @skip(if: $shouldSkip) {
1602
- body
1578
+ operation = parseOp(`
1579
+ query {
1580
+ allValues {
1581
+ ... on SubInterface1 {
1582
+ a
1603
1583
  }
1604
1584
  }
1605
- `;
1606
-
1607
- const operationContext = buildOperationContext({
1608
- schema,
1609
- operationDocument,
1610
- });
1611
-
1612
- const queryPlan = queryPlanner.buildQueryPlan(operationContext);
1613
-
1614
- const variables = { shouldInclude: true, shouldSkip: true };
1615
- const response = await executeQueryPlan(
1616
- queryPlan,
1617
- serviceMap,
1618
- buildRequestContext(variables),
1619
- operationContext,
1620
- );
1621
-
1622
- expect(response.data).toMatchObject({});
1623
- expect(queryPlan).toMatchInlineSnapshot(`
1624
- QueryPlan {
1625
- Fetch(service: "reviews", inclusionConditions: [{ include: "shouldInclude", skip: "shouldSkip" }]) {
1626
- {
1627
- topReviews @include(if: $shouldInclude) @skip(if: $shouldSkip) {
1628
- body
1585
+ }
1586
+ `, schema);
1587
+
1588
+ queryPlan = buildPlan(operation, queryPlanner);
1589
+
1590
+ // TODO: we're actually type-exploding in this case because currently, as soon as we need to type-explode, we do
1591
+ // so into all the runtime types, while here it could make sense to only type-explode into the direct sub-types=
1592
+ // (the sub-interfaces). We should fix this (but it's only sub-optimal, not incorrect).
1593
+ expect(queryPlan).toMatchInlineSnapshot(`
1594
+ QueryPlan {
1595
+ Fetch(service: "S1") {
1596
+ {
1597
+ allValues {
1598
+ __typename
1599
+ ... on T1 {
1600
+ a
1601
+ }
1602
+ ... on T2 {
1603
+ a
1604
+ }
1605
+ ... on T4 {
1606
+ a
1629
1607
  }
1630
1608
  }
1609
+ }
1610
+ },
1611
+ }
1612
+ `);
1613
+
1614
+ response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
1615
+ expect(response.data).toMatchInlineSnapshot(`
1616
+ Object {
1617
+ "allValues": Array [
1618
+ Object {
1619
+ "a": 1,
1631
1620
  },
1632
- }
1621
+ Object {
1622
+ "a": 2,
1623
+ },
1624
+ Object {
1625
+ "a": 12,
1626
+ },
1627
+ Object {
1628
+ "a": 24,
1629
+ },
1630
+ Object {},
1631
+ Object {
1632
+ "a": 0,
1633
+ },
1634
+ Object {
1635
+ "a": 10,
1636
+ },
1637
+ Object {
1638
+ "a": 20,
1639
+ },
1640
+ ],
1641
+ }
1633
1642
  `);
1634
-
1635
- expect({ queryPlan, variables }).not.toCallService('reviews');
1636
- });
1637
1643
  });
1638
1644
  });
1639
1645
  });