@apollo/gateway 2.0.0-alpha.0 → 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 (154) hide show
  1. package/README.md +1 -1
  2. package/dist/__generated__/graphqlTypes.d.ts +13 -11
  3. package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
  4. package/dist/__generated__/graphqlTypes.js.map +1 -1
  5. package/dist/config.d.ts +45 -24
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +30 -31
  8. package/dist/config.js.map +1 -1
  9. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  10. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  11. package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
  12. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  13. package/dist/datasources/types.d.ts +1 -1
  14. package/dist/datasources/types.d.ts.map +1 -1
  15. package/dist/executeQueryPlan.d.ts.map +1 -1
  16. package/dist/executeQueryPlan.js +6 -6
  17. package/dist/executeQueryPlan.js.map +1 -1
  18. package/dist/index.d.ts +36 -23
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +197 -297
  21. package/dist/index.js.map +1 -1
  22. package/dist/operationContext.js +0 -1
  23. package/dist/operationContext.js.map +1 -1
  24. package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
  25. package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
  26. package/dist/schema-helper/addResolversToSchema.js +62 -0
  27. package/dist/schema-helper/addResolversToSchema.js.map +1 -0
  28. package/dist/schema-helper/error.d.ts +6 -0
  29. package/dist/schema-helper/error.d.ts.map +1 -0
  30. package/dist/schema-helper/error.js +14 -0
  31. package/dist/schema-helper/error.js.map +1 -0
  32. package/dist/schema-helper/index.d.ts +4 -0
  33. package/dist/schema-helper/index.d.ts.map +1 -0
  34. package/dist/schema-helper/index.js +16 -0
  35. package/dist/schema-helper/index.js.map +1 -0
  36. package/dist/schema-helper/resolverMap.d.ts +16 -0
  37. package/dist/schema-helper/resolverMap.d.ts.map +1 -0
  38. package/dist/schema-helper/resolverMap.js +3 -0
  39. package/dist/schema-helper/resolverMap.js.map +1 -0
  40. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
  41. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
  42. package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
  43. package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
  44. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
  45. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
  46. package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
  47. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
  48. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
  49. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
  50. package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
  51. package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
  52. package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
  53. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
  54. package/dist/supergraphManagers/LocalCompose/index.js +55 -0
  55. package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
  56. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +32 -0
  57. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
  58. package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
  59. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
  60. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +21 -0
  61. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  62. package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +41 -10
  63. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
  64. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +13 -0
  65. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
  66. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +85 -0
  67. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
  68. package/dist/supergraphManagers/index.d.ts +5 -0
  69. package/dist/supergraphManagers/index.d.ts.map +1 -0
  70. package/dist/supergraphManagers/index.js +12 -0
  71. package/dist/supergraphManagers/index.js.map +1 -0
  72. package/dist/utilities/array.js +1 -1
  73. package/dist/utilities/array.js.map +1 -1
  74. package/dist/utilities/createHash.d.ts +2 -0
  75. package/dist/utilities/createHash.d.ts.map +1 -0
  76. package/dist/utilities/createHash.js +15 -0
  77. package/dist/utilities/createHash.js.map +1 -0
  78. package/dist/utilities/isNodeLike.d.ts +3 -0
  79. package/dist/utilities/isNodeLike.d.ts.map +1 -0
  80. package/dist/utilities/isNodeLike.js +8 -0
  81. package/dist/utilities/isNodeLike.js.map +1 -0
  82. package/package.json +9 -9
  83. package/src/__generated__/graphqlTypes.ts +13 -11
  84. package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
  85. package/src/__tests__/buildQueryPlan.test.ts +1 -1
  86. package/src/__tests__/executeQueryPlan.test.ts +1171 -77
  87. package/src/__tests__/execution-utils.ts +5 -7
  88. package/src/__tests__/gateway/buildService.test.ts +3 -3
  89. package/src/__tests__/gateway/endToEnd.test.ts +1 -1
  90. package/src/__tests__/gateway/executor.test.ts +3 -1
  91. package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -121
  92. package/src/__tests__/gateway/opentelemetry.test.ts +8 -3
  93. package/src/__tests__/gateway/queryPlanCache.test.ts +25 -9
  94. package/src/__tests__/gateway/reporting.test.ts +42 -13
  95. package/src/__tests__/gateway/supergraphSdl.test.ts +397 -0
  96. package/src/__tests__/integration/aliases.test.ts +9 -3
  97. package/src/__tests__/integration/configuration.test.ts +140 -21
  98. package/src/__tests__/integration/logger.test.ts +2 -2
  99. package/src/__tests__/integration/networkRequests.test.ts +126 -149
  100. package/src/__tests__/integration/nockMocks.ts +57 -16
  101. package/src/__tests__/nockAssertions.ts +20 -0
  102. package/src/config.ts +153 -77
  103. package/src/core/__tests__/core.test.ts +6 -6
  104. package/src/datasources/LocalGraphQLDataSource.ts +1 -1
  105. package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
  106. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
  107. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
  108. package/src/datasources/types.ts +1 -1
  109. package/src/executeQueryPlan.ts +18 -9
  110. package/src/index.ts +323 -481
  111. package/src/make-fetch-happen.d.ts +1 -1
  112. package/src/operationContext.ts +2 -2
  113. package/src/schema-helper/addResolversToSchema.ts +83 -0
  114. package/src/schema-helper/error.ts +11 -0
  115. package/src/schema-helper/index.ts +3 -0
  116. package/src/schema-helper/resolverMap.ts +23 -0
  117. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
  118. package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +7 -7
  119. package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
  120. package/src/supergraphManagers/IntrospectAndCompose/index.ts +160 -0
  121. package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +7 -7
  122. package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
  123. package/src/supergraphManagers/LocalCompose/index.ts +79 -0
  124. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +343 -0
  125. package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
  126. package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
  127. package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +63 -14
  128. package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
  129. package/src/supergraphManagers/index.ts +4 -0
  130. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +13 -10
  131. package/src/utilities/array.ts +1 -1
  132. package/src/utilities/createHash.ts +10 -0
  133. package/src/utilities/isNodeLike.ts +11 -0
  134. package/CHANGELOG.md +0 -452
  135. package/dist/legacyLoadServicesFromStorage.d.ts +0 -20
  136. package/dist/legacyLoadServicesFromStorage.d.ts.map +0 -1
  137. package/dist/legacyLoadServicesFromStorage.js +0 -62
  138. package/dist/legacyLoadServicesFromStorage.js.map +0 -1
  139. package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
  140. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
  141. package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
  142. package/dist/loadSupergraphSdlFromStorage.d.ts +0 -12
  143. package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
  144. package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
  145. package/dist/outOfBandReporter.d.ts +0 -15
  146. package/dist/outOfBandReporter.d.ts.map +0 -1
  147. package/dist/outOfBandReporter.js +0 -88
  148. package/dist/outOfBandReporter.js.map +0 -1
  149. package/src/__tests__/gateway/composedSdl.test.ts +0 -44
  150. package/src/__tests__/integration/legacyNetworkRequests.test.ts +0 -279
  151. package/src/__tests__/integration/legacyNockMocks.ts +0 -113
  152. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -664
  153. package/src/legacyLoadServicesFromStorage.ts +0 -170
  154. package/src/outOfBandReporter.ts +0 -128
package/src/index.ts CHANGED
@@ -12,20 +12,17 @@ import {
12
12
  GraphQLSchema,
13
13
  VariableDefinitionNode,
14
14
  } from 'graphql';
15
- import {
16
- ServiceDefinition,
17
- } from '@apollo/federation';
18
15
  import loglevel from 'loglevel';
19
-
20
16
  import { buildOperationContext, OperationContext } from './operationContext';
21
17
  import {
22
18
  executeQueryPlan,
23
19
  ServiceMap,
24
20
  defaultFieldResolverWithAliasSupport,
25
21
  } from './executeQueryPlan';
26
-
27
- import { getServiceDefinitionsFromRemoteEndpoint } from './loadServicesFromRemoteEndpoint';
28
- import { GraphQLDataSource, GraphQLDataSourceRequestKind } from './datasources/types';
22
+ import {
23
+ GraphQLDataSource,
24
+ GraphQLDataSourceRequestKind,
25
+ } from './datasources/types';
29
26
  import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource';
30
27
  import { getVariableValues } from 'graphql/execution/values';
31
28
  import fetcher from 'make-fetch-happen';
@@ -40,39 +37,30 @@ import {
40
37
  ServiceEndpointDefinition,
41
38
  Experimental_DidFailCompositionCallback,
42
39
  Experimental_DidResolveQueryPlanCallback,
43
- Experimental_DidUpdateCompositionCallback,
40
+ Experimental_DidUpdateSupergraphCallback,
44
41
  Experimental_UpdateComposition,
45
42
  CompositionInfo,
46
43
  GatewayConfig,
47
- StaticGatewayConfig,
48
- RemoteGatewayConfig,
49
- ManagedGatewayConfig,
50
44
  isManuallyManagedConfig,
51
45
  isLocalConfig,
52
- isRemoteConfig,
46
+ isServiceListConfig,
53
47
  isManagedConfig,
54
- isDynamicConfig,
55
- isStaticConfig,
56
- CompositionMetadata,
57
- isSupergraphSdlUpdate,
58
- isServiceDefinitionUpdate,
59
- ServiceDefinitionUpdate,
60
48
  SupergraphSdlUpdate,
61
- CompositionUpdate,
62
- isPrecomposedManagedConfig,
63
- isLegacyManagedConfig,
49
+ isManuallyManagedSupergraphSdlGatewayConfig,
50
+ ManagedGatewayConfig,
51
+ isStaticSupergraphSdlConfig,
52
+ SupergraphManager,
64
53
  } from './config';
65
- import { loadSupergraphSdlFromStorage } from './loadSupergraphSdlFromStorage';
66
- import { getServiceDefinitionsFromStorage } from './legacyLoadServicesFromStorage';
67
54
  import { SpanStatusCode } from '@opentelemetry/api';
68
55
  import { OpenTelemetrySpanNames, tracer } from './utilities/opentelemetry';
69
-
56
+ import { createHash } from './utilities/createHash';
70
57
  import {
71
- buildSupergraphSchema,
72
- operationFromDocument,
73
- Schema,
74
- } from '@apollo/federation-internals';
75
- 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';
76
64
 
77
65
  type DataSourceMap = {
78
66
  [serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
@@ -111,20 +99,6 @@ export function getDefaultFetcher() {
111
99
  });
112
100
  }
113
101
 
114
- /**
115
- * TODO(trevor:cloudconfig): Stop exporting this
116
- * @deprecated This will be removed in a future version of @apollo/gateway
117
- */
118
- export const getDefaultGcsFetcher = deprecate(
119
- getDefaultFetcher,
120
- `'getDefaultGcsFetcher' is deprecated. Use 'getDefaultFetcher' instead.`,
121
- );
122
- /**
123
- * TODO(trevor:cloudconfig): Stop exporting this
124
- * @deprecated This will be removed in a future version of @apollo/gateway
125
- */
126
- export const GCS_RETRY_COUNT = 5;
127
-
128
102
  export const HEALTH_CHECK_QUERY =
129
103
  'query __ApolloServiceHealthCheck__ { __typename }';
130
104
  export const SERVICE_DEFINITION_QUERY =
@@ -136,12 +110,7 @@ type GatewayState =
136
110
  | { phase: 'loaded' }
137
111
  | { phase: 'stopping'; stoppingDonePromise: Promise<void> }
138
112
  | { phase: 'stopped' }
139
- | {
140
- phase: 'waiting to poll';
141
- pollWaitTimer: NodeJS.Timer;
142
- doneWaiting: () => void;
143
- }
144
- | { phase: 'polling'; pollingDonePromise: Promise<void> };
113
+ | { phase: 'updating schema' };
145
114
 
146
115
  // We want to be compatible with `load()` as called by both AS2 and AS3, so we
147
116
  // define its argument types ourselves instead of relying on imports.
@@ -192,9 +161,6 @@ export class ApolloGateway implements GraphQLService {
192
161
  coreSupergraphSdl: string;
193
162
  }) => void
194
163
  >();
195
- private serviceDefinitions: ServiceDefinition[] = [];
196
- private compositionMetadata?: CompositionMetadata;
197
- private serviceSdlCache = new Map<string, string>();
198
164
  private warnedStates: WarnedStates = Object.create(null);
199
165
  private queryPlanner?: QueryPlanner;
200
166
  private supergraphSdl?: string;
@@ -206,24 +172,12 @@ export class ApolloGateway implements GraphQLService {
206
172
  // The information made available here will give insight into the resulting
207
173
  // query plan and the inputs that generated it.
208
174
  private experimental_didResolveQueryPlan?: Experimental_DidResolveQueryPlanCallback;
209
- // Observe composition failures and the ServiceList that caused them. This
210
- // enables reporting any issues that occur during composition. Implementors
211
- // will be interested in addressing these immediately.
212
- private experimental_didFailComposition?: Experimental_DidFailCompositionCallback;
213
- // Used to communicated composition changes, and what definitions caused
214
- // those updates
215
- private experimental_didUpdateComposition?: Experimental_DidUpdateCompositionCallback;
216
- // Used for overriding the default service list fetcher. This should return
217
- // an array of ServiceDefinition. *This function must be awaited.*
218
- private updateServiceDefinitions: Experimental_UpdateComposition;
219
- // how often service defs should be loaded/updated (in ms)
220
- private experimental_pollInterval?: number;
221
- // Configure the endpoint by which gateway will access its precomposed schema.
222
- // * `string` means use that endpoint
223
- // * `null` will revert the gateway to legacy mode (polling GCS and composing the schema itself).
224
- // * `undefined` means the gateway is not using managed federation
225
- // TODO(trevor:cloudconfig): `null` should be disallowed in the future.
226
- private schemaConfigDeliveryEndpoint?: string | null;
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>)[] = [];
227
181
 
228
182
  constructor(config?: GatewayConfig) {
229
183
  this.config = {
@@ -243,50 +197,15 @@ export class ApolloGateway implements GraphQLService {
243
197
  // set up experimental observability callbacks and config settings
244
198
  this.experimental_didResolveQueryPlan =
245
199
  config?.experimental_didResolveQueryPlan;
246
- this.experimental_didFailComposition =
247
- config?.experimental_didFailComposition;
248
- this.experimental_didUpdateComposition =
249
- config?.experimental_didUpdateComposition;
250
-
251
- this.experimental_pollInterval = config?.experimental_pollInterval;
252
-
253
- // 1. If config is set to a `string`, use it
254
- // 2. If config is explicitly set to `null`, fallback to GCS
255
- // 3. If the env var is set, use that
256
- // 4. If config is `undefined`, use the default uplink URL
257
-
258
- // This if case unobviously handles 1, 2, and 4.
259
- if (isPrecomposedManagedConfig(this.config)) {
260
- const envEndpoint = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
261
- this.schemaConfigDeliveryEndpoint =
262
- this.config.schemaConfigDeliveryEndpoint ??
263
- envEndpoint ??
264
- 'https://uplink.api.apollographql.com/';
265
- } else if (isLegacyManagedConfig(this.config)) {
266
- this.schemaConfigDeliveryEndpoint = null;
267
- }
200
+ this.experimental_didUpdateSupergraph =
201
+ config?.experimental_didUpdateSupergraph;
268
202
 
269
- if (isManuallyManagedConfig(this.config)) {
270
- // Use the provided updater function if provided by the user, else default
271
- if ('experimental_updateSupergraphSdl' in this.config) {
272
- this.updateServiceDefinitions =
273
- this.config.experimental_updateSupergraphSdl;
274
- } else if ('experimental_updateServiceDefinitions' in this.config) {
275
- this.updateServiceDefinitions =
276
- this.config.experimental_updateServiceDefinitions;
277
- } else {
278
- throw Error(
279
- 'Programming error: unexpected manual configuration provided',
280
- );
281
- }
282
- } else {
283
- this.updateServiceDefinitions = this.loadServiceDefinitions;
284
- }
203
+ this.pollIntervalInMs =
204
+ config?.pollIntervalInMs ?? config?.experimental_pollInterval;
285
205
 
286
- if (isDynamicConfig(this.config)) {
287
- this.issueDynamicWarningsIfApplicable();
288
- }
206
+ this.issueConfigurationWarningsIfApplicable();
289
207
 
208
+ this.logger.debug('Gateway successfully initialized (but not yet loaded)');
290
209
  this.state = { phase: 'initialized' };
291
210
  }
292
211
 
@@ -321,25 +240,26 @@ export class ApolloGateway implements GraphQLService {
321
240
  });
322
241
  }
323
242
 
324
- private issueDynamicWarningsIfApplicable() {
243
+ private issueConfigurationWarningsIfApplicable() {
325
244
  // Warn against a pollInterval of < 10s in managed mode and reset it to 10s
326
245
  if (
327
246
  isManagedConfig(this.config) &&
328
- this.config.experimental_pollInterval &&
329
- this.config.experimental_pollInterval < 10000
247
+ this.pollIntervalInMs &&
248
+ this.pollIntervalInMs < 10000
330
249
  ) {
331
- this.experimental_pollInterval = 10000;
250
+ this.pollIntervalInMs = 10000;
332
251
  this.logger.warn(
333
252
  'Polling Apollo services at a frequency of less than once per 10 ' +
334
253
  'seconds (10000) is disallowed. Instead, the minimum allowed ' +
335
254
  'pollInterval of 10000 will be used. Please reconfigure your ' +
336
- 'experimental_pollInterval accordingly. If this is problematic for ' +
255
+ '`pollIntervalInMs` accordingly. If this is problematic for ' +
337
256
  'your team, please contact support.',
338
257
  );
339
258
  }
340
259
 
341
260
  // Warn against using the pollInterval and a serviceList simultaneously
342
- if (this.config.experimental_pollInterval && isRemoteConfig(this.config)) {
261
+ // TODO(trevor:removeServiceList)
262
+ if (this.pollIntervalInMs && isServiceListConfig(this.config)) {
343
263
  this.logger.warn(
344
264
  'Polling running services is dangerous and not recommended in production. ' +
345
265
  'Polling should only be used against a registry. ' +
@@ -359,12 +279,26 @@ export class ApolloGateway implements GraphQLService {
359
279
  'are provided.',
360
280
  );
361
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
+ }
362
294
  }
363
295
 
364
296
  public async load(options?: {
365
297
  apollo?: ApolloConfigFromAS2Or3;
366
298
  engine?: GraphQLServiceEngineConfig;
367
299
  }) {
300
+ this.logger.debug('Loading gateway...');
301
+
368
302
  if (this.state.phase !== 'initialized') {
369
303
  throw Error(
370
304
  `ApolloGateway.load called in surprising state ${this.state.phase}`,
@@ -390,40 +324,92 @@ export class ApolloGateway implements GraphQLService {
390
324
  };
391
325
  }
392
326
 
393
- // Before @apollo/gateway v0.23, ApolloGateway didn't expect stop() to be
394
- // called after it started. The only thing that stop() did at that point was
395
- // cancel the poll timer, and so to prevent that timer from keeping an
396
- // otherwise-finished Node process alive, ApolloGateway unconditionally
397
- // called unref() on that timeout. As part of making the ApolloGateway
398
- // lifecycle more predictable and concrete (and to allow for a future where
399
- // there are other reasons to make sure to explicitly stop your gateway),
400
- // v0.23 tries to avoid calling unref().
401
- //
402
- // Apollo Server v2.20 and newer calls gateway.stop() from its stop()
403
- // method, so as long as you're using v2.20, ApolloGateway won't keep
404
- // running after you stop your server, and your Node process can shut down.
405
- // To make this change a bit less backwards-incompatible, we detect if it
406
- // looks like you're using an older version of Apollo Server; if so, we
407
- // still call unref(). Specifically: Apollo Server has always passed an
408
- // options object to load(), and before v2.18 it did not pass the `apollo`
409
- // key on it. So if we detect that particular pattern, we assume we're with
410
- // pre-v2.18 Apollo Server and we still call unref(). So this will be a
411
- // behavior change only for:
412
- // - non-Apollo-Server uses of ApolloGateway (where you can add your own
413
- // call to gateway.stop())
414
- // - Apollo Server v2.18 and v2.19 (where you can either do the small
415
- // compatible upgrade or add your own call to gateway.stop())
416
- // - if you don't call stop() on your ApolloServer (but in that case other
417
- // things like usage reporting will also stop shutdown, so you should fix
418
- // that)
419
- const unrefTimer = !!options && !options.apollo;
420
-
421
327
  this.maybeWarnOnConflictingConfig();
422
328
 
423
329
  // Handles initial assignment of `this.schema`, `this.queryPlanner`
424
- isStaticConfig(this.config)
425
- ? this.loadStatic(this.config)
426
- : 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
+ }
427
413
 
428
414
  const mode = isManagedConfig(this.config) ? 'managed' : 'unmanaged';
429
415
  this.logger.info(
@@ -440,122 +426,148 @@ export class ApolloGateway implements GraphQLService {
440
426
  };
441
427
  }
442
428
 
443
- // Synchronously load a statically configured schema, update class instance's
444
- // schema and query planner.
445
- private loadStatic(config: StaticGatewayConfig) {
446
- let schema: Schema;
447
- let supergraphSdl: string;
448
- try {
449
- ({ schema, supergraphSdl } = isLocalConfig(config)
450
- ? this.createSchemaFromServiceList(config.localServiceList)
451
- : this.createSchemaFromSupergraphSdl(config.supergraphSdl));
452
- this.supergraphSdl = supergraphSdl;
453
- this.updateWithSchemaAndNotify(schema, supergraphSdl, true);
454
- } catch (e) {
455
- this.state = { phase: 'failed to load' };
456
- throw e;
457
- }
458
- 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
+ );
459
450
  }
460
451
 
461
- // Asynchronously load a dynamically configured schema. `this.updateSchema`
462
- // is responsible for updating the class instance's schema and query planner.
463
- 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
+ ) {
464
459
  try {
465
- 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);
466
481
  } catch (e) {
467
482
  this.state = { phase: 'failed to load' };
483
+ await this.performCleanupAndLogErrors();
468
484
  throw e;
469
485
  }
470
486
 
471
487
  this.state = { phase: 'loaded' };
472
- if (this.shouldBeginPolling()) {
473
- this.pollServices(unrefTimer);
474
- }
475
488
  }
476
489
 
477
- private shouldBeginPolling() {
478
- return isManagedConfig(this.config) || this.experimental_pollInterval;
479
- }
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
+ }
480
522
 
481
- private async updateSchema(): Promise<void> {
482
- 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
+ }
483
534
 
484
- // This may throw, but an error here is caught and logged upstream
485
- 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);
486
550
 
487
- if (isSupergraphSdlUpdate(result)) {
488
- await this.updateWithSupergraphSdl(result);
489
- } else if (isServiceDefinitionUpdate(result)) {
490
- await this.updateByComposition(result);
491
- } else {
551
+ try {
552
+ await this.serviceHealthCheck(serviceMap);
553
+ } catch (e) {
492
554
  throw new Error(
493
- '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,
494
559
  );
495
560
  }
496
561
  }
497
562
 
498
- private async updateByComposition(
499
- result: ServiceDefinitionUpdate,
500
- ): Promise<void> {
501
- if (
502
- !result.serviceDefinitions ||
503
- JSON.stringify(this.serviceDefinitions) ===
504
- JSON.stringify(result.serviceDefinitions)
505
- ) {
506
- this.logger.debug('No change in service definitions since last check.');
507
- return;
508
- }
509
-
510
- const previousSchema = this.schema;
511
- const previousServiceDefinitions = this.serviceDefinitions;
512
- const previousCompositionMetadata = this.compositionMetadata;
513
-
514
- if (previousSchema) {
515
- this.logger.info('New service definitions were found.');
516
- }
517
-
518
- await this.maybePerformServiceHealthCheck(result);
519
-
520
- this.compositionMetadata = result.compositionMetadata;
521
- this.serviceDefinitions = result.serviceDefinitions;
522
-
523
- const { schema, supergraphSdl } = this.createSchemaFromServiceList(
524
- result.serviceDefinitions,
525
- );
526
-
527
- if (!supergraphSdl) {
528
- this.logger.error(
529
- "A valid schema couldn't be composed. Falling back to previous schema.",
530
- );
531
- } else {
532
- this.updateWithSchemaAndNotify(schema, supergraphSdl);
533
-
534
- if (this.experimental_didUpdateComposition) {
535
- this.experimental_didUpdateComposition(
536
- {
537
- serviceDefinitions: result.serviceDefinitions,
538
- schema: schema.toGraphQLJSSchema(),
539
- ...(this.compositionMetadata && {
540
- compositionMetadata: this.compositionMetadata,
541
- }),
542
- },
543
- previousServiceDefinitions &&
544
- previousSchema && {
545
- serviceDefinitions: previousServiceDefinitions,
546
- schema: previousSchema,
547
- ...(previousCompositionMetadata && {
548
- compositionMetadata: previousCompositionMetadata,
549
- }),
550
- },
551
- );
552
- }
553
- }
563
+ private externalGetDataSourceCallback({
564
+ name,
565
+ url,
566
+ }: ServiceEndpointDefinition) {
567
+ return this.getOrCreateDataSource({ name, url });
554
568
  }
555
569
 
556
- private async updateWithSupergraphSdl(
557
- result: SupergraphSdlUpdate,
558
- ): Promise<void> {
570
+ private updateWithSupergraphSdl(result: SupergraphSdlUpdate) {
559
571
  if (result.id === this.compositionId) {
560
572
  this.logger.debug('No change in composition since last check.');
561
573
  return;
@@ -565,7 +577,9 @@ export class ApolloGateway implements GraphQLService {
565
577
  // In the case that it throws, the gateway will:
566
578
  // * on initial load, throw the error
567
579
  // * on update, log the error and don't update
568
- const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(result.supergraphSdl);
580
+ const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
581
+ result.supergraphSdl,
582
+ );
569
583
 
570
584
  const previousSchema = this.schema;
571
585
  const previousSupergraphSdl = this.supergraphSdl;
@@ -575,11 +589,8 @@ export class ApolloGateway implements GraphQLService {
575
589
  this.logger.info('Updated Supergraph SDL was found.');
576
590
  }
577
591
 
578
- await this.maybePerformServiceHealthCheck(result);
579
-
580
592
  this.compositionId = result.id;
581
- this.supergraphSdl = result.supergraphSdl;
582
-
593
+ this.supergraphSdl = supergraphSdl;
583
594
 
584
595
  if (!supergraphSdl) {
585
596
  this.logger.error(
@@ -588,11 +599,11 @@ export class ApolloGateway implements GraphQLService {
588
599
  } else {
589
600
  this.updateWithSchemaAndNotify(schema, supergraphSdl);
590
601
 
591
- if (this.experimental_didUpdateComposition) {
592
- this.experimental_didUpdateComposition(
602
+ if (this.experimental_didUpdateSupergraph) {
603
+ this.experimental_didUpdateSupergraph(
593
604
  {
594
605
  compositionId: result.id,
595
- supergraphSdl: result.supergraphSdl,
606
+ supergraphSdl,
596
607
  schema: schema.toGraphQLJSSchema(),
597
608
  },
598
609
  previousCompositionId && previousSupergraphSdl && previousSchema
@@ -617,8 +628,10 @@ export class ApolloGateway implements GraphQLService {
617
628
  legacyDontNotifyOnSchemaChangeListeners: boolean = false,
618
629
  ): void {
619
630
  if (this.queryPlanStore) this.queryPlanStore.flush();
620
- this.apiSchema = coreSchema.toAPISchema();
621
- this.schema = wrapSchemaWithAliasResolver(this.apiSchema.toGraphQLJSSchema());
631
+ this.apiSchema = coreSchema.toAPISchema();
632
+ this.schema = wrapSchemaWithAliasResolver(
633
+ this.apiSchema.toGraphQLJSSchema(),
634
+ );
622
635
  this.queryPlanner = new QueryPlanner(coreSchema);
623
636
 
624
637
  // Notify onSchemaChange listeners of the updated schema
@@ -653,39 +666,6 @@ export class ApolloGateway implements GraphQLService {
653
666
  });
654
667
  }
655
668
 
656
- private async maybePerformServiceHealthCheck(update: CompositionUpdate) {
657
- // Run service health checks before we commit and update the new schema.
658
- // This is the last chance to bail out of a schema update.
659
- if (this.config.serviceHealthCheck) {
660
- const serviceList = isSupergraphSdlUpdate(update)
661
- ? // Parsing of the supergraph SDL could technically fail and throw here, but parseability has
662
- // already been confirmed slightly earlier in the code path
663
- this.serviceListFromSupergraphSdl(update.supergraphSdl)
664
- : // Existence of this is determined in advance with an early return otherwise
665
- update.serviceDefinitions!;
666
- // Here we need to construct new datasources based on the new schema info
667
- // so we can check the health of the services we're _updating to_.
668
- const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
669
- serviceMap[serviceDef.name] = {
670
- url: serviceDef.url,
671
- dataSource: this.createDataSource(serviceDef),
672
- };
673
- return serviceMap;
674
- }, Object.create(null) as DataSourceMap);
675
-
676
- try {
677
- await this.serviceHealthCheck(serviceMap);
678
- } catch (e) {
679
- throw new Error(
680
- 'The gateway did not update its schema due to failed service health checks. ' +
681
- 'The gateway will continue to operate with the previous schema and reattempt updates. ' +
682
- 'The following error occurred during the health check:\n' +
683
- e.message,
684
- );
685
- }
686
- }
687
- }
688
-
689
669
  /**
690
670
  * This can be used without an argument in order to perform an ad-hoc health check
691
671
  * of the downstream services like so:
@@ -718,40 +698,9 @@ export class ApolloGateway implements GraphQLService {
718
698
  );
719
699
  }
720
700
 
721
- private createSchemaFromServiceList(serviceList: ServiceDefinition[]) {
722
- this.logger.debug(
723
- `Composing schema from service list: \n${serviceList
724
- .map(({ name, url }) => ` ${url || 'local'}: ${name}`)
725
- .join('\n')}`,
726
- );
727
-
728
- const compositionResult = composeServices(serviceList);
729
- const errors = compositionResult.errors;
730
- if (errors) {
731
- if (this.experimental_didFailComposition) {
732
- this.experimental_didFailComposition({
733
- errors,
734
- serviceList,
735
- ...(this.compositionMetadata && {
736
- compositionMetadata: this.compositionMetadata,
737
- }),
738
- });
739
- }
740
- throw Error(
741
- "A valid schema couldn't be composed. The following composition errors were found:\n" +
742
- errors.map((e) => '\t' + e.message).join('\n'),
743
- );
744
- } else {
745
- this.createServices(serviceList);
746
- this.logger.debug('Schema loaded and ready for execution');
747
- return {
748
- schema: compositionResult.schema,
749
- supergraphSdl: compositionResult.supergraphSdl,
750
- };
751
- }
752
- }
753
-
754
- private serviceListFromSupergraphSdl(supergraphSdl: string): Omit<ServiceDefinition, 'typeDefs'>[] {
701
+ private serviceListFromSupergraphSdl(
702
+ supergraphSdl: string,
703
+ ): Omit<ServiceDefinition, 'typeDefs'>[] {
755
704
  return buildSupergraphSchema(supergraphSdl)[1];
756
705
  }
757
706
 
@@ -791,93 +740,16 @@ export class ApolloGateway implements GraphQLService {
791
740
  };
792
741
  }
793
742
 
794
- // This function waits an appropriate amount, updates composition, and calls itself
795
- // again. Note that it is an async function whose Promise is not actually awaited;
796
- // it should never throw itself other than due to a bug in its state machine.
797
- private async pollServices(unrefTimer: boolean) {
798
- switch (this.state.phase) {
799
- case 'stopping':
800
- case 'stopped':
801
- case 'failed to load':
802
- return;
803
- case 'initialized':
804
- throw Error('pollServices should not be called before load!');
805
- case 'polling':
806
- throw Error(
807
- 'pollServices should not be called while in the middle of polling!',
808
- );
809
- case 'waiting to poll':
810
- throw Error(
811
- 'pollServices should not be called while already waiting to poll!',
812
- );
813
- case 'loaded':
814
- // This is the normal case.
815
- break;
816
- default:
817
- throw new UnreachableCaseError(this.state);
818
- }
819
-
820
- // Transition into 'waiting to poll' and set a timer. The timer resolves the
821
- // Promise we're awaiting here; note that calling stop() also can resolve
822
- // that Promise.
823
- await new Promise<void>((doneWaiting) => {
824
- this.state = {
825
- phase: 'waiting to poll',
826
- doneWaiting,
827
- pollWaitTimer: setTimeout(() => {
828
- // Note that we might be in 'stopped', in which case we just do
829
- // nothing.
830
- if (this.state.phase == 'waiting to poll') {
831
- this.state.doneWaiting();
832
- }
833
- }, this.experimental_pollInterval || 10000),
834
- };
835
- if (unrefTimer) {
836
- this.state.pollWaitTimer.unref();
837
- }
838
- });
839
-
840
- // If we've been stopped, then we're done. The cast here is because TS
841
- // doesn't understand that this.state can change during the await
842
- // (https://github.com/microsoft/TypeScript/issues/9998).
843
- if ((this.state as GatewayState).phase !== 'waiting to poll') {
844
- return;
845
- }
846
-
847
- let pollingDone: () => void;
848
- this.state = {
849
- phase: 'polling',
850
- pollingDonePromise: new Promise<void>((res) => {
851
- pollingDone = res;
852
- }),
853
- };
854
-
855
- try {
856
- await this.updateSchema();
857
- } catch (err) {
858
- this.logger.error((err && err.message) || err);
859
- }
860
-
861
- if (this.state.phase === 'polling') {
862
- // If we weren't stopped, we should transition back to the initial 'loading' state and trigger
863
- // another call to itself. (Do that in a setImmediate to avoid unbounded stack sizes.)
864
- this.state = { phase: 'loaded' };
865
- setImmediate(() => this.pollServices(unrefTimer));
866
- }
867
-
868
- // Whether we were stopped or not, let any concurrent stop() call finish.
869
- pollingDone!();
870
- }
871
-
872
- private createAndCacheDataSource(
743
+ private getOrCreateDataSource(
873
744
  serviceDef: ServiceEndpointDefinition,
874
745
  ): GraphQLDataSource {
875
746
  // If the DataSource has already been created, early return
876
747
  if (
877
748
  this.serviceMap[serviceDef.name] &&
878
749
  serviceDef.url === this.serviceMap[serviceDef.name].url
879
- )
750
+ ) {
880
751
  return this.serviceMap[serviceDef.name].dataSource;
752
+ }
881
753
 
882
754
  const dataSource = this.createDataSource(serviceDef);
883
755
 
@@ -905,58 +777,7 @@ export class ApolloGateway implements GraphQLService {
905
777
 
906
778
  private createServices(services: ServiceEndpointDefinition[]) {
907
779
  for (const serviceDef of services) {
908
- this.createAndCacheDataSource(serviceDef);
909
- }
910
- }
911
-
912
- protected async loadServiceDefinitions(
913
- config: RemoteGatewayConfig | ManagedGatewayConfig,
914
- ): Promise<CompositionUpdate> {
915
- if (isRemoteConfig(config)) {
916
- const serviceList = config.serviceList.map((serviceDefinition) => ({
917
- ...serviceDefinition,
918
- dataSource: this.createAndCacheDataSource(serviceDefinition),
919
- }));
920
-
921
- return getServiceDefinitionsFromRemoteEndpoint({
922
- serviceList,
923
- async getServiceIntrospectionHeaders(service) {
924
- return typeof config.introspectionHeaders === 'function'
925
- ? await config.introspectionHeaders(service)
926
- : config.introspectionHeaders;
927
- },
928
- serviceSdlCache: this.serviceSdlCache,
929
- });
930
- }
931
-
932
- const canUseManagedConfig =
933
- this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
934
- if (!canUseManagedConfig) {
935
- throw new Error(
936
- 'When a manual configuration is not provided, gateway requires an Apollo ' +
937
- 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
938
- 'for more information. Manual configuration options include: ' +
939
- '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
940
- );
941
- }
942
-
943
- // TODO(trevor:cloudconfig): This condition goes away completely
944
- if (isPrecomposedManagedConfig(config)) {
945
- return loadSupergraphSdlFromStorage({
946
- graphRef: this.apolloConfig!.graphRef!,
947
- apiKey: this.apolloConfig!.key!,
948
- endpoint: this.schemaConfigDeliveryEndpoint!,
949
- fetcher: this.fetcher,
950
- });
951
- } else if (isLegacyManagedConfig(config)) {
952
- return getServiceDefinitionsFromStorage({
953
- graphRef: this.apolloConfig!.graphRef!,
954
- apiKeyHash: this.apolloConfig!.keyHash!,
955
- federationVersion: config.federationVersion || 1,
956
- fetcher: this.fetcher,
957
- });
958
- } else {
959
- throw new Error('Programming error: unhandled configuration');
780
+ this.getOrCreateDataSource(serviceDef);
960
781
  }
961
782
  }
962
783
 
@@ -1032,7 +853,11 @@ export class ApolloGateway implements GraphQLService {
1032
853
  OpenTelemetrySpanNames.PLAN,
1033
854
  (span) => {
1034
855
  try {
1035
- const operation = operationFromDocument(this.apiSchema!, document, request.operationName);
856
+ const operation = operationFromDocument(
857
+ this.apiSchema!,
858
+ document,
859
+ request.operationName,
860
+ );
1036
861
  // TODO(#631): Can we be sure the query planner has been initialized here?
1037
862
  return this.queryPlanner!.buildQueryPlan(operation);
1038
863
  } catch (err) {
@@ -1172,9 +997,24 @@ export class ApolloGateway implements GraphQLService {
1172
997
  });
1173
998
  }
1174
999
 
1175
- // Stops all processes involved with the gateway (for now, just background
1176
- // schema polling). Can be called multiple times safely. Once it (async)
1177
- // 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.
1178
1018
  public async stop() {
1179
1019
  switch (this.state.phase) {
1180
1020
  case 'initialized':
@@ -1197,40 +1037,31 @@ export class ApolloGateway implements GraphQLService {
1197
1037
  }
1198
1038
  return;
1199
1039
  case 'loaded':
1200
- this.state = { phase: 'stopped' }; // nothing to do (we're not polling)
1201
- return;
1202
- case 'waiting to poll': {
1203
- // If we're waiting to poll, we can synchronously transition to fully stopped.
1204
- // We will terminate the current pollServices call and it will succeed quickly.
1205
- const doneWaiting = this.state.doneWaiting;
1206
- clearTimeout(this.state.pollWaitTimer);
1207
- this.state = { phase: 'stopped' };
1208
- doneWaiting();
1209
- return;
1210
- }
1211
- case 'polling': {
1212
- // We're in the middle of running updateSchema. We need to go into 'stopping'
1213
- // mode and let this run complete. First we set things up so that any concurrent
1214
- // calls to stop() will wait until we let them finish. (Those concurrent calls shouldn't
1215
- // just wait on pollingDonePromise themselves because we want to make sure we fully
1216
- // transition to state='stopped' before the other call returns.)
1217
- const pollingDonePromise = this.state.pollingDonePromise;
1218
- let stoppingDone: () => void;
1040
+ const stoppingDonePromise = this.performCleanupAndLogErrors();
1219
1041
  this.state = {
1220
1042
  phase: 'stopping',
1221
- stoppingDonePromise: new Promise<void>((res) => {
1222
- stoppingDone = res;
1223
- }),
1043
+ stoppingDonePromise,
1224
1044
  };
1225
- await pollingDonePromise;
1045
+ await stoppingDonePromise;
1226
1046
  this.state = { phase: 'stopped' };
1227
- stoppingDone!();
1228
1047
  return;
1048
+ case 'updating schema': {
1049
+ throw Error(
1050
+ "`ApolloGateway.stop` shouldn't be called from inside a schema change listener",
1051
+ );
1229
1052
  }
1230
1053
  default:
1231
1054
  throw new UnreachableCaseError(this.state);
1232
1055
  }
1233
1056
  }
1057
+
1058
+ public __testing() {
1059
+ return {
1060
+ state: this.state,
1061
+ compositionId: this.compositionId,
1062
+ supergraphSdl: this.supergraphSdl,
1063
+ };
1064
+ }
1234
1065
  }
1235
1066
 
1236
1067
  ApolloGateway.prototype.onSchemaChange = deprecate(
@@ -1277,11 +1108,22 @@ export {
1277
1108
  ServiceMap,
1278
1109
  Experimental_DidFailCompositionCallback,
1279
1110
  Experimental_DidResolveQueryPlanCallback,
1280
- Experimental_DidUpdateCompositionCallback,
1111
+ Experimental_DidUpdateSupergraphCallback,
1281
1112
  Experimental_UpdateComposition,
1282
1113
  GatewayConfig,
1283
1114
  ServiceEndpointDefinition,
1115
+ ServiceDefinition,
1284
1116
  CompositionInfo,
1117
+ IntrospectAndCompose,
1118
+ LocalCompose,
1285
1119
  };
1286
1120
 
1287
1121
  export * from './datasources';
1122
+
1123
+ export {
1124
+ SupergraphSdlUpdateFunction,
1125
+ SubgraphHealthCheckFunction,
1126
+ GetDataSourceFunction,
1127
+ SupergraphSdlHook,
1128
+ SupergraphManager
1129
+ } from './config';