@apollo/gateway 2.0.0-alpha.2 → 2.0.0-alpha.6

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 (130) hide show
  1. package/dist/config.d.ts +43 -15
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +28 -18
  4. package/dist/config.js.map +1 -1
  5. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  6. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  7. package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
  8. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  9. package/dist/executeQueryPlan.d.ts.map +1 -1
  10. package/dist/executeQueryPlan.js +2 -2
  11. package/dist/executeQueryPlan.js.map +1 -1
  12. package/dist/index.d.ts +37 -22
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +199 -283
  15. package/dist/index.js.map +1 -1
  16. package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
  17. package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
  18. package/dist/schema-helper/addResolversToSchema.js +62 -0
  19. package/dist/schema-helper/addResolversToSchema.js.map +1 -0
  20. package/dist/schema-helper/error.d.ts +6 -0
  21. package/dist/schema-helper/error.d.ts.map +1 -0
  22. package/dist/schema-helper/error.js +14 -0
  23. package/dist/schema-helper/error.js.map +1 -0
  24. package/dist/schema-helper/index.d.ts +4 -0
  25. package/dist/schema-helper/index.d.ts.map +1 -0
  26. package/dist/schema-helper/index.js +16 -0
  27. package/dist/schema-helper/index.js.map +1 -0
  28. package/dist/schema-helper/resolverMap.d.ts +16 -0
  29. package/dist/schema-helper/resolverMap.d.ts.map +1 -0
  30. package/dist/schema-helper/resolverMap.js +3 -0
  31. package/dist/schema-helper/resolverMap.js.map +1 -0
  32. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
  33. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
  34. package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
  35. package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
  36. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
  37. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
  38. package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
  39. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
  40. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
  41. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
  42. package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
  43. package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
  44. package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
  45. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
  46. package/dist/supergraphManagers/LocalCompose/index.js +55 -0
  47. package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
  48. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +33 -0
  49. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
  50. package/dist/supergraphManagers/UplinkFetcher/index.js +98 -0
  51. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
  52. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +25 -0
  53. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  54. package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +40 -15
  55. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
  56. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +13 -0
  57. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
  58. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +85 -0
  59. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
  60. package/dist/supergraphManagers/index.d.ts +6 -0
  61. package/dist/supergraphManagers/index.d.ts.map +1 -0
  62. package/dist/supergraphManagers/index.js +14 -0
  63. package/dist/supergraphManagers/index.js.map +1 -0
  64. package/dist/utilities/createHash.d.ts +2 -0
  65. package/dist/utilities/createHash.d.ts.map +1 -0
  66. package/dist/utilities/createHash.js +15 -0
  67. package/dist/utilities/createHash.js.map +1 -0
  68. package/dist/utilities/isNodeLike.d.ts +3 -0
  69. package/dist/utilities/isNodeLike.d.ts.map +1 -0
  70. package/dist/utilities/isNodeLike.js +8 -0
  71. package/dist/utilities/isNodeLike.js.map +1 -0
  72. package/package.json +9 -7
  73. package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
  74. package/src/__tests__/build-query-plan.feature +52 -0
  75. package/src/__tests__/executeQueryPlan.test.ts +599 -1
  76. package/src/__tests__/execution-utils.ts +3 -3
  77. package/src/__tests__/gateway/buildService.test.ts +3 -3
  78. package/src/__tests__/gateway/endToEnd.test.ts +1 -1
  79. package/src/__tests__/gateway/executor.test.ts +1 -1
  80. package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -125
  81. package/src/__tests__/gateway/opentelemetry.test.ts +8 -4
  82. package/src/__tests__/gateway/queryPlanCache.test.ts +25 -12
  83. package/src/__tests__/gateway/reporting.test.ts +42 -13
  84. package/src/__tests__/gateway/supergraphSdl.test.ts +397 -0
  85. package/src/__tests__/integration/aliases.test.ts +9 -4
  86. package/src/__tests__/integration/configuration.test.ts +146 -9
  87. package/src/__tests__/integration/logger.test.ts +1 -1
  88. package/src/__tests__/integration/networkRequests.test.ts +99 -147
  89. package/src/__tests__/integration/nockMocks.ts +30 -16
  90. package/src/__tests__/nockAssertions.ts +20 -0
  91. package/src/config.ts +149 -38
  92. package/src/datasources/LocalGraphQLDataSource.ts +1 -1
  93. package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
  94. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
  95. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
  96. package/src/executeQueryPlan.ts +14 -2
  97. package/src/index.ts +325 -452
  98. package/src/schema-helper/addResolversToSchema.ts +83 -0
  99. package/src/schema-helper/error.ts +11 -0
  100. package/src/schema-helper/index.ts +3 -0
  101. package/src/schema-helper/resolverMap.ts +23 -0
  102. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
  103. package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +5 -5
  104. package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
  105. package/src/supergraphManagers/IntrospectAndCompose/index.ts +160 -0
  106. package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
  107. package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
  108. package/src/supergraphManagers/LocalCompose/index.ts +79 -0
  109. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +397 -0
  110. package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
  111. package/src/supergraphManagers/UplinkFetcher/index.ts +130 -0
  112. package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +68 -17
  113. package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
  114. package/src/supergraphManagers/index.ts +5 -0
  115. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +12 -9
  116. package/src/utilities/createHash.ts +10 -0
  117. package/src/utilities/isNodeLike.ts +11 -0
  118. package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
  119. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
  120. package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
  121. package/dist/loadSupergraphSdlFromStorage.d.ts +0 -13
  122. package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
  123. package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
  124. package/dist/outOfBandReporter.d.ts +0 -15
  125. package/dist/outOfBandReporter.d.ts.map +0 -1
  126. package/dist/outOfBandReporter.js +0 -88
  127. package/dist/outOfBandReporter.js.map +0 -1
  128. package/src/__tests__/gateway/composedSdl.test.ts +0 -44
  129. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -694
  130. package/src/outOfBandReporter.ts +0 -128
package/src/index.ts CHANGED
@@ -13,16 +13,16 @@ import {
13
13
  VariableDefinitionNode,
14
14
  } from 'graphql';
15
15
  import loglevel from 'loglevel';
16
-
17
16
  import { buildOperationContext, OperationContext } from './operationContext';
18
17
  import {
19
18
  executeQueryPlan,
20
19
  ServiceMap,
21
20
  defaultFieldResolverWithAliasSupport,
22
21
  } from './executeQueryPlan';
23
-
24
- import { getServiceDefinitionsFromRemoteEndpoint } from './loadServicesFromRemoteEndpoint';
25
- import { GraphQLDataSource, GraphQLDataSourceRequestKind } from './datasources/types';
22
+ import {
23
+ GraphQLDataSource,
24
+ GraphQLDataSourceRequestKind,
25
+ } from './datasources/types';
26
26
  import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource';
27
27
  import { getVariableValues } from 'graphql/execution/values';
28
28
  import fetcher from 'make-fetch-happen';
@@ -37,37 +37,30 @@ import {
37
37
  ServiceEndpointDefinition,
38
38
  Experimental_DidFailCompositionCallback,
39
39
  Experimental_DidResolveQueryPlanCallback,
40
- Experimental_DidUpdateCompositionCallback,
40
+ Experimental_DidUpdateSupergraphCallback,
41
41
  Experimental_UpdateComposition,
42
42
  CompositionInfo,
43
43
  GatewayConfig,
44
- StaticGatewayConfig,
45
- RemoteGatewayConfig,
46
- ManagedGatewayConfig,
47
44
  isManuallyManagedConfig,
48
45
  isLocalConfig,
49
- isRemoteConfig,
46
+ isServiceListConfig,
50
47
  isManagedConfig,
51
- isDynamicConfig,
52
- isStaticConfig,
53
- CompositionMetadata,
54
- isSupergraphSdlUpdate,
55
- isServiceDefinitionUpdate,
56
- ServiceDefinitionUpdate,
57
48
  SupergraphSdlUpdate,
58
- CompositionUpdate,
49
+ isManuallyManagedSupergraphSdlGatewayConfig,
50
+ ManagedGatewayConfig,
51
+ isStaticSupergraphSdlConfig,
52
+ SupergraphManager,
59
53
  } from './config';
60
- import { loadSupergraphSdlFromStorage } from './loadSupergraphSdlFromStorage';
61
54
  import { SpanStatusCode } from '@opentelemetry/api';
62
55
  import { OpenTelemetrySpanNames, tracer } from './utilities/opentelemetry';
63
-
56
+ import { createHash } from './utilities/createHash';
64
57
  import {
65
- buildSupergraphSchema,
66
- operationFromDocument,
67
- Schema,
68
- ServiceDefinition,
69
- } from '@apollo/federation-internals';
70
- import { composeServices } from '@apollo/composition'
58
+ IntrospectAndCompose,
59
+ UplinkFetcher,
60
+ LegacyFetcher,
61
+ LocalCompose,
62
+ } from './supergraphManagers';
63
+ import { buildSupergraphSchema, operationFromDocument, Schema, ServiceDefinition } from '@apollo/federation-internals';
71
64
 
72
65
  type DataSourceMap = {
73
66
  [serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
@@ -82,8 +75,6 @@ type WarnedStates = {
82
75
  remoteWithLocalConfig?: boolean;
83
76
  };
84
77
 
85
- export { ServiceDefinition } from '@apollo/federation-internals';
86
-
87
78
  export function getDefaultFetcher() {
88
79
  const { name, version } = require('../package.json');
89
80
  return fetcher.defaults({
@@ -119,12 +110,7 @@ type GatewayState =
119
110
  | { phase: 'loaded' }
120
111
  | { phase: 'stopping'; stoppingDonePromise: Promise<void> }
121
112
  | { phase: 'stopped' }
122
- | {
123
- phase: 'waiting to poll';
124
- pollWaitTimer: NodeJS.Timer;
125
- doneWaiting: () => void;
126
- }
127
- | { phase: 'polling'; pollingDonePromise: Promise<void> };
113
+ | { phase: 'updating schema' };
128
114
 
129
115
  // We want to be compatible with `load()` as called by both AS2 and AS3, so we
130
116
  // define its argument types ourselves instead of relying on imports.
@@ -175,9 +161,6 @@ export class ApolloGateway implements GraphQLService {
175
161
  coreSupergraphSdl: string;
176
162
  }) => void
177
163
  >();
178
- private serviceDefinitions: ServiceDefinition[] = [];
179
- private compositionMetadata?: CompositionMetadata;
180
- private serviceSdlCache = new Map<string, string>();
181
164
  private warnedStates: WarnedStates = Object.create(null);
182
165
  private queryPlanner?: QueryPlanner;
183
166
  private supergraphSdl?: string;
@@ -189,22 +172,12 @@ export class ApolloGateway implements GraphQLService {
189
172
  // The information made available here will give insight into the resulting
190
173
  // query plan and the inputs that generated it.
191
174
  private experimental_didResolveQueryPlan?: Experimental_DidResolveQueryPlanCallback;
192
- // Observe composition failures and the ServiceList that caused them. This
193
- // enables reporting any issues that occur during composition. Implementors
194
- // will be interested in addressing these immediately.
195
- private experimental_didFailComposition?: Experimental_DidFailCompositionCallback;
196
- // Used to communicated composition changes, and what definitions caused
197
- // those updates
198
- private experimental_didUpdateComposition?: Experimental_DidUpdateCompositionCallback;
199
- // Used for overriding the default service list fetcher. This should return
200
- // an array of ServiceDefinition. *This function must be awaited.*
201
- private updateServiceDefinitions: Experimental_UpdateComposition;
202
- // how often service defs should be loaded/updated (in ms)
203
- private experimental_pollInterval?: number;
204
- // Configure the endpoint by which gateway will access its precomposed schema.
205
- // * `string` means use that endpoint
206
- // * `undefined` means the gateway is not using managed federation
207
- private schemaConfigDeliveryEndpoint?: string;
175
+ // Used to communicate supergraph updates
176
+ private experimental_didUpdateSupergraph?: Experimental_DidUpdateSupergraphCallback;
177
+ // how often service defs should be loaded/updated
178
+ private pollIntervalInMs?: number;
179
+ // Functions to call during gateway cleanup (when stop() is called)
180
+ private toDispose: (() => Promise<void>)[] = [];
208
181
 
209
182
  constructor(config?: GatewayConfig) {
210
183
  this.config = {
@@ -224,45 +197,15 @@ export class ApolloGateway implements GraphQLService {
224
197
  // set up experimental observability callbacks and config settings
225
198
  this.experimental_didResolveQueryPlan =
226
199
  config?.experimental_didResolveQueryPlan;
227
- this.experimental_didFailComposition =
228
- config?.experimental_didFailComposition;
229
- this.experimental_didUpdateComposition =
230
- config?.experimental_didUpdateComposition;
231
-
232
- this.experimental_pollInterval = config?.experimental_pollInterval;
233
-
234
- // 1. If config is set to a `string`, use it
235
- // 2. If the env var is set, use that
236
- // 3. If config is `undefined`, use the default uplink URL
237
- if (isManagedConfig(this.config)) {
238
- const envEndpoint = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
239
- this.schemaConfigDeliveryEndpoint =
240
- this.config.schemaConfigDeliveryEndpoint ??
241
- envEndpoint ??
242
- 'https://uplink.api.apollographql.com/';
243
- }
200
+ this.experimental_didUpdateSupergraph =
201
+ config?.experimental_didUpdateSupergraph;
244
202
 
245
- if (isManuallyManagedConfig(this.config)) {
246
- // Use the provided updater function if provided by the user, else default
247
- if ('experimental_updateSupergraphSdl' in this.config) {
248
- this.updateServiceDefinitions =
249
- this.config.experimental_updateSupergraphSdl;
250
- } else if ('experimental_updateServiceDefinitions' in this.config) {
251
- this.updateServiceDefinitions =
252
- this.config.experimental_updateServiceDefinitions;
253
- } else {
254
- throw Error(
255
- 'Programming error: unexpected manual configuration provided',
256
- );
257
- }
258
- } else {
259
- this.updateServiceDefinitions = this.loadServiceDefinitions;
260
- }
203
+ this.pollIntervalInMs =
204
+ config?.pollIntervalInMs ?? config?.experimental_pollInterval;
261
205
 
262
- if (isDynamicConfig(this.config)) {
263
- this.issueDynamicWarningsIfApplicable();
264
- }
206
+ this.issueConfigurationWarningsIfApplicable();
265
207
 
208
+ this.logger.debug('Gateway successfully initialized (but not yet loaded)');
266
209
  this.state = { phase: 'initialized' };
267
210
  }
268
211
 
@@ -297,25 +240,26 @@ export class ApolloGateway implements GraphQLService {
297
240
  });
298
241
  }
299
242
 
300
- private issueDynamicWarningsIfApplicable() {
243
+ private issueConfigurationWarningsIfApplicable() {
301
244
  // Warn against a pollInterval of < 10s in managed mode and reset it to 10s
302
245
  if (
303
246
  isManagedConfig(this.config) &&
304
- this.config.experimental_pollInterval &&
305
- this.config.experimental_pollInterval < 10000
247
+ this.pollIntervalInMs &&
248
+ this.pollIntervalInMs < 10000
306
249
  ) {
307
- this.experimental_pollInterval = 10000;
250
+ this.pollIntervalInMs = 10000;
308
251
  this.logger.warn(
309
252
  'Polling Apollo services at a frequency of less than once per 10 ' +
310
253
  'seconds (10000) is disallowed. Instead, the minimum allowed ' +
311
254
  'pollInterval of 10000 will be used. Please reconfigure your ' +
312
- 'experimental_pollInterval accordingly. If this is problematic for ' +
255
+ '`pollIntervalInMs` accordingly. If this is problematic for ' +
313
256
  'your team, please contact support.',
314
257
  );
315
258
  }
316
259
 
317
260
  // Warn against using the pollInterval and a serviceList simultaneously
318
- if (this.config.experimental_pollInterval && isRemoteConfig(this.config)) {
261
+ // TODO(trevor:removeServiceList)
262
+ if (this.pollIntervalInMs && isServiceListConfig(this.config)) {
319
263
  this.logger.warn(
320
264
  'Polling running services is dangerous and not recommended in production. ' +
321
265
  'Polling should only be used against a registry. ' +
@@ -335,12 +279,26 @@ export class ApolloGateway implements GraphQLService {
335
279
  'are provided.',
336
280
  );
337
281
  }
282
+
283
+ if ('schemaConfigDeliveryEndpoint' in this.config) {
284
+ this.logger.warn(
285
+ 'The `schemaConfigDeliveryEndpoint` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the equivalent (array form) `uplinkEndpoints` configuration option.',
286
+ );
287
+ }
288
+
289
+ if ('experimental_pollInterval' in this.config) {
290
+ this.logger.warn(
291
+ 'The `experimental_pollInterval` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the equivalent `pollIntervalInMs` configuration option.',
292
+ );
293
+ }
338
294
  }
339
295
 
340
296
  public async load(options?: {
341
297
  apollo?: ApolloConfigFromAS2Or3;
342
298
  engine?: GraphQLServiceEngineConfig;
343
299
  }) {
300
+ this.logger.debug('Loading gateway...');
301
+
344
302
  if (this.state.phase !== 'initialized') {
345
303
  throw Error(
346
304
  `ApolloGateway.load called in surprising state ${this.state.phase}`,
@@ -366,40 +324,92 @@ export class ApolloGateway implements GraphQLService {
366
324
  };
367
325
  }
368
326
 
369
- // Before @apollo/gateway v0.23, ApolloGateway didn't expect stop() to be
370
- // called after it started. The only thing that stop() did at that point was
371
- // cancel the poll timer, and so to prevent that timer from keeping an
372
- // otherwise-finished Node process alive, ApolloGateway unconditionally
373
- // called unref() on that timeout. As part of making the ApolloGateway
374
- // lifecycle more predictable and concrete (and to allow for a future where
375
- // there are other reasons to make sure to explicitly stop your gateway),
376
- // v0.23 tries to avoid calling unref().
377
- //
378
- // Apollo Server v2.20 and newer calls gateway.stop() from its stop()
379
- // method, so as long as you're using v2.20, ApolloGateway won't keep
380
- // running after you stop your server, and your Node process can shut down.
381
- // To make this change a bit less backwards-incompatible, we detect if it
382
- // looks like you're using an older version of Apollo Server; if so, we
383
- // still call unref(). Specifically: Apollo Server has always passed an
384
- // options object to load(), and before v2.18 it did not pass the `apollo`
385
- // key on it. So if we detect that particular pattern, we assume we're with
386
- // pre-v2.18 Apollo Server and we still call unref(). So this will be a
387
- // behavior change only for:
388
- // - non-Apollo-Server uses of ApolloGateway (where you can add your own
389
- // call to gateway.stop())
390
- // - Apollo Server v2.18 and v2.19 (where you can either do the small
391
- // compatible upgrade or add your own call to gateway.stop())
392
- // - if you don't call stop() on your ApolloServer (but in that case other
393
- // things like usage reporting will also stop shutdown, so you should fix
394
- // that)
395
- const unrefTimer = !!options && !options.apollo;
396
-
397
327
  this.maybeWarnOnConflictingConfig();
398
328
 
399
329
  // Handles initial assignment of `this.schema`, `this.queryPlanner`
400
- isStaticConfig(this.config)
401
- ? this.loadStatic(this.config)
402
- : await this.loadDynamic(unrefTimer);
330
+ if (isStaticSupergraphSdlConfig(this.config)) {
331
+ const supergraphSdl = this.config.supergraphSdl;
332
+ await this.initializeSupergraphManager({
333
+ initialize: async () => {
334
+ return {
335
+ supergraphSdl,
336
+ };
337
+ },
338
+ });
339
+ } else if (isLocalConfig(this.config)) {
340
+ // TODO(trevor:removeServiceList)
341
+ await this.initializeSupergraphManager(
342
+ new LocalCompose({
343
+ localServiceList: this.config.localServiceList,
344
+ logger: this.logger,
345
+ }),
346
+ );
347
+ } else if (isManuallyManagedSupergraphSdlGatewayConfig(this.config)) {
348
+ const supergraphManager =
349
+ typeof this.config.supergraphSdl === 'object'
350
+ ? this.config.supergraphSdl
351
+ : { initialize: this.config.supergraphSdl };
352
+ await this.initializeSupergraphManager(supergraphManager);
353
+ } else if (
354
+ 'experimental_updateServiceDefinitions' in this.config ||
355
+ 'experimental_updateSupergraphSdl' in this.config
356
+ ) {
357
+ const updateServiceDefinitions =
358
+ 'experimental_updateServiceDefinitions' in this.config
359
+ ? this.config.experimental_updateServiceDefinitions
360
+ : this.config.experimental_updateSupergraphSdl;
361
+
362
+ await this.initializeSupergraphManager(
363
+ new LegacyFetcher({
364
+ logger: this.logger,
365
+ gatewayConfig: this.config,
366
+ updateServiceDefinitions,
367
+ pollIntervalInMs: this.pollIntervalInMs,
368
+ subgraphHealthCheck: this.config.serviceHealthCheck,
369
+ }),
370
+ );
371
+ } else if (isServiceListConfig(this.config)) {
372
+ // TODO(trevor:removeServiceList)
373
+ this.logger.warn(
374
+ 'The `serviceList` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to its replacement `IntrospectAndCompose`. More information on `IntrospectAndCompose` can be found in the documentation.',
375
+ );
376
+ await this.initializeSupergraphManager(
377
+ new IntrospectAndCompose({
378
+ subgraphs: this.config.serviceList,
379
+ pollIntervalInMs: this.pollIntervalInMs,
380
+ logger: this.logger,
381
+ subgraphHealthCheck: this.config.serviceHealthCheck,
382
+ introspectionHeaders: this.config.introspectionHeaders,
383
+ }),
384
+ );
385
+ } else {
386
+ // isManagedConfig(this.config)
387
+ const canUseManagedConfig =
388
+ this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
389
+ if (!canUseManagedConfig) {
390
+ throw new Error(
391
+ 'When a manual configuration is not provided, gateway requires an Apollo ' +
392
+ 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
393
+ 'for more information. Manual configuration options include: ' +
394
+ '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
395
+ );
396
+ }
397
+ const uplinkEndpoints = this.getUplinkEndpoints(this.config);
398
+
399
+ await this.initializeSupergraphManager(
400
+ new UplinkFetcher({
401
+ graphRef: this.apolloConfig!.graphRef!,
402
+ apiKey: this.apolloConfig!.key!,
403
+ uplinkEndpoints,
404
+ maxRetries:
405
+ this.config.uplinkMaxRetries ?? uplinkEndpoints.length * 3 - 1, // -1 for the initial request
406
+ subgraphHealthCheck: this.config.serviceHealthCheck,
407
+ fetcher: this.fetcher,
408
+ logger: this.logger,
409
+ pollIntervalInMs: this.pollIntervalInMs ?? 10000,
410
+ }),
411
+ );
412
+ }
403
413
 
404
414
  const mode = isManagedConfig(this.config) ? 'managed' : 'unmanaged';
405
415
  this.logger.info(
@@ -416,122 +426,148 @@ export class ApolloGateway implements GraphQLService {
416
426
  };
417
427
  }
418
428
 
419
- // Synchronously load a statically configured schema, update class instance's
420
- // schema and query planner.
421
- private loadStatic(config: StaticGatewayConfig) {
422
- let schema: Schema;
423
- let supergraphSdl: string;
424
- try {
425
- ({ schema, supergraphSdl } = isLocalConfig(config)
426
- ? this.createSchemaFromServiceList(config.localServiceList)
427
- : this.createSchemaFromSupergraphSdl(config.supergraphSdl));
428
- this.supergraphSdl = supergraphSdl;
429
- this.updateWithSchemaAndNotify(schema, supergraphSdl, true);
430
- } catch (e) {
431
- this.state = { phase: 'failed to load' };
432
- throw e;
433
- }
434
- this.state = { phase: 'loaded' };
429
+ private getUplinkEndpoints(config: ManagedGatewayConfig) {
430
+ /**
431
+ * Configuration priority order:
432
+ * 1. `uplinkEndpoints` configuration option
433
+ * 2. (deprecated) `schemaConfigDeliveryEndpoint` configuration option
434
+ * 3. APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT environment variable
435
+ * 4. default (GCP and AWS)
436
+ */
437
+ const rawEndpointsString =
438
+ process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
439
+ const envEndpoints = rawEndpointsString?.split(',') ?? null;
440
+ return (
441
+ config.uplinkEndpoints ??
442
+ (config.schemaConfigDeliveryEndpoint
443
+ ? [config.schemaConfigDeliveryEndpoint]
444
+ : null) ??
445
+ envEndpoints ?? [
446
+ 'https://uplink.api.apollographql.com/',
447
+ 'https://aws.uplink.api.apollographql.com/',
448
+ ]
449
+ );
450
+ }
451
+
452
+ private getIdForSupergraphSdl(supergraphSdl: string) {
453
+ return createHash('sha256').update(supergraphSdl).digest('hex');
435
454
  }
436
455
 
437
- // Asynchronously load a dynamically configured schema. `this.updateSchema`
438
- // is responsible for updating the class instance's schema and query planner.
439
- private async loadDynamic(unrefTimer: boolean) {
456
+ private async initializeSupergraphManager<T extends SupergraphManager>(
457
+ supergraphManager: T,
458
+ ) {
440
459
  try {
441
- await this.updateSchema();
460
+ const result = await supergraphManager.initialize({
461
+ update: this.externalSupergraphUpdateCallback.bind(this),
462
+ healthCheck: this.externalSubgraphHealthCheckCallback.bind(this),
463
+ getDataSource: this.externalGetDataSourceCallback.bind(this),
464
+ });
465
+ if (!result?.supergraphSdl) {
466
+ throw new Error(
467
+ 'Provided `supergraphSdl` function did not return an object containing a `supergraphSdl` property',
468
+ );
469
+ }
470
+ if (result?.cleanup) {
471
+ if (typeof result.cleanup === 'function') {
472
+ this.toDispose.push(result.cleanup);
473
+ } else {
474
+ this.logger.error(
475
+ 'Provided `supergraphSdl` function returned an invalid `cleanup` property (must be a function)',
476
+ );
477
+ }
478
+ }
479
+
480
+ this.externalSupergraphUpdateCallback(result.supergraphSdl);
442
481
  } catch (e) {
443
482
  this.state = { phase: 'failed to load' };
483
+ await this.performCleanupAndLogErrors();
444
484
  throw e;
445
485
  }
446
486
 
447
487
  this.state = { phase: 'loaded' };
448
- if (this.shouldBeginPolling()) {
449
- this.pollServices(unrefTimer);
450
- }
451
488
  }
452
489
 
453
- private shouldBeginPolling() {
454
- return isManagedConfig(this.config) || this.experimental_pollInterval;
455
- }
490
+ /**
491
+ * @throws Error
492
+ * when called from a state other than `loaded` or `intialized`
493
+ *
494
+ * @throws Error
495
+ * when the provided supergraphSdl is invalid
496
+ */
497
+ private externalSupergraphUpdateCallback(supergraphSdl: string) {
498
+ switch (this.state.phase) {
499
+ case 'failed to load':
500
+ throw new Error(
501
+ "Can't call `update` callback after gateway failed to load.",
502
+ );
503
+ case 'updating schema':
504
+ throw new Error(
505
+ "Can't call `update` callback while supergraph update is in progress.",
506
+ );
507
+ case 'stopped':
508
+ throw new Error(
509
+ "Can't call `update` callback after gateway has been stopped.",
510
+ );
511
+ case 'stopping':
512
+ throw new Error(
513
+ "Can't call `update` callback while gateway is stopping.",
514
+ );
515
+ case 'loaded':
516
+ case 'initialized':
517
+ // typical case
518
+ break;
519
+ default:
520
+ throw new UnreachableCaseError(this.state);
521
+ }
456
522
 
457
- private async updateSchema(): Promise<void> {
458
- this.logger.debug('Checking for composition updates...');
523
+ this.state = { phase: 'updating schema' };
524
+ try {
525
+ this.updateWithSupergraphSdl({
526
+ supergraphSdl,
527
+ id: this.getIdForSupergraphSdl(supergraphSdl),
528
+ });
529
+ } finally {
530
+ // if update fails, we still want to go back to `loaded` state
531
+ this.state = { phase: 'loaded' };
532
+ }
533
+ }
459
534
 
460
- // This may throw, but an error here is caught and logged upstream
461
- const result = await this.updateServiceDefinitions(this.config);
535
+ /**
536
+ * @throws Error
537
+ * when any subgraph fails the health check
538
+ */
539
+ private async externalSubgraphHealthCheckCallback(supergraphSdl: string) {
540
+ const serviceList = this.serviceListFromSupergraphSdl(supergraphSdl);
541
+ // Here we need to construct new datasources based on the new schema info
542
+ // so we can check the health of the services we're _updating to_.
543
+ const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
544
+ serviceMap[serviceDef.name] = {
545
+ url: serviceDef.url,
546
+ dataSource: this.createDataSource(serviceDef),
547
+ };
548
+ return serviceMap;
549
+ }, Object.create(null) as DataSourceMap);
462
550
 
463
- if (isSupergraphSdlUpdate(result)) {
464
- await this.updateWithSupergraphSdl(result);
465
- } else if (isServiceDefinitionUpdate(result)) {
466
- await this.updateByComposition(result);
467
- } else {
551
+ try {
552
+ await this.serviceHealthCheck(serviceMap);
553
+ } catch (e) {
468
554
  throw new Error(
469
- 'Programming error: unexpected result type from `updateServiceDefinitions`',
555
+ 'The gateway subgraphs health check failed. Updating to the provided ' +
556
+ '`supergraphSdl` will likely result in future request failures to ' +
557
+ 'subgraphs. The following error occurred during the health check:\n' +
558
+ e.message,
470
559
  );
471
560
  }
472
561
  }
473
562
 
474
- private async updateByComposition(
475
- result: ServiceDefinitionUpdate,
476
- ): Promise<void> {
477
- if (
478
- !result.serviceDefinitions ||
479
- JSON.stringify(this.serviceDefinitions) ===
480
- JSON.stringify(result.serviceDefinitions)
481
- ) {
482
- this.logger.debug('No change in service definitions since last check.');
483
- return;
484
- }
485
-
486
- const previousSchema = this.schema;
487
- const previousServiceDefinitions = this.serviceDefinitions;
488
- const previousCompositionMetadata = this.compositionMetadata;
489
-
490
- if (previousSchema) {
491
- this.logger.info('New service definitions were found.');
492
- }
493
-
494
- await this.maybePerformServiceHealthCheck(result);
495
-
496
- this.compositionMetadata = result.compositionMetadata;
497
- this.serviceDefinitions = result.serviceDefinitions;
498
-
499
- const { schema, supergraphSdl } = this.createSchemaFromServiceList(
500
- result.serviceDefinitions,
501
- );
502
-
503
- if (!supergraphSdl) {
504
- this.logger.error(
505
- "A valid schema couldn't be composed. Falling back to previous schema.",
506
- );
507
- } else {
508
- this.updateWithSchemaAndNotify(schema, supergraphSdl);
509
-
510
- if (this.experimental_didUpdateComposition) {
511
- this.experimental_didUpdateComposition(
512
- {
513
- serviceDefinitions: result.serviceDefinitions,
514
- schema: schema.toGraphQLJSSchema(),
515
- ...(this.compositionMetadata && {
516
- compositionMetadata: this.compositionMetadata,
517
- }),
518
- },
519
- previousServiceDefinitions &&
520
- previousSchema && {
521
- serviceDefinitions: previousServiceDefinitions,
522
- schema: previousSchema,
523
- ...(previousCompositionMetadata && {
524
- compositionMetadata: previousCompositionMetadata,
525
- }),
526
- },
527
- );
528
- }
529
- }
563
+ private externalGetDataSourceCallback({
564
+ name,
565
+ url,
566
+ }: ServiceEndpointDefinition) {
567
+ return this.getOrCreateDataSource({ name, url });
530
568
  }
531
569
 
532
- private async updateWithSupergraphSdl(
533
- result: SupergraphSdlUpdate,
534
- ): Promise<void> {
570
+ private updateWithSupergraphSdl(result: SupergraphSdlUpdate) {
535
571
  if (result.id === this.compositionId) {
536
572
  this.logger.debug('No change in composition since last check.');
537
573
  return;
@@ -541,7 +577,9 @@ export class ApolloGateway implements GraphQLService {
541
577
  // In the case that it throws, the gateway will:
542
578
  // * on initial load, throw the error
543
579
  // * on update, log the error and don't update
544
- const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(result.supergraphSdl);
580
+ const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
581
+ result.supergraphSdl,
582
+ );
545
583
 
546
584
  const previousSchema = this.schema;
547
585
  const previousSupergraphSdl = this.supergraphSdl;
@@ -551,11 +589,8 @@ export class ApolloGateway implements GraphQLService {
551
589
  this.logger.info('Updated Supergraph SDL was found.');
552
590
  }
553
591
 
554
- await this.maybePerformServiceHealthCheck(result);
555
-
556
592
  this.compositionId = result.id;
557
- this.supergraphSdl = result.supergraphSdl;
558
-
593
+ this.supergraphSdl = supergraphSdl;
559
594
 
560
595
  if (!supergraphSdl) {
561
596
  this.logger.error(
@@ -564,11 +599,11 @@ export class ApolloGateway implements GraphQLService {
564
599
  } else {
565
600
  this.updateWithSchemaAndNotify(schema, supergraphSdl);
566
601
 
567
- if (this.experimental_didUpdateComposition) {
568
- this.experimental_didUpdateComposition(
602
+ if (this.experimental_didUpdateSupergraph) {
603
+ this.experimental_didUpdateSupergraph(
569
604
  {
570
605
  compositionId: result.id,
571
- supergraphSdl: result.supergraphSdl,
606
+ supergraphSdl,
572
607
  schema: schema.toGraphQLJSSchema(),
573
608
  },
574
609
  previousCompositionId && previousSupergraphSdl && previousSchema
@@ -594,7 +629,9 @@ export class ApolloGateway implements GraphQLService {
594
629
  ): void {
595
630
  if (this.queryPlanStore) this.queryPlanStore.flush();
596
631
  this.apiSchema = coreSchema.toAPISchema();
597
- this.schema = wrapSchemaWithAliasResolver(this.apiSchema.toGraphQLJSSchema());
632
+ this.schema = wrapSchemaWithAliasResolver(
633
+ this.apiSchema.toGraphQLJSSchema(),
634
+ );
598
635
  this.queryPlanner = new QueryPlanner(coreSchema);
599
636
 
600
637
  // Notify onSchemaChange listeners of the updated schema
@@ -629,39 +666,6 @@ export class ApolloGateway implements GraphQLService {
629
666
  });
630
667
  }
631
668
 
632
- private async maybePerformServiceHealthCheck(update: CompositionUpdate) {
633
- // Run service health checks before we commit and update the new schema.
634
- // This is the last chance to bail out of a schema update.
635
- if (this.config.serviceHealthCheck) {
636
- const serviceList = isSupergraphSdlUpdate(update)
637
- ? // Parsing of the supergraph SDL could technically fail and throw here, but parseability has
638
- // already been confirmed slightly earlier in the code path
639
- this.serviceListFromSupergraphSdl(update.supergraphSdl)
640
- : // Existence of this is determined in advance with an early return otherwise
641
- update.serviceDefinitions!;
642
- // Here we need to construct new datasources based on the new schema info
643
- // so we can check the health of the services we're _updating to_.
644
- const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
645
- serviceMap[serviceDef.name] = {
646
- url: serviceDef.url,
647
- dataSource: this.createDataSource(serviceDef),
648
- };
649
- return serviceMap;
650
- }, Object.create(null) as DataSourceMap);
651
-
652
- try {
653
- await this.serviceHealthCheck(serviceMap);
654
- } catch (e) {
655
- throw new Error(
656
- 'The gateway did not update its schema due to failed service health checks. ' +
657
- 'The gateway will continue to operate with the previous schema and reattempt updates. ' +
658
- 'The following error occurred during the health check:\n' +
659
- e.message,
660
- );
661
- }
662
- }
663
- }
664
-
665
669
  /**
666
670
  * This can be used without an argument in order to perform an ad-hoc health check
667
671
  * of the downstream services like so:
@@ -694,40 +698,9 @@ export class ApolloGateway implements GraphQLService {
694
698
  );
695
699
  }
696
700
 
697
- private createSchemaFromServiceList(serviceList: ServiceDefinition[]) {
698
- this.logger.debug(
699
- `Composing schema from service list: \n${serviceList
700
- .map(({ name, url }) => ` ${url || 'local'}: ${name}`)
701
- .join('\n')}`,
702
- );
703
-
704
- const compositionResult = composeServices(serviceList);
705
- const errors = compositionResult.errors;
706
- if (errors) {
707
- if (this.experimental_didFailComposition) {
708
- this.experimental_didFailComposition({
709
- errors,
710
- serviceList,
711
- ...(this.compositionMetadata && {
712
- compositionMetadata: this.compositionMetadata,
713
- }),
714
- });
715
- }
716
- throw Error(
717
- "A valid schema couldn't be composed. The following composition errors were found:\n" +
718
- errors.map((e) => '\t' + e.message).join('\n'),
719
- );
720
- } else {
721
- this.createServices(serviceList);
722
- this.logger.debug('Schema loaded and ready for execution');
723
- return {
724
- schema: compositionResult.schema,
725
- supergraphSdl: compositionResult.supergraphSdl,
726
- };
727
- }
728
- }
729
-
730
- private serviceListFromSupergraphSdl(supergraphSdl: string): Omit<ServiceDefinition, 'typeDefs'>[] {
701
+ private serviceListFromSupergraphSdl(
702
+ supergraphSdl: string,
703
+ ): Omit<ServiceDefinition, 'typeDefs'>[] {
731
704
  return buildSupergraphSchema(supergraphSdl)[1];
732
705
  }
733
706
 
@@ -767,93 +740,16 @@ export class ApolloGateway implements GraphQLService {
767
740
  };
768
741
  }
769
742
 
770
- // This function waits an appropriate amount, updates composition, and calls itself
771
- // again. Note that it is an async function whose Promise is not actually awaited;
772
- // it should never throw itself other than due to a bug in its state machine.
773
- private async pollServices(unrefTimer: boolean) {
774
- switch (this.state.phase) {
775
- case 'stopping':
776
- case 'stopped':
777
- case 'failed to load':
778
- return;
779
- case 'initialized':
780
- throw Error('pollServices should not be called before load!');
781
- case 'polling':
782
- throw Error(
783
- 'pollServices should not be called while in the middle of polling!',
784
- );
785
- case 'waiting to poll':
786
- throw Error(
787
- 'pollServices should not be called while already waiting to poll!',
788
- );
789
- case 'loaded':
790
- // This is the normal case.
791
- break;
792
- default:
793
- throw new UnreachableCaseError(this.state);
794
- }
795
-
796
- // Transition into 'waiting to poll' and set a timer. The timer resolves the
797
- // Promise we're awaiting here; note that calling stop() also can resolve
798
- // that Promise.
799
- await new Promise<void>((doneWaiting) => {
800
- this.state = {
801
- phase: 'waiting to poll',
802
- doneWaiting,
803
- pollWaitTimer: setTimeout(() => {
804
- // Note that we might be in 'stopped', in which case we just do
805
- // nothing.
806
- if (this.state.phase == 'waiting to poll') {
807
- this.state.doneWaiting();
808
- }
809
- }, this.experimental_pollInterval || 10000),
810
- };
811
- if (unrefTimer) {
812
- this.state.pollWaitTimer.unref();
813
- }
814
- });
815
-
816
- // If we've been stopped, then we're done. The cast here is because TS
817
- // doesn't understand that this.state can change during the await
818
- // (https://github.com/microsoft/TypeScript/issues/9998).
819
- if ((this.state as GatewayState).phase !== 'waiting to poll') {
820
- return;
821
- }
822
-
823
- let pollingDone: () => void;
824
- this.state = {
825
- phase: 'polling',
826
- pollingDonePromise: new Promise<void>((res) => {
827
- pollingDone = res;
828
- }),
829
- };
830
-
831
- try {
832
- await this.updateSchema();
833
- } catch (err) {
834
- this.logger.error((err && err.message) || err);
835
- }
836
-
837
- if (this.state.phase === 'polling') {
838
- // If we weren't stopped, we should transition back to the initial 'loading' state and trigger
839
- // another call to itself. (Do that in a setImmediate to avoid unbounded stack sizes.)
840
- this.state = { phase: 'loaded' };
841
- setImmediate(() => this.pollServices(unrefTimer));
842
- }
843
-
844
- // Whether we were stopped or not, let any concurrent stop() call finish.
845
- pollingDone!();
846
- }
847
-
848
- private createAndCacheDataSource(
743
+ private getOrCreateDataSource(
849
744
  serviceDef: ServiceEndpointDefinition,
850
745
  ): GraphQLDataSource {
851
746
  // If the DataSource has already been created, early return
852
747
  if (
853
748
  this.serviceMap[serviceDef.name] &&
854
749
  serviceDef.url === this.serviceMap[serviceDef.name].url
855
- )
750
+ ) {
856
751
  return this.serviceMap[serviceDef.name].dataSource;
752
+ }
857
753
 
858
754
  const dataSource = this.createDataSource(serviceDef);
859
755
 
@@ -881,55 +777,8 @@ export class ApolloGateway implements GraphQLService {
881
777
 
882
778
  private createServices(services: ServiceEndpointDefinition[]) {
883
779
  for (const serviceDef of services) {
884
- this.createAndCacheDataSource(serviceDef);
885
- }
886
- }
887
-
888
- protected async loadServiceDefinitions(
889
- config: RemoteGatewayConfig | ManagedGatewayConfig,
890
- ): Promise<CompositionUpdate> {
891
- if (isRemoteConfig(config)) {
892
- const serviceList = config.serviceList.map((serviceDefinition) => ({
893
- ...serviceDefinition,
894
- dataSource: this.createAndCacheDataSource(serviceDefinition),
895
- }));
896
-
897
- return getServiceDefinitionsFromRemoteEndpoint({
898
- serviceList,
899
- async getServiceIntrospectionHeaders(service) {
900
- return typeof config.introspectionHeaders === 'function'
901
- ? await config.introspectionHeaders(service)
902
- : config.introspectionHeaders;
903
- },
904
- serviceSdlCache: this.serviceSdlCache,
905
- });
906
- }
907
-
908
- const canUseManagedConfig =
909
- this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
910
- if (!canUseManagedConfig) {
911
- throw new Error(
912
- 'When a manual configuration is not provided, gateway requires an Apollo ' +
913
- 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
914
- 'for more information. Manual configuration options include: ' +
915
- '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
916
- );
780
+ this.getOrCreateDataSource(serviceDef);
917
781
  }
918
-
919
- const result = await loadSupergraphSdlFromStorage({
920
- graphRef: this.apolloConfig!.graphRef!,
921
- apiKey: this.apolloConfig!.key!,
922
- endpoint: this.schemaConfigDeliveryEndpoint!,
923
- fetcher: this.fetcher,
924
- compositionId: this.compositionId ?? null,
925
- });
926
-
927
- return (
928
- result ?? {
929
- id: this.compositionId!,
930
- supergraphSdl: this.supergraphSdl!,
931
- }
932
- );
933
782
  }
934
783
 
935
784
  private maybeWarnOnConflictingConfig() {
@@ -1004,7 +853,11 @@ export class ApolloGateway implements GraphQLService {
1004
853
  OpenTelemetrySpanNames.PLAN,
1005
854
  (span) => {
1006
855
  try {
1007
- const operation = operationFromDocument(this.apiSchema!, document, request.operationName);
856
+ const operation = operationFromDocument(
857
+ this.apiSchema!,
858
+ document,
859
+ request.operationName,
860
+ );
1008
861
  // TODO(#631): Can we be sure the query planner has been initialized here?
1009
862
  return this.queryPlanner!.buildQueryPlan(operation);
1010
863
  } catch (err) {
@@ -1144,9 +997,24 @@ export class ApolloGateway implements GraphQLService {
1144
997
  });
1145
998
  }
1146
999
 
1147
- // Stops all processes involved with the gateway (for now, just background
1148
- // schema polling). Can be called multiple times safely. Once it (async)
1149
- // returns, all gateway background activity will be finished.
1000
+ private async performCleanupAndLogErrors() {
1001
+ if (this.toDispose.length === 0) return;
1002
+
1003
+ await Promise.all(
1004
+ this.toDispose.map((p) =>
1005
+ p().catch((e) => {
1006
+ this.logger.error(
1007
+ 'Error occured while calling user provided `cleanup` function: ' +
1008
+ (e.message ?? e),
1009
+ );
1010
+ }),
1011
+ ),
1012
+ );
1013
+ this.toDispose = [];
1014
+ }
1015
+
1016
+ // Stops all processes involved with the gateway. Can be called multiple times
1017
+ // safely. Once it (async) returns, all gateway background activity will be finished.
1150
1018
  public async stop() {
1151
1019
  switch (this.state.phase) {
1152
1020
  case 'initialized':
@@ -1169,40 +1037,31 @@ export class ApolloGateway implements GraphQLService {
1169
1037
  }
1170
1038
  return;
1171
1039
  case 'loaded':
1172
- this.state = { phase: 'stopped' }; // nothing to do (we're not polling)
1173
- return;
1174
- case 'waiting to poll': {
1175
- // If we're waiting to poll, we can synchronously transition to fully stopped.
1176
- // We will terminate the current pollServices call and it will succeed quickly.
1177
- const doneWaiting = this.state.doneWaiting;
1178
- clearTimeout(this.state.pollWaitTimer);
1179
- this.state = { phase: 'stopped' };
1180
- doneWaiting();
1181
- return;
1182
- }
1183
- case 'polling': {
1184
- // We're in the middle of running updateSchema. We need to go into 'stopping'
1185
- // mode and let this run complete. First we set things up so that any concurrent
1186
- // calls to stop() will wait until we let them finish. (Those concurrent calls shouldn't
1187
- // just wait on pollingDonePromise themselves because we want to make sure we fully
1188
- // transition to state='stopped' before the other call returns.)
1189
- const pollingDonePromise = this.state.pollingDonePromise;
1190
- let stoppingDone: () => void;
1040
+ const stoppingDonePromise = this.performCleanupAndLogErrors();
1191
1041
  this.state = {
1192
1042
  phase: 'stopping',
1193
- stoppingDonePromise: new Promise<void>((res) => {
1194
- stoppingDone = res;
1195
- }),
1043
+ stoppingDonePromise,
1196
1044
  };
1197
- await pollingDonePromise;
1045
+ await stoppingDonePromise;
1198
1046
  this.state = { phase: 'stopped' };
1199
- stoppingDone!();
1200
1047
  return;
1048
+ case 'updating schema': {
1049
+ throw Error(
1050
+ "`ApolloGateway.stop` shouldn't be called from inside a schema change listener",
1051
+ );
1201
1052
  }
1202
1053
  default:
1203
1054
  throw new UnreachableCaseError(this.state);
1204
1055
  }
1205
1056
  }
1057
+
1058
+ public __testing() {
1059
+ return {
1060
+ state: this.state,
1061
+ compositionId: this.compositionId,
1062
+ supergraphSdl: this.supergraphSdl,
1063
+ };
1064
+ }
1206
1065
  }
1207
1066
 
1208
1067
  ApolloGateway.prototype.onSchemaChange = deprecate(
@@ -1249,11 +1108,25 @@ export {
1249
1108
  ServiceMap,
1250
1109
  Experimental_DidFailCompositionCallback,
1251
1110
  Experimental_DidResolveQueryPlanCallback,
1252
- Experimental_DidUpdateCompositionCallback,
1111
+ Experimental_DidUpdateSupergraphCallback,
1253
1112
  Experimental_UpdateComposition,
1254
1113
  GatewayConfig,
1255
1114
  ServiceEndpointDefinition,
1115
+ ServiceDefinition,
1256
1116
  CompositionInfo,
1117
+ IntrospectAndCompose,
1118
+ LocalCompose,
1257
1119
  };
1258
1120
 
1259
1121
  export * from './datasources';
1122
+
1123
+ export {
1124
+ SupergraphSdlUpdateFunction,
1125
+ SubgraphHealthCheckFunction,
1126
+ GetDataSourceFunction,
1127
+ SupergraphSdlHook,
1128
+ SupergraphManager
1129
+ } from './config';
1130
+
1131
+ export { UplinkFetcherError } from "./supergraphManagers"
1132
+