@apollo/gateway 0.26.0-alpha.2 → 0.26.3

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 (50) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/config.d.ts +2 -2
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  6. package/dist/datasources/RemoteGraphQLDataSource.js +6 -5
  7. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  8. package/dist/executeQueryPlan.d.ts +1 -1
  9. package/dist/executeQueryPlan.d.ts.map +1 -1
  10. package/dist/index.d.ts +4 -15
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +15 -20
  13. package/dist/index.js.map +1 -1
  14. package/dist/loadServicesFromRemoteEndpoint.d.ts +8 -8
  15. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
  16. package/dist/loadServicesFromRemoteEndpoint.js +3 -3
  17. package/dist/loadServicesFromRemoteEndpoint.js.map +1 -1
  18. package/dist/operationContext.d.ts +17 -0
  19. package/dist/operationContext.d.ts.map +1 -0
  20. package/dist/{buildQueryPlan.js → operationContext.js} +3 -19
  21. package/dist/operationContext.js.map +1 -0
  22. package/dist/utilities/assert.d.ts +2 -0
  23. package/dist/utilities/assert.d.ts.map +1 -0
  24. package/dist/utilities/assert.js +10 -0
  25. package/dist/utilities/assert.js.map +1 -0
  26. package/dist/utilities/graphql.d.ts +0 -1
  27. package/dist/utilities/graphql.d.ts.map +1 -1
  28. package/dist/utilities/graphql.js +6 -9
  29. package/dist/utilities/graphql.js.map +1 -1
  30. package/package.json +8 -8
  31. package/src/__tests__/buildQueryPlan.test.ts +28 -76
  32. package/src/__tests__/executeQueryPlan.test.ts +22 -58
  33. package/src/__tests__/execution-utils.ts +8 -9
  34. package/src/__tests__/gateway/queryPlanCache.test.ts +3 -6
  35. package/src/__tests__/integration/configuration.test.ts +96 -0
  36. package/src/__tests__/integration/nockMocks.ts +1 -1
  37. package/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +5 -1
  38. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +4 -4
  39. package/src/__tests__/queryPlanCucumber.test.ts +4 -8
  40. package/src/config.ts +8 -6
  41. package/src/datasources/RemoteGraphQLDataSource.ts +7 -6
  42. package/src/executeQueryPlan.ts +1 -1
  43. package/src/index.ts +16 -33
  44. package/src/loadServicesFromRemoteEndpoint.ts +12 -10
  45. package/src/{buildQueryPlan.ts → operationContext.ts} +6 -35
  46. package/src/utilities/assert.ts +14 -0
  47. package/src/utilities/graphql.ts +16 -9
  48. package/dist/buildQueryPlan.d.ts +0 -17
  49. package/dist/buildQueryPlan.d.ts.map +0 -1
  50. package/dist/buildQueryPlan.js.map +0 -1
@@ -12,18 +12,17 @@ import {
12
12
  } from '@apollo/federation';
13
13
 
14
14
  import {
15
- buildQueryPlan,
16
15
  executeQueryPlan,
17
16
  buildOperationContext,
18
17
  } from '@apollo/gateway';
19
- import { QueryPlan } from '@apollo/query-planner';
18
+ import { buildComposedSchema, QueryPlanner, QueryPlan } from '@apollo/query-planner';
20
19
  import { LocalGraphQLDataSource } from '../datasources/LocalGraphQLDataSource';
21
20
  import { mergeDeep } from 'apollo-utilities';
22
21
 
23
22
  import { queryPlanSerializer, astSerializer } from 'apollo-federation-integration-testsuite';
24
23
  import gql from 'graphql-tag';
25
24
  import { fixtures } from 'apollo-federation-integration-testsuite';
26
- import { getQueryPlanner } from '@apollo/query-planner';
25
+ import { parse } from 'graphql';
27
26
 
28
27
  const prettyFormat = require('pretty-format');
29
28
 
@@ -56,16 +55,14 @@ export async function execute(
56
55
  }),
57
56
  );
58
57
 
59
- const { schema, queryPlannerPointer } = getFederatedTestingSchema(services);
58
+ const { schema, queryPlanner } = getFederatedTestingSchema(services);
60
59
 
61
60
  const operationContext = buildOperationContext({
62
61
  schema,
63
62
  operationDocument: gql`${request.query}`,
64
- operationString: request.query!,
65
- queryPlannerPointer,
66
63
  });
67
64
 
68
- const queryPlan = buildQueryPlan(operationContext);
65
+ const queryPlan = queryPlanner.buildQueryPlan(operationContext);
69
66
 
70
67
  const result = await executeQueryPlan(
71
68
  queryPlan,
@@ -107,9 +104,11 @@ export function getFederatedTestingSchema(services: ServiceDefinitionModule[] =
107
104
  throw new GraphQLSchemaValidationError(compositionResult.errors);
108
105
  }
109
106
 
110
- const queryPlannerPointer = getQueryPlanner(compositionResult.supergraphSdl);
107
+ const schema = buildComposedSchema(parse(compositionResult.supergraphSdl))
111
108
 
112
- return { serviceMap, schema: compositionResult.schema, queryPlannerPointer };
109
+ const queryPlanner = new QueryPlanner(schema);
110
+
111
+ return { serviceMap, schema, queryPlanner };
113
112
  }
114
113
 
115
114
  export function getTestingSupergraphSdl(services: typeof fixtures = fixtures) {
@@ -5,12 +5,9 @@ import { buildFederatedSchema } from '@apollo/federation';
5
5
  import { LocalGraphQLDataSource } from '../../datasources/LocalGraphQLDataSource';
6
6
  import { ApolloGateway } from '../../';
7
7
  import { fixtures } from 'apollo-federation-integration-testsuite';
8
-
8
+ import { QueryPlanner } from '@apollo/query-planner';
9
9
  it('caches the query plan for a request', async () => {
10
- const planner = require('../../buildQueryPlan');
11
- const originalPlanner = planner.buildQueryPlan;
12
-
13
- planner.buildQueryPlan = jest.fn(originalPlanner);
10
+ const buildQueryPlanSpy = jest.spyOn(QueryPlanner.prototype, 'buildQueryPlan');
14
11
 
15
12
  const gateway = new ApolloGateway({
16
13
  localServiceList: fixtures,
@@ -51,7 +48,7 @@ it('caches the query plan for a request', async () => {
51
48
  });
52
49
 
53
50
  expect(result.data).toEqual(secondResult.data);
54
- expect(planner.buildQueryPlan).toHaveBeenCalledTimes(1);
51
+ expect(buildQueryPlanSpy).toHaveBeenCalledTimes(1);
55
52
  });
56
53
 
57
54
  it('supports multiple operations and operationName', async () => {
@@ -1,4 +1,5 @@
1
1
  import gql from 'graphql-tag';
2
+ import http from 'http';
2
3
  import mockedEnv from 'mocked-env';
3
4
  import { Logger } from 'apollo-server-types';
4
5
  import { ApolloGateway } from '../..';
@@ -204,6 +205,101 @@ describe('gateway config / env behavior', () => {
204
205
  }
205
206
  });
206
207
 
208
+ describe('introspection headers', () => {
209
+ it('should allow not passing introspectionHeaders', async () => {
210
+ const receivedHeaders = jest.fn();
211
+ const nock = mockSdlQuerySuccess(service);
212
+ nock.on('request', (req: http.ClientRequest) =>
213
+ receivedHeaders(req.getHeaders()),
214
+ );
215
+
216
+ gateway = new ApolloGateway({
217
+ serviceList: [{ name: 'accounts', url: service.url }],
218
+ });
219
+
220
+ await gateway.load(mockApolloConfig);
221
+
222
+ expect(receivedHeaders).toHaveBeenCalledWith(
223
+ expect.objectContaining({
224
+ host: 'localhost:4001',
225
+ }),
226
+ );
227
+ });
228
+
229
+ it('should use static headers', async () => {
230
+ const receivedHeaders = jest.fn();
231
+ const nock = mockSdlQuerySuccess(service);
232
+ nock.on('request', (req: http.ClientRequest) =>
233
+ receivedHeaders(req.getHeaders()),
234
+ );
235
+
236
+ gateway = new ApolloGateway({
237
+ serviceList: [{ name: 'accounts', url: service.url }],
238
+ introspectionHeaders: {
239
+ Authorization: 'Bearer static',
240
+ },
241
+ });
242
+
243
+ await gateway.load(mockApolloConfig);
244
+
245
+ expect(receivedHeaders).toHaveBeenCalledWith(
246
+ expect.objectContaining({
247
+ authorization: ['Bearer static'],
248
+ }),
249
+ );
250
+ });
251
+
252
+ it('should use dynamic async headers', async () => {
253
+ const receivedHeaders = jest.fn();
254
+ const nock = mockSdlQuerySuccess(service);
255
+ nock.on('request', (req: http.ClientRequest) =>
256
+ receivedHeaders(req.getHeaders()),
257
+ );
258
+
259
+ gateway = new ApolloGateway({
260
+ serviceList: [{ name: 'accounts', url: service.url }],
261
+ introspectionHeaders: async ({ name }) => ({
262
+ Authorization: 'Bearer dynamic-async',
263
+ 'X-Service-Name': name,
264
+ }),
265
+ });
266
+
267
+ await gateway.load(mockApolloConfig);
268
+
269
+ expect(receivedHeaders).toHaveBeenCalledWith(
270
+ expect.objectContaining({
271
+ authorization: ['Bearer dynamic-async'],
272
+ 'x-service-name': ['accounts'],
273
+ }),
274
+ );
275
+ });
276
+
277
+ it('should use dynamic non-async headers', async () => {
278
+ const receivedHeaders = jest.fn();
279
+ const nock = mockSdlQuerySuccess(service);
280
+ nock.on('request', (req: http.ClientRequest) =>
281
+ receivedHeaders(req.getHeaders()),
282
+ );
283
+
284
+ gateway = new ApolloGateway({
285
+ serviceList: [{ name: 'accounts', url: service.url }],
286
+ introspectionHeaders: ({ name }) => ({
287
+ Authorization: 'Bearer dynamic-sync',
288
+ 'X-Service-Name': name,
289
+ }),
290
+ });
291
+
292
+ await gateway.load(mockApolloConfig);
293
+
294
+ expect(receivedHeaders).toHaveBeenCalledWith(
295
+ expect.objectContaining({
296
+ authorization: ['Bearer dynamic-sync'],
297
+ 'x-service-name': ['accounts'],
298
+ }),
299
+ );
300
+ });
301
+ });
302
+
207
303
  // TODO(trevor:cloudconfig): this behavior will be updated
208
304
  describe('schema config delivery endpoint configuration', () => {
209
305
  it('A code config overrides the env variable', async () => {
@@ -28,7 +28,7 @@ function mockSdlQuery({ url }: MockService) {
28
28
  }
29
29
 
30
30
  export function mockSdlQuerySuccess(service: MockService) {
31
- mockSdlQuery(service).reply(200, {
31
+ return mockSdlQuery(service).reply(200, {
32
32
  data: { _service: { sdl: print(service.typeDefs) } },
33
33
  });
34
34
  }
@@ -10,6 +10,7 @@ describe('getServiceDefinitionsFromRemoteEndpoint', () => {
10
10
  getServiceDefinitionsFromRemoteEndpoint({
11
11
  serviceList,
12
12
  serviceSdlCache,
13
+ getServiceIntrospectionHeaders: async () => ({})
13
14
  }),
14
15
  ).rejects.toThrowError(
15
16
  "Tried to load schema for 'test' but no 'url' was specified.",
@@ -30,7 +31,10 @@ describe('getServiceDefinitionsFromRemoteEndpoint', () => {
30
31
  getServiceDefinitionsFromRemoteEndpoint({
31
32
  serviceList,
32
33
  serviceSdlCache,
34
+ getServiceIntrospectionHeaders: async () => ({}),
33
35
  }),
34
- ).rejects.toThrowError(/^Couldn't load service definitions for "test" at http:\/\/host-which-better-not-resolve\/graphql: request to http:\/\/host-which-better-not-resolve\/graphql failed, reason: getaddrinfo (ENOTFOUND|EAI_AGAIN)/);
36
+ ).rejects.toThrowError(
37
+ /^Couldn't load service definitions for "test" at http:\/\/host-which-better-not-resolve\/graphql: request to http:\/\/host-which-better-not-resolve\/graphql failed, reason: getaddrinfo (ENOTFOUND|EAI_AGAIN)/,
38
+ );
35
39
  });
36
40
  });
@@ -74,7 +74,7 @@ describe('loadSupergraphSdlFromStorage', () => {
74
74
  price: String @join__field(graph: PRODUCT)
75
75
  details: ProductDetailsBook @join__field(graph: PRODUCT)
76
76
  reviews: [Review] @join__field(graph: REVIEWS)
77
- relatedReviews: [Review!]! @join__field(graph: REVIEWS, requires: \\"similarBooks { isbn }\\")
77
+ relatedReviews: [Review!]! @join__field(graph: REVIEWS, requires: \\"similarBooks{isbn}\\")
78
78
  }
79
79
 
80
80
  union Brand = Ikea | Amazon
@@ -250,7 +250,7 @@ describe('loadSupergraphSdlFromStorage', () => {
250
250
  type User
251
251
  @join__owner(graph: ACCOUNTS)
252
252
  @join__type(graph: ACCOUNTS, key: \\"id\\")
253
- @join__type(graph: ACCOUNTS, key: \\"username name { first last }\\")
253
+ @join__type(graph: ACCOUNTS, key: \\"username name{first last}\\")
254
254
  @join__type(graph: INVENTORY, key: \\"id\\")
255
255
  @join__type(graph: PRODUCT, key: \\"id\\")
256
256
  @join__type(graph: REVIEWS, key: \\"id\\")
@@ -261,12 +261,12 @@ describe('loadSupergraphSdlFromStorage', () => {
261
261
  birthDate(locale: String): String @join__field(graph: ACCOUNTS)
262
262
  account: AccountType @join__field(graph: ACCOUNTS)
263
263
  metadata: [UserMetadata] @join__field(graph: ACCOUNTS)
264
- goodDescription: Boolean @join__field(graph: INVENTORY, requires: \\"metadata { description }\\")
264
+ goodDescription: Boolean @join__field(graph: INVENTORY, requires: \\"metadata{description}\\")
265
265
  vehicle: Vehicle @join__field(graph: PRODUCT)
266
266
  thing: Thing @join__field(graph: PRODUCT)
267
267
  reviews: [Review] @join__field(graph: REVIEWS)
268
268
  numberOfReviews: Int! @join__field(graph: REVIEWS)
269
- goodAddress: Boolean @join__field(graph: REVIEWS, requires: \\"metadata { address }\\")
269
+ goodAddress: Boolean @join__field(graph: REVIEWS, requires: \\"metadata{address}\\")
270
270
  }
271
271
 
272
272
  type UserMetadata {
@@ -2,8 +2,8 @@ import gql from 'graphql-tag';
2
2
  import { defineFeature, loadFeature } from 'jest-cucumber';
3
3
  import { DocumentNode } from 'graphql';
4
4
 
5
- import { QueryPlan } from '@apollo/query-planner';
6
- import { buildQueryPlan, buildOperationContext, BuildQueryPlanOptions } from '../buildQueryPlan';
5
+ import { QueryPlan, BuildQueryPlanOptions } from '@apollo/query-planner';
6
+ import { buildOperationContext } from '../operationContext';
7
7
  import { getFederatedTestingSchema } from './execution-utils';
8
8
 
9
9
  const buildQueryPlanFeature = loadFeature(
@@ -20,17 +20,15 @@ features.forEach((feature) => {
20
20
  feature.scenarios.forEach((scenario) => {
21
21
  test(scenario.title, async ({ given, when, then }) => {
22
22
  let operationDocument: DocumentNode;
23
- let operationString: string;
24
23
  let queryPlan: QueryPlan;
25
24
  let options: BuildQueryPlanOptions = { autoFragmentization: false };
26
25
 
27
26
  // throws on composition errors
28
- const { schema, queryPlannerPointer } = getFederatedTestingSchema();
27
+ const { schema, queryPlanner } = getFederatedTestingSchema();
29
28
 
30
29
  const givenQuery = () => {
31
30
  given(/^query$/im, (operation: string) => {
32
31
  operationDocument = gql(operation);
33
- operationString = operation;
34
32
  })
35
33
  }
36
34
 
@@ -42,12 +40,10 @@ features.forEach((feature) => {
42
40
 
43
41
  const thenQueryPlanShouldBe = () => {
44
42
  then(/^query plan$/i, (expectedQueryPlan: string) => {
45
- queryPlan = buildQueryPlan(
43
+ queryPlan = queryPlanner.buildQueryPlan(
46
44
  buildOperationContext({
47
45
  schema,
48
46
  operationDocument,
49
- operationString,
50
- queryPlannerPointer,
51
47
  }),
52
48
  options
53
49
  );
package/src/config.ts CHANGED
@@ -5,7 +5,7 @@ import { GraphQLRequestContextExecutionDidStart, Logger } from "apollo-server-ty
5
5
  import { ServiceDefinition } from "@apollo/federation";
6
6
  import { GraphQLDataSource } from './datasources/types';
7
7
  import { QueryPlan } from '@apollo/query-planner';
8
- import { OperationContext } from './';
8
+ import { OperationContext } from './operationContext';
9
9
  import { ServiceMap } from './executeQueryPlan';
10
10
 
11
11
  export type ServiceEndpointDefinition = Pick<ServiceDefinition, 'name' | 'url'>;
@@ -127,17 +127,19 @@ interface GatewayConfigBase {
127
127
  fetcher?: typeof fetch;
128
128
  serviceHealthCheck?: boolean;
129
129
  /**
130
- * @deprecated This configuration option shouldn't be used unless by
131
- * recommendation from Apollo staff. This behavior will be
132
- * defaulted in a future release and this option will strictly be
133
- * used as an override.
130
+ * Enable an upcoming mechanism for fetching a graph’s schema and
131
+ * configuration when using managed federation. This mechanism is currently in
132
+ * a preview state and should not be used in production graphs.
133
+ * See https://go.apollo.dev/g/supergraph-preview for details.
134
134
  */
135
135
  experimental_schemaConfigDeliveryEndpoint?: null | string;
136
136
  }
137
137
 
138
138
  export interface RemoteGatewayConfig extends GatewayConfigBase {
139
139
  serviceList: ServiceEndpointDefinition[];
140
- introspectionHeaders?: HeadersInit;
140
+ introspectionHeaders?:
141
+ | HeadersInit
142
+ | ((service: ServiceEndpointDefinition) => Promise<HeadersInit> | HeadersInit);
141
143
  }
142
144
 
143
145
  // TODO(trevor:cloudconfig): This type goes away
@@ -78,10 +78,6 @@ export class RemoteGraphQLDataSource<TContext extends Record<string, any> = Reco
78
78
  throw new Error("Missing query");
79
79
  }
80
80
 
81
- const apqHash = createSHA('sha256')
82
- .update(request.query)
83
- .digest('hex');
84
-
85
81
  const { query, ...requestWithoutQuery } = request;
86
82
 
87
83
  const respond = (response: GraphQLResponse, request: GraphQLRequest) =>
@@ -90,6 +86,10 @@ export class RemoteGraphQLDataSource<TContext extends Record<string, any> = Reco
90
86
  : response;
91
87
 
92
88
  if (this.apq) {
89
+ const apqHash = createSHA('sha256')
90
+ .update(request.query)
91
+ .digest('hex');
92
+
93
93
  // Take the original extensions and extend them with
94
94
  // the necessary "extensions" for APQ handshaking.
95
95
  requestWithoutQuery.extensions = {
@@ -140,9 +140,10 @@ export class RemoteGraphQLDataSource<TContext extends Record<string, any> = Reco
140
140
  // being transmitted. Instead, we want those to be used to indicate what
141
141
  // we're accessing (e.g. url) and what we access it with (e.g. headers).
142
142
  const { http, ...requestWithoutHttp } = request;
143
+ const stringifiedRequestWithoutHttp = JSON.stringify(requestWithoutHttp);
143
144
  const fetchRequest = new Request(http.url, {
144
145
  ...http,
145
- body: JSON.stringify(requestWithoutHttp),
146
+ body: stringifiedRequestWithoutHttp,
146
147
  });
147
148
 
148
149
  let fetchResponse: Response | undefined;
@@ -152,7 +153,7 @@ export class RemoteGraphQLDataSource<TContext extends Record<string, any> = Reco
152
153
  // Use the fetcher's `Request` implementation for compatibility
153
154
  fetchResponse = await this.fetcher(http.url, {
154
155
  ...http,
155
- body: JSON.stringify(requestWithoutHttp)
156
+ body: stringifiedRequestWithoutHttp,
156
157
  });
157
158
 
158
159
  if (!fetchResponse.ok) {
@@ -14,7 +14,7 @@ import {
14
14
  import { Trace, google } from 'apollo-reporting-protobuf';
15
15
  import { defaultRootOperationNameLookup } from '@apollo/federation';
16
16
  import { GraphQLDataSource } from './datasources/types';
17
- import { OperationContext } from './';
17
+ import { OperationContext } from './operationContext';
18
18
  import {
19
19
  FetchNode,
20
20
  PlanNode,
package/src/index.ts CHANGED
@@ -18,8 +18,6 @@ import {
18
18
  VariableDefinitionNode,
19
19
  DocumentNode,
20
20
  print,
21
- FragmentDefinitionNode,
22
- OperationDefinitionNode,
23
21
  parse,
24
22
  } from 'graphql';
25
23
  import {
@@ -29,7 +27,7 @@ import {
29
27
  } from '@apollo/federation';
30
28
  import loglevel from 'loglevel';
31
29
 
32
- import { buildQueryPlan, buildOperationContext } from './buildQueryPlan';
30
+ import { buildOperationContext, OperationContext } from './operationContext';
33
31
  import {
34
32
  executeQueryPlan,
35
33
  ServiceMap,
@@ -43,7 +41,7 @@ import { getVariableValues } from 'graphql/execution/values';
43
41
  import fetcher from 'make-fetch-happen';
44
42
  import { HttpRequestCache } from './cache';
45
43
  import { fetch } from 'apollo-server-env';
46
- import { getQueryPlanner, QueryPlannerPointer, QueryPlan, prettyFormatQueryPlan } from '@apollo/query-planner';
44
+ import { QueryPlanner, QueryPlan, prettyFormatQueryPlan } from '@apollo/query-planner';
47
45
  import {
48
46
  ServiceEndpointDefinition,
49
47
  Experimental_DidFailCompositionCallback,
@@ -73,16 +71,6 @@ import { loadSupergraphSdlFromStorage } from './loadSupergraphSdlFromStorage';
73
71
  import { getServiceDefinitionsFromStorage } from './legacyLoadServicesFromStorage';
74
72
  import { buildComposedSchema } from '@apollo/query-planner';
75
73
 
76
- type FragmentMap = { [fragmentName: string]: FragmentDefinitionNode };
77
-
78
- export type OperationContext = {
79
- schema: GraphQLSchema;
80
- operation: OperationDefinitionNode;
81
- fragments: FragmentMap;
82
- queryPlannerPointer: QueryPlannerPointer;
83
- operationString: string;
84
- };
85
-
86
74
  type DataSourceMap = {
87
75
  [serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
88
76
  };
@@ -160,7 +148,7 @@ export class ApolloGateway implements GraphQLService {
160
148
  private compositionMetadata?: CompositionMetadata;
161
149
  private serviceSdlCache = new Map<string, string>();
162
150
  private warnedStates: WarnedStates = Object.create(null);
163
- private queryPlannerPointer?: QueryPlannerPointer;
151
+ private queryPlanner?: QueryPlanner;
164
152
  private parsedSupergraphSdl?: DocumentNode;
165
153
  private fetcher: typeof fetch;
166
154
  private compositionId?: string;
@@ -367,7 +355,7 @@ export class ApolloGateway implements GraphQLService {
367
355
 
368
356
  this.maybeWarnOnConflictingConfig();
369
357
 
370
- // Handles initial assignment of `this.schema`, `this.queryPlannerPointer`
358
+ // Handles initial assignment of `this.schema`, `this.queryPlanner`
371
359
  isStaticConfig(this.config)
372
360
  ? this.loadStatic(this.config)
373
361
  : await this.loadDynamic(unrefTimer);
@@ -404,7 +392,7 @@ export class ApolloGateway implements GraphQLService {
404
392
  this.schema = schema;
405
393
  // TODO(trevor): #580 redundant parse
406
394
  this.parsedSupergraphSdl = parse(supergraphSdl);
407
- this.queryPlannerPointer = getQueryPlanner(supergraphSdl);
395
+ this.queryPlanner = new QueryPlanner(schema);
408
396
  this.state = { phase: 'loaded' };
409
397
  }
410
398
 
@@ -482,7 +470,7 @@ export class ApolloGateway implements GraphQLService {
482
470
  );
483
471
  } else {
484
472
  this.schema = schema;
485
- this.queryPlannerPointer = getQueryPlanner(supergraphSdl);
473
+ this.queryPlanner = new QueryPlanner(schema);
486
474
 
487
475
  // Notify the schema listeners of the updated schema
488
476
  try {
@@ -555,7 +543,7 @@ export class ApolloGateway implements GraphQLService {
555
543
  );
556
544
  } else {
557
545
  this.schema = schema;
558
- this.queryPlannerPointer = getQueryPlanner(supergraphSdl);
546
+ this.queryPlanner = new QueryPlanner(schema);
559
547
 
560
548
  // Notify the schema listeners of the updated schema
561
549
  try {
@@ -707,12 +695,7 @@ export class ApolloGateway implements GraphQLService {
707
695
  throw Error(`Couldn't find graph map in composed schema`);
708
696
  }
709
697
 
710
- const serviceList = Object.values(graphMap).map(graph => ({
711
- name: graph.name,
712
- url: graph.url
713
- }))
714
-
715
- return serviceList;
698
+ return Array.from(graphMap.values());
716
699
  }
717
700
 
718
701
  private createSchemaFromSupergraphSdl(supergraphSdl: string) {
@@ -868,9 +851,11 @@ export class ApolloGateway implements GraphQLService {
868
851
 
869
852
  return getServiceDefinitionsFromRemoteEndpoint({
870
853
  serviceList,
871
- ...(config.introspectionHeaders
872
- ? { headers: config.introspectionHeaders }
873
- : {}),
854
+ async getServiceIntrospectionHeaders(service) {
855
+ return typeof config.introspectionHeaders === 'function'
856
+ ? await config.introspectionHeaders(service)
857
+ : config.introspectionHeaders;
858
+ },
874
859
  serviceSdlCache: this.serviceSdlCache,
875
860
  });
876
861
  }
@@ -943,13 +928,11 @@ export class ApolloGateway implements GraphQLService {
943
928
  public executor = async <TContext>(
944
929
  requestContext: GraphQLRequestContextExecutionDidStart<TContext>,
945
930
  ): Promise<GraphQLExecutionResult> => {
946
- const { request, document, queryHash, source } = requestContext;
931
+ const { request, document, queryHash } = requestContext;
947
932
  const queryPlanStoreKey = queryHash + (request.operationName || '');
948
933
  const operationContext = buildOperationContext({
949
934
  schema: this.schema!,
950
935
  operationDocument: document,
951
- operationString: source,
952
- queryPlannerPointer: this.queryPlannerPointer!,
953
936
  operationName: request.operationName,
954
937
  });
955
938
 
@@ -970,7 +953,8 @@ export class ApolloGateway implements GraphQLService {
970
953
  }
971
954
 
972
955
  if (!queryPlan) {
973
- queryPlan = buildQueryPlan(operationContext, {
956
+ // TODO(#631): Can we be sure the query planner has been initialized here?
957
+ queryPlan = this.queryPlanner!.buildQueryPlan(operationContext, {
974
958
  autoFragmentization: Boolean(
975
959
  this.config.experimental_autoFragmentization,
976
960
  ),
@@ -1175,7 +1159,6 @@ class UnreachableCaseError extends Error {
1175
1159
  }
1176
1160
 
1177
1161
  export {
1178
- buildQueryPlan,
1179
1162
  executeQueryPlan,
1180
1163
  buildOperationContext,
1181
1164
  ServiceMap,
@@ -3,20 +3,22 @@ import { parse } from 'graphql';
3
3
  import { Headers, HeadersInit } from 'node-fetch';
4
4
  import { GraphQLDataSource } from './datasources/types';
5
5
  import { SERVICE_DEFINITION_QUERY } from './';
6
- import { CompositionUpdate } from './config';
6
+ import { CompositionUpdate, ServiceEndpointDefinition } from './config';
7
7
  import { ServiceDefinition } from '@apollo/federation';
8
8
 
9
+ type Service = ServiceEndpointDefinition & {
10
+ dataSource: GraphQLDataSource;
11
+ };
12
+
9
13
  export async function getServiceDefinitionsFromRemoteEndpoint({
10
14
  serviceList,
11
- headers = {},
15
+ getServiceIntrospectionHeaders,
12
16
  serviceSdlCache,
13
17
  }: {
14
- serviceList: {
15
- name: string;
16
- url?: string;
17
- dataSource: GraphQLDataSource;
18
- }[];
19
- headers?: HeadersInit;
18
+ serviceList: Service[];
19
+ getServiceIntrospectionHeaders: (
20
+ service: ServiceEndpointDefinition,
21
+ ) => Promise<HeadersInit | undefined>;
20
22
  serviceSdlCache: Map<string, string>;
21
23
  }): Promise<CompositionUpdate> {
22
24
  if (!serviceList || !serviceList.length) {
@@ -27,7 +29,7 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
27
29
 
28
30
  let isNewSchema = false;
29
31
  // for each service, fetch its introspection schema
30
- const promiseOfServiceList = serviceList.map(({ name, url, dataSource }) => {
32
+ const promiseOfServiceList = serviceList.map(async ({ name, url, dataSource }) => {
31
33
  if (!url) {
32
34
  throw new Error(
33
35
  `Tried to load schema for '${name}' but no 'url' was specified.`);
@@ -38,7 +40,7 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
38
40
  http: {
39
41
  url,
40
42
  method: 'POST',
41
- headers: new Headers(headers),
43
+ headers: new Headers(await getServiceIntrospectionHeaders({ name, url })),
42
44
  },
43
45
  };
44
46
 
@@ -5,41 +5,26 @@ import {
5
5
  GraphQLSchema,
6
6
  Kind,
7
7
  OperationDefinitionNode,
8
- print,
9
8
  } from 'graphql';
10
- import { OperationContext } from './';
11
- import { getQueryPlan, QueryPlan, QueryPlannerPointer } from '@apollo/query-planner';
12
9
 
13
- export interface BuildQueryPlanOptions {
14
- autoFragmentization: boolean;
15
- }
16
-
17
- export function buildQueryPlan(
18
- operationContext: OperationContext,
19
- options: BuildQueryPlanOptions = { autoFragmentization: false },
20
- ): QueryPlan {
10
+ type FragmentMap = { [fragmentName: string]: FragmentDefinitionNode };
21
11
 
22
- return getQueryPlan(
23
- operationContext.queryPlannerPointer,
24
- operationContext.operationString,
25
- options
26
- );
27
- }
12
+ export type OperationContext = {
13
+ schema: GraphQLSchema;
14
+ operation: OperationDefinitionNode;
15
+ fragments: FragmentMap;
16
+ };
28
17
 
29
18
  // Adapted from buildExecutionContext in graphql-js
30
19
  interface BuildOperationContextOptions {
31
20
  schema: GraphQLSchema;
32
21
  operationDocument: DocumentNode;
33
- operationString: string;
34
- queryPlannerPointer: QueryPlannerPointer;
35
22
  operationName?: string;
36
23
  };
37
24
 
38
25
  export function buildOperationContext({
39
26
  schema,
40
27
  operationDocument,
41
- operationString,
42
- queryPlannerPointer,
43
28
  operationName,
44
29
  }: BuildOperationContextOptions): OperationContext {
45
30
  let operation: OperationDefinitionNode | undefined;
@@ -77,23 +62,9 @@ export function buildOperationContext({
77
62
  }
78
63
  }
79
64
 
80
- // In the case of multiple operations specified (operationName presence validated above),
81
- // `operation` === the operation specified by `operationName`
82
- const trimmedOperationString = operationCount > 1
83
- ? print({
84
- kind: Kind.DOCUMENT,
85
- definitions: [
86
- operation,
87
- ...Object.values(fragments),
88
- ],
89
- })
90
- : operationString;
91
-
92
65
  return {
93
66
  schema,
94
67
  operation,
95
68
  fragments,
96
- queryPlannerPointer,
97
- operationString: trimmedOperationString
98
69
  };
99
70
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * For lack of a "home of federation utilities", this function is copy/pasted
3
+ * verbatim across the federation, gateway, and query-planner packages. Any changes
4
+ * made here should be reflected in the other two locations as well.
5
+ *
6
+ * @param condition
7
+ * @param message
8
+ * @throws {@link Error}
9
+ */
10
+ export function assert(condition: any, message: string): asserts condition {
11
+ if (!condition) {
12
+ throw new Error(message);
13
+ }
14
+ }