@apollo/gateway 0.45.1 → 0.46.0-alpha.0

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 (87) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/config.d.ts +42 -16
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +28 -18
  5. package/dist/config.js.map +1 -1
  6. package/dist/index.d.ts +35 -23
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +205 -308
  9. package/dist/index.js.map +1 -1
  10. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
  11. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
  12. package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
  13. package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
  14. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
  15. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
  16. package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
  17. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
  18. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
  19. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
  20. package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
  21. package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
  22. package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
  23. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
  24. package/dist/supergraphManagers/LocalCompose/index.js +55 -0
  25. package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
  26. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +32 -0
  27. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
  28. package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
  29. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
  30. package/dist/{loadSupergraphSdlFromStorage.d.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts} +1 -1
  31. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  32. package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +1 -1
  33. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
  34. package/dist/{outOfBandReporter.d.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts} +0 -0
  35. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
  36. package/dist/{outOfBandReporter.js → supergraphManagers/UplinkFetcher/outOfBandReporter.js} +2 -2
  37. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
  38. package/dist/supergraphManagers/index.d.ts +5 -0
  39. package/dist/supergraphManagers/index.d.ts.map +1 -0
  40. package/dist/supergraphManagers/index.js +12 -0
  41. package/dist/supergraphManagers/index.js.map +1 -0
  42. package/dist/utilities/createHash.d.ts +2 -0
  43. package/dist/utilities/createHash.d.ts.map +1 -0
  44. package/dist/utilities/createHash.js +15 -0
  45. package/dist/utilities/createHash.js.map +1 -0
  46. package/dist/utilities/isNodeLike.d.ts +3 -0
  47. package/dist/utilities/isNodeLike.d.ts.map +1 -0
  48. package/dist/utilities/isNodeLike.js +8 -0
  49. package/dist/utilities/isNodeLike.js.map +1 -0
  50. package/package.json +6 -4
  51. package/src/__tests__/execution-utils.ts +2 -2
  52. package/src/__tests__/gateway/buildService.test.ts +2 -2
  53. package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -99
  54. package/src/__tests__/gateway/opentelemetry.test.ts +8 -3
  55. package/src/__tests__/gateway/queryPlanCache.test.ts +25 -9
  56. package/src/__tests__/gateway/reporting.test.ts +4 -6
  57. package/src/__tests__/gateway/supergraphSdl.test.ts +390 -0
  58. package/src/__tests__/integration/aliases.test.ts +9 -3
  59. package/src/__tests__/integration/configuration.test.ts +109 -12
  60. package/src/__tests__/integration/logger.test.ts +1 -1
  61. package/src/__tests__/integration/networkRequests.test.ts +81 -118
  62. package/src/__tests__/integration/nockMocks.ts +15 -8
  63. package/src/config.ts +149 -40
  64. package/src/index.ts +314 -485
  65. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
  66. package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +5 -5
  67. package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
  68. package/src/supergraphManagers/IntrospectAndCompose/index.ts +163 -0
  69. package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
  70. package/src/supergraphManagers/LegacyFetcher/index.ts +229 -0
  71. package/src/supergraphManagers/LocalCompose/index.ts +83 -0
  72. package/src/{__tests__ → supergraphManagers/UplinkFetcher/__tests__}/loadSupergraphSdlFromStorage.test.ts +4 -4
  73. package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
  74. package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
  75. package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +3 -3
  76. package/src/{outOfBandReporter.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.ts} +2 -2
  77. package/src/supergraphManagers/index.ts +4 -0
  78. package/src/utilities/createHash.ts +10 -0
  79. package/src/utilities/isNodeLike.ts +11 -0
  80. package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
  81. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
  82. package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
  83. package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
  84. package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
  85. package/dist/outOfBandReporter.d.ts.map +0 -1
  86. package/dist/outOfBandReporter.js.map +0 -1
  87. package/src/__tests__/gateway/composedSdl.test.ts +0 -44
package/src/index.ts CHANGED
@@ -16,22 +16,18 @@ import {
16
16
  parse,
17
17
  Source,
18
18
  } from 'graphql';
19
- import {
20
- composeAndValidate,
21
- compositionHasErrors,
22
- ServiceDefinition,
23
- } from '@apollo/federation';
19
+ import { ServiceDefinition } from '@apollo/federation';
24
20
  import loglevel from 'loglevel';
25
-
26
21
  import { buildOperationContext, OperationContext } from './operationContext';
27
22
  import {
28
23
  executeQueryPlan,
29
24
  ServiceMap,
30
25
  defaultFieldResolverWithAliasSupport,
31
26
  } from './executeQueryPlan';
32
-
33
- import { getServiceDefinitionsFromRemoteEndpoint } from './loadServicesFromRemoteEndpoint';
34
- import { GraphQLDataSource, GraphQLDataSourceRequestKind } from './datasources/types';
27
+ import {
28
+ GraphQLDataSource,
29
+ GraphQLDataSourceRequestKind,
30
+ } from './datasources/types';
35
31
  import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource';
36
32
  import { getVariableValues } from 'graphql/execution/values';
37
33
  import fetcher from 'make-fetch-happen';
@@ -47,32 +43,32 @@ import {
47
43
  ServiceEndpointDefinition,
48
44
  Experimental_DidFailCompositionCallback,
49
45
  Experimental_DidResolveQueryPlanCallback,
50
- Experimental_DidUpdateCompositionCallback,
46
+ Experimental_DidUpdateSupergraphCallback,
51
47
  Experimental_UpdateComposition,
52
48
  CompositionInfo,
53
49
  GatewayConfig,
54
- StaticGatewayConfig,
55
- RemoteGatewayConfig,
56
- ManagedGatewayConfig,
57
50
  isManuallyManagedConfig,
58
51
  isLocalConfig,
59
- isRemoteConfig,
52
+ isServiceListConfig,
60
53
  isManagedConfig,
61
- isDynamicConfig,
62
- isStaticConfig,
63
- CompositionMetadata,
64
- isSupergraphSdlUpdate,
65
- isServiceDefinitionUpdate,
66
- ServiceDefinitionUpdate,
67
54
  SupergraphSdlUpdate,
68
- CompositionUpdate,
55
+ isManuallyManagedSupergraphSdlGatewayConfig,
56
+ ManagedGatewayConfig,
57
+ isStaticSupergraphSdlConfig,
58
+ SupergraphManager,
69
59
  } from './config';
70
60
  import { buildComposedSchema } from '@apollo/query-planner';
71
- import { loadSupergraphSdlFromUplinks } from './loadSupergraphSdlFromStorage';
72
61
  import { SpanStatusCode } from '@opentelemetry/api';
73
62
  import { OpenTelemetrySpanNames, tracer } from './utilities/opentelemetry';
74
63
  import { CoreSchema } from '@apollo/core-schema';
75
64
  import { featureSupport } from './core';
65
+ import { createHash } from './utilities/createHash';
66
+ import {
67
+ IntrospectAndCompose,
68
+ UplinkFetcher,
69
+ LegacyFetcher,
70
+ LocalCompose,
71
+ } from './supergraphManagers';
76
72
 
77
73
  type DataSourceMap = {
78
74
  [serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
@@ -122,12 +118,7 @@ type GatewayState =
122
118
  | { phase: 'loaded' }
123
119
  | { phase: 'stopping'; stoppingDonePromise: Promise<void> }
124
120
  | { phase: 'stopped' }
125
- | {
126
- phase: 'waiting to poll';
127
- pollWaitTimer: NodeJS.Timer;
128
- doneWaiting: () => void;
129
- }
130
- | { phase: 'polling'; pollingDonePromise: Promise<void> };
121
+ | { phase: 'updating schema' };
131
122
 
132
123
  // We want to be compatible with `load()` as called by both AS2 and AS3, so we
133
124
  // define its argument types ourselves instead of relying on imports.
@@ -171,9 +162,6 @@ export class ApolloGateway implements GraphQLService {
171
162
  coreSupergraphSdl: string;
172
163
  }) => void
173
164
  >();
174
- private serviceDefinitions: ServiceDefinition[] = [];
175
- private compositionMetadata?: CompositionMetadata;
176
- private serviceSdlCache = new Map<string, string>();
177
165
  private warnedStates: WarnedStates = Object.create(null);
178
166
  private queryPlanner?: QueryPlanner;
179
167
  private supergraphSdl?: string;
@@ -181,30 +169,17 @@ export class ApolloGateway implements GraphQLService {
181
169
  private fetcher: typeof fetch;
182
170
  private compositionId?: string;
183
171
  private state: GatewayState;
184
- private errorReportingEndpoint: string | undefined =
185
- process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT ?? undefined;
186
172
 
187
173
  // Observe query plan, service info, and operation info prior to execution.
188
174
  // The information made available here will give insight into the resulting
189
175
  // query plan and the inputs that generated it.
190
176
  private experimental_didResolveQueryPlan?: Experimental_DidResolveQueryPlanCallback;
191
- // Observe composition failures and the ServiceList that caused them. This
192
- // enables reporting any issues that occur during composition. Implementors
193
- // will be interested in addressing these immediately.
194
- private experimental_didFailComposition?: Experimental_DidFailCompositionCallback;
195
- // Used to communicated composition changes, and what definitions caused
196
- // those updates
197
- private experimental_didUpdateComposition?: Experimental_DidUpdateCompositionCallback;
198
- // Used for overriding the default service list fetcher. This should return
199
- // an array of ServiceDefinition. *This function must be awaited.*
200
- private updateServiceDefinitions: Experimental_UpdateComposition;
201
- // how often service defs should be loaded/updated (in ms)
202
- private experimental_pollInterval?: number;
203
- // Configure the endpoints by which gateway will access its precomposed schema.
204
- // * An array of URLs means use these endpoints to obtain schema, if one is unavailable then try the next.
205
- // * `undefined` means the gateway is not using managed federation
206
- private uplinkEndpoints?: string[];
207
- private uplinkMaxRetries?: number;
177
+ // Used to communicate supergraph updates
178
+ private experimental_didUpdateSupergraph?: Experimental_DidUpdateSupergraphCallback;
179
+ // how often service defs should be loaded/updated
180
+ private pollIntervalInMs?: number;
181
+ // Functions to call during gateway cleanup (when stop() is called)
182
+ private toDispose: (() => Promise<void>)[] = [];
208
183
 
209
184
  constructor(config?: GatewayConfig) {
210
185
  this.config = {
@@ -224,54 +199,15 @@ export class ApolloGateway implements GraphQLService {
224
199
  // set up experimental observability callbacks and config settings
225
200
  this.experimental_didResolveQueryPlan =
226
201
  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 URLs
237
- if (isManagedConfig(this.config)) {
238
- const rawEndpointsString = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
239
- const envEndpoints = rawEndpointsString?.split(",") ?? null;
240
-
241
- if (this.config.schemaConfigDeliveryEndpoint && !this.config.uplinkEndpoints) {
242
- this.uplinkEndpoints = [this.config.schemaConfigDeliveryEndpoint];
243
- } else {
244
- this.uplinkEndpoints = this.config.uplinkEndpoints ??
245
- envEndpoints ?? [
246
- 'https://uplink.api.apollographql.com/',
247
- 'https://aws.uplink.api.apollographql.com/'
248
- ];
249
- }
202
+ this.experimental_didUpdateSupergraph =
203
+ config?.experimental_didUpdateSupergraph;
250
204
 
251
- this.uplinkMaxRetries = this.config.uplinkMaxRetries ?? this.uplinkEndpoints.length * 3;
252
- }
205
+ this.pollIntervalInMs =
206
+ config?.pollIntervalInMs ?? config?.experimental_pollInterval;
253
207
 
254
- if (isManuallyManagedConfig(this.config)) {
255
- // Use the provided updater function if provided by the user, else default
256
- if ('experimental_updateSupergraphSdl' in this.config) {
257
- this.updateServiceDefinitions =
258
- this.config.experimental_updateSupergraphSdl;
259
- } else if ('experimental_updateServiceDefinitions' in this.config) {
260
- this.updateServiceDefinitions =
261
- this.config.experimental_updateServiceDefinitions;
262
- } else {
263
- throw Error(
264
- 'Programming error: unexpected manual configuration provided',
265
- );
266
- }
267
- } else {
268
- this.updateServiceDefinitions = this.loadServiceDefinitions;
269
- }
270
-
271
- if (isDynamicConfig(this.config)) {
272
- this.issueDynamicWarningsIfApplicable();
273
- }
208
+ this.issueConfigurationWarningsIfApplicable();
274
209
 
210
+ this.logger.debug('Gateway successfully initialized (but not yet loaded)');
275
211
  this.state = { phase: 'initialized' };
276
212
  }
277
213
 
@@ -306,25 +242,26 @@ export class ApolloGateway implements GraphQLService {
306
242
  });
307
243
  }
308
244
 
309
- private issueDynamicWarningsIfApplicable() {
245
+ private issueConfigurationWarningsIfApplicable() {
310
246
  // Warn against a pollInterval of < 10s in managed mode and reset it to 10s
311
247
  if (
312
248
  isManagedConfig(this.config) &&
313
- this.config.experimental_pollInterval &&
314
- this.config.experimental_pollInterval < 10000
249
+ this.pollIntervalInMs &&
250
+ this.pollIntervalInMs < 10000
315
251
  ) {
316
- this.experimental_pollInterval = 10000;
252
+ this.pollIntervalInMs = 10000;
317
253
  this.logger.warn(
318
254
  'Polling Apollo services at a frequency of less than once per 10 ' +
319
255
  'seconds (10000) is disallowed. Instead, the minimum allowed ' +
320
256
  'pollInterval of 10000 will be used. Please reconfigure your ' +
321
- 'experimental_pollInterval accordingly. If this is problematic for ' +
257
+ '`pollIntervalInMs` accordingly. If this is problematic for ' +
322
258
  'your team, please contact support.',
323
259
  );
324
260
  }
325
261
 
326
262
  // Warn against using the pollInterval and a serviceList simultaneously
327
- if (this.config.experimental_pollInterval && isRemoteConfig(this.config)) {
263
+ // TODO(trevor:removeServiceList)
264
+ if (this.pollIntervalInMs && isServiceListConfig(this.config)) {
328
265
  this.logger.warn(
329
266
  'Polling running services is dangerous and not recommended in production. ' +
330
267
  'Polling should only be used against a registry. ' +
@@ -344,12 +281,26 @@ export class ApolloGateway implements GraphQLService {
344
281
  'are provided.',
345
282
  );
346
283
  }
284
+
285
+ if ('schemaConfigDeliveryEndpoint' in this.config) {
286
+ this.logger.warn(
287
+ '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.',
288
+ );
289
+ }
290
+
291
+ if ('experimental_pollInterval' in this.config) {
292
+ this.logger.warn(
293
+ '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.',
294
+ );
295
+ }
347
296
  }
348
297
 
349
298
  public async load(options?: {
350
299
  apollo?: ApolloConfigFromAS2Or3;
351
300
  engine?: GraphQLServiceEngineConfig;
352
301
  }) {
302
+ this.logger.debug('Loading gateway...');
303
+
353
304
  if (this.state.phase !== 'initialized') {
354
305
  throw Error(
355
306
  `ApolloGateway.load called in surprising state ${this.state.phase}`,
@@ -375,40 +326,88 @@ export class ApolloGateway implements GraphQLService {
375
326
  };
376
327
  }
377
328
 
378
- // Before @apollo/gateway v0.23, ApolloGateway didn't expect stop() to be
379
- // called after it started. The only thing that stop() did at that point was
380
- // cancel the poll timer, and so to prevent that timer from keeping an
381
- // otherwise-finished Node process alive, ApolloGateway unconditionally
382
- // called unref() on that timeout. As part of making the ApolloGateway
383
- // lifecycle more predictable and concrete (and to allow for a future where
384
- // there are other reasons to make sure to explicitly stop your gateway),
385
- // v0.23 tries to avoid calling unref().
386
- //
387
- // Apollo Server v2.20 and newer calls gateway.stop() from its stop()
388
- // method, so as long as you're using v2.20, ApolloGateway won't keep
389
- // running after you stop your server, and your Node process can shut down.
390
- // To make this change a bit less backwards-incompatible, we detect if it
391
- // looks like you're using an older version of Apollo Server; if so, we
392
- // still call unref(). Specifically: Apollo Server has always passed an
393
- // options object to load(), and before v2.18 it did not pass the `apollo`
394
- // key on it. So if we detect that particular pattern, we assume we're with
395
- // pre-v2.18 Apollo Server and we still call unref(). So this will be a
396
- // behavior change only for:
397
- // - non-Apollo-Server uses of ApolloGateway (where you can add your own
398
- // call to gateway.stop())
399
- // - Apollo Server v2.18 and v2.19 (where you can either do the small
400
- // compatible upgrade or add your own call to gateway.stop())
401
- // - if you don't call stop() on your ApolloServer (but in that case other
402
- // things like usage reporting will also stop shutdown, so you should fix
403
- // that)
404
- const unrefTimer = !!options && !options.apollo;
405
-
406
329
  this.maybeWarnOnConflictingConfig();
407
330
 
408
331
  // Handles initial assignment of `this.schema`, `this.queryPlanner`
409
- isStaticConfig(this.config)
410
- ? this.loadStatic(this.config)
411
- : await this.loadDynamic(unrefTimer);
332
+ if (isStaticSupergraphSdlConfig(this.config)) {
333
+ const supergraphSdl = this.config.supergraphSdl;
334
+ await this.initializeSupergraphManager({
335
+ initialize: async () => {
336
+ return {
337
+ supergraphSdl,
338
+ };
339
+ },
340
+ });
341
+ } else if (isLocalConfig(this.config)) {
342
+ // TODO(trevor:removeServiceList)
343
+ await this.initializeSupergraphManager(new LocalCompose({
344
+ localServiceList: this.config.localServiceList,
345
+ logger: this.logger,
346
+ }));
347
+ } else if (isManuallyManagedSupergraphSdlGatewayConfig(this.config)) {
348
+ const supergraphManager = typeof this.config.supergraphSdl === 'object'
349
+ ? this.config.supergraphSdl
350
+ : { initialize: this.config.supergraphSdl };
351
+ await this.initializeSupergraphManager(supergraphManager);
352
+ } else if (
353
+ 'experimental_updateServiceDefinitions' in this.config || 'experimental_updateSupergraphSdl' in this.config
354
+ ) {
355
+ const updateServiceDefinitions =
356
+ 'experimental_updateServiceDefinitions' in this.config
357
+ ? this.config.experimental_updateServiceDefinitions
358
+ : this.config.experimental_updateSupergraphSdl;
359
+
360
+ await this.initializeSupergraphManager(
361
+ new LegacyFetcher({
362
+ logger: this.logger,
363
+ gatewayConfig: this.config,
364
+ updateServiceDefinitions,
365
+ pollIntervalInMs: this.pollIntervalInMs,
366
+ subgraphHealthCheck: this.config.serviceHealthCheck,
367
+ }),
368
+ );
369
+ } else if (isServiceListConfig(this.config)) {
370
+ // TODO(trevor:removeServiceList)
371
+ this.logger.warn(
372
+ '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.',
373
+ );
374
+ await this.initializeSupergraphManager(
375
+ new IntrospectAndCompose({
376
+ subgraphs: this.config.serviceList,
377
+ pollIntervalInMs: this.pollIntervalInMs,
378
+ logger: this.logger,
379
+ subgraphHealthCheck: this.config.serviceHealthCheck,
380
+ introspectionHeaders: this.config.introspectionHeaders,
381
+ }),
382
+ );
383
+ } else {
384
+ // isManagedConfig(this.config)
385
+ const canUseManagedConfig =
386
+ this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
387
+ if (!canUseManagedConfig) {
388
+ throw new Error(
389
+ 'When a manual configuration is not provided, gateway requires an Apollo ' +
390
+ 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
391
+ 'for more information. Manual configuration options include: ' +
392
+ '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
393
+ );
394
+ }
395
+ const uplinkEndpoints = this.getUplinkEndpoints(this.config);
396
+
397
+ await this.initializeSupergraphManager(
398
+ new UplinkFetcher({
399
+ graphRef: this.apolloConfig!.graphRef!,
400
+ apiKey: this.apolloConfig!.key!,
401
+ uplinkEndpoints,
402
+ maxRetries:
403
+ this.config.uplinkMaxRetries ?? uplinkEndpoints.length * 3,
404
+ subgraphHealthCheck: this.config.serviceHealthCheck,
405
+ fetcher: this.fetcher,
406
+ logger: this.logger,
407
+ pollIntervalInMs: this.pollIntervalInMs ?? 10000,
408
+ }),
409
+ );
410
+ }
412
411
 
413
412
  const mode = isManagedConfig(this.config) ? 'managed' : 'unmanaged';
414
413
  this.logger.info(
@@ -425,135 +424,157 @@ export class ApolloGateway implements GraphQLService {
425
424
  };
426
425
  }
427
426
 
428
- // Synchronously load a statically configured schema, update class instance's
429
- // schema and query planner.
430
- private loadStatic(config: StaticGatewayConfig) {
431
- let schema: GraphQLSchema;
432
- let supergraphSdl: string;
433
- try {
434
- ({ schema, supergraphSdl } = isLocalConfig(config)
435
- ? this.createSchemaFromServiceList(config.localServiceList)
436
- : this.createSchemaFromSupergraphSdl(config.supergraphSdl));
437
- // TODO(trevor): #580 redundant parse
438
- this.parsedSupergraphSdl = parse(supergraphSdl);
439
- this.supergraphSdl = supergraphSdl;
440
- this.updateWithSchemaAndNotify(schema, supergraphSdl, true);
441
- } catch (e) {
442
- this.state = { phase: 'failed to load' };
443
- throw e;
444
- }
445
- this.state = { phase: 'loaded' };
427
+ private getUplinkEndpoints(config: ManagedGatewayConfig) {
428
+ /**
429
+ * Configuration priority order:
430
+ * 1. `uplinkEndpoints` configuration option
431
+ * 2. (deprecated) `schemaConfigDeliveryEndpoint` configuration option
432
+ * 3. APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT environment variable
433
+ * 4. default (GCP and AWS)
434
+ */
435
+ const rawEndpointsString =
436
+ process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
437
+ const envEndpoints = rawEndpointsString?.split(',') ?? null;
438
+ return config.uplinkEndpoints ??
439
+ (config.schemaConfigDeliveryEndpoint
440
+ ? [config.schemaConfigDeliveryEndpoint]
441
+ : null) ??
442
+ envEndpoints ?? [
443
+ 'https://uplink.api.apollographql.com/',
444
+ 'https://aws.uplink.api.apollographql.com/',
445
+ ];
446
446
  }
447
447
 
448
- // Asynchronously load a dynamically configured schema. `this.updateSchema`
449
- // is responsible for updating the class instance's schema and query planner.
450
- private async loadDynamic(unrefTimer: boolean) {
448
+ private getIdForSupergraphSdl(supergraphSdl: string) {
449
+ return createHash('sha256').update(supergraphSdl).digest('hex');
450
+ }
451
+
452
+ private async initializeSupergraphManager<T extends SupergraphManager>(
453
+ supergraphManager: T,
454
+ ) {
451
455
  try {
452
- await this.updateSchema();
456
+ const result = await supergraphManager.initialize({
457
+ update: this.externalSupergraphUpdateCallback.bind(this),
458
+ healthCheck: this.externalSubgraphHealthCheckCallback.bind(this),
459
+ getDataSource: this.externalGetDataSourceCallback.bind(this),
460
+ });
461
+ if (!result?.supergraphSdl) {
462
+ throw new Error(
463
+ 'Provided `supergraphSdl` function did not return an object containing a `supergraphSdl` property',
464
+ );
465
+ }
466
+ if (result?.cleanup) {
467
+ if (typeof result.cleanup === 'function') {
468
+ this.toDispose.push(result.cleanup);
469
+ } else {
470
+ this.logger.error(
471
+ 'Provided `supergraphSdl` function returned an invalid `cleanup` property (must be a function)',
472
+ );
473
+ }
474
+ }
475
+
476
+ this.externalSupergraphUpdateCallback(result.supergraphSdl);
453
477
  } catch (e) {
454
478
  this.state = { phase: 'failed to load' };
479
+ await this.performCleanupAndLogErrors();
455
480
  throw e;
456
481
  }
457
482
 
458
483
  this.state = { phase: 'loaded' };
459
- if (this.shouldBeginPolling()) {
460
- this.pollServices(unrefTimer);
461
- }
462
- }
463
-
464
- private shouldBeginPolling() {
465
- return isManagedConfig(this.config) || this.experimental_pollInterval;
466
- }
467
-
468
- private async updateSchema(): Promise<void> {
469
- this.logger.debug('Checking for composition updates...');
470
-
471
- // This may throw, but an error here is caught and logged upstream
472
- const result = await this.updateServiceDefinitions(this.config);
473
-
474
- if (isSupergraphSdlUpdate(result)) {
475
- await this.updateWithSupergraphSdl(result);
476
- } else if (isServiceDefinitionUpdate(result)) {
477
- await this.updateByComposition(result);
478
- } else {
479
- throw new Error(
480
- 'Programming error: unexpected result type from `updateServiceDefinitions`',
481
- );
482
- }
483
484
  }
484
485
 
485
- private async updateByComposition(
486
- result: ServiceDefinitionUpdate,
487
- ): Promise<void> {
488
- if (
489
- !result.serviceDefinitions ||
490
- JSON.stringify(this.serviceDefinitions) ===
491
- JSON.stringify(result.serviceDefinitions)
492
- ) {
493
- this.logger.debug('No change in service definitions since last check.');
494
- return;
486
+ /**
487
+ * @throws Error
488
+ * when called from a state other than `loaded` or `intialized`
489
+ *
490
+ * @throws Error
491
+ * when the provided supergraphSdl is invalid
492
+ */
493
+ private externalSupergraphUpdateCallback(supergraphSdl: string) {
494
+ switch (this.state.phase) {
495
+ case 'failed to load':
496
+ throw new Error(
497
+ "Can't call `update` callback after gateway failed to load.",
498
+ );
499
+ case 'updating schema':
500
+ throw new Error(
501
+ "Can't call `update` callback while supergraph update is in progress.",
502
+ );
503
+ case 'stopped':
504
+ throw new Error(
505
+ "Can't call `update` callback after gateway has been stopped.",
506
+ );
507
+ case 'stopping':
508
+ throw new Error(
509
+ "Can't call `update` callback while gateway is stopping.",
510
+ );
511
+ case 'loaded':
512
+ case 'initialized':
513
+ // typical case
514
+ break;
515
+ default:
516
+ throw new UnreachableCaseError(this.state);
495
517
  }
496
518
 
497
- const previousSchema = this.schema;
498
- const previousServiceDefinitions = this.serviceDefinitions;
499
- const previousCompositionMetadata = this.compositionMetadata;
500
-
501
- if (previousSchema) {
502
- this.logger.info('New service definitions were found.');
519
+ this.state = { phase: 'updating schema' };
520
+ try {
521
+ this.updateWithSupergraphSdl({
522
+ supergraphSdl,
523
+ id: this.getIdForSupergraphSdl(supergraphSdl),
524
+ });
525
+ } finally {
526
+ // if update fails, we still want to go back to `loaded` state
527
+ this.state = { phase: 'loaded' };
503
528
  }
529
+ }
504
530
 
505
- await this.maybePerformServiceHealthCheck(result);
506
-
507
- this.compositionMetadata = result.compositionMetadata;
508
- this.serviceDefinitions = result.serviceDefinitions;
509
-
510
- const { schema, supergraphSdl } = this.createSchemaFromServiceList(
511
- result.serviceDefinitions,
512
- );
531
+ /**
532
+ * @throws Error
533
+ * when any subgraph fails the health check
534
+ */
535
+ private async externalSubgraphHealthCheckCallback(supergraphSdl: string) {
536
+ const parsedSupergraphSdl =
537
+ supergraphSdl === this.supergraphSdl
538
+ ? this.parsedSupergraphSdl
539
+ : parse(supergraphSdl);
540
+
541
+ const serviceList = this.serviceListFromSupergraphSdl(parsedSupergraphSdl!);
542
+ // Here we need to construct new datasources based on the new schema info
543
+ // so we can check the health of the services we're _updating to_.
544
+ const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
545
+ serviceMap[serviceDef.name] = {
546
+ url: serviceDef.url,
547
+ dataSource: this.createDataSource(serviceDef),
548
+ };
549
+ return serviceMap;
550
+ }, Object.create(null) as DataSourceMap);
513
551
 
514
- if (!supergraphSdl) {
515
- this.logger.error(
516
- "A valid schema couldn't be composed. Falling back to previous schema.",
552
+ try {
553
+ await this.serviceHealthCheck(serviceMap);
554
+ } catch (e) {
555
+ throw new Error(
556
+ 'The gateway subgraphs health check failed. Updating to the provided ' +
557
+ '`supergraphSdl` will likely result in future request failures to ' +
558
+ 'subgraphs. The following error occurred during the health check:\n' +
559
+ e.message,
517
560
  );
518
- } else {
519
- this.updateWithSchemaAndNotify(schema, supergraphSdl);
520
-
521
- if (this.experimental_didUpdateComposition) {
522
- this.experimental_didUpdateComposition(
523
- {
524
- serviceDefinitions: result.serviceDefinitions,
525
- schema,
526
- ...(this.compositionMetadata && {
527
- compositionMetadata: this.compositionMetadata,
528
- }),
529
- },
530
- previousServiceDefinitions &&
531
- previousSchema && {
532
- serviceDefinitions: previousServiceDefinitions,
533
- schema: previousSchema,
534
- ...(previousCompositionMetadata && {
535
- compositionMetadata: previousCompositionMetadata,
536
- }),
537
- },
538
- );
539
- }
540
561
  }
541
562
  }
542
563
 
543
- private async updateWithSupergraphSdl(
544
- result: SupergraphSdlUpdate,
545
- ): Promise<void> {
546
- if (result.id === this.compositionId) {
547
- this.logger.debug('No change in composition since last check.');
548
- return;
549
- }
564
+ private externalGetDataSourceCallback({
565
+ name,
566
+ url,
567
+ }: ServiceEndpointDefinition) {
568
+ return this.getOrCreateDataSource({ name, url });
569
+ }
550
570
 
571
+ private updateWithSupergraphSdl({ supergraphSdl, id }: SupergraphSdlUpdate) {
551
572
  // TODO(trevor): #580 redundant parse
552
573
  // This may throw, so we'll calculate early (specifically before making any updates)
553
574
  // In the case that it throws, the gateway will:
554
575
  // * on initial load, throw the error
555
576
  // * on update, log the error and don't update
556
- const parsedSupergraphSdl = parse(result.supergraphSdl);
577
+ const parsedSupergraphSdl = parse(supergraphSdl);
557
578
 
558
579
  const previousSchema = this.schema;
559
580
  const previousSupergraphSdl = this.parsedSupergraphSdl;
@@ -563,28 +584,25 @@ export class ApolloGateway implements GraphQLService {
563
584
  this.logger.info('Updated Supergraph SDL was found.');
564
585
  }
565
586
 
566
- await this.maybePerformServiceHealthCheck(result);
567
-
568
- this.compositionId = result.id;
569
- this.supergraphSdl = result.supergraphSdl;
587
+ this.compositionId = id;
588
+ this.supergraphSdl = supergraphSdl;
570
589
  this.parsedSupergraphSdl = parsedSupergraphSdl;
571
590
 
572
- const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
573
- result.supergraphSdl,
574
- );
591
+ const { schema, supergraphSdl: generatedSupergraphSdl } =
592
+ this.createSchemaFromSupergraphSdl(supergraphSdl);
575
593
 
576
- if (!supergraphSdl) {
594
+ if (!generatedSupergraphSdl) {
577
595
  this.logger.error(
578
596
  "A valid schema couldn't be composed. Falling back to previous schema.",
579
597
  );
580
598
  } else {
581
- this.updateWithSchemaAndNotify(schema, supergraphSdl);
599
+ this.updateWithSchemaAndNotify(schema, generatedSupergraphSdl);
582
600
 
583
- if (this.experimental_didUpdateComposition) {
584
- this.experimental_didUpdateComposition(
601
+ if (this.experimental_didUpdateSupergraph) {
602
+ this.experimental_didUpdateSupergraph(
585
603
  {
586
- compositionId: result.id,
587
- supergraphSdl: result.supergraphSdl,
604
+ compositionId: id,
605
+ supergraphSdl,
588
606
  schema,
589
607
  },
590
608
  previousCompositionId && previousSupergraphSdl && previousSchema
@@ -644,40 +662,6 @@ export class ApolloGateway implements GraphQLService {
644
662
  });
645
663
  }
646
664
 
647
- private async maybePerformServiceHealthCheck(update: CompositionUpdate) {
648
- // Run service health checks before we commit and update the new schema.
649
- // This is the last chance to bail out of a schema update.
650
- if (this.config.serviceHealthCheck) {
651
- const serviceList = isSupergraphSdlUpdate(update)
652
- ? // TODO(trevor): #580 redundant parse
653
- // Parsing could technically fail and throw here, but parseability has
654
- // already been confirmed slightly earlier in the code path
655
- this.serviceListFromSupergraphSdl(parse(update.supergraphSdl))
656
- : // Existence of this is determined in advance with an early return otherwise
657
- update.serviceDefinitions!;
658
- // Here we need to construct new datasources based on the new schema info
659
- // so we can check the health of the services we're _updating to_.
660
- const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
661
- serviceMap[serviceDef.name] = {
662
- url: serviceDef.url,
663
- dataSource: this.createDataSource(serviceDef),
664
- };
665
- return serviceMap;
666
- }, Object.create(null) as DataSourceMap);
667
-
668
- try {
669
- await this.serviceHealthCheck(serviceMap);
670
- } catch (e) {
671
- throw new Error(
672
- 'The gateway did not update its schema due to failed service health checks. ' +
673
- 'The gateway will continue to operate with the previous schema and reattempt updates. ' +
674
- 'The following error occurred during the health check:\n' +
675
- e.message,
676
- );
677
- }
678
- }
679
- }
680
-
681
665
  /**
682
666
  * This can be used without an argument in order to perform an ad-hoc health check
683
667
  * of the downstream services like so:
@@ -710,51 +694,6 @@ export class ApolloGateway implements GraphQLService {
710
694
  );
711
695
  }
712
696
 
713
- private createSchemaFromServiceList(serviceList: ServiceDefinition[]) {
714
- this.logger.debug(
715
- `Composing schema from service list: \n${serviceList
716
- .map(({ name, url }) => ` ${url || 'local'}: ${name}`)
717
- .join('\n')}`,
718
- );
719
-
720
- const compositionResult = composeAndValidate(serviceList);
721
-
722
- if (compositionHasErrors(compositionResult)) {
723
- const { errors } = compositionResult;
724
- if (this.experimental_didFailComposition) {
725
- this.experimental_didFailComposition({
726
- errors,
727
- serviceList,
728
- ...(this.compositionMetadata && {
729
- compositionMetadata: this.compositionMetadata,
730
- }),
731
- });
732
- }
733
- throw Error(
734
- "A valid schema couldn't be composed. The following composition errors were found:\n" +
735
- errors.map((e) => '\t' + e.message).join('\n'),
736
- );
737
- } else {
738
- const { supergraphSdl } = compositionResult;
739
- this.createServices(serviceList);
740
-
741
- const schema = buildComposedSchema(parse(supergraphSdl));
742
-
743
- this.logger.debug('Schema loaded and ready for execution');
744
-
745
- // This is a workaround for automatic wrapping of all fields, which Apollo
746
- // Server does in the case of implementing resolver wrapping for plugins.
747
- // Here we wrap all fields with support for resolving aliases as part of the
748
- // root value which happens because aliases are resolved by sub services and
749
- // the shape of the root value already contains the aliased fields as
750
- // responseNames
751
- return {
752
- schema: wrapSchemaWithAliasResolver(schema),
753
- supergraphSdl,
754
- };
755
- }
756
- }
757
-
758
697
  private serviceListFromSupergraphSdl(
759
698
  supergraphSdl: DocumentNode,
760
699
  ): Omit<ServiceDefinition, 'typeDefs'>[] {
@@ -819,93 +758,16 @@ export class ApolloGateway implements GraphQLService {
819
758
  };
820
759
  }
821
760
 
822
- // This function waits an appropriate amount, updates composition, and calls itself
823
- // again. Note that it is an async function whose Promise is not actually awaited;
824
- // it should never throw itself other than due to a bug in its state machine.
825
- private async pollServices(unrefTimer: boolean) {
826
- switch (this.state.phase) {
827
- case 'stopping':
828
- case 'stopped':
829
- case 'failed to load':
830
- return;
831
- case 'initialized':
832
- throw Error('pollServices should not be called before load!');
833
- case 'polling':
834
- throw Error(
835
- 'pollServices should not be called while in the middle of polling!',
836
- );
837
- case 'waiting to poll':
838
- throw Error(
839
- 'pollServices should not be called while already waiting to poll!',
840
- );
841
- case 'loaded':
842
- // This is the normal case.
843
- break;
844
- default:
845
- throw new UnreachableCaseError(this.state);
846
- }
847
-
848
- // Transition into 'waiting to poll' and set a timer. The timer resolves the
849
- // Promise we're awaiting here; note that calling stop() also can resolve
850
- // that Promise.
851
- await new Promise<void>((doneWaiting) => {
852
- this.state = {
853
- phase: 'waiting to poll',
854
- doneWaiting,
855
- pollWaitTimer: setTimeout(() => {
856
- // Note that we might be in 'stopped', in which case we just do
857
- // nothing.
858
- if (this.state.phase == 'waiting to poll') {
859
- this.state.doneWaiting();
860
- }
861
- }, this.experimental_pollInterval || 10000),
862
- };
863
- if (unrefTimer) {
864
- this.state.pollWaitTimer.unref();
865
- }
866
- });
867
-
868
- // If we've been stopped, then we're done. The cast here is because TS
869
- // doesn't understand that this.state can change during the await
870
- // (https://github.com/microsoft/TypeScript/issues/9998).
871
- if ((this.state as GatewayState).phase !== 'waiting to poll') {
872
- return;
873
- }
874
-
875
- let pollingDone: () => void;
876
- this.state = {
877
- phase: 'polling',
878
- pollingDonePromise: new Promise<void>((res) => {
879
- pollingDone = res;
880
- }),
881
- };
882
-
883
- try {
884
- await this.updateSchema();
885
- } catch (err) {
886
- this.logger.error((err && err.message) || err);
887
- }
888
-
889
- if (this.state.phase === 'polling') {
890
- // If we weren't stopped, we should transition back to the initial 'loading' state and trigger
891
- // another call to itself. (Do that in a setImmediate to avoid unbounded stack sizes.)
892
- this.state = { phase: 'loaded' };
893
- setImmediate(() => this.pollServices(unrefTimer));
894
- }
895
-
896
- // Whether we were stopped or not, let any concurrent stop() call finish.
897
- pollingDone!();
898
- }
899
-
900
- private createAndCacheDataSource(
761
+ private getOrCreateDataSource(
901
762
  serviceDef: ServiceEndpointDefinition,
902
763
  ): GraphQLDataSource {
903
764
  // If the DataSource has already been created, early return
904
765
  if (
905
766
  this.serviceMap[serviceDef.name] &&
906
767
  serviceDef.url === this.serviceMap[serviceDef.name].url
907
- )
768
+ ) {
908
769
  return this.serviceMap[serviceDef.name].dataSource;
770
+ }
909
771
 
910
772
  const dataSource = this.createDataSource(serviceDef);
911
773
 
@@ -933,57 +795,8 @@ export class ApolloGateway implements GraphQLService {
933
795
 
934
796
  private createServices(services: ServiceEndpointDefinition[]) {
935
797
  for (const serviceDef of services) {
936
- this.createAndCacheDataSource(serviceDef);
937
- }
938
- }
939
-
940
- protected async loadServiceDefinitions(
941
- config: RemoteGatewayConfig | ManagedGatewayConfig,
942
- ): Promise<CompositionUpdate> {
943
- if (isRemoteConfig(config)) {
944
- const serviceList = config.serviceList.map((serviceDefinition) => ({
945
- ...serviceDefinition,
946
- dataSource: this.createAndCacheDataSource(serviceDefinition),
947
- }));
948
-
949
- return getServiceDefinitionsFromRemoteEndpoint({
950
- serviceList,
951
- async getServiceIntrospectionHeaders(service) {
952
- return typeof config.introspectionHeaders === 'function'
953
- ? await config.introspectionHeaders(service)
954
- : config.introspectionHeaders;
955
- },
956
- serviceSdlCache: this.serviceSdlCache,
957
- });
958
- }
959
-
960
- const canUseManagedConfig =
961
- this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
962
- if (!canUseManagedConfig) {
963
- throw new Error(
964
- 'When a manual configuration is not provided, gateway requires an Apollo ' +
965
- 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
966
- 'for more information. Manual configuration options include: ' +
967
- '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
968
- );
798
+ this.getOrCreateDataSource(serviceDef);
969
799
  }
970
-
971
- const result = await loadSupergraphSdlFromUplinks({
972
- graphRef: this.apolloConfig!.graphRef!,
973
- apiKey: this.apolloConfig!.key!,
974
- endpoints: this.uplinkEndpoints!,
975
- errorReportingEndpoint: this.errorReportingEndpoint,
976
- fetcher: this.fetcher,
977
- compositionId: this.compositionId ?? null,
978
- maxRetries: this.uplinkMaxRetries!,
979
- });
980
-
981
- return (
982
- result ?? {
983
- id: this.compositionId!,
984
- supergraphSdl: this.supergraphSdl!,
985
- }
986
- );
987
800
  }
988
801
 
989
802
  private maybeWarnOnConflictingConfig() {
@@ -1202,9 +1015,24 @@ export class ApolloGateway implements GraphQLService {
1202
1015
  });
1203
1016
  }
1204
1017
 
1205
- // Stops all processes involved with the gateway (for now, just background
1206
- // schema polling). Can be called multiple times safely. Once it (async)
1207
- // returns, all gateway background activity will be finished.
1018
+ private async performCleanupAndLogErrors() {
1019
+ if (this.toDispose.length === 0) return;
1020
+
1021
+ await Promise.all(
1022
+ this.toDispose.map((p) =>
1023
+ p().catch((e) => {
1024
+ this.logger.error(
1025
+ 'Error occured while calling user provided `cleanup` function: ' +
1026
+ (e.message ?? e),
1027
+ );
1028
+ }),
1029
+ ),
1030
+ );
1031
+ this.toDispose = [];
1032
+ }
1033
+
1034
+ // Stops all processes involved with the gateway. Can be called multiple times
1035
+ // safely. Once it (async) returns, all gateway background activity will be finished.
1208
1036
  public async stop() {
1209
1037
  switch (this.state.phase) {
1210
1038
  case 'initialized':
@@ -1227,40 +1055,31 @@ export class ApolloGateway implements GraphQLService {
1227
1055
  }
1228
1056
  return;
1229
1057
  case 'loaded':
1230
- this.state = { phase: 'stopped' }; // nothing to do (we're not polling)
1231
- return;
1232
- case 'waiting to poll': {
1233
- // If we're waiting to poll, we can synchronously transition to fully stopped.
1234
- // We will terminate the current pollServices call and it will succeed quickly.
1235
- const doneWaiting = this.state.doneWaiting;
1236
- clearTimeout(this.state.pollWaitTimer);
1237
- this.state = { phase: 'stopped' };
1238
- doneWaiting();
1239
- return;
1240
- }
1241
- case 'polling': {
1242
- // We're in the middle of running updateSchema. We need to go into 'stopping'
1243
- // mode and let this run complete. First we set things up so that any concurrent
1244
- // calls to stop() will wait until we let them finish. (Those concurrent calls shouldn't
1245
- // just wait on pollingDonePromise themselves because we want to make sure we fully
1246
- // transition to state='stopped' before the other call returns.)
1247
- const pollingDonePromise = this.state.pollingDonePromise;
1248
- let stoppingDone: () => void;
1058
+ const stoppingDonePromise = this.performCleanupAndLogErrors();
1249
1059
  this.state = {
1250
1060
  phase: 'stopping',
1251
- stoppingDonePromise: new Promise<void>((res) => {
1252
- stoppingDone = res;
1253
- }),
1061
+ stoppingDonePromise,
1254
1062
  };
1255
- await pollingDonePromise;
1063
+ await stoppingDonePromise;
1256
1064
  this.state = { phase: 'stopped' };
1257
- stoppingDone!();
1258
1065
  return;
1066
+ case 'updating schema': {
1067
+ throw Error(
1068
+ '`ApolloGateway.stop` shouldn\'t be called from inside a schema change listener',
1069
+ );
1259
1070
  }
1260
1071
  default:
1261
1072
  throw new UnreachableCaseError(this.state);
1262
1073
  }
1263
1074
  }
1075
+
1076
+ public __testing() {
1077
+ return {
1078
+ state: this.state,
1079
+ compositionId: this.compositionId,
1080
+ supergraphSdl: this.supergraphSdl,
1081
+ };
1082
+ }
1264
1083
  }
1265
1084
 
1266
1085
  ApolloGateway.prototype.onSchemaChange = deprecate(
@@ -1307,11 +1126,21 @@ export {
1307
1126
  ServiceMap,
1308
1127
  Experimental_DidFailCompositionCallback,
1309
1128
  Experimental_DidResolveQueryPlanCallback,
1310
- Experimental_DidUpdateCompositionCallback,
1129
+ Experimental_DidUpdateSupergraphCallback,
1311
1130
  Experimental_UpdateComposition,
1312
1131
  GatewayConfig,
1313
1132
  ServiceEndpointDefinition,
1314
1133
  CompositionInfo,
1134
+ IntrospectAndCompose,
1135
+ LocalCompose,
1315
1136
  };
1316
1137
 
1317
1138
  export * from './datasources';
1139
+
1140
+ export {
1141
+ SupergraphSdlUpdateFunction,
1142
+ SubgraphHealthCheckFunction,
1143
+ GetDataSourceFunction,
1144
+ SupergraphSdlHook,
1145
+ SupergraphManager
1146
+ } from './config';