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

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 (198) hide show
  1. package/LICENSE +95 -0
  2. package/README.md +1 -1
  3. package/dist/__generated__/graphqlTypes.d.ts +130 -0
  4. package/dist/__generated__/graphqlTypes.d.ts.map +1 -0
  5. package/dist/__generated__/graphqlTypes.js +25 -0
  6. package/dist/__generated__/graphqlTypes.js.map +1 -0
  7. package/dist/config.d.ts +104 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +47 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/datasources/LocalGraphQLDataSource.d.ts +3 -3
  12. package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
  13. package/dist/datasources/LocalGraphQLDataSource.js +5 -5
  14. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  15. package/dist/datasources/RemoteGraphQLDataSource.d.ts +6 -4
  16. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  17. package/dist/datasources/RemoteGraphQLDataSource.js +60 -17
  18. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  19. package/dist/datasources/index.d.ts +1 -1
  20. package/dist/datasources/index.d.ts.map +1 -1
  21. package/dist/datasources/index.js +1 -0
  22. package/dist/datasources/index.js.map +1 -1
  23. package/dist/datasources/parseCacheControlHeader.d.ts +2 -0
  24. package/dist/datasources/parseCacheControlHeader.d.ts.map +1 -0
  25. package/dist/datasources/parseCacheControlHeader.js +16 -0
  26. package/dist/datasources/parseCacheControlHeader.js.map +1 -0
  27. package/dist/datasources/types.d.ts +16 -1
  28. package/dist/datasources/types.d.ts.map +1 -1
  29. package/dist/datasources/types.js +7 -0
  30. package/dist/datasources/types.js.map +1 -1
  31. package/dist/executeQueryPlan.d.ts +2 -1
  32. package/dist/executeQueryPlan.d.ts.map +1 -1
  33. package/dist/executeQueryPlan.js +199 -112
  34. package/dist/executeQueryPlan.js.map +1 -1
  35. package/dist/index.d.ts +62 -80
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +543 -234
  38. package/dist/index.js.map +1 -1
  39. package/dist/loadServicesFromRemoteEndpoint.d.ts +9 -9
  40. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
  41. package/dist/loadServicesFromRemoteEndpoint.js +13 -8
  42. package/dist/loadServicesFromRemoteEndpoint.js.map +1 -1
  43. package/dist/loadSupergraphSdlFromStorage.d.ts +13 -0
  44. package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  45. package/dist/loadSupergraphSdlFromStorage.js +101 -0
  46. package/dist/loadSupergraphSdlFromStorage.js.map +1 -0
  47. package/dist/operationContext.d.ts +17 -0
  48. package/dist/operationContext.d.ts.map +1 -0
  49. package/dist/operationContext.js +42 -0
  50. package/dist/operationContext.js.map +1 -0
  51. package/dist/outOfBandReporter.d.ts +15 -0
  52. package/dist/outOfBandReporter.d.ts.map +1 -0
  53. package/dist/outOfBandReporter.js +88 -0
  54. package/dist/outOfBandReporter.js.map +1 -0
  55. package/dist/utilities/array.d.ts +1 -2
  56. package/dist/utilities/array.d.ts.map +1 -1
  57. package/dist/utilities/array.js +7 -14
  58. package/dist/utilities/array.js.map +1 -1
  59. package/dist/utilities/assert.d.ts +2 -0
  60. package/dist/utilities/assert.d.ts.map +1 -0
  61. package/dist/utilities/assert.js +10 -0
  62. package/dist/utilities/assert.js.map +1 -0
  63. package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts +3 -0
  64. package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts.map +1 -0
  65. package/dist/utilities/cleanErrorOfInaccessibleNames.js +27 -0
  66. package/dist/utilities/cleanErrorOfInaccessibleNames.js.map +1 -0
  67. package/dist/utilities/deepMerge.js +2 -2
  68. package/dist/utilities/deepMerge.js.map +1 -1
  69. package/dist/utilities/graphql.d.ts +1 -4
  70. package/dist/utilities/graphql.d.ts.map +1 -1
  71. package/dist/utilities/graphql.js +3 -36
  72. package/dist/utilities/graphql.js.map +1 -1
  73. package/dist/utilities/opentelemetry.d.ts +10 -0
  74. package/dist/utilities/opentelemetry.d.ts.map +1 -0
  75. package/dist/utilities/opentelemetry.js +19 -0
  76. package/dist/utilities/opentelemetry.js.map +1 -0
  77. package/package.json +30 -21
  78. package/src/__generated__/graphqlTypes.ts +140 -0
  79. package/src/__mocks__/apollo-server-env.ts +56 -0
  80. package/src/__mocks__/make-fetch-happen-fetcher.ts +55 -0
  81. package/src/__mocks__/tsconfig.json +7 -0
  82. package/src/__tests__/build-query-plan.feature +40 -311
  83. package/src/__tests__/buildQueryPlan.test.ts +246 -426
  84. package/src/__tests__/executeQueryPlan.test.ts +1691 -194
  85. package/src/__tests__/execution-utils.ts +33 -26
  86. package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +195 -0
  87. package/src/__tests__/gateway/buildService.test.ts +16 -19
  88. package/src/__tests__/gateway/composedSdl.test.ts +44 -0
  89. package/src/__tests__/gateway/endToEnd.test.ts +166 -0
  90. package/src/__tests__/gateway/executor.test.ts +49 -43
  91. package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -29
  92. package/src/__tests__/gateway/opentelemetry.test.ts +123 -0
  93. package/src/__tests__/gateway/queryPlanCache.test.ts +19 -20
  94. package/src/__tests__/gateway/reporting.test.ts +76 -55
  95. package/src/__tests__/integration/abstract-types.test.ts +1086 -22
  96. package/src/__tests__/integration/aliases.test.ts +5 -6
  97. package/src/__tests__/integration/boolean.test.ts +40 -38
  98. package/src/__tests__/integration/complex-key.test.ts +41 -56
  99. package/src/__tests__/integration/configuration.test.ts +321 -0
  100. package/src/__tests__/integration/custom-directives.test.ts +61 -46
  101. package/src/__tests__/integration/fragments.test.ts +8 -2
  102. package/src/__tests__/integration/list-key.test.ts +2 -2
  103. package/src/__tests__/integration/logger.test.ts +2 -2
  104. package/src/__tests__/integration/multiple-key.test.ts +11 -12
  105. package/src/__tests__/integration/mutations.test.ts +8 -5
  106. package/src/__tests__/integration/networkRequests.test.ts +447 -289
  107. package/src/__tests__/integration/nockMocks.ts +95 -66
  108. package/src/__tests__/integration/provides.test.ts +9 -6
  109. package/src/__tests__/integration/requires.test.ts +17 -15
  110. package/src/__tests__/integration/scope.test.ts +557 -0
  111. package/src/__tests__/integration/unions.test.ts +1 -1
  112. package/src/__tests__/integration/value-types.test.ts +35 -32
  113. package/src/__tests__/integration/variables.test.ts +8 -2
  114. package/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +6 -2
  115. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +694 -0
  116. package/src/__tests__/queryPlanCucumber.test.ts +11 -61
  117. package/src/__tests__/testSetup.ts +1 -4
  118. package/src/__tests__/tsconfig.json +2 -1
  119. package/src/config.ts +225 -0
  120. package/src/core/__tests__/core.test.ts +412 -0
  121. package/src/datasources/LocalGraphQLDataSource.ts +9 -10
  122. package/src/datasources/RemoteGraphQLDataSource.ts +117 -43
  123. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +11 -4
  124. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +148 -79
  125. package/src/datasources/__tests__/tsconfig.json +4 -2
  126. package/src/datasources/index.ts +1 -1
  127. package/src/datasources/parseCacheControlHeader.ts +43 -0
  128. package/src/datasources/types.ts +47 -2
  129. package/src/executeQueryPlan.ts +264 -153
  130. package/src/index.ts +925 -480
  131. package/src/loadServicesFromRemoteEndpoint.ts +24 -17
  132. package/src/loadSupergraphSdlFromStorage.ts +140 -0
  133. package/src/make-fetch-happen.d.ts +2 -2
  134. package/src/operationContext.ts +70 -0
  135. package/src/outOfBandReporter.ts +128 -0
  136. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +104 -0
  137. package/src/utilities/__tests__/tsconfig.json +8 -0
  138. package/src/utilities/array.ts +6 -28
  139. package/src/utilities/assert.ts +14 -0
  140. package/src/utilities/cleanErrorOfInaccessibleNames.ts +29 -0
  141. package/src/utilities/graphql.ts +0 -64
  142. package/src/utilities/opentelemetry.ts +13 -0
  143. package/CHANGELOG.md +0 -226
  144. package/LICENSE.md +0 -20
  145. package/dist/FieldSet.d.ts +0 -18
  146. package/dist/FieldSet.d.ts.map +0 -1
  147. package/dist/FieldSet.js +0 -96
  148. package/dist/FieldSet.js.map +0 -1
  149. package/dist/QueryPlan.d.ts +0 -41
  150. package/dist/QueryPlan.d.ts.map +0 -1
  151. package/dist/QueryPlan.js +0 -15
  152. package/dist/QueryPlan.js.map +0 -1
  153. package/dist/buildQueryPlan.d.ts +0 -44
  154. package/dist/buildQueryPlan.d.ts.map +0 -1
  155. package/dist/buildQueryPlan.js +0 -670
  156. package/dist/buildQueryPlan.js.map +0 -1
  157. package/dist/loadServicesFromStorage.d.ts +0 -21
  158. package/dist/loadServicesFromStorage.d.ts.map +0 -1
  159. package/dist/loadServicesFromStorage.js +0 -64
  160. package/dist/loadServicesFromStorage.js.map +0 -1
  161. package/dist/snapshotSerializers/astSerializer.d.ts +0 -3
  162. package/dist/snapshotSerializers/astSerializer.d.ts.map +0 -1
  163. package/dist/snapshotSerializers/astSerializer.js +0 -14
  164. package/dist/snapshotSerializers/astSerializer.js.map +0 -1
  165. package/dist/snapshotSerializers/index.d.ts +0 -13
  166. package/dist/snapshotSerializers/index.d.ts.map +0 -1
  167. package/dist/snapshotSerializers/index.js +0 -15
  168. package/dist/snapshotSerializers/index.js.map +0 -1
  169. package/dist/snapshotSerializers/queryPlanSerializer.d.ts +0 -3
  170. package/dist/snapshotSerializers/queryPlanSerializer.d.ts.map +0 -1
  171. package/dist/snapshotSerializers/queryPlanSerializer.js +0 -78
  172. package/dist/snapshotSerializers/queryPlanSerializer.js.map +0 -1
  173. package/dist/snapshotSerializers/selectionSetSerializer.d.ts +0 -3
  174. package/dist/snapshotSerializers/selectionSetSerializer.d.ts.map +0 -1
  175. package/dist/snapshotSerializers/selectionSetSerializer.js +0 -12
  176. package/dist/snapshotSerializers/selectionSetSerializer.js.map +0 -1
  177. package/dist/snapshotSerializers/typeSerializer.d.ts +0 -3
  178. package/dist/snapshotSerializers/typeSerializer.d.ts.map +0 -1
  179. package/dist/snapshotSerializers/typeSerializer.js +0 -12
  180. package/dist/snapshotSerializers/typeSerializer.js.map +0 -1
  181. package/dist/utilities/MultiMap.d.ts +0 -4
  182. package/dist/utilities/MultiMap.d.ts.map +0 -1
  183. package/dist/utilities/MultiMap.js +0 -17
  184. package/dist/utilities/MultiMap.js.map +0 -1
  185. package/src/FieldSet.ts +0 -169
  186. package/src/QueryPlan.ts +0 -57
  187. package/src/__tests__/matchers/toCallService.ts +0 -105
  188. package/src/__tests__/matchers/toHaveBeenCalledBefore.ts +0 -40
  189. package/src/__tests__/matchers/toHaveFetched.ts +0 -81
  190. package/src/__tests__/matchers/toMatchAST.ts +0 -64
  191. package/src/buildQueryPlan.ts +0 -1190
  192. package/src/loadServicesFromStorage.ts +0 -170
  193. package/src/snapshotSerializers/astSerializer.ts +0 -21
  194. package/src/snapshotSerializers/index.ts +0 -21
  195. package/src/snapshotSerializers/queryPlanSerializer.ts +0 -144
  196. package/src/snapshotSerializers/selectionSetSerializer.ts +0 -13
  197. package/src/snapshotSerializers/typeSerializer.ts +0 -11
  198. package/src/utilities/MultiMap.ts +0 -11
package/dist/index.js CHANGED
@@ -7,199 +7,312 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
7
7
  o[k2] = m[k];
8
8
  }));
9
9
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
- for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
11
  };
12
12
  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.serializeQueryPlan = exports.executeQueryPlan = exports.buildQueryPlan = exports.ApolloGateway = exports.SERVICE_DEFINITION_QUERY = exports.HEALTH_CHECK_QUERY = exports.getDefaultGcsFetcher = exports.GCS_RETRY_COUNT = void 0;
16
+ exports.buildOperationContext = exports.executeQueryPlan = exports.ApolloGateway = exports.SERVICE_DEFINITION_QUERY = exports.HEALTH_CHECK_QUERY = exports.getDefaultFetcher = void 0;
17
+ const util_1 = require("util");
17
18
  const apollo_server_caching_1 = require("apollo-server-caching");
18
19
  const graphql_1 = require("graphql");
19
- const apollo_graphql_1 = require("apollo-graphql");
20
- const federation_1 = require("@apollo/federation");
21
20
  const loglevel_1 = __importDefault(require("loglevel"));
22
- const buildQueryPlan_1 = require("./buildQueryPlan");
23
- Object.defineProperty(exports, "buildQueryPlan", { enumerable: true, get: function () { return buildQueryPlan_1.buildQueryPlan; } });
24
- Object.defineProperty(exports, "buildOperationContext", { enumerable: true, get: function () { return buildQueryPlan_1.buildOperationContext; } });
21
+ const operationContext_1 = require("./operationContext");
22
+ Object.defineProperty(exports, "buildOperationContext", { enumerable: true, get: function () { return operationContext_1.buildOperationContext; } });
25
23
  const executeQueryPlan_1 = require("./executeQueryPlan");
26
24
  Object.defineProperty(exports, "executeQueryPlan", { enumerable: true, get: function () { return executeQueryPlan_1.executeQueryPlan; } });
27
25
  const loadServicesFromRemoteEndpoint_1 = require("./loadServicesFromRemoteEndpoint");
28
- const loadServicesFromStorage_1 = require("./loadServicesFromStorage");
29
- const QueryPlan_1 = require("./QueryPlan");
30
- Object.defineProperty(exports, "serializeQueryPlan", { enumerable: true, get: function () { return QueryPlan_1.serializeQueryPlan; } });
26
+ const types_1 = require("./datasources/types");
31
27
  const RemoteGraphQLDataSource_1 = require("./datasources/RemoteGraphQLDataSource");
32
28
  const values_1 = require("graphql/execution/values");
33
29
  const make_fetch_happen_1 = __importDefault(require("make-fetch-happen"));
34
30
  const cache_1 = require("./cache");
35
- function isLocalConfig(config) {
36
- return 'localServiceList' in config;
37
- }
38
- function isRemoteConfig(config) {
39
- return 'serviceList' in config;
40
- }
41
- function isManagedConfig(config) {
42
- return !isRemoteConfig(config) && !isLocalConfig(config);
43
- }
44
- exports.GCS_RETRY_COUNT = 5;
45
- function getDefaultGcsFetcher() {
31
+ const query_planner_1 = require("@apollo/query-planner");
32
+ const config_1 = require("./config");
33
+ const loadSupergraphSdlFromStorage_1 = require("./loadSupergraphSdlFromStorage");
34
+ const api_1 = require("@opentelemetry/api");
35
+ const opentelemetry_1 = require("./utilities/opentelemetry");
36
+ const federation_internals_1 = require("@apollo/federation-internals");
37
+ const composition_1 = require("@apollo/composition");
38
+ function getDefaultFetcher() {
39
+ const { name, version } = require('../package.json');
46
40
  return make_fetch_happen_1.default.defaults({
47
41
  cacheManager: new cache_1.HttpRequestCache(),
48
42
  headers: {
49
- 'user-agent': `apollo-gateway/${require('../package.json').version}`,
43
+ 'apollographql-client-name': name,
44
+ 'apollographql-client-version': version,
45
+ 'user-agent': `${name}/${version}`,
46
+ 'content-type': 'application/json',
50
47
  },
51
48
  retry: {
52
- retries: exports.GCS_RETRY_COUNT,
49
+ retries: 5,
53
50
  factor: 2,
54
51
  minTimeout: 1000,
55
52
  randomize: true,
56
53
  },
57
54
  });
58
55
  }
59
- exports.getDefaultGcsFetcher = getDefaultGcsFetcher;
56
+ exports.getDefaultFetcher = getDefaultFetcher;
60
57
  exports.HEALTH_CHECK_QUERY = 'query __ApolloServiceHealthCheck__ { __typename }';
61
58
  exports.SERVICE_DEFINITION_QUERY = 'query __ApolloGetServiceDefinition__ { _service { sdl } }';
62
59
  class ApolloGateway {
63
60
  constructor(config) {
61
+ var _a, _b;
64
62
  this.serviceMap = Object.create(null);
65
63
  this.onSchemaChangeListeners = new Set();
64
+ this.onSchemaLoadOrUpdateListeners = new Set();
66
65
  this.serviceDefinitions = [];
67
66
  this.serviceSdlCache = new Map();
68
67
  this.warnedStates = Object.create(null);
69
- this.fetcher = getDefaultGcsFetcher();
70
68
  this.executor = async (requestContext) => {
71
- const { request, document, queryHash } = requestContext;
72
- const queryPlanStoreKey = queryHash + (request.operationName || '');
73
- const operationContext = buildQueryPlan_1.buildOperationContext(this.schema, document, request.operationName);
74
- const validationErrors = this.validateIncomingRequest(requestContext, operationContext);
75
- if (validationErrors.length > 0) {
76
- return { errors: validationErrors };
77
- }
78
- let queryPlan;
79
- if (this.queryPlanStore) {
80
- queryPlan = await this.queryPlanStore.get(queryPlanStoreKey);
81
- }
82
- if (!queryPlan) {
83
- queryPlan = buildQueryPlan_1.buildQueryPlan(operationContext, {
84
- autoFragmentization: Boolean(this.config.experimental_autoFragmentization),
85
- });
86
- if (this.queryPlanStore) {
87
- Promise.resolve(this.queryPlanStore.set(queryPlanStoreKey, queryPlan)).catch(err => this.logger.warn('Could not store queryPlan' + ((err && err.message) || err)));
69
+ const spanAttributes = requestContext.operationName
70
+ ? { operationName: requestContext.operationName }
71
+ : {};
72
+ return opentelemetry_1.tracer.startActiveSpan(opentelemetry_1.OpenTelemetrySpanNames.REQUEST, { attributes: spanAttributes }, async (span) => {
73
+ try {
74
+ const { request, document, queryHash } = requestContext;
75
+ const queryPlanStoreKey = queryHash + (request.operationName || '');
76
+ const operationContext = (0, operationContext_1.buildOperationContext)({
77
+ schema: this.schema,
78
+ operationDocument: document,
79
+ operationName: request.operationName,
80
+ });
81
+ const validationErrors = this.validateIncomingRequest(requestContext, operationContext);
82
+ if (validationErrors.length > 0) {
83
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR });
84
+ return { errors: validationErrors };
85
+ }
86
+ let queryPlan;
87
+ if (this.queryPlanStore) {
88
+ queryPlan = await this.queryPlanStore.get(queryPlanStoreKey);
89
+ }
90
+ if (!queryPlan) {
91
+ queryPlan = opentelemetry_1.tracer.startActiveSpan(opentelemetry_1.OpenTelemetrySpanNames.PLAN, (span) => {
92
+ try {
93
+ const operation = (0, federation_internals_1.operationFromDocument)(this.apiSchema, document, request.operationName);
94
+ return this.queryPlanner.buildQueryPlan(operation);
95
+ }
96
+ catch (err) {
97
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR });
98
+ throw err;
99
+ }
100
+ finally {
101
+ span.end();
102
+ }
103
+ });
104
+ if (this.queryPlanStore) {
105
+ Promise.resolve(this.queryPlanStore.set(queryPlanStoreKey, queryPlan)).catch((err) => this.logger.warn('Could not store queryPlan' + ((err && err.message) || err)));
106
+ }
107
+ }
108
+ const serviceMap = Object.entries(this.serviceMap).reduce((serviceDataSources, [serviceName, { dataSource }]) => {
109
+ serviceDataSources[serviceName] = dataSource;
110
+ return serviceDataSources;
111
+ }, Object.create(null));
112
+ if (this.experimental_didResolveQueryPlan) {
113
+ this.experimental_didResolveQueryPlan({
114
+ queryPlan,
115
+ serviceMap,
116
+ requestContext,
117
+ operationContext,
118
+ });
119
+ }
120
+ const response = await (0, executeQueryPlan_1.executeQueryPlan)(queryPlan, serviceMap, requestContext, operationContext);
121
+ const shouldShowQueryPlan = this.config.__exposeQueryPlanExperimental &&
122
+ request.http &&
123
+ request.http.headers &&
124
+ request.http.headers.get('Apollo-Query-Plan-Experimental');
125
+ const serializedQueryPlan = queryPlan.node && (this.config.debug || shouldShowQueryPlan)
126
+ ?
127
+ (0, query_planner_1.prettyFormatQueryPlan)(queryPlan)
128
+ : null;
129
+ if (this.config.debug && serializedQueryPlan) {
130
+ this.logger.debug(serializedQueryPlan);
131
+ }
132
+ if (shouldShowQueryPlan) {
133
+ response.extensions = {
134
+ __queryPlanExperimental: serializedQueryPlan || true,
135
+ };
136
+ }
137
+ if (response.errors) {
138
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR });
139
+ }
140
+ return response;
88
141
  }
89
- }
90
- const serviceMap = Object.entries(this.serviceMap).reduce((serviceDataSources, [serviceName, { dataSource }]) => {
91
- serviceDataSources[serviceName] = dataSource;
92
- return serviceDataSources;
93
- }, Object.create(null));
94
- if (this.experimental_didResolveQueryPlan) {
95
- this.experimental_didResolveQueryPlan({
96
- queryPlan,
97
- serviceMap,
98
- requestContext,
99
- operationContext,
100
- });
101
- }
102
- const response = await executeQueryPlan_1.executeQueryPlan(queryPlan, serviceMap, requestContext, operationContext);
103
- const shouldShowQueryPlan = this.config.__exposeQueryPlanExperimental &&
104
- request.http &&
105
- request.http.headers &&
106
- request.http.headers.get('Apollo-Query-Plan-Experimental');
107
- const serializedQueryPlan = queryPlan.node && (this.config.debug || shouldShowQueryPlan)
108
- ? QueryPlan_1.serializeQueryPlan(queryPlan)
109
- : null;
110
- if (this.config.debug && serializedQueryPlan) {
111
- this.logger.debug(serializedQueryPlan);
112
- }
113
- if (shouldShowQueryPlan) {
114
- response.extensions = {
115
- __queryPlanExperimental: serializedQueryPlan || true,
116
- };
117
- }
118
- return response;
142
+ catch (err) {
143
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR });
144
+ throw err;
145
+ }
146
+ finally {
147
+ span.end();
148
+ }
149
+ });
119
150
  };
120
151
  this.config = {
121
152
  __exposeQueryPlanExperimental: process.env.NODE_ENV !== 'production',
122
153
  ...config,
123
154
  };
124
- if (this.config.logger) {
125
- this.logger = this.config.logger;
155
+ this.logger = this.initLogger();
156
+ this.queryPlanStore = this.initQueryPlanStore(config === null || config === void 0 ? void 0 : config.experimental_approximateQueryPlanStoreMiB);
157
+ this.fetcher = (config === null || config === void 0 ? void 0 : config.fetcher) || getDefaultFetcher();
158
+ this.experimental_didResolveQueryPlan =
159
+ config === null || config === void 0 ? void 0 : config.experimental_didResolveQueryPlan;
160
+ this.experimental_didFailComposition =
161
+ config === null || config === void 0 ? void 0 : config.experimental_didFailComposition;
162
+ this.experimental_didUpdateComposition =
163
+ config === null || config === void 0 ? void 0 : config.experimental_didUpdateComposition;
164
+ this.experimental_pollInterval = config === null || config === void 0 ? void 0 : config.experimental_pollInterval;
165
+ if ((0, config_1.isManagedConfig)(this.config)) {
166
+ const envEndpoint = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
167
+ this.schemaConfigDeliveryEndpoint =
168
+ (_b = (_a = this.config.schemaConfigDeliveryEndpoint) !== null && _a !== void 0 ? _a : envEndpoint) !== null && _b !== void 0 ? _b : 'https://uplink.api.apollographql.com/';
126
169
  }
127
- else {
128
- const loglevelLogger = loglevel_1.default.getLogger(`apollo-gateway`);
129
- if (this.config.debug === true) {
130
- loglevelLogger.setLevel(loglevelLogger.levels.DEBUG);
170
+ if ((0, config_1.isManuallyManagedConfig)(this.config)) {
171
+ if ('experimental_updateSupergraphSdl' in this.config) {
172
+ this.updateServiceDefinitions =
173
+ this.config.experimental_updateSupergraphSdl;
131
174
  }
132
- else {
133
- loglevelLogger.setLevel(loglevelLogger.levels.WARN);
134
- }
135
- this.logger = loglevelLogger;
136
- }
137
- if (isLocalConfig(this.config)) {
138
- this.schema = this.createSchema(this.config.localServiceList);
139
- }
140
- this.initializeQueryPlanStore();
141
- this.updateServiceDefinitions = this.loadServiceDefinitions;
142
- if (config) {
143
- this.updateServiceDefinitions =
144
- config.experimental_updateServiceDefinitions ||
145
- this.updateServiceDefinitions;
146
- this.experimental_didResolveQueryPlan =
147
- config.experimental_didResolveQueryPlan;
148
- this.experimental_didFailComposition =
149
- config.experimental_didFailComposition;
150
- this.experimental_didUpdateComposition =
151
- config.experimental_didUpdateComposition;
152
- this.experimental_approximateQueryPlanStoreMiB =
153
- config.experimental_approximateQueryPlanStoreMiB;
154
- if (isManagedConfig(config) &&
155
- config.experimental_pollInterval &&
156
- config.experimental_pollInterval < 10000) {
157
- this.experimental_pollInterval = 10000;
158
- this.logger.warn('Polling Apollo services at a frequency of less than once per 10 seconds (10000) is disallowed. Instead, the minimum allowed pollInterval of 10000 will be used. Please reconfigure your experimental_pollInterval accordingly. If this is problematic for your team, please contact support.');
175
+ else if ('experimental_updateServiceDefinitions' in this.config) {
176
+ this.updateServiceDefinitions =
177
+ this.config.experimental_updateServiceDefinitions;
159
178
  }
160
179
  else {
161
- this.experimental_pollInterval = config.experimental_pollInterval;
162
- }
163
- if (config.experimental_pollInterval && isRemoteConfig(config)) {
164
- this.logger.warn('Polling running services is dangerous and not recommended in production. ' +
165
- 'Polling should only be used against a registry. ' +
166
- 'If you are polling running services, use with caution.');
167
- }
168
- if (config.fetcher) {
169
- this.fetcher = config.fetcher;
180
+ throw Error('Programming error: unexpected manual configuration provided');
170
181
  }
171
182
  }
183
+ else {
184
+ this.updateServiceDefinitions = this.loadServiceDefinitions;
185
+ }
186
+ if ((0, config_1.isDynamicConfig)(this.config)) {
187
+ this.issueDynamicWarningsIfApplicable();
188
+ }
189
+ this.state = { phase: 'initialized' };
190
+ }
191
+ initLogger() {
192
+ if (this.config.logger) {
193
+ return this.config.logger;
194
+ }
195
+ const loglevelLogger = loglevel_1.default.getLogger(`apollo-gateway`);
196
+ if (this.config.debug === true) {
197
+ loglevelLogger.setLevel(loglevelLogger.levels.DEBUG);
198
+ }
199
+ else {
200
+ loglevelLogger.setLevel(loglevelLogger.levels.WARN);
201
+ }
202
+ return loglevelLogger;
203
+ }
204
+ initQueryPlanStore(approximateQueryPlanStoreMiB) {
205
+ return new apollo_server_caching_1.InMemoryLRUCache({
206
+ maxSize: Math.pow(2, 20) * (approximateQueryPlanStoreMiB || 30),
207
+ sizeCalculator: approximateObjectSize,
208
+ });
209
+ }
210
+ issueDynamicWarningsIfApplicable() {
211
+ if ((0, config_1.isManagedConfig)(this.config) &&
212
+ this.config.experimental_pollInterval &&
213
+ this.config.experimental_pollInterval < 10000) {
214
+ this.experimental_pollInterval = 10000;
215
+ this.logger.warn('Polling Apollo services at a frequency of less than once per 10 ' +
216
+ 'seconds (10000) is disallowed. Instead, the minimum allowed ' +
217
+ 'pollInterval of 10000 will be used. Please reconfigure your ' +
218
+ 'experimental_pollInterval accordingly. If this is problematic for ' +
219
+ 'your team, please contact support.');
220
+ }
221
+ if (this.config.experimental_pollInterval && (0, config_1.isRemoteConfig)(this.config)) {
222
+ this.logger.warn('Polling running services is dangerous and not recommended in production. ' +
223
+ 'Polling should only be used against a registry. ' +
224
+ 'If you are polling running services, use with caution.');
225
+ }
226
+ if ((0, config_1.isManuallyManagedConfig)(this.config) &&
227
+ 'experimental_updateSupergraphSdl' in this.config &&
228
+ 'experimental_updateServiceDefinitions' in this.config) {
229
+ this.logger.warn('Gateway found two manual update configurations when only one should be ' +
230
+ 'provided. Gateway will default to using the provided `experimental_updateSupergraphSdl` ' +
231
+ 'function when both `experimental_updateSupergraphSdl` and experimental_updateServiceDefinitions` ' +
232
+ 'are provided.');
233
+ }
172
234
  }
173
235
  async load(options) {
174
- if (options && options.engine) {
175
- if (!options.engine.graphVariant)
176
- this.logger.warn('No graph variant provided. Defaulting to `current`.');
177
- this.engineConfig = options.engine;
178
- }
179
- await this.updateComposition();
180
- if ((isManagedConfig(this.config) || this.experimental_pollInterval) &&
181
- !this.pollingTimer) {
182
- this.pollServices();
183
- }
184
- const { graphId, graphVariant } = (options && options.engine) || {};
185
- const mode = isManagedConfig(this.config) ? 'managed' : 'unmanaged';
186
- this.logger.info(`Gateway successfully loaded schema.\n\t* Mode: ${mode}${graphId ? `\n\t* Service: ${graphId}@${graphVariant || 'current'}` : ''}`);
236
+ if (this.state.phase !== 'initialized') {
237
+ throw Error(`ApolloGateway.load called in surprising state ${this.state.phase}`);
238
+ }
239
+ if (options === null || options === void 0 ? void 0 : options.apollo) {
240
+ const { key, keyHash, graphRef, graphId, graphVariant } = options.apollo;
241
+ this.apolloConfig = {
242
+ key,
243
+ keyHash,
244
+ graphRef: graphRef !== null && graphRef !== void 0 ? graphRef : (graphId ? `${graphId}@${graphVariant !== null && graphVariant !== void 0 ? graphVariant : 'current'}` : undefined),
245
+ };
246
+ }
247
+ else if (options === null || options === void 0 ? void 0 : options.engine) {
248
+ const { apiKeyHash, graphId, graphVariant } = options.engine;
249
+ this.apolloConfig = {
250
+ keyHash: apiKeyHash,
251
+ graphRef: graphId
252
+ ? `${graphId}@${graphVariant !== null && graphVariant !== void 0 ? graphVariant : 'current'}`
253
+ : undefined,
254
+ };
255
+ }
256
+ const unrefTimer = !!options && !options.apollo;
257
+ this.maybeWarnOnConflictingConfig();
258
+ (0, config_1.isStaticConfig)(this.config)
259
+ ? this.loadStatic(this.config)
260
+ : await this.loadDynamic(unrefTimer);
261
+ const mode = (0, config_1.isManagedConfig)(this.config) ? 'managed' : 'unmanaged';
262
+ this.logger.info(`Gateway successfully loaded schema.\n\t* Mode: ${mode}${this.apolloConfig && this.apolloConfig.graphRef
263
+ ? `\n\t* Service: ${this.apolloConfig.graphRef}`
264
+ : ''}`);
187
265
  return {
188
266
  schema: this.schema,
189
267
  executor: this.executor,
190
268
  };
191
269
  }
192
- async updateComposition() {
193
- let result;
194
- this.logger.debug('Checking service definitions...');
270
+ loadStatic(config) {
271
+ let schema;
272
+ let supergraphSdl;
195
273
  try {
196
- result = await this.updateServiceDefinitions(this.config);
274
+ ({ schema, supergraphSdl } = (0, config_1.isLocalConfig)(config)
275
+ ? this.createSchemaFromServiceList(config.localServiceList)
276
+ : this.createSchemaFromSupergraphSdl(config.supergraphSdl));
277
+ this.supergraphSdl = supergraphSdl;
278
+ this.updateWithSchemaAndNotify(schema, supergraphSdl, true);
197
279
  }
198
280
  catch (e) {
199
- this.logger.error("Error checking for changes to service definitions: " +
200
- (e && e.message || e));
281
+ this.state = { phase: 'failed to load' };
201
282
  throw e;
202
283
  }
284
+ this.state = { phase: 'loaded' };
285
+ }
286
+ async loadDynamic(unrefTimer) {
287
+ try {
288
+ await this.updateSchema();
289
+ }
290
+ catch (e) {
291
+ this.state = { phase: 'failed to load' };
292
+ throw e;
293
+ }
294
+ this.state = { phase: 'loaded' };
295
+ if (this.shouldBeginPolling()) {
296
+ this.pollServices(unrefTimer);
297
+ }
298
+ }
299
+ shouldBeginPolling() {
300
+ return (0, config_1.isManagedConfig)(this.config) || this.experimental_pollInterval;
301
+ }
302
+ async updateSchema() {
303
+ this.logger.debug('Checking for composition updates...');
304
+ const result = await this.updateServiceDefinitions(this.config);
305
+ if ((0, config_1.isSupergraphSdlUpdate)(result)) {
306
+ await this.updateWithSupergraphSdl(result);
307
+ }
308
+ else if ((0, config_1.isServiceDefinitionUpdate)(result)) {
309
+ await this.updateByComposition(result);
310
+ }
311
+ else {
312
+ throw new Error('Programming error: unexpected result type from `updateServiceDefinitions`');
313
+ }
314
+ }
315
+ async updateByComposition(result) {
203
316
  if (!result.serviceDefinitions ||
204
317
  JSON.stringify(this.serviceDefinitions) ===
205
318
  JSON.stringify(result.serviceDefinitions)) {
@@ -210,10 +323,110 @@ class ApolloGateway {
210
323
  const previousServiceDefinitions = this.serviceDefinitions;
211
324
  const previousCompositionMetadata = this.compositionMetadata;
212
325
  if (previousSchema) {
213
- this.logger.info("New service definitions were found.");
326
+ this.logger.info('New service definitions were found.');
327
+ }
328
+ await this.maybePerformServiceHealthCheck(result);
329
+ this.compositionMetadata = result.compositionMetadata;
330
+ this.serviceDefinitions = result.serviceDefinitions;
331
+ const { schema, supergraphSdl } = this.createSchemaFromServiceList(result.serviceDefinitions);
332
+ if (!supergraphSdl) {
333
+ this.logger.error("A valid schema couldn't be composed. Falling back to previous schema.");
334
+ }
335
+ else {
336
+ this.updateWithSchemaAndNotify(schema, supergraphSdl);
337
+ if (this.experimental_didUpdateComposition) {
338
+ this.experimental_didUpdateComposition({
339
+ serviceDefinitions: result.serviceDefinitions,
340
+ schema: schema.toGraphQLJSSchema(),
341
+ ...(this.compositionMetadata && {
342
+ compositionMetadata: this.compositionMetadata,
343
+ }),
344
+ }, previousServiceDefinitions &&
345
+ previousSchema && {
346
+ serviceDefinitions: previousServiceDefinitions,
347
+ schema: previousSchema,
348
+ ...(previousCompositionMetadata && {
349
+ compositionMetadata: previousCompositionMetadata,
350
+ }),
351
+ });
352
+ }
353
+ }
354
+ }
355
+ async updateWithSupergraphSdl(result) {
356
+ if (result.id === this.compositionId) {
357
+ this.logger.debug('No change in composition since last check.');
358
+ return;
214
359
  }
360
+ const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(result.supergraphSdl);
361
+ const previousSchema = this.schema;
362
+ const previousSupergraphSdl = this.supergraphSdl;
363
+ const previousCompositionId = this.compositionId;
364
+ if (previousSchema) {
365
+ this.logger.info('Updated Supergraph SDL was found.');
366
+ }
367
+ await this.maybePerformServiceHealthCheck(result);
368
+ this.compositionId = result.id;
369
+ this.supergraphSdl = result.supergraphSdl;
370
+ if (!supergraphSdl) {
371
+ this.logger.error("A valid schema couldn't be composed. Falling back to previous schema.");
372
+ }
373
+ else {
374
+ this.updateWithSchemaAndNotify(schema, supergraphSdl);
375
+ if (this.experimental_didUpdateComposition) {
376
+ this.experimental_didUpdateComposition({
377
+ compositionId: result.id,
378
+ supergraphSdl: result.supergraphSdl,
379
+ schema: schema.toGraphQLJSSchema(),
380
+ }, previousCompositionId && previousSupergraphSdl && previousSchema
381
+ ? {
382
+ compositionId: previousCompositionId,
383
+ supergraphSdl: previousSupergraphSdl,
384
+ schema: previousSchema,
385
+ }
386
+ : undefined);
387
+ }
388
+ }
389
+ }
390
+ updateWithSchemaAndNotify(coreSchema, coreSupergraphSdl, legacyDontNotifyOnSchemaChangeListeners = false) {
391
+ if (this.queryPlanStore)
392
+ this.queryPlanStore.flush();
393
+ this.apiSchema = coreSchema.toAPISchema();
394
+ this.schema = wrapSchemaWithAliasResolver(this.apiSchema.toGraphQLJSSchema());
395
+ this.queryPlanner = new query_planner_1.QueryPlanner(coreSchema);
396
+ if (!legacyDontNotifyOnSchemaChangeListeners) {
397
+ this.onSchemaChangeListeners.forEach((listener) => {
398
+ try {
399
+ listener(this.schema);
400
+ }
401
+ catch (e) {
402
+ this.logger.error("An error was thrown from an 'onSchemaChange' listener. " +
403
+ 'The schema will still update: ' +
404
+ ((e && e.message) || e));
405
+ }
406
+ });
407
+ }
408
+ this.onSchemaLoadOrUpdateListeners.forEach((listener) => {
409
+ try {
410
+ listener({
411
+ apiSchema: this.schema,
412
+ coreSupergraphSdl,
413
+ });
414
+ }
415
+ catch (e) {
416
+ this.logger.error("An error was thrown from an 'onSchemaLoadOrUpdate' listener. " +
417
+ 'The schema will still update: ' +
418
+ ((e && e.message) || e));
419
+ }
420
+ });
421
+ }
422
+ async maybePerformServiceHealthCheck(update) {
215
423
  if (this.config.serviceHealthCheck) {
216
- const serviceMap = result.serviceDefinitions.reduce((serviceMap, serviceDef) => {
424
+ const serviceList = (0, config_1.isSupergraphSdlUpdate)(update)
425
+ ?
426
+ this.serviceListFromSupergraphSdl(update.supergraphSdl)
427
+ :
428
+ update.serviceDefinitions;
429
+ const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
217
430
  serviceMap[serviceDef.name] = {
218
431
  url: serviceDef.url,
219
432
  dataSource: this.createDataSource(serviceDef),
@@ -224,51 +437,32 @@ class ApolloGateway {
224
437
  await this.serviceHealthCheck(serviceMap);
225
438
  }
226
439
  catch (e) {
227
- this.logger.error('The gateway did not update its schema due to failed service health checks. ' +
228
- 'The gateway will continue to operate with the previous schema and reattempt updates.' + e);
229
- throw e;
440
+ throw new Error('The gateway did not update its schema due to failed service health checks. ' +
441
+ 'The gateway will continue to operate with the previous schema and reattempt updates. ' +
442
+ 'The following error occurred during the health check:\n' +
443
+ e.message);
230
444
  }
231
445
  }
232
- this.compositionMetadata = result.compositionMetadata;
233
- this.serviceDefinitions = result.serviceDefinitions;
234
- if (this.queryPlanStore)
235
- this.queryPlanStore.flush();
236
- this.schema = this.createSchema(result.serviceDefinitions);
237
- try {
238
- this.onSchemaChangeListeners.forEach(listener => listener(this.schema));
239
- }
240
- catch (e) {
241
- this.logger.error("An error was thrown from an 'onSchemaChange' listener. " +
242
- "The schema will still update: " + (e && e.message || e));
243
- }
244
- if (this.experimental_didUpdateComposition) {
245
- this.experimental_didUpdateComposition({
246
- serviceDefinitions: result.serviceDefinitions,
247
- schema: this.schema,
248
- ...(this.compositionMetadata && {
249
- compositionMetadata: this.compositionMetadata,
250
- }),
251
- }, previousServiceDefinitions &&
252
- previousSchema && {
253
- serviceDefinitions: previousServiceDefinitions,
254
- schema: previousSchema,
255
- ...(previousCompositionMetadata && {
256
- compositionMetadata: previousCompositionMetadata,
257
- }),
258
- });
259
- }
260
446
  }
261
447
  serviceHealthCheck(serviceMap = this.serviceMap) {
262
448
  return Promise.all(Object.entries(serviceMap).map(([name, { dataSource }]) => dataSource
263
- .process({ request: { query: exports.HEALTH_CHECK_QUERY }, context: {} })
264
- .then(response => ({ name, response }))));
449
+ .process({
450
+ kind: types_1.GraphQLDataSourceRequestKind.HEALTH_CHECK,
451
+ request: { query: exports.HEALTH_CHECK_QUERY },
452
+ context: {},
453
+ })
454
+ .then((response) => ({ name, response }))
455
+ .catch((e) => {
456
+ throw new Error(`[${name}]: ${e.message}`);
457
+ })));
265
458
  }
266
- createSchema(serviceList) {
459
+ createSchemaFromServiceList(serviceList) {
267
460
  this.logger.debug(`Composing schema from service list: \n${serviceList
268
461
  .map(({ name, url }) => ` ${url || 'local'}: ${name}`)
269
462
  .join('\n')}`);
270
- const { schema, errors } = federation_1.composeAndValidate(serviceList);
271
- if (errors && errors.length > 0) {
463
+ const compositionResult = (0, composition_1.composeServices)(serviceList);
464
+ const errors = compositionResult.errors;
465
+ if (errors) {
272
466
  if (this.experimental_didFailComposition) {
273
467
  this.experimental_didFailComposition({
274
468
  errors,
@@ -278,11 +472,28 @@ class ApolloGateway {
278
472
  }),
279
473
  });
280
474
  }
281
- throw new apollo_graphql_1.GraphQLSchemaValidationError(errors);
475
+ throw Error("A valid schema couldn't be composed. The following composition errors were found:\n" +
476
+ errors.map((e) => '\t' + e.message).join('\n'));
282
477
  }
478
+ else {
479
+ this.createServices(serviceList);
480
+ this.logger.debug('Schema loaded and ready for execution');
481
+ return {
482
+ schema: compositionResult.schema,
483
+ supergraphSdl: compositionResult.supergraphSdl,
484
+ };
485
+ }
486
+ }
487
+ serviceListFromSupergraphSdl(supergraphSdl) {
488
+ return (0, federation_internals_1.buildSupergraphSchema)(supergraphSdl)[1];
489
+ }
490
+ createSchemaFromSupergraphSdl(supergraphSdl) {
491
+ const [schema, serviceList] = (0, federation_internals_1.buildSupergraphSchema)(supergraphSdl);
283
492
  this.createServices(serviceList);
284
- this.logger.debug('Schema loaded and ready for execution');
285
- return wrapSchemaWithAliasResolver(schema);
493
+ return {
494
+ schema,
495
+ supergraphSdl,
496
+ };
286
497
  }
287
498
  onSchemaChange(callback) {
288
499
  this.onSchemaChangeListeners.add(callback);
@@ -290,21 +501,64 @@ class ApolloGateway {
290
501
  this.onSchemaChangeListeners.delete(callback);
291
502
  };
292
503
  }
293
- async pollServices() {
294
- if (this.pollingTimer)
295
- clearTimeout(this.pollingTimer);
296
- await new Promise(res => {
297
- var _a;
298
- this.pollingTimer = setTimeout(() => res(), this.experimental_pollInterval || 10000);
299
- (_a = this.pollingTimer) === null || _a === void 0 ? void 0 : _a.unref();
504
+ onSchemaLoadOrUpdate(callback) {
505
+ this.onSchemaLoadOrUpdateListeners.add(callback);
506
+ return () => {
507
+ this.onSchemaLoadOrUpdateListeners.delete(callback);
508
+ };
509
+ }
510
+ async pollServices(unrefTimer) {
511
+ switch (this.state.phase) {
512
+ case 'stopping':
513
+ case 'stopped':
514
+ case 'failed to load':
515
+ return;
516
+ case 'initialized':
517
+ throw Error('pollServices should not be called before load!');
518
+ case 'polling':
519
+ throw Error('pollServices should not be called while in the middle of polling!');
520
+ case 'waiting to poll':
521
+ throw Error('pollServices should not be called while already waiting to poll!');
522
+ case 'loaded':
523
+ break;
524
+ default:
525
+ throw new UnreachableCaseError(this.state);
526
+ }
527
+ await new Promise((doneWaiting) => {
528
+ this.state = {
529
+ phase: 'waiting to poll',
530
+ doneWaiting,
531
+ pollWaitTimer: setTimeout(() => {
532
+ if (this.state.phase == 'waiting to poll') {
533
+ this.state.doneWaiting();
534
+ }
535
+ }, this.experimental_pollInterval || 10000),
536
+ };
537
+ if (unrefTimer) {
538
+ this.state.pollWaitTimer.unref();
539
+ }
300
540
  });
541
+ if (this.state.phase !== 'waiting to poll') {
542
+ return;
543
+ }
544
+ let pollingDone;
545
+ this.state = {
546
+ phase: 'polling',
547
+ pollingDonePromise: new Promise((res) => {
548
+ pollingDone = res;
549
+ }),
550
+ };
301
551
  try {
302
- await this.updateComposition();
552
+ await this.updateSchema();
303
553
  }
304
554
  catch (err) {
305
- this.logger.error(err && err.message || err);
555
+ this.logger.error((err && err.message) || err);
556
+ }
557
+ if (this.state.phase === 'polling') {
558
+ this.state = { phase: 'loaded' };
559
+ setImmediate(() => this.pollServices(unrefTimer));
306
560
  }
307
- this.pollServices();
561
+ pollingDone();
308
562
  }
309
563
  createAndCacheDataSource(serviceDef) {
310
564
  if (this.serviceMap[serviceDef.name] &&
@@ -315,7 +569,7 @@ class ApolloGateway {
315
569
  return dataSource;
316
570
  }
317
571
  createDataSource(serviceDef) {
318
- if (!serviceDef.url && !isLocalConfig(this.config)) {
572
+ if (!serviceDef.url && !(0, config_1.isLocalConfig)(this.config)) {
319
573
  this.logger.error(`Service definition for service ${serviceDef.name} is missing a url`);
320
574
  }
321
575
  return this.config.buildService
@@ -330,79 +584,129 @@ class ApolloGateway {
330
584
  }
331
585
  }
332
586
  async loadServiceDefinitions(config) {
333
- const getManagedConfig = (engineConfig) => {
334
- return loadServicesFromStorage_1.getServiceDefinitionsFromStorage({
335
- graphId: engineConfig.graphId,
336
- apiKeyHash: engineConfig.apiKeyHash,
337
- graphVariant: engineConfig.graphVariant,
338
- federationVersion: config.federationVersion || 1,
339
- fetcher: this.fetcher,
340
- });
341
- };
342
- if (isLocalConfig(config) || isRemoteConfig(config)) {
343
- if (this.engineConfig && !this.warnedStates.remoteWithLocalConfig) {
344
- this.warnedStates.remoteWithLocalConfig = true;
345
- getManagedConfig(this.engineConfig).then(() => {
346
- this.logger.warn("A local gateway service list is overriding an Apollo Graph " +
347
- "Manager managed configuration. To use the managed " +
348
- "configuration, do not specify a service list locally.");
349
- }).catch(() => { });
350
- }
351
- }
352
- if (isLocalConfig(config)) {
353
- return { isNewSchema: false };
354
- }
355
- if (isRemoteConfig(config)) {
356
- const serviceList = config.serviceList.map(serviceDefinition => ({
587
+ var _a, _b, _c;
588
+ if ((0, config_1.isRemoteConfig)(config)) {
589
+ const serviceList = config.serviceList.map((serviceDefinition) => ({
357
590
  ...serviceDefinition,
358
591
  dataSource: this.createAndCacheDataSource(serviceDefinition),
359
592
  }));
360
- return loadServicesFromRemoteEndpoint_1.getServiceDefinitionsFromRemoteEndpoint({
593
+ return (0, loadServicesFromRemoteEndpoint_1.getServiceDefinitionsFromRemoteEndpoint)({
361
594
  serviceList,
362
- ...(config.introspectionHeaders
363
- ? { headers: config.introspectionHeaders }
364
- : {}),
595
+ async getServiceIntrospectionHeaders(service) {
596
+ return typeof config.introspectionHeaders === 'function'
597
+ ? await config.introspectionHeaders(service)
598
+ : config.introspectionHeaders;
599
+ },
365
600
  serviceSdlCache: this.serviceSdlCache,
366
601
  });
367
602
  }
368
- if (!this.engineConfig) {
369
- throw new Error('When `serviceList` is not set, an Apollo Engine configuration must be provided. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ for more information.');
603
+ const canUseManagedConfig = ((_a = this.apolloConfig) === null || _a === void 0 ? void 0 : _a.graphRef) && ((_b = this.apolloConfig) === null || _b === void 0 ? void 0 : _b.keyHash);
604
+ if (!canUseManagedConfig) {
605
+ throw new Error('When a manual configuration is not provided, gateway requires an Apollo ' +
606
+ 'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
607
+ 'for more information. Manual configuration options include: ' +
608
+ '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.');
609
+ }
610
+ const result = await (0, loadSupergraphSdlFromStorage_1.loadSupergraphSdlFromStorage)({
611
+ graphRef: this.apolloConfig.graphRef,
612
+ apiKey: this.apolloConfig.key,
613
+ endpoint: this.schemaConfigDeliveryEndpoint,
614
+ fetcher: this.fetcher,
615
+ compositionId: (_c = this.compositionId) !== null && _c !== void 0 ? _c : null,
616
+ });
617
+ return (result !== null && result !== void 0 ? result : {
618
+ id: this.compositionId,
619
+ supergraphSdl: this.supergraphSdl,
620
+ });
621
+ }
622
+ maybeWarnOnConflictingConfig() {
623
+ var _a, _b;
624
+ const canUseManagedConfig = ((_a = this.apolloConfig) === null || _a === void 0 ? void 0 : _a.graphRef) && ((_b = this.apolloConfig) === null || _b === void 0 ? void 0 : _b.keyHash);
625
+ if (!(0, config_1.isManagedConfig)(this.config) &&
626
+ canUseManagedConfig &&
627
+ !this.warnedStates.remoteWithLocalConfig) {
628
+ this.warnedStates.remoteWithLocalConfig = true;
629
+ this.logger.warn('A local gateway configuration is overriding a managed federation ' +
630
+ 'configuration. To use the managed ' +
631
+ 'configuration, do not specify a service list or supergraphSdl locally.');
370
632
  }
371
- return getManagedConfig(this.engineConfig);
372
633
  }
373
634
  validateIncomingRequest(requestContext, operationContext) {
374
- const variableDefinitions = operationContext.operation
375
- .variableDefinitions;
376
- if (!variableDefinitions)
377
- return [];
378
- const { errors } = values_1.getVariableValues(operationContext.schema, variableDefinitions, requestContext.request.variables);
379
- return errors || [];
380
- }
381
- initializeQueryPlanStore() {
382
- this.queryPlanStore = new apollo_server_caching_1.InMemoryLRUCache({
383
- maxSize: Math.pow(2, 20) *
384
- (this.experimental_approximateQueryPlanStoreMiB || 30),
385
- sizeCalculator: approximateObjectSize,
635
+ return opentelemetry_1.tracer.startActiveSpan(opentelemetry_1.OpenTelemetrySpanNames.VALIDATE, (span) => {
636
+ try {
637
+ const variableDefinitions = operationContext.operation
638
+ .variableDefinitions;
639
+ if (!variableDefinitions)
640
+ return [];
641
+ const { errors } = (0, values_1.getVariableValues)(operationContext.schema, variableDefinitions, requestContext.request.variables || {});
642
+ if (errors) {
643
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR });
644
+ }
645
+ return errors || [];
646
+ }
647
+ catch (err) {
648
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR });
649
+ throw err;
650
+ }
651
+ finally {
652
+ span.end();
653
+ }
386
654
  });
387
655
  }
388
656
  async stop() {
389
- if (this.pollingTimer) {
390
- clearTimeout(this.pollingTimer);
391
- this.pollingTimer = undefined;
657
+ switch (this.state.phase) {
658
+ case 'initialized':
659
+ case 'failed to load':
660
+ throw Error('ApolloGateway.stop does not need to be called before ApolloGateway.load is called successfully');
661
+ case 'stopped':
662
+ return;
663
+ case 'stopping':
664
+ await this.state.stoppingDonePromise;
665
+ if (this.state.phase !== 'stopped') {
666
+ throw Error(`Expected to be stopped when done stopping, but instead ${this.state.phase}`);
667
+ }
668
+ return;
669
+ case 'loaded':
670
+ this.state = { phase: 'stopped' };
671
+ return;
672
+ case 'waiting to poll': {
673
+ const doneWaiting = this.state.doneWaiting;
674
+ clearTimeout(this.state.pollWaitTimer);
675
+ this.state = { phase: 'stopped' };
676
+ doneWaiting();
677
+ return;
678
+ }
679
+ case 'polling': {
680
+ const pollingDonePromise = this.state.pollingDonePromise;
681
+ let stoppingDone;
682
+ this.state = {
683
+ phase: 'stopping',
684
+ stoppingDonePromise: new Promise((res) => {
685
+ stoppingDone = res;
686
+ }),
687
+ };
688
+ await pollingDonePromise;
689
+ this.state = { phase: 'stopped' };
690
+ stoppingDone();
691
+ return;
692
+ }
693
+ default:
694
+ throw new UnreachableCaseError(this.state);
392
695
  }
393
696
  }
394
697
  }
395
698
  exports.ApolloGateway = ApolloGateway;
699
+ ApolloGateway.prototype.onSchemaChange = (0, util_1.deprecate)(ApolloGateway.prototype.onSchemaChange, `'ApolloGateway.prototype.onSchemaChange' is deprecated. Use 'ApolloGateway.prototype.onSchemaLoadOrUpdate' instead.`);
396
700
  function approximateObjectSize(obj) {
397
701
  return Buffer.byteLength(JSON.stringify(obj), 'utf8');
398
702
  }
399
703
  function wrapSchemaWithAliasResolver(schema) {
400
704
  const typeMap = schema.getTypeMap();
401
- Object.keys(typeMap).forEach(typeName => {
705
+ Object.keys(typeMap).forEach((typeName) => {
402
706
  const type = typeMap[typeName];
403
- if (graphql_1.isObjectType(type) && !graphql_1.isIntrospectionType(type)) {
707
+ if ((0, graphql_1.isObjectType)(type) && !(0, graphql_1.isIntrospectionType)(type)) {
404
708
  const fields = type.getFields();
405
- Object.keys(fields).forEach(fieldName => {
709
+ Object.keys(fields).forEach((fieldName) => {
406
710
  const field = fields[fieldName];
407
711
  field.resolve = executeQueryPlan_1.defaultFieldResolverWithAliasSupport;
408
712
  });
@@ -410,5 +714,10 @@ function wrapSchemaWithAliasResolver(schema) {
410
714
  });
411
715
  return schema;
412
716
  }
717
+ class UnreachableCaseError extends Error {
718
+ constructor(val) {
719
+ super(`Unreachable case: ${val}`);
720
+ }
721
+ }
413
722
  __exportStar(require("./datasources"), exports);
414
723
  //# sourceMappingURL=index.js.map