@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.
- package/README.md +1 -1
- package/dist/__generated__/graphqlTypes.d.ts +13 -11
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/dist/config.d.ts +45 -24
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -31
- package/dist/config.js.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/datasources/types.d.ts +1 -1
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +6 -6
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +36 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +197 -297
- package/dist/index.js.map +1 -1
- package/dist/operationContext.js +0 -1
- package/dist/operationContext.js.map +1 -1
- package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
- package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
- package/dist/schema-helper/addResolversToSchema.js +62 -0
- package/dist/schema-helper/addResolversToSchema.js.map +1 -0
- package/dist/schema-helper/error.d.ts +6 -0
- package/dist/schema-helper/error.d.ts.map +1 -0
- package/dist/schema-helper/error.js +14 -0
- package/dist/schema-helper/error.js.map +1 -0
- package/dist/schema-helper/index.d.ts +4 -0
- package/dist/schema-helper/index.d.ts.map +1 -0
- package/dist/schema-helper/index.js +16 -0
- package/dist/schema-helper/index.js.map +1 -0
- package/dist/schema-helper/resolverMap.d.ts +16 -0
- package/dist/schema-helper/resolverMap.d.ts.map +1 -0
- package/dist/schema-helper/resolverMap.js +3 -0
- package/dist/schema-helper/resolverMap.js.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
- package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
- package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
- package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
- package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
- package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
- package/dist/supergraphManagers/LocalCompose/index.js +55 -0
- package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts +32 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +21 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +41 -10
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +13 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +85 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
- package/dist/supergraphManagers/index.d.ts +5 -0
- package/dist/supergraphManagers/index.d.ts.map +1 -0
- package/dist/supergraphManagers/index.js +12 -0
- package/dist/supergraphManagers/index.js.map +1 -0
- package/dist/utilities/array.js +1 -1
- package/dist/utilities/array.js.map +1 -1
- package/dist/utilities/createHash.d.ts +2 -0
- package/dist/utilities/createHash.d.ts.map +1 -0
- package/dist/utilities/createHash.js +15 -0
- package/dist/utilities/createHash.js.map +1 -0
- package/dist/utilities/isNodeLike.d.ts +3 -0
- package/dist/utilities/isNodeLike.d.ts.map +1 -0
- package/dist/utilities/isNodeLike.js +8 -0
- package/dist/utilities/isNodeLike.js.map +1 -0
- package/package.json +9 -9
- package/src/__generated__/graphqlTypes.ts +13 -11
- package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
- package/src/__tests__/buildQueryPlan.test.ts +1 -1
- package/src/__tests__/executeQueryPlan.test.ts +1171 -77
- package/src/__tests__/execution-utils.ts +5 -7
- package/src/__tests__/gateway/buildService.test.ts +3 -3
- package/src/__tests__/gateway/endToEnd.test.ts +1 -1
- package/src/__tests__/gateway/executor.test.ts +3 -1
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -121
- package/src/__tests__/gateway/opentelemetry.test.ts +8 -3
- package/src/__tests__/gateway/queryPlanCache.test.ts +25 -9
- package/src/__tests__/gateway/reporting.test.ts +42 -13
- package/src/__tests__/gateway/supergraphSdl.test.ts +397 -0
- package/src/__tests__/integration/aliases.test.ts +9 -3
- package/src/__tests__/integration/configuration.test.ts +140 -21
- package/src/__tests__/integration/logger.test.ts +2 -2
- package/src/__tests__/integration/networkRequests.test.ts +126 -149
- package/src/__tests__/integration/nockMocks.ts +57 -16
- package/src/__tests__/nockAssertions.ts +20 -0
- package/src/config.ts +153 -77
- package/src/core/__tests__/core.test.ts +6 -6
- package/src/datasources/LocalGraphQLDataSource.ts +1 -1
- package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
- package/src/datasources/types.ts +1 -1
- package/src/executeQueryPlan.ts +18 -9
- package/src/index.ts +323 -481
- package/src/make-fetch-happen.d.ts +1 -1
- package/src/operationContext.ts +2 -2
- package/src/schema-helper/addResolversToSchema.ts +83 -0
- package/src/schema-helper/error.ts +11 -0
- package/src/schema-helper/index.ts +3 -0
- package/src/schema-helper/resolverMap.ts +23 -0
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
- package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +7 -7
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/IntrospectAndCompose/index.ts +160 -0
- package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +7 -7
- package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
- package/src/supergraphManagers/LocalCompose/index.ts +79 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +343 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
- package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +63 -14
- package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
- package/src/supergraphManagers/index.ts +4 -0
- package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +13 -10
- package/src/utilities/array.ts +1 -1
- package/src/utilities/createHash.ts +10 -0
- package/src/utilities/isNodeLike.ts +11 -0
- package/CHANGELOG.md +0 -452
- package/dist/legacyLoadServicesFromStorage.d.ts +0 -20
- package/dist/legacyLoadServicesFromStorage.d.ts.map +0 -1
- package/dist/legacyLoadServicesFromStorage.js +0 -62
- package/dist/legacyLoadServicesFromStorage.js.map +0 -1
- package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
- package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
- package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +0 -12
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
- package/dist/outOfBandReporter.d.ts +0 -15
- package/dist/outOfBandReporter.d.ts.map +0 -1
- package/dist/outOfBandReporter.js +0 -88
- package/dist/outOfBandReporter.js.map +0 -1
- package/src/__tests__/gateway/composedSdl.test.ts +0 -44
- package/src/__tests__/integration/legacyNetworkRequests.test.ts +0 -279
- package/src/__tests__/integration/legacyNockMocks.ts +0 -113
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -664
- package/src/legacyLoadServicesFromStorage.ts +0 -170
- 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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
+
isServiceListConfig,
|
|
53
47
|
isManagedConfig,
|
|
54
|
-
isDynamicConfig,
|
|
55
|
-
isStaticConfig,
|
|
56
|
-
CompositionMetadata,
|
|
57
|
-
isSupergraphSdlUpdate,
|
|
58
|
-
isServiceDefinitionUpdate,
|
|
59
|
-
ServiceDefinitionUpdate,
|
|
60
48
|
SupergraphSdlUpdate,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
//
|
|
210
|
-
|
|
211
|
-
//
|
|
212
|
-
private
|
|
213
|
-
//
|
|
214
|
-
|
|
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.
|
|
247
|
-
config?.
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
329
|
-
this.
|
|
247
|
+
this.pollIntervalInMs &&
|
|
248
|
+
this.pollIntervalInMs < 10000
|
|
330
249
|
) {
|
|
331
|
-
this.
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
|
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
|
-
|
|
478
|
-
|
|
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
|
-
|
|
482
|
-
|
|
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
|
-
|
|
485
|
-
|
|
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
|
-
|
|
488
|
-
await this.
|
|
489
|
-
}
|
|
490
|
-
await this.updateByComposition(result);
|
|
491
|
-
} else {
|
|
551
|
+
try {
|
|
552
|
+
await this.serviceHealthCheck(serviceMap);
|
|
553
|
+
} catch (e) {
|
|
492
554
|
throw new Error(
|
|
493
|
-
'
|
|
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
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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.
|
|
592
|
-
this.
|
|
602
|
+
if (this.experimental_didUpdateSupergraph) {
|
|
603
|
+
this.experimental_didUpdateSupergraph(
|
|
593
604
|
{
|
|
594
605
|
compositionId: result.id,
|
|
595
|
-
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(
|
|
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
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
-
|
|
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
|
|
1222
|
-
stoppingDone = res;
|
|
1223
|
-
}),
|
|
1043
|
+
stoppingDonePromise,
|
|
1224
1044
|
};
|
|
1225
|
-
await
|
|
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
|
-
|
|
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';
|