@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/dist/index.js CHANGED
@@ -13,7 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  return (mod && mod.__esModule) ? mod : { "default": mod };
14
14
  };
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.buildOperationContext = exports.executeQueryPlan = exports.ApolloGateway = exports.SERVICE_DEFINITION_QUERY = exports.HEALTH_CHECK_QUERY = exports.getDefaultFetcher = void 0;
16
+ exports.LocalCompose = exports.IntrospectAndCompose = exports.buildOperationContext = exports.executeQueryPlan = exports.ApolloGateway = exports.SERVICE_DEFINITION_QUERY = exports.HEALTH_CHECK_QUERY = exports.getDefaultFetcher = void 0;
17
17
  const util_1 = require("util");
18
18
  const apollo_server_caching_1 = require("apollo-server-caching");
19
19
  const graphql_1 = require("graphql");
@@ -22,7 +22,6 @@ const operationContext_1 = require("./operationContext");
22
22
  Object.defineProperty(exports, "buildOperationContext", { enumerable: true, get: function () { return operationContext_1.buildOperationContext; } });
23
23
  const executeQueryPlan_1 = require("./executeQueryPlan");
24
24
  Object.defineProperty(exports, "executeQueryPlan", { enumerable: true, get: function () { return executeQueryPlan_1.executeQueryPlan; } });
25
- const loadServicesFromRemoteEndpoint_1 = require("./loadServicesFromRemoteEndpoint");
26
25
  const types_1 = require("./datasources/types");
27
26
  const RemoteGraphQLDataSource_1 = require("./datasources/RemoteGraphQLDataSource");
28
27
  const values_1 = require("graphql/execution/values");
@@ -30,11 +29,13 @@ const make_fetch_happen_1 = __importDefault(require("make-fetch-happen"));
30
29
  const cache_1 = require("./cache");
31
30
  const query_planner_1 = require("@apollo/query-planner");
32
31
  const config_1 = require("./config");
33
- const loadSupergraphSdlFromStorage_1 = require("./loadSupergraphSdlFromStorage");
34
32
  const api_1 = require("@opentelemetry/api");
35
33
  const opentelemetry_1 = require("./utilities/opentelemetry");
34
+ const createHash_1 = require("./utilities/createHash");
35
+ const supergraphManagers_1 = require("./supergraphManagers");
36
+ Object.defineProperty(exports, "IntrospectAndCompose", { enumerable: true, get: function () { return supergraphManagers_1.IntrospectAndCompose; } });
37
+ Object.defineProperty(exports, "LocalCompose", { enumerable: true, get: function () { return supergraphManagers_1.LocalCompose; } });
36
38
  const federation_internals_1 = require("@apollo/federation-internals");
37
- const composition_1 = require("@apollo/composition");
38
39
  function getDefaultFetcher() {
39
40
  const { name, version } = require('../package.json');
40
41
  return make_fetch_happen_1.default.defaults({
@@ -58,14 +59,12 @@ exports.HEALTH_CHECK_QUERY = 'query __ApolloServiceHealthCheck__ { __typename }'
58
59
  exports.SERVICE_DEFINITION_QUERY = 'query __ApolloGetServiceDefinition__ { _service { sdl } }';
59
60
  class ApolloGateway {
60
61
  constructor(config) {
61
- var _a, _b, _c, _d, _e;
62
+ var _a;
62
63
  this.serviceMap = Object.create(null);
63
64
  this.onSchemaChangeListeners = new Set();
64
65
  this.onSchemaLoadOrUpdateListeners = new Set();
65
- this.serviceDefinitions = [];
66
- this.serviceSdlCache = new Map();
67
66
  this.warnedStates = Object.create(null);
68
- this.errorReportingEndpoint = (_a = process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT) !== null && _a !== void 0 ? _a : undefined;
67
+ this.toDispose = [];
69
68
  this.executor = async (requestContext) => {
70
69
  const spanAttributes = requestContext.operationName
71
70
  ? { operationName: requestContext.operationName }
@@ -158,44 +157,12 @@ class ApolloGateway {
158
157
  this.fetcher = (config === null || config === void 0 ? void 0 : config.fetcher) || getDefaultFetcher();
159
158
  this.experimental_didResolveQueryPlan =
160
159
  config === null || config === void 0 ? void 0 : config.experimental_didResolveQueryPlan;
161
- this.experimental_didFailComposition =
162
- config === null || config === void 0 ? void 0 : config.experimental_didFailComposition;
163
- this.experimental_didUpdateComposition =
164
- config === null || config === void 0 ? void 0 : config.experimental_didUpdateComposition;
165
- this.experimental_pollInterval = config === null || config === void 0 ? void 0 : config.experimental_pollInterval;
166
- if ((0, config_1.isManagedConfig)(this.config)) {
167
- const rawEndpointsString = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
168
- const envEndpoints = (_b = rawEndpointsString === null || rawEndpointsString === void 0 ? void 0 : rawEndpointsString.split(",")) !== null && _b !== void 0 ? _b : null;
169
- if (this.config.schemaConfigDeliveryEndpoint && !this.config.uplinkEndpoints) {
170
- this.uplinkEndpoints = [this.config.schemaConfigDeliveryEndpoint];
171
- }
172
- else {
173
- this.uplinkEndpoints = (_d = (_c = this.config.uplinkEndpoints) !== null && _c !== void 0 ? _c : envEndpoints) !== null && _d !== void 0 ? _d : [
174
- 'https://uplink.api.apollographql.com/',
175
- 'https://aws.uplink.api.apollographql.com/'
176
- ];
177
- }
178
- this.uplinkMaxRetries = (_e = this.config.uplinkMaxRetries) !== null && _e !== void 0 ? _e : this.uplinkEndpoints.length * 3;
179
- }
180
- if ((0, config_1.isManuallyManagedConfig)(this.config)) {
181
- if ('experimental_updateSupergraphSdl' in this.config) {
182
- this.updateServiceDefinitions =
183
- this.config.experimental_updateSupergraphSdl;
184
- }
185
- else if ('experimental_updateServiceDefinitions' in this.config) {
186
- this.updateServiceDefinitions =
187
- this.config.experimental_updateServiceDefinitions;
188
- }
189
- else {
190
- throw Error('Programming error: unexpected manual configuration provided');
191
- }
192
- }
193
- else {
194
- this.updateServiceDefinitions = this.loadServiceDefinitions;
195
- }
196
- if ((0, config_1.isDynamicConfig)(this.config)) {
197
- this.issueDynamicWarningsIfApplicable();
198
- }
160
+ this.experimental_didUpdateSupergraph =
161
+ config === null || config === void 0 ? void 0 : config.experimental_didUpdateSupergraph;
162
+ this.pollIntervalInMs =
163
+ (_a = config === null || config === void 0 ? void 0 : config.pollIntervalInMs) !== null && _a !== void 0 ? _a : config === null || config === void 0 ? void 0 : config.experimental_pollInterval;
164
+ this.issueConfigurationWarningsIfApplicable();
165
+ this.logger.debug('Gateway successfully initialized (but not yet loaded)');
199
166
  this.state = { phase: 'initialized' };
200
167
  }
201
168
  initLogger() {
@@ -217,18 +184,18 @@ class ApolloGateway {
217
184
  sizeCalculator: approximateObjectSize,
218
185
  });
219
186
  }
220
- issueDynamicWarningsIfApplicable() {
187
+ issueConfigurationWarningsIfApplicable() {
221
188
  if ((0, config_1.isManagedConfig)(this.config) &&
222
- this.config.experimental_pollInterval &&
223
- this.config.experimental_pollInterval < 10000) {
224
- this.experimental_pollInterval = 10000;
189
+ this.pollIntervalInMs &&
190
+ this.pollIntervalInMs < 10000) {
191
+ this.pollIntervalInMs = 10000;
225
192
  this.logger.warn('Polling Apollo services at a frequency of less than once per 10 ' +
226
193
  'seconds (10000) is disallowed. Instead, the minimum allowed ' +
227
194
  'pollInterval of 10000 will be used. Please reconfigure your ' +
228
- 'experimental_pollInterval accordingly. If this is problematic for ' +
195
+ '`pollIntervalInMs` accordingly. If this is problematic for ' +
229
196
  'your team, please contact support.');
230
197
  }
231
- if (this.config.experimental_pollInterval && (0, config_1.isRemoteConfig)(this.config)) {
198
+ if (this.pollIntervalInMs && (0, config_1.isServiceListConfig)(this.config)) {
232
199
  this.logger.warn('Polling running services is dangerous and not recommended in production. ' +
233
200
  'Polling should only be used against a registry. ' +
234
201
  'If you are polling running services, use with caution.');
@@ -241,8 +208,16 @@ class ApolloGateway {
241
208
  'function when both `experimental_updateSupergraphSdl` and experimental_updateServiceDefinitions` ' +
242
209
  'are provided.');
243
210
  }
211
+ if ('schemaConfigDeliveryEndpoint' in this.config) {
212
+ this.logger.warn('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.');
213
+ }
214
+ if ('experimental_pollInterval' in this.config) {
215
+ this.logger.warn('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.');
216
+ }
244
217
  }
245
218
  async load(options) {
219
+ var _a, _b, _c, _d;
220
+ this.logger.debug('Loading gateway...');
246
221
  if (this.state.phase !== 'initialized') {
247
222
  throw Error(`ApolloGateway.load called in surprising state ${this.state.phase}`);
248
223
  }
@@ -263,11 +238,72 @@ class ApolloGateway {
263
238
  : undefined,
264
239
  };
265
240
  }
266
- const unrefTimer = !!options && !options.apollo;
267
241
  this.maybeWarnOnConflictingConfig();
268
- (0, config_1.isStaticConfig)(this.config)
269
- ? this.loadStatic(this.config)
270
- : await this.loadDynamic(unrefTimer);
242
+ if ((0, config_1.isStaticSupergraphSdlConfig)(this.config)) {
243
+ const supergraphSdl = this.config.supergraphSdl;
244
+ await this.initializeSupergraphManager({
245
+ initialize: async () => {
246
+ return {
247
+ supergraphSdl,
248
+ };
249
+ },
250
+ });
251
+ }
252
+ else if ((0, config_1.isLocalConfig)(this.config)) {
253
+ await this.initializeSupergraphManager(new supergraphManagers_1.LocalCompose({
254
+ localServiceList: this.config.localServiceList,
255
+ logger: this.logger,
256
+ }));
257
+ }
258
+ else if ((0, config_1.isManuallyManagedSupergraphSdlGatewayConfig)(this.config)) {
259
+ const supergraphManager = typeof this.config.supergraphSdl === 'object'
260
+ ? this.config.supergraphSdl
261
+ : { initialize: this.config.supergraphSdl };
262
+ await this.initializeSupergraphManager(supergraphManager);
263
+ }
264
+ else if ('experimental_updateServiceDefinitions' in this.config ||
265
+ 'experimental_updateSupergraphSdl' in this.config) {
266
+ const updateServiceDefinitions = 'experimental_updateServiceDefinitions' in this.config
267
+ ? this.config.experimental_updateServiceDefinitions
268
+ : this.config.experimental_updateSupergraphSdl;
269
+ await this.initializeSupergraphManager(new supergraphManagers_1.LegacyFetcher({
270
+ logger: this.logger,
271
+ gatewayConfig: this.config,
272
+ updateServiceDefinitions,
273
+ pollIntervalInMs: this.pollIntervalInMs,
274
+ subgraphHealthCheck: this.config.serviceHealthCheck,
275
+ }));
276
+ }
277
+ else if ((0, config_1.isServiceListConfig)(this.config)) {
278
+ this.logger.warn('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.');
279
+ await this.initializeSupergraphManager(new supergraphManagers_1.IntrospectAndCompose({
280
+ subgraphs: this.config.serviceList,
281
+ pollIntervalInMs: this.pollIntervalInMs,
282
+ logger: this.logger,
283
+ subgraphHealthCheck: this.config.serviceHealthCheck,
284
+ introspectionHeaders: this.config.introspectionHeaders,
285
+ }));
286
+ }
287
+ else {
288
+ const canUseManagedConfig = ((_a = this.apolloConfig) === null || _a === void 0 ? void 0 : _a.graphRef) && ((_b = this.apolloConfig) === null || _b === void 0 ? void 0 : _b.keyHash);
289
+ if (!canUseManagedConfig) {
290
+ throw new Error('When a manual configuration is not provided, gateway requires an Apollo ' +
291
+ 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
292
+ 'for more information. Manual configuration options include: ' +
293
+ '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.');
294
+ }
295
+ const uplinkEndpoints = this.getUplinkEndpoints(this.config);
296
+ await this.initializeSupergraphManager(new supergraphManagers_1.UplinkFetcher({
297
+ graphRef: this.apolloConfig.graphRef,
298
+ apiKey: this.apolloConfig.key,
299
+ uplinkEndpoints,
300
+ maxRetries: (_c = this.config.uplinkMaxRetries) !== null && _c !== void 0 ? _c : uplinkEndpoints.length * 3,
301
+ subgraphHealthCheck: this.config.serviceHealthCheck,
302
+ fetcher: this.fetcher,
303
+ logger: this.logger,
304
+ pollIntervalInMs: (_d = this.pollIntervalInMs) !== null && _d !== void 0 ? _d : 10000,
305
+ }));
306
+ }
271
307
  const mode = (0, config_1.isManagedConfig)(this.config) ? 'managed' : 'unmanaged';
272
308
  this.logger.info(`Gateway successfully loaded schema.\n\t* Mode: ${mode}${this.apolloConfig && this.apolloConfig.graphRef
273
309
  ? `\n\t* Service: ${this.apolloConfig.graphRef}`
@@ -277,92 +313,97 @@ class ApolloGateway {
277
313
  executor: this.executor,
278
314
  };
279
315
  }
280
- loadStatic(config) {
281
- let schema;
282
- let supergraphSdl;
283
- try {
284
- ({ schema, supergraphSdl } = (0, config_1.isLocalConfig)(config)
285
- ? this.createSchemaFromServiceList(config.localServiceList)
286
- : this.createSchemaFromSupergraphSdl(config.supergraphSdl));
287
- this.supergraphSdl = supergraphSdl;
288
- this.updateWithSchemaAndNotify(schema, supergraphSdl, true);
289
- }
290
- catch (e) {
291
- this.state = { phase: 'failed to load' };
292
- throw e;
293
- }
294
- this.state = { phase: 'loaded' };
316
+ getUplinkEndpoints(config) {
317
+ var _a, _b, _c, _d;
318
+ const rawEndpointsString = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
319
+ const envEndpoints = (_a = rawEndpointsString === null || rawEndpointsString === void 0 ? void 0 : rawEndpointsString.split(',')) !== null && _a !== void 0 ? _a : null;
320
+ return ((_d = (_c = (_b = config.uplinkEndpoints) !== null && _b !== void 0 ? _b : (config.schemaConfigDeliveryEndpoint
321
+ ? [config.schemaConfigDeliveryEndpoint]
322
+ : null)) !== null && _c !== void 0 ? _c : envEndpoints) !== null && _d !== void 0 ? _d : [
323
+ 'https://uplink.api.apollographql.com/',
324
+ 'https://aws.uplink.api.apollographql.com/',
325
+ ]);
326
+ }
327
+ getIdForSupergraphSdl(supergraphSdl) {
328
+ return (0, createHash_1.createHash)('sha256').update(supergraphSdl).digest('hex');
295
329
  }
296
- async loadDynamic(unrefTimer) {
330
+ async initializeSupergraphManager(supergraphManager) {
297
331
  try {
298
- await this.updateSchema();
332
+ const result = await supergraphManager.initialize({
333
+ update: this.externalSupergraphUpdateCallback.bind(this),
334
+ healthCheck: this.externalSubgraphHealthCheckCallback.bind(this),
335
+ getDataSource: this.externalGetDataSourceCallback.bind(this),
336
+ });
337
+ if (!(result === null || result === void 0 ? void 0 : result.supergraphSdl)) {
338
+ throw new Error('Provided `supergraphSdl` function did not return an object containing a `supergraphSdl` property');
339
+ }
340
+ if (result === null || result === void 0 ? void 0 : result.cleanup) {
341
+ if (typeof result.cleanup === 'function') {
342
+ this.toDispose.push(result.cleanup);
343
+ }
344
+ else {
345
+ this.logger.error('Provided `supergraphSdl` function returned an invalid `cleanup` property (must be a function)');
346
+ }
347
+ }
348
+ this.externalSupergraphUpdateCallback(result.supergraphSdl);
299
349
  }
300
350
  catch (e) {
301
351
  this.state = { phase: 'failed to load' };
352
+ await this.performCleanupAndLogErrors();
302
353
  throw e;
303
354
  }
304
355
  this.state = { phase: 'loaded' };
305
- if (this.shouldBeginPolling()) {
306
- this.pollServices(unrefTimer);
307
- }
308
356
  }
309
- shouldBeginPolling() {
310
- return (0, config_1.isManagedConfig)(this.config) || this.experimental_pollInterval;
311
- }
312
- async updateSchema() {
313
- this.logger.debug('Checking for composition updates...');
314
- const result = await this.updateServiceDefinitions(this.config);
315
- if ((0, config_1.isSupergraphSdlUpdate)(result)) {
316
- await this.updateWithSupergraphSdl(result);
357
+ externalSupergraphUpdateCallback(supergraphSdl) {
358
+ switch (this.state.phase) {
359
+ case 'failed to load':
360
+ throw new Error("Can't call `update` callback after gateway failed to load.");
361
+ case 'updating schema':
362
+ throw new Error("Can't call `update` callback while supergraph update is in progress.");
363
+ case 'stopped':
364
+ throw new Error("Can't call `update` callback after gateway has been stopped.");
365
+ case 'stopping':
366
+ throw new Error("Can't call `update` callback while gateway is stopping.");
367
+ case 'loaded':
368
+ case 'initialized':
369
+ break;
370
+ default:
371
+ throw new UnreachableCaseError(this.state);
317
372
  }
318
- else if ((0, config_1.isServiceDefinitionUpdate)(result)) {
319
- await this.updateByComposition(result);
373
+ this.state = { phase: 'updating schema' };
374
+ try {
375
+ this.updateWithSupergraphSdl({
376
+ supergraphSdl,
377
+ id: this.getIdForSupergraphSdl(supergraphSdl),
378
+ });
320
379
  }
321
- else {
322
- throw new Error('Programming error: unexpected result type from `updateServiceDefinitions`');
380
+ finally {
381
+ this.state = { phase: 'loaded' };
323
382
  }
324
383
  }
325
- async updateByComposition(result) {
326
- if (!result.serviceDefinitions ||
327
- JSON.stringify(this.serviceDefinitions) ===
328
- JSON.stringify(result.serviceDefinitions)) {
329
- this.logger.debug('No change in service definitions since last check.');
330
- return;
331
- }
332
- const previousSchema = this.schema;
333
- const previousServiceDefinitions = this.serviceDefinitions;
334
- const previousCompositionMetadata = this.compositionMetadata;
335
- if (previousSchema) {
336
- this.logger.info('New service definitions were found.');
337
- }
338
- await this.maybePerformServiceHealthCheck(result);
339
- this.compositionMetadata = result.compositionMetadata;
340
- this.serviceDefinitions = result.serviceDefinitions;
341
- const { schema, supergraphSdl } = this.createSchemaFromServiceList(result.serviceDefinitions);
342
- if (!supergraphSdl) {
343
- this.logger.error("A valid schema couldn't be composed. Falling back to previous schema.");
384
+ async externalSubgraphHealthCheckCallback(supergraphSdl) {
385
+ const serviceList = this.serviceListFromSupergraphSdl(supergraphSdl);
386
+ const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
387
+ serviceMap[serviceDef.name] = {
388
+ url: serviceDef.url,
389
+ dataSource: this.createDataSource(serviceDef),
390
+ };
391
+ return serviceMap;
392
+ }, Object.create(null));
393
+ try {
394
+ await this.serviceHealthCheck(serviceMap);
344
395
  }
345
- else {
346
- this.updateWithSchemaAndNotify(schema, supergraphSdl);
347
- if (this.experimental_didUpdateComposition) {
348
- this.experimental_didUpdateComposition({
349
- serviceDefinitions: result.serviceDefinitions,
350
- schema: schema.toGraphQLJSSchema(),
351
- ...(this.compositionMetadata && {
352
- compositionMetadata: this.compositionMetadata,
353
- }),
354
- }, previousServiceDefinitions &&
355
- previousSchema && {
356
- serviceDefinitions: previousServiceDefinitions,
357
- schema: previousSchema,
358
- ...(previousCompositionMetadata && {
359
- compositionMetadata: previousCompositionMetadata,
360
- }),
361
- });
362
- }
396
+ catch (e) {
397
+ throw new Error('The gateway subgraphs health check failed. Updating to the provided ' +
398
+ '`supergraphSdl` will likely result in future request failures to ' +
399
+ 'subgraphs. The following error occurred during the health check:\n' +
400
+ e.message);
363
401
  }
364
402
  }
365
- async updateWithSupergraphSdl(result) {
403
+ externalGetDataSourceCallback({ name, url, }) {
404
+ return this.getOrCreateDataSource({ name, url });
405
+ }
406
+ updateWithSupergraphSdl(result) {
366
407
  if (result.id === this.compositionId) {
367
408
  this.logger.debug('No change in composition since last check.');
368
409
  return;
@@ -374,18 +415,17 @@ class ApolloGateway {
374
415
  if (previousSchema) {
375
416
  this.logger.info('Updated Supergraph SDL was found.');
376
417
  }
377
- await this.maybePerformServiceHealthCheck(result);
378
418
  this.compositionId = result.id;
379
- this.supergraphSdl = result.supergraphSdl;
419
+ this.supergraphSdl = supergraphSdl;
380
420
  if (!supergraphSdl) {
381
421
  this.logger.error("A valid schema couldn't be composed. Falling back to previous schema.");
382
422
  }
383
423
  else {
384
424
  this.updateWithSchemaAndNotify(schema, supergraphSdl);
385
- if (this.experimental_didUpdateComposition) {
386
- this.experimental_didUpdateComposition({
425
+ if (this.experimental_didUpdateSupergraph) {
426
+ this.experimental_didUpdateSupergraph({
387
427
  compositionId: result.id,
388
- supergraphSdl: result.supergraphSdl,
428
+ supergraphSdl,
389
429
  schema: schema.toGraphQLJSSchema(),
390
430
  }, previousCompositionId && previousSupergraphSdl && previousSchema
391
431
  ? {
@@ -429,31 +469,6 @@ class ApolloGateway {
429
469
  }
430
470
  });
431
471
  }
432
- async maybePerformServiceHealthCheck(update) {
433
- if (this.config.serviceHealthCheck) {
434
- const serviceList = (0, config_1.isSupergraphSdlUpdate)(update)
435
- ?
436
- this.serviceListFromSupergraphSdl(update.supergraphSdl)
437
- :
438
- update.serviceDefinitions;
439
- const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
440
- serviceMap[serviceDef.name] = {
441
- url: serviceDef.url,
442
- dataSource: this.createDataSource(serviceDef),
443
- };
444
- return serviceMap;
445
- }, Object.create(null));
446
- try {
447
- await this.serviceHealthCheck(serviceMap);
448
- }
449
- catch (e) {
450
- throw new Error('The gateway did not update its schema due to failed service health checks. ' +
451
- 'The gateway will continue to operate with the previous schema and reattempt updates. ' +
452
- 'The following error occurred during the health check:\n' +
453
- e.message);
454
- }
455
- }
456
- }
457
472
  serviceHealthCheck(serviceMap = this.serviceMap) {
458
473
  return Promise.all(Object.entries(serviceMap).map(([name, { dataSource }]) => dataSource
459
474
  .process({
@@ -466,34 +481,6 @@ class ApolloGateway {
466
481
  throw new Error(`[${name}]: ${e.message}`);
467
482
  })));
468
483
  }
469
- createSchemaFromServiceList(serviceList) {
470
- this.logger.debug(`Composing schema from service list: \n${serviceList
471
- .map(({ name, url }) => ` ${url || 'local'}: ${name}`)
472
- .join('\n')}`);
473
- const compositionResult = (0, composition_1.composeServices)(serviceList);
474
- const errors = compositionResult.errors;
475
- if (errors) {
476
- if (this.experimental_didFailComposition) {
477
- this.experimental_didFailComposition({
478
- errors,
479
- serviceList,
480
- ...(this.compositionMetadata && {
481
- compositionMetadata: this.compositionMetadata,
482
- }),
483
- });
484
- }
485
- throw Error("A valid schema couldn't be composed. The following composition errors were found:\n" +
486
- errors.map((e) => '\t' + e.message).join('\n'));
487
- }
488
- else {
489
- this.createServices(serviceList);
490
- this.logger.debug('Schema loaded and ready for execution');
491
- return {
492
- schema: compositionResult.schema,
493
- supergraphSdl: compositionResult.supergraphSdl,
494
- };
495
- }
496
- }
497
484
  serviceListFromSupergraphSdl(supergraphSdl) {
498
485
  return (0, federation_internals_1.buildSupergraphSchema)(supergraphSdl)[1];
499
486
  }
@@ -517,63 +504,11 @@ class ApolloGateway {
517
504
  this.onSchemaLoadOrUpdateListeners.delete(callback);
518
505
  };
519
506
  }
520
- async pollServices(unrefTimer) {
521
- switch (this.state.phase) {
522
- case 'stopping':
523
- case 'stopped':
524
- case 'failed to load':
525
- return;
526
- case 'initialized':
527
- throw Error('pollServices should not be called before load!');
528
- case 'polling':
529
- throw Error('pollServices should not be called while in the middle of polling!');
530
- case 'waiting to poll':
531
- throw Error('pollServices should not be called while already waiting to poll!');
532
- case 'loaded':
533
- break;
534
- default:
535
- throw new UnreachableCaseError(this.state);
536
- }
537
- await new Promise((doneWaiting) => {
538
- this.state = {
539
- phase: 'waiting to poll',
540
- doneWaiting,
541
- pollWaitTimer: setTimeout(() => {
542
- if (this.state.phase == 'waiting to poll') {
543
- this.state.doneWaiting();
544
- }
545
- }, this.experimental_pollInterval || 10000),
546
- };
547
- if (unrefTimer) {
548
- this.state.pollWaitTimer.unref();
549
- }
550
- });
551
- if (this.state.phase !== 'waiting to poll') {
552
- return;
553
- }
554
- let pollingDone;
555
- this.state = {
556
- phase: 'polling',
557
- pollingDonePromise: new Promise((res) => {
558
- pollingDone = res;
559
- }),
560
- };
561
- try {
562
- await this.updateSchema();
563
- }
564
- catch (err) {
565
- this.logger.error((err && err.message) || err);
566
- }
567
- if (this.state.phase === 'polling') {
568
- this.state = { phase: 'loaded' };
569
- setImmediate(() => this.pollServices(unrefTimer));
570
- }
571
- pollingDone();
572
- }
573
- createAndCacheDataSource(serviceDef) {
507
+ getOrCreateDataSource(serviceDef) {
574
508
  if (this.serviceMap[serviceDef.name] &&
575
- serviceDef.url === this.serviceMap[serviceDef.name].url)
509
+ serviceDef.url === this.serviceMap[serviceDef.name].url) {
576
510
  return this.serviceMap[serviceDef.name].dataSource;
511
+ }
577
512
  const dataSource = this.createDataSource(serviceDef);
578
513
  this.serviceMap[serviceDef.name] = { url: serviceDef.url, dataSource };
579
514
  return dataSource;
@@ -590,46 +525,8 @@ class ApolloGateway {
590
525
  }
591
526
  createServices(services) {
592
527
  for (const serviceDef of services) {
593
- this.createAndCacheDataSource(serviceDef);
594
- }
595
- }
596
- async loadServiceDefinitions(config) {
597
- var _a, _b, _c;
598
- if ((0, config_1.isRemoteConfig)(config)) {
599
- const serviceList = config.serviceList.map((serviceDefinition) => ({
600
- ...serviceDefinition,
601
- dataSource: this.createAndCacheDataSource(serviceDefinition),
602
- }));
603
- return (0, loadServicesFromRemoteEndpoint_1.getServiceDefinitionsFromRemoteEndpoint)({
604
- serviceList,
605
- async getServiceIntrospectionHeaders(service) {
606
- return typeof config.introspectionHeaders === 'function'
607
- ? await config.introspectionHeaders(service)
608
- : config.introspectionHeaders;
609
- },
610
- serviceSdlCache: this.serviceSdlCache,
611
- });
528
+ this.getOrCreateDataSource(serviceDef);
612
529
  }
613
- const canUseManagedConfig = ((_a = this.apolloConfig) === null || _a === void 0 ? void 0 : _a.graphRef) && ((_b = this.apolloConfig) === null || _b === void 0 ? void 0 : _b.keyHash);
614
- if (!canUseManagedConfig) {
615
- throw new Error('When a manual configuration is not provided, gateway requires an Apollo ' +
616
- 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
617
- 'for more information. Manual configuration options include: ' +
618
- '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.');
619
- }
620
- const result = await (0, loadSupergraphSdlFromStorage_1.loadSupergraphSdlFromUplinks)({
621
- graphRef: this.apolloConfig.graphRef,
622
- apiKey: this.apolloConfig.key,
623
- endpoints: this.uplinkEndpoints,
624
- errorReportingEndpoint: this.errorReportingEndpoint,
625
- fetcher: this.fetcher,
626
- compositionId: (_c = this.compositionId) !== null && _c !== void 0 ? _c : null,
627
- maxRetries: this.uplinkMaxRetries,
628
- });
629
- return (result !== null && result !== void 0 ? result : {
630
- id: this.compositionId,
631
- supergraphSdl: this.supergraphSdl,
632
- });
633
530
  }
634
531
  maybeWarnOnConflictingConfig() {
635
532
  var _a, _b;
@@ -665,6 +562,16 @@ class ApolloGateway {
665
562
  }
666
563
  });
667
564
  }
565
+ async performCleanupAndLogErrors() {
566
+ if (this.toDispose.length === 0)
567
+ return;
568
+ await Promise.all(this.toDispose.map((p) => p().catch((e) => {
569
+ var _a;
570
+ this.logger.error('Error occured while calling user provided `cleanup` function: ' +
571
+ ((_a = e.message) !== null && _a !== void 0 ? _a : e));
572
+ })));
573
+ this.toDispose = [];
574
+ }
668
575
  async stop() {
669
576
  switch (this.state.phase) {
670
577
  case 'initialized':
@@ -679,33 +586,28 @@ class ApolloGateway {
679
586
  }
680
587
  return;
681
588
  case 'loaded':
682
- this.state = { phase: 'stopped' };
683
- return;
684
- case 'waiting to poll': {
685
- const doneWaiting = this.state.doneWaiting;
686
- clearTimeout(this.state.pollWaitTimer);
687
- this.state = { phase: 'stopped' };
688
- doneWaiting();
689
- return;
690
- }
691
- case 'polling': {
692
- const pollingDonePromise = this.state.pollingDonePromise;
693
- let stoppingDone;
589
+ const stoppingDonePromise = this.performCleanupAndLogErrors();
694
590
  this.state = {
695
591
  phase: 'stopping',
696
- stoppingDonePromise: new Promise((res) => {
697
- stoppingDone = res;
698
- }),
592
+ stoppingDonePromise,
699
593
  };
700
- await pollingDonePromise;
594
+ await stoppingDonePromise;
701
595
  this.state = { phase: 'stopped' };
702
- stoppingDone();
703
596
  return;
597
+ case 'updating schema': {
598
+ throw Error("`ApolloGateway.stop` shouldn't be called from inside a schema change listener");
704
599
  }
705
600
  default:
706
601
  throw new UnreachableCaseError(this.state);
707
602
  }
708
603
  }
604
+ __testing() {
605
+ return {
606
+ state: this.state,
607
+ compositionId: this.compositionId,
608
+ supergraphSdl: this.supergraphSdl,
609
+ };
610
+ }
709
611
  }
710
612
  exports.ApolloGateway = ApolloGateway;
711
613
  ApolloGateway.prototype.onSchemaChange = (0, util_1.deprecate)(ApolloGateway.prototype.onSchemaChange, `'ApolloGateway.prototype.onSchemaChange' is deprecated. Use 'ApolloGateway.prototype.onSchemaLoadOrUpdate' instead.`);