@apollo/gateway 0.45.1 → 0.46.0-alpha.0
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/CHANGELOG.md +21 -0
- package/dist/config.d.ts +42 -16
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -18
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +35 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -308
- package/dist/index.js.map +1 -1
- 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 +6 -4
- package/src/__tests__/execution-utils.ts +2 -2
- package/src/__tests__/gateway/buildService.test.ts +2 -2
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -99
- 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 +4 -6
- package/src/__tests__/gateway/supergraphSdl.test.ts +390 -0
- package/src/__tests__/integration/aliases.test.ts +9 -3
- 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 -118
- package/src/__tests__/integration/nockMocks.ts +15 -8
- package/src/config.ts +149 -40
- package/src/index.ts +314 -485
- 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 +163 -0
- package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
- package/src/supergraphManagers/LegacyFetcher/index.ts +229 -0
- package/src/supergraphManagers/LocalCompose/index.ts +83 -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/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
|
@@ -16,22 +16,18 @@ import {
|
|
|
16
16
|
parse,
|
|
17
17
|
Source,
|
|
18
18
|
} from 'graphql';
|
|
19
|
-
import {
|
|
20
|
-
composeAndValidate,
|
|
21
|
-
compositionHasErrors,
|
|
22
|
-
ServiceDefinition,
|
|
23
|
-
} from '@apollo/federation';
|
|
19
|
+
import { ServiceDefinition } from '@apollo/federation';
|
|
24
20
|
import loglevel from 'loglevel';
|
|
25
|
-
|
|
26
21
|
import { buildOperationContext, OperationContext } from './operationContext';
|
|
27
22
|
import {
|
|
28
23
|
executeQueryPlan,
|
|
29
24
|
ServiceMap,
|
|
30
25
|
defaultFieldResolverWithAliasSupport,
|
|
31
26
|
} from './executeQueryPlan';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
import {
|
|
28
|
+
GraphQLDataSource,
|
|
29
|
+
GraphQLDataSourceRequestKind,
|
|
30
|
+
} from './datasources/types';
|
|
35
31
|
import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource';
|
|
36
32
|
import { getVariableValues } from 'graphql/execution/values';
|
|
37
33
|
import fetcher from 'make-fetch-happen';
|
|
@@ -47,32 +43,32 @@ import {
|
|
|
47
43
|
ServiceEndpointDefinition,
|
|
48
44
|
Experimental_DidFailCompositionCallback,
|
|
49
45
|
Experimental_DidResolveQueryPlanCallback,
|
|
50
|
-
|
|
46
|
+
Experimental_DidUpdateSupergraphCallback,
|
|
51
47
|
Experimental_UpdateComposition,
|
|
52
48
|
CompositionInfo,
|
|
53
49
|
GatewayConfig,
|
|
54
|
-
StaticGatewayConfig,
|
|
55
|
-
RemoteGatewayConfig,
|
|
56
|
-
ManagedGatewayConfig,
|
|
57
50
|
isManuallyManagedConfig,
|
|
58
51
|
isLocalConfig,
|
|
59
|
-
|
|
52
|
+
isServiceListConfig,
|
|
60
53
|
isManagedConfig,
|
|
61
|
-
isDynamicConfig,
|
|
62
|
-
isStaticConfig,
|
|
63
|
-
CompositionMetadata,
|
|
64
|
-
isSupergraphSdlUpdate,
|
|
65
|
-
isServiceDefinitionUpdate,
|
|
66
|
-
ServiceDefinitionUpdate,
|
|
67
54
|
SupergraphSdlUpdate,
|
|
68
|
-
|
|
55
|
+
isManuallyManagedSupergraphSdlGatewayConfig,
|
|
56
|
+
ManagedGatewayConfig,
|
|
57
|
+
isStaticSupergraphSdlConfig,
|
|
58
|
+
SupergraphManager,
|
|
69
59
|
} from './config';
|
|
70
60
|
import { buildComposedSchema } from '@apollo/query-planner';
|
|
71
|
-
import { loadSupergraphSdlFromUplinks } from './loadSupergraphSdlFromStorage';
|
|
72
61
|
import { SpanStatusCode } from '@opentelemetry/api';
|
|
73
62
|
import { OpenTelemetrySpanNames, tracer } from './utilities/opentelemetry';
|
|
74
63
|
import { CoreSchema } from '@apollo/core-schema';
|
|
75
64
|
import { featureSupport } from './core';
|
|
65
|
+
import { createHash } from './utilities/createHash';
|
|
66
|
+
import {
|
|
67
|
+
IntrospectAndCompose,
|
|
68
|
+
UplinkFetcher,
|
|
69
|
+
LegacyFetcher,
|
|
70
|
+
LocalCompose,
|
|
71
|
+
} from './supergraphManagers';
|
|
76
72
|
|
|
77
73
|
type DataSourceMap = {
|
|
78
74
|
[serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
|
|
@@ -122,12 +118,7 @@ type GatewayState =
|
|
|
122
118
|
| { phase: 'loaded' }
|
|
123
119
|
| { phase: 'stopping'; stoppingDonePromise: Promise<void> }
|
|
124
120
|
| { phase: 'stopped' }
|
|
125
|
-
| {
|
|
126
|
-
phase: 'waiting to poll';
|
|
127
|
-
pollWaitTimer: NodeJS.Timer;
|
|
128
|
-
doneWaiting: () => void;
|
|
129
|
-
}
|
|
130
|
-
| { phase: 'polling'; pollingDonePromise: Promise<void> };
|
|
121
|
+
| { phase: 'updating schema' };
|
|
131
122
|
|
|
132
123
|
// We want to be compatible with `load()` as called by both AS2 and AS3, so we
|
|
133
124
|
// define its argument types ourselves instead of relying on imports.
|
|
@@ -171,9 +162,6 @@ export class ApolloGateway implements GraphQLService {
|
|
|
171
162
|
coreSupergraphSdl: string;
|
|
172
163
|
}) => void
|
|
173
164
|
>();
|
|
174
|
-
private serviceDefinitions: ServiceDefinition[] = [];
|
|
175
|
-
private compositionMetadata?: CompositionMetadata;
|
|
176
|
-
private serviceSdlCache = new Map<string, string>();
|
|
177
165
|
private warnedStates: WarnedStates = Object.create(null);
|
|
178
166
|
private queryPlanner?: QueryPlanner;
|
|
179
167
|
private supergraphSdl?: string;
|
|
@@ -181,30 +169,17 @@ export class ApolloGateway implements GraphQLService {
|
|
|
181
169
|
private fetcher: typeof fetch;
|
|
182
170
|
private compositionId?: string;
|
|
183
171
|
private state: GatewayState;
|
|
184
|
-
private errorReportingEndpoint: string | undefined =
|
|
185
|
-
process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT ?? undefined;
|
|
186
172
|
|
|
187
173
|
// Observe query plan, service info, and operation info prior to execution.
|
|
188
174
|
// The information made available here will give insight into the resulting
|
|
189
175
|
// query plan and the inputs that generated it.
|
|
190
176
|
private experimental_didResolveQueryPlan?: Experimental_DidResolveQueryPlanCallback;
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
private
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
private experimental_didUpdateComposition?: Experimental_DidUpdateCompositionCallback;
|
|
198
|
-
// Used for overriding the default service list fetcher. This should return
|
|
199
|
-
// an array of ServiceDefinition. *This function must be awaited.*
|
|
200
|
-
private updateServiceDefinitions: Experimental_UpdateComposition;
|
|
201
|
-
// how often service defs should be loaded/updated (in ms)
|
|
202
|
-
private experimental_pollInterval?: number;
|
|
203
|
-
// Configure the endpoints by which gateway will access its precomposed schema.
|
|
204
|
-
// * An array of URLs means use these endpoints to obtain schema, if one is unavailable then try the next.
|
|
205
|
-
// * `undefined` means the gateway is not using managed federation
|
|
206
|
-
private uplinkEndpoints?: string[];
|
|
207
|
-
private uplinkMaxRetries?: number;
|
|
177
|
+
// Used to communicate supergraph updates
|
|
178
|
+
private experimental_didUpdateSupergraph?: Experimental_DidUpdateSupergraphCallback;
|
|
179
|
+
// how often service defs should be loaded/updated
|
|
180
|
+
private pollIntervalInMs?: number;
|
|
181
|
+
// Functions to call during gateway cleanup (when stop() is called)
|
|
182
|
+
private toDispose: (() => Promise<void>)[] = [];
|
|
208
183
|
|
|
209
184
|
constructor(config?: GatewayConfig) {
|
|
210
185
|
this.config = {
|
|
@@ -224,54 +199,15 @@ export class ApolloGateway implements GraphQLService {
|
|
|
224
199
|
// set up experimental observability callbacks and config settings
|
|
225
200
|
this.experimental_didResolveQueryPlan =
|
|
226
201
|
config?.experimental_didResolveQueryPlan;
|
|
227
|
-
this.
|
|
228
|
-
config?.
|
|
229
|
-
this.experimental_didUpdateComposition =
|
|
230
|
-
config?.experimental_didUpdateComposition;
|
|
231
|
-
|
|
232
|
-
this.experimental_pollInterval = config?.experimental_pollInterval;
|
|
233
|
-
|
|
234
|
-
// 1. If config is set to a `string`, use it
|
|
235
|
-
// 2. If the env var is set, use that
|
|
236
|
-
// 3. If config is `undefined`, use the default uplink URLs
|
|
237
|
-
if (isManagedConfig(this.config)) {
|
|
238
|
-
const rawEndpointsString = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
|
|
239
|
-
const envEndpoints = rawEndpointsString?.split(",") ?? null;
|
|
240
|
-
|
|
241
|
-
if (this.config.schemaConfigDeliveryEndpoint && !this.config.uplinkEndpoints) {
|
|
242
|
-
this.uplinkEndpoints = [this.config.schemaConfigDeliveryEndpoint];
|
|
243
|
-
} else {
|
|
244
|
-
this.uplinkEndpoints = this.config.uplinkEndpoints ??
|
|
245
|
-
envEndpoints ?? [
|
|
246
|
-
'https://uplink.api.apollographql.com/',
|
|
247
|
-
'https://aws.uplink.api.apollographql.com/'
|
|
248
|
-
];
|
|
249
|
-
}
|
|
202
|
+
this.experimental_didUpdateSupergraph =
|
|
203
|
+
config?.experimental_didUpdateSupergraph;
|
|
250
204
|
|
|
251
|
-
|
|
252
|
-
|
|
205
|
+
this.pollIntervalInMs =
|
|
206
|
+
config?.pollIntervalInMs ?? config?.experimental_pollInterval;
|
|
253
207
|
|
|
254
|
-
|
|
255
|
-
// Use the provided updater function if provided by the user, else default
|
|
256
|
-
if ('experimental_updateSupergraphSdl' in this.config) {
|
|
257
|
-
this.updateServiceDefinitions =
|
|
258
|
-
this.config.experimental_updateSupergraphSdl;
|
|
259
|
-
} else if ('experimental_updateServiceDefinitions' in this.config) {
|
|
260
|
-
this.updateServiceDefinitions =
|
|
261
|
-
this.config.experimental_updateServiceDefinitions;
|
|
262
|
-
} else {
|
|
263
|
-
throw Error(
|
|
264
|
-
'Programming error: unexpected manual configuration provided',
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
} else {
|
|
268
|
-
this.updateServiceDefinitions = this.loadServiceDefinitions;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (isDynamicConfig(this.config)) {
|
|
272
|
-
this.issueDynamicWarningsIfApplicable();
|
|
273
|
-
}
|
|
208
|
+
this.issueConfigurationWarningsIfApplicable();
|
|
274
209
|
|
|
210
|
+
this.logger.debug('Gateway successfully initialized (but not yet loaded)');
|
|
275
211
|
this.state = { phase: 'initialized' };
|
|
276
212
|
}
|
|
277
213
|
|
|
@@ -306,25 +242,26 @@ export class ApolloGateway implements GraphQLService {
|
|
|
306
242
|
});
|
|
307
243
|
}
|
|
308
244
|
|
|
309
|
-
private
|
|
245
|
+
private issueConfigurationWarningsIfApplicable() {
|
|
310
246
|
// Warn against a pollInterval of < 10s in managed mode and reset it to 10s
|
|
311
247
|
if (
|
|
312
248
|
isManagedConfig(this.config) &&
|
|
313
|
-
this.
|
|
314
|
-
this.
|
|
249
|
+
this.pollIntervalInMs &&
|
|
250
|
+
this.pollIntervalInMs < 10000
|
|
315
251
|
) {
|
|
316
|
-
this.
|
|
252
|
+
this.pollIntervalInMs = 10000;
|
|
317
253
|
this.logger.warn(
|
|
318
254
|
'Polling Apollo services at a frequency of less than once per 10 ' +
|
|
319
255
|
'seconds (10000) is disallowed. Instead, the minimum allowed ' +
|
|
320
256
|
'pollInterval of 10000 will be used. Please reconfigure your ' +
|
|
321
|
-
'
|
|
257
|
+
'`pollIntervalInMs` accordingly. If this is problematic for ' +
|
|
322
258
|
'your team, please contact support.',
|
|
323
259
|
);
|
|
324
260
|
}
|
|
325
261
|
|
|
326
262
|
// Warn against using the pollInterval and a serviceList simultaneously
|
|
327
|
-
|
|
263
|
+
// TODO(trevor:removeServiceList)
|
|
264
|
+
if (this.pollIntervalInMs && isServiceListConfig(this.config)) {
|
|
328
265
|
this.logger.warn(
|
|
329
266
|
'Polling running services is dangerous and not recommended in production. ' +
|
|
330
267
|
'Polling should only be used against a registry. ' +
|
|
@@ -344,12 +281,26 @@ export class ApolloGateway implements GraphQLService {
|
|
|
344
281
|
'are provided.',
|
|
345
282
|
);
|
|
346
283
|
}
|
|
284
|
+
|
|
285
|
+
if ('schemaConfigDeliveryEndpoint' in this.config) {
|
|
286
|
+
this.logger.warn(
|
|
287
|
+
'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.',
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if ('experimental_pollInterval' in this.config) {
|
|
292
|
+
this.logger.warn(
|
|
293
|
+
'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.',
|
|
294
|
+
);
|
|
295
|
+
}
|
|
347
296
|
}
|
|
348
297
|
|
|
349
298
|
public async load(options?: {
|
|
350
299
|
apollo?: ApolloConfigFromAS2Or3;
|
|
351
300
|
engine?: GraphQLServiceEngineConfig;
|
|
352
301
|
}) {
|
|
302
|
+
this.logger.debug('Loading gateway...');
|
|
303
|
+
|
|
353
304
|
if (this.state.phase !== 'initialized') {
|
|
354
305
|
throw Error(
|
|
355
306
|
`ApolloGateway.load called in surprising state ${this.state.phase}`,
|
|
@@ -375,40 +326,88 @@ export class ApolloGateway implements GraphQLService {
|
|
|
375
326
|
};
|
|
376
327
|
}
|
|
377
328
|
|
|
378
|
-
// Before @apollo/gateway v0.23, ApolloGateway didn't expect stop() to be
|
|
379
|
-
// called after it started. The only thing that stop() did at that point was
|
|
380
|
-
// cancel the poll timer, and so to prevent that timer from keeping an
|
|
381
|
-
// otherwise-finished Node process alive, ApolloGateway unconditionally
|
|
382
|
-
// called unref() on that timeout. As part of making the ApolloGateway
|
|
383
|
-
// lifecycle more predictable and concrete (and to allow for a future where
|
|
384
|
-
// there are other reasons to make sure to explicitly stop your gateway),
|
|
385
|
-
// v0.23 tries to avoid calling unref().
|
|
386
|
-
//
|
|
387
|
-
// Apollo Server v2.20 and newer calls gateway.stop() from its stop()
|
|
388
|
-
// method, so as long as you're using v2.20, ApolloGateway won't keep
|
|
389
|
-
// running after you stop your server, and your Node process can shut down.
|
|
390
|
-
// To make this change a bit less backwards-incompatible, we detect if it
|
|
391
|
-
// looks like you're using an older version of Apollo Server; if so, we
|
|
392
|
-
// still call unref(). Specifically: Apollo Server has always passed an
|
|
393
|
-
// options object to load(), and before v2.18 it did not pass the `apollo`
|
|
394
|
-
// key on it. So if we detect that particular pattern, we assume we're with
|
|
395
|
-
// pre-v2.18 Apollo Server and we still call unref(). So this will be a
|
|
396
|
-
// behavior change only for:
|
|
397
|
-
// - non-Apollo-Server uses of ApolloGateway (where you can add your own
|
|
398
|
-
// call to gateway.stop())
|
|
399
|
-
// - Apollo Server v2.18 and v2.19 (where you can either do the small
|
|
400
|
-
// compatible upgrade or add your own call to gateway.stop())
|
|
401
|
-
// - if you don't call stop() on your ApolloServer (but in that case other
|
|
402
|
-
// things like usage reporting will also stop shutdown, so you should fix
|
|
403
|
-
// that)
|
|
404
|
-
const unrefTimer = !!options && !options.apollo;
|
|
405
|
-
|
|
406
329
|
this.maybeWarnOnConflictingConfig();
|
|
407
330
|
|
|
408
331
|
// Handles initial assignment of `this.schema`, `this.queryPlanner`
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
332
|
+
if (isStaticSupergraphSdlConfig(this.config)) {
|
|
333
|
+
const supergraphSdl = this.config.supergraphSdl;
|
|
334
|
+
await this.initializeSupergraphManager({
|
|
335
|
+
initialize: async () => {
|
|
336
|
+
return {
|
|
337
|
+
supergraphSdl,
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
} else if (isLocalConfig(this.config)) {
|
|
342
|
+
// TODO(trevor:removeServiceList)
|
|
343
|
+
await this.initializeSupergraphManager(new LocalCompose({
|
|
344
|
+
localServiceList: this.config.localServiceList,
|
|
345
|
+
logger: this.logger,
|
|
346
|
+
}));
|
|
347
|
+
} else if (isManuallyManagedSupergraphSdlGatewayConfig(this.config)) {
|
|
348
|
+
const supergraphManager = typeof this.config.supergraphSdl === 'object'
|
|
349
|
+
? this.config.supergraphSdl
|
|
350
|
+
: { initialize: this.config.supergraphSdl };
|
|
351
|
+
await this.initializeSupergraphManager(supergraphManager);
|
|
352
|
+
} else if (
|
|
353
|
+
'experimental_updateServiceDefinitions' in this.config || 'experimental_updateSupergraphSdl' in this.config
|
|
354
|
+
) {
|
|
355
|
+
const updateServiceDefinitions =
|
|
356
|
+
'experimental_updateServiceDefinitions' in this.config
|
|
357
|
+
? this.config.experimental_updateServiceDefinitions
|
|
358
|
+
: this.config.experimental_updateSupergraphSdl;
|
|
359
|
+
|
|
360
|
+
await this.initializeSupergraphManager(
|
|
361
|
+
new LegacyFetcher({
|
|
362
|
+
logger: this.logger,
|
|
363
|
+
gatewayConfig: this.config,
|
|
364
|
+
updateServiceDefinitions,
|
|
365
|
+
pollIntervalInMs: this.pollIntervalInMs,
|
|
366
|
+
subgraphHealthCheck: this.config.serviceHealthCheck,
|
|
367
|
+
}),
|
|
368
|
+
);
|
|
369
|
+
} else if (isServiceListConfig(this.config)) {
|
|
370
|
+
// TODO(trevor:removeServiceList)
|
|
371
|
+
this.logger.warn(
|
|
372
|
+
'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.',
|
|
373
|
+
);
|
|
374
|
+
await this.initializeSupergraphManager(
|
|
375
|
+
new IntrospectAndCompose({
|
|
376
|
+
subgraphs: this.config.serviceList,
|
|
377
|
+
pollIntervalInMs: this.pollIntervalInMs,
|
|
378
|
+
logger: this.logger,
|
|
379
|
+
subgraphHealthCheck: this.config.serviceHealthCheck,
|
|
380
|
+
introspectionHeaders: this.config.introspectionHeaders,
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
383
|
+
} else {
|
|
384
|
+
// isManagedConfig(this.config)
|
|
385
|
+
const canUseManagedConfig =
|
|
386
|
+
this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
|
|
387
|
+
if (!canUseManagedConfig) {
|
|
388
|
+
throw new Error(
|
|
389
|
+
'When a manual configuration is not provided, gateway requires an Apollo ' +
|
|
390
|
+
'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
|
|
391
|
+
'for more information. Manual configuration options include: ' +
|
|
392
|
+
'`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
const uplinkEndpoints = this.getUplinkEndpoints(this.config);
|
|
396
|
+
|
|
397
|
+
await this.initializeSupergraphManager(
|
|
398
|
+
new UplinkFetcher({
|
|
399
|
+
graphRef: this.apolloConfig!.graphRef!,
|
|
400
|
+
apiKey: this.apolloConfig!.key!,
|
|
401
|
+
uplinkEndpoints,
|
|
402
|
+
maxRetries:
|
|
403
|
+
this.config.uplinkMaxRetries ?? uplinkEndpoints.length * 3,
|
|
404
|
+
subgraphHealthCheck: this.config.serviceHealthCheck,
|
|
405
|
+
fetcher: this.fetcher,
|
|
406
|
+
logger: this.logger,
|
|
407
|
+
pollIntervalInMs: this.pollIntervalInMs ?? 10000,
|
|
408
|
+
}),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
412
411
|
|
|
413
412
|
const mode = isManagedConfig(this.config) ? 'managed' : 'unmanaged';
|
|
414
413
|
this.logger.info(
|
|
@@ -425,135 +424,157 @@ export class ApolloGateway implements GraphQLService {
|
|
|
425
424
|
};
|
|
426
425
|
}
|
|
427
426
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
427
|
+
private getUplinkEndpoints(config: ManagedGatewayConfig) {
|
|
428
|
+
/**
|
|
429
|
+
* Configuration priority order:
|
|
430
|
+
* 1. `uplinkEndpoints` configuration option
|
|
431
|
+
* 2. (deprecated) `schemaConfigDeliveryEndpoint` configuration option
|
|
432
|
+
* 3. APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT environment variable
|
|
433
|
+
* 4. default (GCP and AWS)
|
|
434
|
+
*/
|
|
435
|
+
const rawEndpointsString =
|
|
436
|
+
process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
|
|
437
|
+
const envEndpoints = rawEndpointsString?.split(',') ?? null;
|
|
438
|
+
return config.uplinkEndpoints ??
|
|
439
|
+
(config.schemaConfigDeliveryEndpoint
|
|
440
|
+
? [config.schemaConfigDeliveryEndpoint]
|
|
441
|
+
: null) ??
|
|
442
|
+
envEndpoints ?? [
|
|
443
|
+
'https://uplink.api.apollographql.com/',
|
|
444
|
+
'https://aws.uplink.api.apollographql.com/',
|
|
445
|
+
];
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
448
|
+
private getIdForSupergraphSdl(supergraphSdl: string) {
|
|
449
|
+
return createHash('sha256').update(supergraphSdl).digest('hex');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private async initializeSupergraphManager<T extends SupergraphManager>(
|
|
453
|
+
supergraphManager: T,
|
|
454
|
+
) {
|
|
451
455
|
try {
|
|
452
|
-
await
|
|
456
|
+
const result = await supergraphManager.initialize({
|
|
457
|
+
update: this.externalSupergraphUpdateCallback.bind(this),
|
|
458
|
+
healthCheck: this.externalSubgraphHealthCheckCallback.bind(this),
|
|
459
|
+
getDataSource: this.externalGetDataSourceCallback.bind(this),
|
|
460
|
+
});
|
|
461
|
+
if (!result?.supergraphSdl) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
'Provided `supergraphSdl` function did not return an object containing a `supergraphSdl` property',
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
if (result?.cleanup) {
|
|
467
|
+
if (typeof result.cleanup === 'function') {
|
|
468
|
+
this.toDispose.push(result.cleanup);
|
|
469
|
+
} else {
|
|
470
|
+
this.logger.error(
|
|
471
|
+
'Provided `supergraphSdl` function returned an invalid `cleanup` property (must be a function)',
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
this.externalSupergraphUpdateCallback(result.supergraphSdl);
|
|
453
477
|
} catch (e) {
|
|
454
478
|
this.state = { phase: 'failed to load' };
|
|
479
|
+
await this.performCleanupAndLogErrors();
|
|
455
480
|
throw e;
|
|
456
481
|
}
|
|
457
482
|
|
|
458
483
|
this.state = { phase: 'loaded' };
|
|
459
|
-
if (this.shouldBeginPolling()) {
|
|
460
|
-
this.pollServices(unrefTimer);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
private shouldBeginPolling() {
|
|
465
|
-
return isManagedConfig(this.config) || this.experimental_pollInterval;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
private async updateSchema(): Promise<void> {
|
|
469
|
-
this.logger.debug('Checking for composition updates...');
|
|
470
|
-
|
|
471
|
-
// This may throw, but an error here is caught and logged upstream
|
|
472
|
-
const result = await this.updateServiceDefinitions(this.config);
|
|
473
|
-
|
|
474
|
-
if (isSupergraphSdlUpdate(result)) {
|
|
475
|
-
await this.updateWithSupergraphSdl(result);
|
|
476
|
-
} else if (isServiceDefinitionUpdate(result)) {
|
|
477
|
-
await this.updateByComposition(result);
|
|
478
|
-
} else {
|
|
479
|
-
throw new Error(
|
|
480
|
-
'Programming error: unexpected result type from `updateServiceDefinitions`',
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
484
|
}
|
|
484
485
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
486
|
+
/**
|
|
487
|
+
* @throws Error
|
|
488
|
+
* when called from a state other than `loaded` or `intialized`
|
|
489
|
+
*
|
|
490
|
+
* @throws Error
|
|
491
|
+
* when the provided supergraphSdl is invalid
|
|
492
|
+
*/
|
|
493
|
+
private externalSupergraphUpdateCallback(supergraphSdl: string) {
|
|
494
|
+
switch (this.state.phase) {
|
|
495
|
+
case 'failed to load':
|
|
496
|
+
throw new Error(
|
|
497
|
+
"Can't call `update` callback after gateway failed to load.",
|
|
498
|
+
);
|
|
499
|
+
case 'updating schema':
|
|
500
|
+
throw new Error(
|
|
501
|
+
"Can't call `update` callback while supergraph update is in progress.",
|
|
502
|
+
);
|
|
503
|
+
case 'stopped':
|
|
504
|
+
throw new Error(
|
|
505
|
+
"Can't call `update` callback after gateway has been stopped.",
|
|
506
|
+
);
|
|
507
|
+
case 'stopping':
|
|
508
|
+
throw new Error(
|
|
509
|
+
"Can't call `update` callback while gateway is stopping.",
|
|
510
|
+
);
|
|
511
|
+
case 'loaded':
|
|
512
|
+
case 'initialized':
|
|
513
|
+
// typical case
|
|
514
|
+
break;
|
|
515
|
+
default:
|
|
516
|
+
throw new UnreachableCaseError(this.state);
|
|
495
517
|
}
|
|
496
518
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
519
|
+
this.state = { phase: 'updating schema' };
|
|
520
|
+
try {
|
|
521
|
+
this.updateWithSupergraphSdl({
|
|
522
|
+
supergraphSdl,
|
|
523
|
+
id: this.getIdForSupergraphSdl(supergraphSdl),
|
|
524
|
+
});
|
|
525
|
+
} finally {
|
|
526
|
+
// if update fails, we still want to go back to `loaded` state
|
|
527
|
+
this.state = { phase: 'loaded' };
|
|
503
528
|
}
|
|
529
|
+
}
|
|
504
530
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
531
|
+
/**
|
|
532
|
+
* @throws Error
|
|
533
|
+
* when any subgraph fails the health check
|
|
534
|
+
*/
|
|
535
|
+
private async externalSubgraphHealthCheckCallback(supergraphSdl: string) {
|
|
536
|
+
const parsedSupergraphSdl =
|
|
537
|
+
supergraphSdl === this.supergraphSdl
|
|
538
|
+
? this.parsedSupergraphSdl
|
|
539
|
+
: parse(supergraphSdl);
|
|
540
|
+
|
|
541
|
+
const serviceList = this.serviceListFromSupergraphSdl(parsedSupergraphSdl!);
|
|
542
|
+
// Here we need to construct new datasources based on the new schema info
|
|
543
|
+
// so we can check the health of the services we're _updating to_.
|
|
544
|
+
const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
|
|
545
|
+
serviceMap[serviceDef.name] = {
|
|
546
|
+
url: serviceDef.url,
|
|
547
|
+
dataSource: this.createDataSource(serviceDef),
|
|
548
|
+
};
|
|
549
|
+
return serviceMap;
|
|
550
|
+
}, Object.create(null) as DataSourceMap);
|
|
513
551
|
|
|
514
|
-
|
|
515
|
-
this.
|
|
516
|
-
|
|
552
|
+
try {
|
|
553
|
+
await this.serviceHealthCheck(serviceMap);
|
|
554
|
+
} catch (e) {
|
|
555
|
+
throw new Error(
|
|
556
|
+
'The gateway subgraphs health check failed. Updating to the provided ' +
|
|
557
|
+
'`supergraphSdl` will likely result in future request failures to ' +
|
|
558
|
+
'subgraphs. The following error occurred during the health check:\n' +
|
|
559
|
+
e.message,
|
|
517
560
|
);
|
|
518
|
-
} else {
|
|
519
|
-
this.updateWithSchemaAndNotify(schema, supergraphSdl);
|
|
520
|
-
|
|
521
|
-
if (this.experimental_didUpdateComposition) {
|
|
522
|
-
this.experimental_didUpdateComposition(
|
|
523
|
-
{
|
|
524
|
-
serviceDefinitions: result.serviceDefinitions,
|
|
525
|
-
schema,
|
|
526
|
-
...(this.compositionMetadata && {
|
|
527
|
-
compositionMetadata: this.compositionMetadata,
|
|
528
|
-
}),
|
|
529
|
-
},
|
|
530
|
-
previousServiceDefinitions &&
|
|
531
|
-
previousSchema && {
|
|
532
|
-
serviceDefinitions: previousServiceDefinitions,
|
|
533
|
-
schema: previousSchema,
|
|
534
|
-
...(previousCompositionMetadata && {
|
|
535
|
-
compositionMetadata: previousCompositionMetadata,
|
|
536
|
-
}),
|
|
537
|
-
},
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
561
|
}
|
|
541
562
|
}
|
|
542
563
|
|
|
543
|
-
private
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
564
|
+
private externalGetDataSourceCallback({
|
|
565
|
+
name,
|
|
566
|
+
url,
|
|
567
|
+
}: ServiceEndpointDefinition) {
|
|
568
|
+
return this.getOrCreateDataSource({ name, url });
|
|
569
|
+
}
|
|
550
570
|
|
|
571
|
+
private updateWithSupergraphSdl({ supergraphSdl, id }: SupergraphSdlUpdate) {
|
|
551
572
|
// TODO(trevor): #580 redundant parse
|
|
552
573
|
// This may throw, so we'll calculate early (specifically before making any updates)
|
|
553
574
|
// In the case that it throws, the gateway will:
|
|
554
575
|
// * on initial load, throw the error
|
|
555
576
|
// * on update, log the error and don't update
|
|
556
|
-
const parsedSupergraphSdl = parse(
|
|
577
|
+
const parsedSupergraphSdl = parse(supergraphSdl);
|
|
557
578
|
|
|
558
579
|
const previousSchema = this.schema;
|
|
559
580
|
const previousSupergraphSdl = this.parsedSupergraphSdl;
|
|
@@ -563,28 +584,25 @@ export class ApolloGateway implements GraphQLService {
|
|
|
563
584
|
this.logger.info('Updated Supergraph SDL was found.');
|
|
564
585
|
}
|
|
565
586
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
this.compositionId = result.id;
|
|
569
|
-
this.supergraphSdl = result.supergraphSdl;
|
|
587
|
+
this.compositionId = id;
|
|
588
|
+
this.supergraphSdl = supergraphSdl;
|
|
570
589
|
this.parsedSupergraphSdl = parsedSupergraphSdl;
|
|
571
590
|
|
|
572
|
-
const { schema, supergraphSdl } =
|
|
573
|
-
|
|
574
|
-
);
|
|
591
|
+
const { schema, supergraphSdl: generatedSupergraphSdl } =
|
|
592
|
+
this.createSchemaFromSupergraphSdl(supergraphSdl);
|
|
575
593
|
|
|
576
|
-
if (!
|
|
594
|
+
if (!generatedSupergraphSdl) {
|
|
577
595
|
this.logger.error(
|
|
578
596
|
"A valid schema couldn't be composed. Falling back to previous schema.",
|
|
579
597
|
);
|
|
580
598
|
} else {
|
|
581
|
-
this.updateWithSchemaAndNotify(schema,
|
|
599
|
+
this.updateWithSchemaAndNotify(schema, generatedSupergraphSdl);
|
|
582
600
|
|
|
583
|
-
if (this.
|
|
584
|
-
this.
|
|
601
|
+
if (this.experimental_didUpdateSupergraph) {
|
|
602
|
+
this.experimental_didUpdateSupergraph(
|
|
585
603
|
{
|
|
586
|
-
compositionId:
|
|
587
|
-
supergraphSdl
|
|
604
|
+
compositionId: id,
|
|
605
|
+
supergraphSdl,
|
|
588
606
|
schema,
|
|
589
607
|
},
|
|
590
608
|
previousCompositionId && previousSupergraphSdl && previousSchema
|
|
@@ -644,40 +662,6 @@ export class ApolloGateway implements GraphQLService {
|
|
|
644
662
|
});
|
|
645
663
|
}
|
|
646
664
|
|
|
647
|
-
private async maybePerformServiceHealthCheck(update: CompositionUpdate) {
|
|
648
|
-
// Run service health checks before we commit and update the new schema.
|
|
649
|
-
// This is the last chance to bail out of a schema update.
|
|
650
|
-
if (this.config.serviceHealthCheck) {
|
|
651
|
-
const serviceList = isSupergraphSdlUpdate(update)
|
|
652
|
-
? // TODO(trevor): #580 redundant parse
|
|
653
|
-
// Parsing could technically fail and throw here, but parseability has
|
|
654
|
-
// already been confirmed slightly earlier in the code path
|
|
655
|
-
this.serviceListFromSupergraphSdl(parse(update.supergraphSdl))
|
|
656
|
-
: // Existence of this is determined in advance with an early return otherwise
|
|
657
|
-
update.serviceDefinitions!;
|
|
658
|
-
// Here we need to construct new datasources based on the new schema info
|
|
659
|
-
// so we can check the health of the services we're _updating to_.
|
|
660
|
-
const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
|
|
661
|
-
serviceMap[serviceDef.name] = {
|
|
662
|
-
url: serviceDef.url,
|
|
663
|
-
dataSource: this.createDataSource(serviceDef),
|
|
664
|
-
};
|
|
665
|
-
return serviceMap;
|
|
666
|
-
}, Object.create(null) as DataSourceMap);
|
|
667
|
-
|
|
668
|
-
try {
|
|
669
|
-
await this.serviceHealthCheck(serviceMap);
|
|
670
|
-
} catch (e) {
|
|
671
|
-
throw new Error(
|
|
672
|
-
'The gateway did not update its schema due to failed service health checks. ' +
|
|
673
|
-
'The gateway will continue to operate with the previous schema and reattempt updates. ' +
|
|
674
|
-
'The following error occurred during the health check:\n' +
|
|
675
|
-
e.message,
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
665
|
/**
|
|
682
666
|
* This can be used without an argument in order to perform an ad-hoc health check
|
|
683
667
|
* of the downstream services like so:
|
|
@@ -710,51 +694,6 @@ export class ApolloGateway implements GraphQLService {
|
|
|
710
694
|
);
|
|
711
695
|
}
|
|
712
696
|
|
|
713
|
-
private createSchemaFromServiceList(serviceList: ServiceDefinition[]) {
|
|
714
|
-
this.logger.debug(
|
|
715
|
-
`Composing schema from service list: \n${serviceList
|
|
716
|
-
.map(({ name, url }) => ` ${url || 'local'}: ${name}`)
|
|
717
|
-
.join('\n')}`,
|
|
718
|
-
);
|
|
719
|
-
|
|
720
|
-
const compositionResult = composeAndValidate(serviceList);
|
|
721
|
-
|
|
722
|
-
if (compositionHasErrors(compositionResult)) {
|
|
723
|
-
const { errors } = compositionResult;
|
|
724
|
-
if (this.experimental_didFailComposition) {
|
|
725
|
-
this.experimental_didFailComposition({
|
|
726
|
-
errors,
|
|
727
|
-
serviceList,
|
|
728
|
-
...(this.compositionMetadata && {
|
|
729
|
-
compositionMetadata: this.compositionMetadata,
|
|
730
|
-
}),
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
throw Error(
|
|
734
|
-
"A valid schema couldn't be composed. The following composition errors were found:\n" +
|
|
735
|
-
errors.map((e) => '\t' + e.message).join('\n'),
|
|
736
|
-
);
|
|
737
|
-
} else {
|
|
738
|
-
const { supergraphSdl } = compositionResult;
|
|
739
|
-
this.createServices(serviceList);
|
|
740
|
-
|
|
741
|
-
const schema = buildComposedSchema(parse(supergraphSdl));
|
|
742
|
-
|
|
743
|
-
this.logger.debug('Schema loaded and ready for execution');
|
|
744
|
-
|
|
745
|
-
// This is a workaround for automatic wrapping of all fields, which Apollo
|
|
746
|
-
// Server does in the case of implementing resolver wrapping for plugins.
|
|
747
|
-
// Here we wrap all fields with support for resolving aliases as part of the
|
|
748
|
-
// root value which happens because aliases are resolved by sub services and
|
|
749
|
-
// the shape of the root value already contains the aliased fields as
|
|
750
|
-
// responseNames
|
|
751
|
-
return {
|
|
752
|
-
schema: wrapSchemaWithAliasResolver(schema),
|
|
753
|
-
supergraphSdl,
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
|
|
758
697
|
private serviceListFromSupergraphSdl(
|
|
759
698
|
supergraphSdl: DocumentNode,
|
|
760
699
|
): Omit<ServiceDefinition, 'typeDefs'>[] {
|
|
@@ -819,93 +758,16 @@ export class ApolloGateway implements GraphQLService {
|
|
|
819
758
|
};
|
|
820
759
|
}
|
|
821
760
|
|
|
822
|
-
|
|
823
|
-
// again. Note that it is an async function whose Promise is not actually awaited;
|
|
824
|
-
// it should never throw itself other than due to a bug in its state machine.
|
|
825
|
-
private async pollServices(unrefTimer: boolean) {
|
|
826
|
-
switch (this.state.phase) {
|
|
827
|
-
case 'stopping':
|
|
828
|
-
case 'stopped':
|
|
829
|
-
case 'failed to load':
|
|
830
|
-
return;
|
|
831
|
-
case 'initialized':
|
|
832
|
-
throw Error('pollServices should not be called before load!');
|
|
833
|
-
case 'polling':
|
|
834
|
-
throw Error(
|
|
835
|
-
'pollServices should not be called while in the middle of polling!',
|
|
836
|
-
);
|
|
837
|
-
case 'waiting to poll':
|
|
838
|
-
throw Error(
|
|
839
|
-
'pollServices should not be called while already waiting to poll!',
|
|
840
|
-
);
|
|
841
|
-
case 'loaded':
|
|
842
|
-
// This is the normal case.
|
|
843
|
-
break;
|
|
844
|
-
default:
|
|
845
|
-
throw new UnreachableCaseError(this.state);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// Transition into 'waiting to poll' and set a timer. The timer resolves the
|
|
849
|
-
// Promise we're awaiting here; note that calling stop() also can resolve
|
|
850
|
-
// that Promise.
|
|
851
|
-
await new Promise<void>((doneWaiting) => {
|
|
852
|
-
this.state = {
|
|
853
|
-
phase: 'waiting to poll',
|
|
854
|
-
doneWaiting,
|
|
855
|
-
pollWaitTimer: setTimeout(() => {
|
|
856
|
-
// Note that we might be in 'stopped', in which case we just do
|
|
857
|
-
// nothing.
|
|
858
|
-
if (this.state.phase == 'waiting to poll') {
|
|
859
|
-
this.state.doneWaiting();
|
|
860
|
-
}
|
|
861
|
-
}, this.experimental_pollInterval || 10000),
|
|
862
|
-
};
|
|
863
|
-
if (unrefTimer) {
|
|
864
|
-
this.state.pollWaitTimer.unref();
|
|
865
|
-
}
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
// If we've been stopped, then we're done. The cast here is because TS
|
|
869
|
-
// doesn't understand that this.state can change during the await
|
|
870
|
-
// (https://github.com/microsoft/TypeScript/issues/9998).
|
|
871
|
-
if ((this.state as GatewayState).phase !== 'waiting to poll') {
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
let pollingDone: () => void;
|
|
876
|
-
this.state = {
|
|
877
|
-
phase: 'polling',
|
|
878
|
-
pollingDonePromise: new Promise<void>((res) => {
|
|
879
|
-
pollingDone = res;
|
|
880
|
-
}),
|
|
881
|
-
};
|
|
882
|
-
|
|
883
|
-
try {
|
|
884
|
-
await this.updateSchema();
|
|
885
|
-
} catch (err) {
|
|
886
|
-
this.logger.error((err && err.message) || err);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
if (this.state.phase === 'polling') {
|
|
890
|
-
// If we weren't stopped, we should transition back to the initial 'loading' state and trigger
|
|
891
|
-
// another call to itself. (Do that in a setImmediate to avoid unbounded stack sizes.)
|
|
892
|
-
this.state = { phase: 'loaded' };
|
|
893
|
-
setImmediate(() => this.pollServices(unrefTimer));
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
// Whether we were stopped or not, let any concurrent stop() call finish.
|
|
897
|
-
pollingDone!();
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
private createAndCacheDataSource(
|
|
761
|
+
private getOrCreateDataSource(
|
|
901
762
|
serviceDef: ServiceEndpointDefinition,
|
|
902
763
|
): GraphQLDataSource {
|
|
903
764
|
// If the DataSource has already been created, early return
|
|
904
765
|
if (
|
|
905
766
|
this.serviceMap[serviceDef.name] &&
|
|
906
767
|
serviceDef.url === this.serviceMap[serviceDef.name].url
|
|
907
|
-
)
|
|
768
|
+
) {
|
|
908
769
|
return this.serviceMap[serviceDef.name].dataSource;
|
|
770
|
+
}
|
|
909
771
|
|
|
910
772
|
const dataSource = this.createDataSource(serviceDef);
|
|
911
773
|
|
|
@@ -933,57 +795,8 @@ export class ApolloGateway implements GraphQLService {
|
|
|
933
795
|
|
|
934
796
|
private createServices(services: ServiceEndpointDefinition[]) {
|
|
935
797
|
for (const serviceDef of services) {
|
|
936
|
-
this.
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
protected async loadServiceDefinitions(
|
|
941
|
-
config: RemoteGatewayConfig | ManagedGatewayConfig,
|
|
942
|
-
): Promise<CompositionUpdate> {
|
|
943
|
-
if (isRemoteConfig(config)) {
|
|
944
|
-
const serviceList = config.serviceList.map((serviceDefinition) => ({
|
|
945
|
-
...serviceDefinition,
|
|
946
|
-
dataSource: this.createAndCacheDataSource(serviceDefinition),
|
|
947
|
-
}));
|
|
948
|
-
|
|
949
|
-
return getServiceDefinitionsFromRemoteEndpoint({
|
|
950
|
-
serviceList,
|
|
951
|
-
async getServiceIntrospectionHeaders(service) {
|
|
952
|
-
return typeof config.introspectionHeaders === 'function'
|
|
953
|
-
? await config.introspectionHeaders(service)
|
|
954
|
-
: config.introspectionHeaders;
|
|
955
|
-
},
|
|
956
|
-
serviceSdlCache: this.serviceSdlCache,
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
const canUseManagedConfig =
|
|
961
|
-
this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
|
|
962
|
-
if (!canUseManagedConfig) {
|
|
963
|
-
throw new Error(
|
|
964
|
-
'When a manual configuration is not provided, gateway requires an Apollo ' +
|
|
965
|
-
'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
|
|
966
|
-
'for more information. Manual configuration options include: ' +
|
|
967
|
-
'`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
|
|
968
|
-
);
|
|
798
|
+
this.getOrCreateDataSource(serviceDef);
|
|
969
799
|
}
|
|
970
|
-
|
|
971
|
-
const result = await loadSupergraphSdlFromUplinks({
|
|
972
|
-
graphRef: this.apolloConfig!.graphRef!,
|
|
973
|
-
apiKey: this.apolloConfig!.key!,
|
|
974
|
-
endpoints: this.uplinkEndpoints!,
|
|
975
|
-
errorReportingEndpoint: this.errorReportingEndpoint,
|
|
976
|
-
fetcher: this.fetcher,
|
|
977
|
-
compositionId: this.compositionId ?? null,
|
|
978
|
-
maxRetries: this.uplinkMaxRetries!,
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
return (
|
|
982
|
-
result ?? {
|
|
983
|
-
id: this.compositionId!,
|
|
984
|
-
supergraphSdl: this.supergraphSdl!,
|
|
985
|
-
}
|
|
986
|
-
);
|
|
987
800
|
}
|
|
988
801
|
|
|
989
802
|
private maybeWarnOnConflictingConfig() {
|
|
@@ -1202,9 +1015,24 @@ export class ApolloGateway implements GraphQLService {
|
|
|
1202
1015
|
});
|
|
1203
1016
|
}
|
|
1204
1017
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1018
|
+
private async performCleanupAndLogErrors() {
|
|
1019
|
+
if (this.toDispose.length === 0) return;
|
|
1020
|
+
|
|
1021
|
+
await Promise.all(
|
|
1022
|
+
this.toDispose.map((p) =>
|
|
1023
|
+
p().catch((e) => {
|
|
1024
|
+
this.logger.error(
|
|
1025
|
+
'Error occured while calling user provided `cleanup` function: ' +
|
|
1026
|
+
(e.message ?? e),
|
|
1027
|
+
);
|
|
1028
|
+
}),
|
|
1029
|
+
),
|
|
1030
|
+
);
|
|
1031
|
+
this.toDispose = [];
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Stops all processes involved with the gateway. Can be called multiple times
|
|
1035
|
+
// safely. Once it (async) returns, all gateway background activity will be finished.
|
|
1208
1036
|
public async stop() {
|
|
1209
1037
|
switch (this.state.phase) {
|
|
1210
1038
|
case 'initialized':
|
|
@@ -1227,40 +1055,31 @@ export class ApolloGateway implements GraphQLService {
|
|
|
1227
1055
|
}
|
|
1228
1056
|
return;
|
|
1229
1057
|
case 'loaded':
|
|
1230
|
-
|
|
1231
|
-
return;
|
|
1232
|
-
case 'waiting to poll': {
|
|
1233
|
-
// If we're waiting to poll, we can synchronously transition to fully stopped.
|
|
1234
|
-
// We will terminate the current pollServices call and it will succeed quickly.
|
|
1235
|
-
const doneWaiting = this.state.doneWaiting;
|
|
1236
|
-
clearTimeout(this.state.pollWaitTimer);
|
|
1237
|
-
this.state = { phase: 'stopped' };
|
|
1238
|
-
doneWaiting();
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
case 'polling': {
|
|
1242
|
-
// We're in the middle of running updateSchema. We need to go into 'stopping'
|
|
1243
|
-
// mode and let this run complete. First we set things up so that any concurrent
|
|
1244
|
-
// calls to stop() will wait until we let them finish. (Those concurrent calls shouldn't
|
|
1245
|
-
// just wait on pollingDonePromise themselves because we want to make sure we fully
|
|
1246
|
-
// transition to state='stopped' before the other call returns.)
|
|
1247
|
-
const pollingDonePromise = this.state.pollingDonePromise;
|
|
1248
|
-
let stoppingDone: () => void;
|
|
1058
|
+
const stoppingDonePromise = this.performCleanupAndLogErrors();
|
|
1249
1059
|
this.state = {
|
|
1250
1060
|
phase: 'stopping',
|
|
1251
|
-
stoppingDonePromise
|
|
1252
|
-
stoppingDone = res;
|
|
1253
|
-
}),
|
|
1061
|
+
stoppingDonePromise,
|
|
1254
1062
|
};
|
|
1255
|
-
await
|
|
1063
|
+
await stoppingDonePromise;
|
|
1256
1064
|
this.state = { phase: 'stopped' };
|
|
1257
|
-
stoppingDone!();
|
|
1258
1065
|
return;
|
|
1066
|
+
case 'updating schema': {
|
|
1067
|
+
throw Error(
|
|
1068
|
+
'`ApolloGateway.stop` shouldn\'t be called from inside a schema change listener',
|
|
1069
|
+
);
|
|
1259
1070
|
}
|
|
1260
1071
|
default:
|
|
1261
1072
|
throw new UnreachableCaseError(this.state);
|
|
1262
1073
|
}
|
|
1263
1074
|
}
|
|
1075
|
+
|
|
1076
|
+
public __testing() {
|
|
1077
|
+
return {
|
|
1078
|
+
state: this.state,
|
|
1079
|
+
compositionId: this.compositionId,
|
|
1080
|
+
supergraphSdl: this.supergraphSdl,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1264
1083
|
}
|
|
1265
1084
|
|
|
1266
1085
|
ApolloGateway.prototype.onSchemaChange = deprecate(
|
|
@@ -1307,11 +1126,21 @@ export {
|
|
|
1307
1126
|
ServiceMap,
|
|
1308
1127
|
Experimental_DidFailCompositionCallback,
|
|
1309
1128
|
Experimental_DidResolveQueryPlanCallback,
|
|
1310
|
-
|
|
1129
|
+
Experimental_DidUpdateSupergraphCallback,
|
|
1311
1130
|
Experimental_UpdateComposition,
|
|
1312
1131
|
GatewayConfig,
|
|
1313
1132
|
ServiceEndpointDefinition,
|
|
1314
1133
|
CompositionInfo,
|
|
1134
|
+
IntrospectAndCompose,
|
|
1135
|
+
LocalCompose,
|
|
1315
1136
|
};
|
|
1316
1137
|
|
|
1317
1138
|
export * from './datasources';
|
|
1139
|
+
|
|
1140
|
+
export {
|
|
1141
|
+
SupergraphSdlUpdateFunction,
|
|
1142
|
+
SubgraphHealthCheckFunction,
|
|
1143
|
+
GetDataSourceFunction,
|
|
1144
|
+
SupergraphSdlHook,
|
|
1145
|
+
SupergraphManager
|
|
1146
|
+
} from './config';
|