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