@apollo/gateway 2.0.0-alpha.3 → 2.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/config.d.ts +41 -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/executeQueryPlan.d.ts.map +1 -1
  7. package/dist/executeQueryPlan.js +1 -1
  8. package/dist/executeQueryPlan.js.map +1 -1
  9. package/dist/index.d.ts +36 -24
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +197 -295
  12. package/dist/index.js.map +1 -1
  13. package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
  14. package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
  15. package/dist/schema-helper/addResolversToSchema.js +62 -0
  16. package/dist/schema-helper/addResolversToSchema.js.map +1 -0
  17. package/dist/schema-helper/error.d.ts +6 -0
  18. package/dist/schema-helper/error.d.ts.map +1 -0
  19. package/dist/schema-helper/error.js +14 -0
  20. package/dist/schema-helper/error.js.map +1 -0
  21. package/dist/schema-helper/index.d.ts +4 -0
  22. package/dist/schema-helper/index.d.ts.map +1 -0
  23. package/dist/schema-helper/index.js +16 -0
  24. package/dist/schema-helper/index.js.map +1 -0
  25. package/dist/schema-helper/resolverMap.d.ts +16 -0
  26. package/dist/schema-helper/resolverMap.d.ts.map +1 -0
  27. package/dist/schema-helper/resolverMap.js +3 -0
  28. package/dist/schema-helper/resolverMap.js.map +1 -0
  29. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
  30. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
  31. package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
  32. package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
  33. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
  34. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
  35. package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
  36. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
  37. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
  38. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
  39. package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
  40. package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
  41. package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
  42. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
  43. package/dist/supergraphManagers/LocalCompose/index.js +55 -0
  44. package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
  45. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +32 -0
  46. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
  47. package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
  48. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
  49. package/dist/{loadSupergraphSdlFromStorage.d.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts} +1 -1
  50. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  51. package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +1 -1
  52. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
  53. package/dist/{outOfBandReporter.d.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts} +0 -0
  54. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
  55. package/dist/{outOfBandReporter.js → supergraphManagers/UplinkFetcher/outOfBandReporter.js} +2 -2
  56. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
  57. package/dist/supergraphManagers/index.d.ts +5 -0
  58. package/dist/supergraphManagers/index.d.ts.map +1 -0
  59. package/dist/supergraphManagers/index.js +12 -0
  60. package/dist/supergraphManagers/index.js.map +1 -0
  61. package/dist/utilities/createHash.d.ts +2 -0
  62. package/dist/utilities/createHash.d.ts.map +1 -0
  63. package/dist/utilities/createHash.js +15 -0
  64. package/dist/utilities/createHash.js.map +1 -0
  65. package/dist/utilities/isNodeLike.d.ts +3 -0
  66. package/dist/utilities/isNodeLike.d.ts.map +1 -0
  67. package/dist/utilities/isNodeLike.js +8 -0
  68. package/dist/utilities/isNodeLike.js.map +1 -0
  69. package/package.json +8 -7
  70. package/src/__tests__/executeQueryPlan.test.ts +1 -1
  71. package/src/__tests__/execution-utils.ts +3 -3
  72. package/src/__tests__/gateway/buildService.test.ts +2 -2
  73. package/src/__tests__/gateway/endToEnd.test.ts +1 -1
  74. package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -125
  75. package/src/__tests__/gateway/opentelemetry.test.ts +8 -4
  76. package/src/__tests__/gateway/queryPlanCache.test.ts +25 -12
  77. package/src/__tests__/gateway/reporting.test.ts +34 -8
  78. package/src/__tests__/gateway/supergraphSdl.test.ts +397 -0
  79. package/src/__tests__/integration/aliases.test.ts +9 -4
  80. package/src/__tests__/integration/configuration.test.ts +109 -12
  81. package/src/__tests__/integration/logger.test.ts +1 -1
  82. package/src/__tests__/integration/networkRequests.test.ts +81 -131
  83. package/src/__tests__/integration/nockMocks.ts +15 -8
  84. package/src/config.ts +148 -39
  85. package/src/datasources/LocalGraphQLDataSource.ts +1 -1
  86. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
  87. package/src/executeQueryPlan.ts +3 -1
  88. package/src/index.ts +322 -466
  89. package/src/schema-helper/addResolversToSchema.ts +83 -0
  90. package/src/schema-helper/error.ts +11 -0
  91. package/src/schema-helper/index.ts +3 -0
  92. package/src/schema-helper/resolverMap.ts +23 -0
  93. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
  94. package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +5 -5
  95. package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
  96. package/src/supergraphManagers/IntrospectAndCompose/index.ts +160 -0
  97. package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
  98. package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
  99. package/src/supergraphManagers/LocalCompose/index.ts +79 -0
  100. package/src/{__tests__ → supergraphManagers/UplinkFetcher/__tests__}/loadSupergraphSdlFromStorage.test.ts +4 -4
  101. package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
  102. package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
  103. package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +3 -3
  104. package/src/{outOfBandReporter.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.ts} +2 -2
  105. package/src/supergraphManagers/index.ts +4 -0
  106. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +12 -9
  107. package/src/utilities/createHash.ts +10 -0
  108. package/src/utilities/isNodeLike.ts +11 -0
  109. package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
  110. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
  111. package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
  112. package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
  113. package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
  114. package/dist/outOfBandReporter.d.ts.map +0 -1
  115. package/dist/outOfBandReporter.js.map +0 -1
  116. package/src/__tests__/gateway/composedSdl.test.ts +0 -44
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 {loadSupergraphSdlFromUplinks} 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,39 +161,23 @@ 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;
184
167
  private fetcher: typeof fetch;
185
168
  private compositionId?: string;
186
169
  private state: GatewayState;
187
- private errorReportingEndpoint: string | undefined =
188
- process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT ?? undefined;
189
170
 
190
171
  // Observe query plan, service info, and operation info prior to execution.
191
172
  // The information made available here will give insight into the resulting
192
173
  // query plan and the inputs that generated it.
193
174
  private experimental_didResolveQueryPlan?: Experimental_DidResolveQueryPlanCallback;
194
- // Observe composition failures and the ServiceList that caused them. This
195
- // enables reporting any issues that occur during composition. Implementors
196
- // will be interested in addressing these immediately.
197
- private experimental_didFailComposition?: Experimental_DidFailCompositionCallback;
198
- // Used to communicated composition changes, and what definitions caused
199
- // those updates
200
- private experimental_didUpdateComposition?: Experimental_DidUpdateCompositionCallback;
201
- // Used for overriding the default service list fetcher. This should return
202
- // an array of ServiceDefinition. *This function must be awaited.*
203
- private updateServiceDefinitions: Experimental_UpdateComposition;
204
- // how often service defs should be loaded/updated (in ms)
205
- private experimental_pollInterval?: number;
206
- // Configure the endpoints by which gateway will access its precomposed schema.
207
- // * An array of URLs means use these endpoints to obtain schema, if one is unavailable then try the next.
208
- // * `undefined` means the gateway is not using managed federation
209
- private uplinkEndpoints?: string[];
210
- private uplinkMaxRetries?: number;
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>)[] = [];
211
181
 
212
182
  constructor(config?: GatewayConfig) {
213
183
  this.config = {
@@ -227,54 +197,15 @@ export class ApolloGateway implements GraphQLService {
227
197
  // set up experimental observability callbacks and config settings
228
198
  this.experimental_didResolveQueryPlan =
229
199
  config?.experimental_didResolveQueryPlan;
230
- this.experimental_didFailComposition =
231
- config?.experimental_didFailComposition;
232
- this.experimental_didUpdateComposition =
233
- config?.experimental_didUpdateComposition;
234
-
235
- this.experimental_pollInterval = config?.experimental_pollInterval;
236
-
237
- // 1. If config is set to a `string`, use it
238
- // 2. If the env var is set, use that
239
- // 3. If config is `undefined`, use the default uplink URLs
240
- if (isManagedConfig(this.config)) {
241
- const rawEndpointsString = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
242
- const envEndpoints = rawEndpointsString?.split(",") ?? null;
243
-
244
- if (this.config.schemaConfigDeliveryEndpoint && !this.config.uplinkEndpoints) {
245
- this.uplinkEndpoints = [this.config.schemaConfigDeliveryEndpoint];
246
- } else {
247
- this.uplinkEndpoints = this.config.uplinkEndpoints ??
248
- envEndpoints ?? [
249
- 'https://uplink.api.apollographql.com/',
250
- 'https://aws.uplink.api.apollographql.com/'
251
- ];
252
- }
200
+ this.experimental_didUpdateSupergraph =
201
+ config?.experimental_didUpdateSupergraph;
253
202
 
254
- this.uplinkMaxRetries = this.config.uplinkMaxRetries ?? this.uplinkEndpoints.length * 3;
255
- }
203
+ this.pollIntervalInMs =
204
+ config?.pollIntervalInMs ?? config?.experimental_pollInterval;
256
205
 
257
- if (isManuallyManagedConfig(this.config)) {
258
- // Use the provided updater function if provided by the user, else default
259
- if ('experimental_updateSupergraphSdl' in this.config) {
260
- this.updateServiceDefinitions =
261
- this.config.experimental_updateSupergraphSdl;
262
- } else if ('experimental_updateServiceDefinitions' in this.config) {
263
- this.updateServiceDefinitions =
264
- this.config.experimental_updateServiceDefinitions;
265
- } else {
266
- throw Error(
267
- 'Programming error: unexpected manual configuration provided',
268
- );
269
- }
270
- } else {
271
- this.updateServiceDefinitions = this.loadServiceDefinitions;
272
- }
273
-
274
- if (isDynamicConfig(this.config)) {
275
- this.issueDynamicWarningsIfApplicable();
276
- }
206
+ this.issueConfigurationWarningsIfApplicable();
277
207
 
208
+ this.logger.debug('Gateway successfully initialized (but not yet loaded)');
278
209
  this.state = { phase: 'initialized' };
279
210
  }
280
211
 
@@ -309,25 +240,26 @@ export class ApolloGateway implements GraphQLService {
309
240
  });
310
241
  }
311
242
 
312
- private issueDynamicWarningsIfApplicable() {
243
+ private issueConfigurationWarningsIfApplicable() {
313
244
  // Warn against a pollInterval of < 10s in managed mode and reset it to 10s
314
245
  if (
315
246
  isManagedConfig(this.config) &&
316
- this.config.experimental_pollInterval &&
317
- this.config.experimental_pollInterval < 10000
247
+ this.pollIntervalInMs &&
248
+ this.pollIntervalInMs < 10000
318
249
  ) {
319
- this.experimental_pollInterval = 10000;
250
+ this.pollIntervalInMs = 10000;
320
251
  this.logger.warn(
321
252
  'Polling Apollo services at a frequency of less than once per 10 ' +
322
253
  'seconds (10000) is disallowed. Instead, the minimum allowed ' +
323
254
  'pollInterval of 10000 will be used. Please reconfigure your ' +
324
- 'experimental_pollInterval accordingly. If this is problematic for ' +
255
+ '`pollIntervalInMs` accordingly. If this is problematic for ' +
325
256
  'your team, please contact support.',
326
257
  );
327
258
  }
328
259
 
329
260
  // Warn against using the pollInterval and a serviceList simultaneously
330
- if (this.config.experimental_pollInterval && isRemoteConfig(this.config)) {
261
+ // TODO(trevor:removeServiceList)
262
+ if (this.pollIntervalInMs && isServiceListConfig(this.config)) {
331
263
  this.logger.warn(
332
264
  'Polling running services is dangerous and not recommended in production. ' +
333
265
  'Polling should only be used against a registry. ' +
@@ -347,12 +279,26 @@ export class ApolloGateway implements GraphQLService {
347
279
  'are provided.',
348
280
  );
349
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
+ }
350
294
  }
351
295
 
352
296
  public async load(options?: {
353
297
  apollo?: ApolloConfigFromAS2Or3;
354
298
  engine?: GraphQLServiceEngineConfig;
355
299
  }) {
300
+ this.logger.debug('Loading gateway...');
301
+
356
302
  if (this.state.phase !== 'initialized') {
357
303
  throw Error(
358
304
  `ApolloGateway.load called in surprising state ${this.state.phase}`,
@@ -378,40 +324,92 @@ export class ApolloGateway implements GraphQLService {
378
324
  };
379
325
  }
380
326
 
381
- // Before @apollo/gateway v0.23, ApolloGateway didn't expect stop() to be
382
- // called after it started. The only thing that stop() did at that point was
383
- // cancel the poll timer, and so to prevent that timer from keeping an
384
- // otherwise-finished Node process alive, ApolloGateway unconditionally
385
- // called unref() on that timeout. As part of making the ApolloGateway
386
- // lifecycle more predictable and concrete (and to allow for a future where
387
- // there are other reasons to make sure to explicitly stop your gateway),
388
- // v0.23 tries to avoid calling unref().
389
- //
390
- // Apollo Server v2.20 and newer calls gateway.stop() from its stop()
391
- // method, so as long as you're using v2.20, ApolloGateway won't keep
392
- // running after you stop your server, and your Node process can shut down.
393
- // To make this change a bit less backwards-incompatible, we detect if it
394
- // looks like you're using an older version of Apollo Server; if so, we
395
- // still call unref(). Specifically: Apollo Server has always passed an
396
- // options object to load(), and before v2.18 it did not pass the `apollo`
397
- // key on it. So if we detect that particular pattern, we assume we're with
398
- // pre-v2.18 Apollo Server and we still call unref(). So this will be a
399
- // behavior change only for:
400
- // - non-Apollo-Server uses of ApolloGateway (where you can add your own
401
- // call to gateway.stop())
402
- // - Apollo Server v2.18 and v2.19 (where you can either do the small
403
- // compatible upgrade or add your own call to gateway.stop())
404
- // - if you don't call stop() on your ApolloServer (but in that case other
405
- // things like usage reporting will also stop shutdown, so you should fix
406
- // that)
407
- const unrefTimer = !!options && !options.apollo;
408
-
409
327
  this.maybeWarnOnConflictingConfig();
410
328
 
411
329
  // Handles initial assignment of `this.schema`, `this.queryPlanner`
412
- isStaticConfig(this.config)
413
- ? this.loadStatic(this.config)
414
- : 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,
406
+ subgraphHealthCheck: this.config.serviceHealthCheck,
407
+ fetcher: this.fetcher,
408
+ logger: this.logger,
409
+ pollIntervalInMs: this.pollIntervalInMs ?? 10000,
410
+ }),
411
+ );
412
+ }
415
413
 
416
414
  const mode = isManagedConfig(this.config) ? 'managed' : 'unmanaged';
417
415
  this.logger.info(
@@ -428,122 +426,148 @@ export class ApolloGateway implements GraphQLService {
428
426
  };
429
427
  }
430
428
 
431
- // Synchronously load a statically configured schema, update class instance's
432
- // schema and query planner.
433
- private loadStatic(config: StaticGatewayConfig) {
434
- let schema: Schema;
435
- let supergraphSdl: string;
436
- try {
437
- ({ schema, supergraphSdl } = isLocalConfig(config)
438
- ? this.createSchemaFromServiceList(config.localServiceList)
439
- : this.createSchemaFromSupergraphSdl(config.supergraphSdl));
440
- this.supergraphSdl = supergraphSdl;
441
- this.updateWithSchemaAndNotify(schema, supergraphSdl, true);
442
- } catch (e) {
443
- this.state = { phase: 'failed to load' };
444
- throw e;
445
- }
446
- 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
+ );
447
450
  }
448
451
 
449
- // Asynchronously load a dynamically configured schema. `this.updateSchema`
450
- // is responsible for updating the class instance's schema and query planner.
451
- private async loadDynamic(unrefTimer: boolean) {
452
+ private getIdForSupergraphSdl(supergraphSdl: string) {
453
+ return createHash('sha256').update(supergraphSdl).digest('hex');
454
+ }
455
+
456
+ private async initializeSupergraphManager<T extends SupergraphManager>(
457
+ supergraphManager: T,
458
+ ) {
452
459
  try {
453
- 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);
454
481
  } catch (e) {
455
482
  this.state = { phase: 'failed to load' };
483
+ await this.performCleanupAndLogErrors();
456
484
  throw e;
457
485
  }
458
486
 
459
487
  this.state = { phase: 'loaded' };
460
- if (this.shouldBeginPolling()) {
461
- this.pollServices(unrefTimer);
462
- }
463
488
  }
464
489
 
465
- private shouldBeginPolling() {
466
- return isManagedConfig(this.config) || this.experimental_pollInterval;
467
- }
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
+ }
468
522
 
469
- private async updateSchema(): Promise<void> {
470
- 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
+ }
471
534
 
472
- // This may throw, but an error here is caught and logged upstream
473
- 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);
474
550
 
475
- if (isSupergraphSdlUpdate(result)) {
476
- await this.updateWithSupergraphSdl(result);
477
- } else if (isServiceDefinitionUpdate(result)) {
478
- await this.updateByComposition(result);
479
- } else {
551
+ try {
552
+ await this.serviceHealthCheck(serviceMap);
553
+ } catch (e) {
480
554
  throw new Error(
481
- '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,
482
559
  );
483
560
  }
484
561
  }
485
562
 
486
- private async updateByComposition(
487
- result: ServiceDefinitionUpdate,
488
- ): Promise<void> {
489
- if (
490
- !result.serviceDefinitions ||
491
- JSON.stringify(this.serviceDefinitions) ===
492
- JSON.stringify(result.serviceDefinitions)
493
- ) {
494
- this.logger.debug('No change in service definitions since last check.');
495
- return;
496
- }
497
-
498
- const previousSchema = this.schema;
499
- const previousServiceDefinitions = this.serviceDefinitions;
500
- const previousCompositionMetadata = this.compositionMetadata;
501
-
502
- if (previousSchema) {
503
- this.logger.info('New service definitions were found.');
504
- }
505
-
506
- await this.maybePerformServiceHealthCheck(result);
507
-
508
- this.compositionMetadata = result.compositionMetadata;
509
- this.serviceDefinitions = result.serviceDefinitions;
510
-
511
- const { schema, supergraphSdl } = this.createSchemaFromServiceList(
512
- result.serviceDefinitions,
513
- );
514
-
515
- if (!supergraphSdl) {
516
- this.logger.error(
517
- "A valid schema couldn't be composed. Falling back to previous schema.",
518
- );
519
- } else {
520
- this.updateWithSchemaAndNotify(schema, supergraphSdl);
521
-
522
- if (this.experimental_didUpdateComposition) {
523
- this.experimental_didUpdateComposition(
524
- {
525
- serviceDefinitions: result.serviceDefinitions,
526
- schema: schema.toGraphQLJSSchema(),
527
- ...(this.compositionMetadata && {
528
- compositionMetadata: this.compositionMetadata,
529
- }),
530
- },
531
- previousServiceDefinitions &&
532
- previousSchema && {
533
- serviceDefinitions: previousServiceDefinitions,
534
- schema: previousSchema,
535
- ...(previousCompositionMetadata && {
536
- compositionMetadata: previousCompositionMetadata,
537
- }),
538
- },
539
- );
540
- }
541
- }
563
+ private externalGetDataSourceCallback({
564
+ name,
565
+ url,
566
+ }: ServiceEndpointDefinition) {
567
+ return this.getOrCreateDataSource({ name, url });
542
568
  }
543
569
 
544
- private async updateWithSupergraphSdl(
545
- result: SupergraphSdlUpdate,
546
- ): Promise<void> {
570
+ private updateWithSupergraphSdl(result: SupergraphSdlUpdate) {
547
571
  if (result.id === this.compositionId) {
548
572
  this.logger.debug('No change in composition since last check.');
549
573
  return;
@@ -553,7 +577,9 @@ export class ApolloGateway implements GraphQLService {
553
577
  // In the case that it throws, the gateway will:
554
578
  // * on initial load, throw the error
555
579
  // * on update, log the error and don't update
556
- const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(result.supergraphSdl);
580
+ const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
581
+ result.supergraphSdl,
582
+ );
557
583
 
558
584
  const previousSchema = this.schema;
559
585
  const previousSupergraphSdl = this.supergraphSdl;
@@ -563,11 +589,8 @@ export class ApolloGateway implements GraphQLService {
563
589
  this.logger.info('Updated Supergraph SDL was found.');
564
590
  }
565
591
 
566
- await this.maybePerformServiceHealthCheck(result);
567
-
568
592
  this.compositionId = result.id;
569
- this.supergraphSdl = result.supergraphSdl;
570
-
593
+ this.supergraphSdl = supergraphSdl;
571
594
 
572
595
  if (!supergraphSdl) {
573
596
  this.logger.error(
@@ -576,11 +599,11 @@ export class ApolloGateway implements GraphQLService {
576
599
  } else {
577
600
  this.updateWithSchemaAndNotify(schema, supergraphSdl);
578
601
 
579
- if (this.experimental_didUpdateComposition) {
580
- this.experimental_didUpdateComposition(
602
+ if (this.experimental_didUpdateSupergraph) {
603
+ this.experimental_didUpdateSupergraph(
581
604
  {
582
605
  compositionId: result.id,
583
- supergraphSdl: result.supergraphSdl,
606
+ supergraphSdl,
584
607
  schema: schema.toGraphQLJSSchema(),
585
608
  },
586
609
  previousCompositionId && previousSupergraphSdl && previousSchema
@@ -606,7 +629,9 @@ export class ApolloGateway implements GraphQLService {
606
629
  ): void {
607
630
  if (this.queryPlanStore) this.queryPlanStore.flush();
608
631
  this.apiSchema = coreSchema.toAPISchema();
609
- this.schema = wrapSchemaWithAliasResolver(this.apiSchema.toGraphQLJSSchema());
632
+ this.schema = wrapSchemaWithAliasResolver(
633
+ this.apiSchema.toGraphQLJSSchema(),
634
+ );
610
635
  this.queryPlanner = new QueryPlanner(coreSchema);
611
636
 
612
637
  // Notify onSchemaChange listeners of the updated schema
@@ -641,39 +666,6 @@ export class ApolloGateway implements GraphQLService {
641
666
  });
642
667
  }
643
668
 
644
- private async maybePerformServiceHealthCheck(update: CompositionUpdate) {
645
- // Run service health checks before we commit and update the new schema.
646
- // This is the last chance to bail out of a schema update.
647
- if (this.config.serviceHealthCheck) {
648
- const serviceList = isSupergraphSdlUpdate(update)
649
- ? // Parsing of the supergraph SDL could technically fail and throw here, but parseability has
650
- // already been confirmed slightly earlier in the code path
651
- this.serviceListFromSupergraphSdl(update.supergraphSdl)
652
- : // Existence of this is determined in advance with an early return otherwise
653
- update.serviceDefinitions!;
654
- // Here we need to construct new datasources based on the new schema info
655
- // so we can check the health of the services we're _updating to_.
656
- const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
657
- serviceMap[serviceDef.name] = {
658
- url: serviceDef.url,
659
- dataSource: this.createDataSource(serviceDef),
660
- };
661
- return serviceMap;
662
- }, Object.create(null) as DataSourceMap);
663
-
664
- try {
665
- await this.serviceHealthCheck(serviceMap);
666
- } catch (e) {
667
- throw new Error(
668
- 'The gateway did not update its schema due to failed service health checks. ' +
669
- 'The gateway will continue to operate with the previous schema and reattempt updates. ' +
670
- 'The following error occurred during the health check:\n' +
671
- e.message,
672
- );
673
- }
674
- }
675
- }
676
-
677
669
  /**
678
670
  * This can be used without an argument in order to perform an ad-hoc health check
679
671
  * of the downstream services like so:
@@ -706,40 +698,9 @@ export class ApolloGateway implements GraphQLService {
706
698
  );
707
699
  }
708
700
 
709
- private createSchemaFromServiceList(serviceList: ServiceDefinition[]) {
710
- this.logger.debug(
711
- `Composing schema from service list: \n${serviceList
712
- .map(({ name, url }) => ` ${url || 'local'}: ${name}`)
713
- .join('\n')}`,
714
- );
715
-
716
- const compositionResult = composeServices(serviceList);
717
- const errors = compositionResult.errors;
718
- if (errors) {
719
- if (this.experimental_didFailComposition) {
720
- this.experimental_didFailComposition({
721
- errors,
722
- serviceList,
723
- ...(this.compositionMetadata && {
724
- compositionMetadata: this.compositionMetadata,
725
- }),
726
- });
727
- }
728
- throw Error(
729
- "A valid schema couldn't be composed. The following composition errors were found:\n" +
730
- errors.map((e) => '\t' + e.message).join('\n'),
731
- );
732
- } else {
733
- this.createServices(serviceList);
734
- this.logger.debug('Schema loaded and ready for execution');
735
- return {
736
- schema: compositionResult.schema,
737
- supergraphSdl: compositionResult.supergraphSdl,
738
- };
739
- }
740
- }
741
-
742
- private serviceListFromSupergraphSdl(supergraphSdl: string): Omit<ServiceDefinition, 'typeDefs'>[] {
701
+ private serviceListFromSupergraphSdl(
702
+ supergraphSdl: string,
703
+ ): Omit<ServiceDefinition, 'typeDefs'>[] {
743
704
  return buildSupergraphSchema(supergraphSdl)[1];
744
705
  }
745
706
 
@@ -779,93 +740,16 @@ export class ApolloGateway implements GraphQLService {
779
740
  };
780
741
  }
781
742
 
782
- // This function waits an appropriate amount, updates composition, and calls itself
783
- // again. Note that it is an async function whose Promise is not actually awaited;
784
- // it should never throw itself other than due to a bug in its state machine.
785
- private async pollServices(unrefTimer: boolean) {
786
- switch (this.state.phase) {
787
- case 'stopping':
788
- case 'stopped':
789
- case 'failed to load':
790
- return;
791
- case 'initialized':
792
- throw Error('pollServices should not be called before load!');
793
- case 'polling':
794
- throw Error(
795
- 'pollServices should not be called while in the middle of polling!',
796
- );
797
- case 'waiting to poll':
798
- throw Error(
799
- 'pollServices should not be called while already waiting to poll!',
800
- );
801
- case 'loaded':
802
- // This is the normal case.
803
- break;
804
- default:
805
- throw new UnreachableCaseError(this.state);
806
- }
807
-
808
- // Transition into 'waiting to poll' and set a timer. The timer resolves the
809
- // Promise we're awaiting here; note that calling stop() also can resolve
810
- // that Promise.
811
- await new Promise<void>((doneWaiting) => {
812
- this.state = {
813
- phase: 'waiting to poll',
814
- doneWaiting,
815
- pollWaitTimer: setTimeout(() => {
816
- // Note that we might be in 'stopped', in which case we just do
817
- // nothing.
818
- if (this.state.phase == 'waiting to poll') {
819
- this.state.doneWaiting();
820
- }
821
- }, this.experimental_pollInterval || 10000),
822
- };
823
- if (unrefTimer) {
824
- this.state.pollWaitTimer.unref();
825
- }
826
- });
827
-
828
- // If we've been stopped, then we're done. The cast here is because TS
829
- // doesn't understand that this.state can change during the await
830
- // (https://github.com/microsoft/TypeScript/issues/9998).
831
- if ((this.state as GatewayState).phase !== 'waiting to poll') {
832
- return;
833
- }
834
-
835
- let pollingDone: () => void;
836
- this.state = {
837
- phase: 'polling',
838
- pollingDonePromise: new Promise<void>((res) => {
839
- pollingDone = res;
840
- }),
841
- };
842
-
843
- try {
844
- await this.updateSchema();
845
- } catch (err) {
846
- this.logger.error((err && err.message) || err);
847
- }
848
-
849
- if (this.state.phase === 'polling') {
850
- // If we weren't stopped, we should transition back to the initial 'loading' state and trigger
851
- // another call to itself. (Do that in a setImmediate to avoid unbounded stack sizes.)
852
- this.state = { phase: 'loaded' };
853
- setImmediate(() => this.pollServices(unrefTimer));
854
- }
855
-
856
- // Whether we were stopped or not, let any concurrent stop() call finish.
857
- pollingDone!();
858
- }
859
-
860
- private createAndCacheDataSource(
743
+ private getOrCreateDataSource(
861
744
  serviceDef: ServiceEndpointDefinition,
862
745
  ): GraphQLDataSource {
863
746
  // If the DataSource has already been created, early return
864
747
  if (
865
748
  this.serviceMap[serviceDef.name] &&
866
749
  serviceDef.url === this.serviceMap[serviceDef.name].url
867
- )
750
+ ) {
868
751
  return this.serviceMap[serviceDef.name].dataSource;
752
+ }
869
753
 
870
754
  const dataSource = this.createDataSource(serviceDef);
871
755
 
@@ -893,57 +777,8 @@ export class ApolloGateway implements GraphQLService {
893
777
 
894
778
  private createServices(services: ServiceEndpointDefinition[]) {
895
779
  for (const serviceDef of services) {
896
- this.createAndCacheDataSource(serviceDef);
897
- }
898
- }
899
-
900
- protected async loadServiceDefinitions(
901
- config: RemoteGatewayConfig | ManagedGatewayConfig,
902
- ): Promise<CompositionUpdate> {
903
- if (isRemoteConfig(config)) {
904
- const serviceList = config.serviceList.map((serviceDefinition) => ({
905
- ...serviceDefinition,
906
- dataSource: this.createAndCacheDataSource(serviceDefinition),
907
- }));
908
-
909
- return getServiceDefinitionsFromRemoteEndpoint({
910
- serviceList,
911
- async getServiceIntrospectionHeaders(service) {
912
- return typeof config.introspectionHeaders === 'function'
913
- ? await config.introspectionHeaders(service)
914
- : config.introspectionHeaders;
915
- },
916
- serviceSdlCache: this.serviceSdlCache,
917
- });
918
- }
919
-
920
- const canUseManagedConfig =
921
- this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
922
- if (!canUseManagedConfig) {
923
- throw new Error(
924
- 'When a manual configuration is not provided, gateway requires an Apollo ' +
925
- 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
926
- 'for more information. Manual configuration options include: ' +
927
- '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
928
- );
780
+ this.getOrCreateDataSource(serviceDef);
929
781
  }
930
-
931
- const result = await loadSupergraphSdlFromUplinks({
932
- graphRef: this.apolloConfig!.graphRef!,
933
- apiKey: this.apolloConfig!.key!,
934
- endpoints: this.uplinkEndpoints!,
935
- errorReportingEndpoint: this.errorReportingEndpoint,
936
- fetcher: this.fetcher,
937
- compositionId: this.compositionId ?? null,
938
- maxRetries: this.uplinkMaxRetries!,
939
- });
940
-
941
- return (
942
- result ?? {
943
- id: this.compositionId!,
944
- supergraphSdl: this.supergraphSdl!,
945
- }
946
- );
947
782
  }
948
783
 
949
784
  private maybeWarnOnConflictingConfig() {
@@ -1018,7 +853,11 @@ export class ApolloGateway implements GraphQLService {
1018
853
  OpenTelemetrySpanNames.PLAN,
1019
854
  (span) => {
1020
855
  try {
1021
- const operation = operationFromDocument(this.apiSchema!, document, request.operationName);
856
+ const operation = operationFromDocument(
857
+ this.apiSchema!,
858
+ document,
859
+ request.operationName,
860
+ );
1022
861
  // TODO(#631): Can we be sure the query planner has been initialized here?
1023
862
  return this.queryPlanner!.buildQueryPlan(operation);
1024
863
  } catch (err) {
@@ -1158,9 +997,24 @@ export class ApolloGateway implements GraphQLService {
1158
997
  });
1159
998
  }
1160
999
 
1161
- // Stops all processes involved with the gateway (for now, just background
1162
- // schema polling). Can be called multiple times safely. Once it (async)
1163
- // 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.
1164
1018
  public async stop() {
1165
1019
  switch (this.state.phase) {
1166
1020
  case 'initialized':
@@ -1183,40 +1037,31 @@ export class ApolloGateway implements GraphQLService {
1183
1037
  }
1184
1038
  return;
1185
1039
  case 'loaded':
1186
- this.state = { phase: 'stopped' }; // nothing to do (we're not polling)
1187
- return;
1188
- case 'waiting to poll': {
1189
- // If we're waiting to poll, we can synchronously transition to fully stopped.
1190
- // We will terminate the current pollServices call and it will succeed quickly.
1191
- const doneWaiting = this.state.doneWaiting;
1192
- clearTimeout(this.state.pollWaitTimer);
1193
- this.state = { phase: 'stopped' };
1194
- doneWaiting();
1195
- return;
1196
- }
1197
- case 'polling': {
1198
- // We're in the middle of running updateSchema. We need to go into 'stopping'
1199
- // mode and let this run complete. First we set things up so that any concurrent
1200
- // calls to stop() will wait until we let them finish. (Those concurrent calls shouldn't
1201
- // just wait on pollingDonePromise themselves because we want to make sure we fully
1202
- // transition to state='stopped' before the other call returns.)
1203
- const pollingDonePromise = this.state.pollingDonePromise;
1204
- let stoppingDone: () => void;
1040
+ const stoppingDonePromise = this.performCleanupAndLogErrors();
1205
1041
  this.state = {
1206
1042
  phase: 'stopping',
1207
- stoppingDonePromise: new Promise<void>((res) => {
1208
- stoppingDone = res;
1209
- }),
1043
+ stoppingDonePromise,
1210
1044
  };
1211
- await pollingDonePromise;
1045
+ await stoppingDonePromise;
1212
1046
  this.state = { phase: 'stopped' };
1213
- stoppingDone!();
1214
1047
  return;
1048
+ case 'updating schema': {
1049
+ throw Error(
1050
+ "`ApolloGateway.stop` shouldn't be called from inside a schema change listener",
1051
+ );
1215
1052
  }
1216
1053
  default:
1217
1054
  throw new UnreachableCaseError(this.state);
1218
1055
  }
1219
1056
  }
1057
+
1058
+ public __testing() {
1059
+ return {
1060
+ state: this.state,
1061
+ compositionId: this.compositionId,
1062
+ supergraphSdl: this.supergraphSdl,
1063
+ };
1064
+ }
1220
1065
  }
1221
1066
 
1222
1067
  ApolloGateway.prototype.onSchemaChange = deprecate(
@@ -1263,11 +1108,22 @@ export {
1263
1108
  ServiceMap,
1264
1109
  Experimental_DidFailCompositionCallback,
1265
1110
  Experimental_DidResolveQueryPlanCallback,
1266
- Experimental_DidUpdateCompositionCallback,
1111
+ Experimental_DidUpdateSupergraphCallback,
1267
1112
  Experimental_UpdateComposition,
1268
1113
  GatewayConfig,
1269
1114
  ServiceEndpointDefinition,
1115
+ ServiceDefinition,
1270
1116
  CompositionInfo,
1117
+ IntrospectAndCompose,
1118
+ LocalCompose,
1271
1119
  };
1272
1120
 
1273
1121
  export * from './datasources';
1122
+
1123
+ export {
1124
+ SupergraphSdlUpdateFunction,
1125
+ SubgraphHealthCheckFunction,
1126
+ GetDataSourceFunction,
1127
+ SupergraphSdlHook,
1128
+ SupergraphManager
1129
+ } from './config';