@apollo/gateway 2.0.0-alpha.2 → 2.0.0-alpha.6
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 +43 -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/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +2 -2
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +37 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +199 -283
- 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 +33 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js +98 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +25 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +40 -15
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +13 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +85 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
- package/dist/supergraphManagers/index.d.ts +6 -0
- package/dist/supergraphManagers/index.d.ts.map +1 -0
- package/dist/supergraphManagers/index.js +14 -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 +9 -7
- package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
- package/src/__tests__/build-query-plan.feature +52 -0
- package/src/__tests__/executeQueryPlan.test.ts +599 -1
- package/src/__tests__/execution-utils.ts +3 -3
- package/src/__tests__/gateway/buildService.test.ts +3 -3
- package/src/__tests__/gateway/endToEnd.test.ts +1 -1
- package/src/__tests__/gateway/executor.test.ts +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 +42 -13
- 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 +146 -9
- package/src/__tests__/integration/logger.test.ts +1 -1
- package/src/__tests__/integration/networkRequests.test.ts +99 -147
- package/src/__tests__/integration/nockMocks.ts +30 -16
- package/src/__tests__/nockAssertions.ts +20 -0
- package/src/config.ts +149 -38
- package/src/datasources/LocalGraphQLDataSource.ts +1 -1
- package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
- package/src/executeQueryPlan.ts +14 -2
- package/src/index.ts +325 -452
- 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/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +397 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/UplinkFetcher/index.ts +130 -0
- package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +68 -17
- package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
- package/src/supergraphManagers/index.ts +5 -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 +0 -13
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
- package/dist/outOfBandReporter.d.ts +0 -15
- package/dist/outOfBandReporter.d.ts.map +0 -1
- package/dist/outOfBandReporter.js +0 -88
- package/dist/outOfBandReporter.js.map +0 -1
- package/src/__tests__/gateway/composedSdl.test.ts +0 -44
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -694
- package/src/outOfBandReporter.ts +0 -128
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 { loadSupergraphSdlFromStorage } 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,9 +161,6 @@ 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;
|
|
@@ -189,22 +172,12 @@ export class ApolloGateway implements GraphQLService {
|
|
|
189
172
|
// The information made available here will give insight into the resulting
|
|
190
173
|
// query plan and the inputs that generated it.
|
|
191
174
|
private experimental_didResolveQueryPlan?: Experimental_DidResolveQueryPlanCallback;
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
private
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
private experimental_didUpdateComposition?: Experimental_DidUpdateCompositionCallback;
|
|
199
|
-
// Used for overriding the default service list fetcher. This should return
|
|
200
|
-
// an array of ServiceDefinition. *This function must be awaited.*
|
|
201
|
-
private updateServiceDefinitions: Experimental_UpdateComposition;
|
|
202
|
-
// how often service defs should be loaded/updated (in ms)
|
|
203
|
-
private experimental_pollInterval?: number;
|
|
204
|
-
// Configure the endpoint by which gateway will access its precomposed schema.
|
|
205
|
-
// * `string` means use that endpoint
|
|
206
|
-
// * `undefined` means the gateway is not using managed federation
|
|
207
|
-
private schemaConfigDeliveryEndpoint?: string;
|
|
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>)[] = [];
|
|
208
181
|
|
|
209
182
|
constructor(config?: GatewayConfig) {
|
|
210
183
|
this.config = {
|
|
@@ -224,45 +197,15 @@ export class ApolloGateway implements GraphQLService {
|
|
|
224
197
|
// set up experimental observability callbacks and config settings
|
|
225
198
|
this.experimental_didResolveQueryPlan =
|
|
226
199
|
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 URL
|
|
237
|
-
if (isManagedConfig(this.config)) {
|
|
238
|
-
const envEndpoint = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
|
|
239
|
-
this.schemaConfigDeliveryEndpoint =
|
|
240
|
-
this.config.schemaConfigDeliveryEndpoint ??
|
|
241
|
-
envEndpoint ??
|
|
242
|
-
'https://uplink.api.apollographql.com/';
|
|
243
|
-
}
|
|
200
|
+
this.experimental_didUpdateSupergraph =
|
|
201
|
+
config?.experimental_didUpdateSupergraph;
|
|
244
202
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if ('experimental_updateSupergraphSdl' in this.config) {
|
|
248
|
-
this.updateServiceDefinitions =
|
|
249
|
-
this.config.experimental_updateSupergraphSdl;
|
|
250
|
-
} else if ('experimental_updateServiceDefinitions' in this.config) {
|
|
251
|
-
this.updateServiceDefinitions =
|
|
252
|
-
this.config.experimental_updateServiceDefinitions;
|
|
253
|
-
} else {
|
|
254
|
-
throw Error(
|
|
255
|
-
'Programming error: unexpected manual configuration provided',
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
} else {
|
|
259
|
-
this.updateServiceDefinitions = this.loadServiceDefinitions;
|
|
260
|
-
}
|
|
203
|
+
this.pollIntervalInMs =
|
|
204
|
+
config?.pollIntervalInMs ?? config?.experimental_pollInterval;
|
|
261
205
|
|
|
262
|
-
|
|
263
|
-
this.issueDynamicWarningsIfApplicable();
|
|
264
|
-
}
|
|
206
|
+
this.issueConfigurationWarningsIfApplicable();
|
|
265
207
|
|
|
208
|
+
this.logger.debug('Gateway successfully initialized (but not yet loaded)');
|
|
266
209
|
this.state = { phase: 'initialized' };
|
|
267
210
|
}
|
|
268
211
|
|
|
@@ -297,25 +240,26 @@ export class ApolloGateway implements GraphQLService {
|
|
|
297
240
|
});
|
|
298
241
|
}
|
|
299
242
|
|
|
300
|
-
private
|
|
243
|
+
private issueConfigurationWarningsIfApplicable() {
|
|
301
244
|
// Warn against a pollInterval of < 10s in managed mode and reset it to 10s
|
|
302
245
|
if (
|
|
303
246
|
isManagedConfig(this.config) &&
|
|
304
|
-
this.
|
|
305
|
-
this.
|
|
247
|
+
this.pollIntervalInMs &&
|
|
248
|
+
this.pollIntervalInMs < 10000
|
|
306
249
|
) {
|
|
307
|
-
this.
|
|
250
|
+
this.pollIntervalInMs = 10000;
|
|
308
251
|
this.logger.warn(
|
|
309
252
|
'Polling Apollo services at a frequency of less than once per 10 ' +
|
|
310
253
|
'seconds (10000) is disallowed. Instead, the minimum allowed ' +
|
|
311
254
|
'pollInterval of 10000 will be used. Please reconfigure your ' +
|
|
312
|
-
'
|
|
255
|
+
'`pollIntervalInMs` accordingly. If this is problematic for ' +
|
|
313
256
|
'your team, please contact support.',
|
|
314
257
|
);
|
|
315
258
|
}
|
|
316
259
|
|
|
317
260
|
// Warn against using the pollInterval and a serviceList simultaneously
|
|
318
|
-
|
|
261
|
+
// TODO(trevor:removeServiceList)
|
|
262
|
+
if (this.pollIntervalInMs && isServiceListConfig(this.config)) {
|
|
319
263
|
this.logger.warn(
|
|
320
264
|
'Polling running services is dangerous and not recommended in production. ' +
|
|
321
265
|
'Polling should only be used against a registry. ' +
|
|
@@ -335,12 +279,26 @@ export class ApolloGateway implements GraphQLService {
|
|
|
335
279
|
'are provided.',
|
|
336
280
|
);
|
|
337
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
|
+
}
|
|
338
294
|
}
|
|
339
295
|
|
|
340
296
|
public async load(options?: {
|
|
341
297
|
apollo?: ApolloConfigFromAS2Or3;
|
|
342
298
|
engine?: GraphQLServiceEngineConfig;
|
|
343
299
|
}) {
|
|
300
|
+
this.logger.debug('Loading gateway...');
|
|
301
|
+
|
|
344
302
|
if (this.state.phase !== 'initialized') {
|
|
345
303
|
throw Error(
|
|
346
304
|
`ApolloGateway.load called in surprising state ${this.state.phase}`,
|
|
@@ -366,40 +324,92 @@ export class ApolloGateway implements GraphQLService {
|
|
|
366
324
|
};
|
|
367
325
|
}
|
|
368
326
|
|
|
369
|
-
// Before @apollo/gateway v0.23, ApolloGateway didn't expect stop() to be
|
|
370
|
-
// called after it started. The only thing that stop() did at that point was
|
|
371
|
-
// cancel the poll timer, and so to prevent that timer from keeping an
|
|
372
|
-
// otherwise-finished Node process alive, ApolloGateway unconditionally
|
|
373
|
-
// called unref() on that timeout. As part of making the ApolloGateway
|
|
374
|
-
// lifecycle more predictable and concrete (and to allow for a future where
|
|
375
|
-
// there are other reasons to make sure to explicitly stop your gateway),
|
|
376
|
-
// v0.23 tries to avoid calling unref().
|
|
377
|
-
//
|
|
378
|
-
// Apollo Server v2.20 and newer calls gateway.stop() from its stop()
|
|
379
|
-
// method, so as long as you're using v2.20, ApolloGateway won't keep
|
|
380
|
-
// running after you stop your server, and your Node process can shut down.
|
|
381
|
-
// To make this change a bit less backwards-incompatible, we detect if it
|
|
382
|
-
// looks like you're using an older version of Apollo Server; if so, we
|
|
383
|
-
// still call unref(). Specifically: Apollo Server has always passed an
|
|
384
|
-
// options object to load(), and before v2.18 it did not pass the `apollo`
|
|
385
|
-
// key on it. So if we detect that particular pattern, we assume we're with
|
|
386
|
-
// pre-v2.18 Apollo Server and we still call unref(). So this will be a
|
|
387
|
-
// behavior change only for:
|
|
388
|
-
// - non-Apollo-Server uses of ApolloGateway (where you can add your own
|
|
389
|
-
// call to gateway.stop())
|
|
390
|
-
// - Apollo Server v2.18 and v2.19 (where you can either do the small
|
|
391
|
-
// compatible upgrade or add your own call to gateway.stop())
|
|
392
|
-
// - if you don't call stop() on your ApolloServer (but in that case other
|
|
393
|
-
// things like usage reporting will also stop shutdown, so you should fix
|
|
394
|
-
// that)
|
|
395
|
-
const unrefTimer = !!options && !options.apollo;
|
|
396
|
-
|
|
397
327
|
this.maybeWarnOnConflictingConfig();
|
|
398
328
|
|
|
399
329
|
// Handles initial assignment of `this.schema`, `this.queryPlanner`
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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 - 1, // -1 for the initial request
|
|
406
|
+
subgraphHealthCheck: this.config.serviceHealthCheck,
|
|
407
|
+
fetcher: this.fetcher,
|
|
408
|
+
logger: this.logger,
|
|
409
|
+
pollIntervalInMs: this.pollIntervalInMs ?? 10000,
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
403
413
|
|
|
404
414
|
const mode = isManagedConfig(this.config) ? 'managed' : 'unmanaged';
|
|
405
415
|
this.logger.info(
|
|
@@ -416,122 +426,148 @@ export class ApolloGateway implements GraphQLService {
|
|
|
416
426
|
};
|
|
417
427
|
}
|
|
418
428
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private getIdForSupergraphSdl(supergraphSdl: string) {
|
|
453
|
+
return createHash('sha256').update(supergraphSdl).digest('hex');
|
|
435
454
|
}
|
|
436
455
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
456
|
+
private async initializeSupergraphManager<T extends SupergraphManager>(
|
|
457
|
+
supergraphManager: T,
|
|
458
|
+
) {
|
|
440
459
|
try {
|
|
441
|
-
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);
|
|
442
481
|
} catch (e) {
|
|
443
482
|
this.state = { phase: 'failed to load' };
|
|
483
|
+
await this.performCleanupAndLogErrors();
|
|
444
484
|
throw e;
|
|
445
485
|
}
|
|
446
486
|
|
|
447
487
|
this.state = { phase: 'loaded' };
|
|
448
|
-
if (this.shouldBeginPolling()) {
|
|
449
|
-
this.pollServices(unrefTimer);
|
|
450
|
-
}
|
|
451
488
|
}
|
|
452
489
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
+
}
|
|
456
522
|
|
|
457
|
-
|
|
458
|
-
|
|
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
|
+
}
|
|
459
534
|
|
|
460
|
-
|
|
461
|
-
|
|
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);
|
|
462
550
|
|
|
463
|
-
|
|
464
|
-
await this.
|
|
465
|
-
}
|
|
466
|
-
await this.updateByComposition(result);
|
|
467
|
-
} else {
|
|
551
|
+
try {
|
|
552
|
+
await this.serviceHealthCheck(serviceMap);
|
|
553
|
+
} catch (e) {
|
|
468
554
|
throw new Error(
|
|
469
|
-
'
|
|
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,
|
|
470
559
|
);
|
|
471
560
|
}
|
|
472
561
|
}
|
|
473
562
|
|
|
474
|
-
private
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
JSON.stringify(this.serviceDefinitions) ===
|
|
480
|
-
JSON.stringify(result.serviceDefinitions)
|
|
481
|
-
) {
|
|
482
|
-
this.logger.debug('No change in service definitions since last check.');
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const previousSchema = this.schema;
|
|
487
|
-
const previousServiceDefinitions = this.serviceDefinitions;
|
|
488
|
-
const previousCompositionMetadata = this.compositionMetadata;
|
|
489
|
-
|
|
490
|
-
if (previousSchema) {
|
|
491
|
-
this.logger.info('New service definitions were found.');
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
await this.maybePerformServiceHealthCheck(result);
|
|
495
|
-
|
|
496
|
-
this.compositionMetadata = result.compositionMetadata;
|
|
497
|
-
this.serviceDefinitions = result.serviceDefinitions;
|
|
498
|
-
|
|
499
|
-
const { schema, supergraphSdl } = this.createSchemaFromServiceList(
|
|
500
|
-
result.serviceDefinitions,
|
|
501
|
-
);
|
|
502
|
-
|
|
503
|
-
if (!supergraphSdl) {
|
|
504
|
-
this.logger.error(
|
|
505
|
-
"A valid schema couldn't be composed. Falling back to previous schema.",
|
|
506
|
-
);
|
|
507
|
-
} else {
|
|
508
|
-
this.updateWithSchemaAndNotify(schema, supergraphSdl);
|
|
509
|
-
|
|
510
|
-
if (this.experimental_didUpdateComposition) {
|
|
511
|
-
this.experimental_didUpdateComposition(
|
|
512
|
-
{
|
|
513
|
-
serviceDefinitions: result.serviceDefinitions,
|
|
514
|
-
schema: schema.toGraphQLJSSchema(),
|
|
515
|
-
...(this.compositionMetadata && {
|
|
516
|
-
compositionMetadata: this.compositionMetadata,
|
|
517
|
-
}),
|
|
518
|
-
},
|
|
519
|
-
previousServiceDefinitions &&
|
|
520
|
-
previousSchema && {
|
|
521
|
-
serviceDefinitions: previousServiceDefinitions,
|
|
522
|
-
schema: previousSchema,
|
|
523
|
-
...(previousCompositionMetadata && {
|
|
524
|
-
compositionMetadata: previousCompositionMetadata,
|
|
525
|
-
}),
|
|
526
|
-
},
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
563
|
+
private externalGetDataSourceCallback({
|
|
564
|
+
name,
|
|
565
|
+
url,
|
|
566
|
+
}: ServiceEndpointDefinition) {
|
|
567
|
+
return this.getOrCreateDataSource({ name, url });
|
|
530
568
|
}
|
|
531
569
|
|
|
532
|
-
private
|
|
533
|
-
result: SupergraphSdlUpdate,
|
|
534
|
-
): Promise<void> {
|
|
570
|
+
private updateWithSupergraphSdl(result: SupergraphSdlUpdate) {
|
|
535
571
|
if (result.id === this.compositionId) {
|
|
536
572
|
this.logger.debug('No change in composition since last check.');
|
|
537
573
|
return;
|
|
@@ -541,7 +577,9 @@ export class ApolloGateway implements GraphQLService {
|
|
|
541
577
|
// In the case that it throws, the gateway will:
|
|
542
578
|
// * on initial load, throw the error
|
|
543
579
|
// * on update, log the error and don't update
|
|
544
|
-
const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
|
|
580
|
+
const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
|
|
581
|
+
result.supergraphSdl,
|
|
582
|
+
);
|
|
545
583
|
|
|
546
584
|
const previousSchema = this.schema;
|
|
547
585
|
const previousSupergraphSdl = this.supergraphSdl;
|
|
@@ -551,11 +589,8 @@ export class ApolloGateway implements GraphQLService {
|
|
|
551
589
|
this.logger.info('Updated Supergraph SDL was found.');
|
|
552
590
|
}
|
|
553
591
|
|
|
554
|
-
await this.maybePerformServiceHealthCheck(result);
|
|
555
|
-
|
|
556
592
|
this.compositionId = result.id;
|
|
557
|
-
this.supergraphSdl =
|
|
558
|
-
|
|
593
|
+
this.supergraphSdl = supergraphSdl;
|
|
559
594
|
|
|
560
595
|
if (!supergraphSdl) {
|
|
561
596
|
this.logger.error(
|
|
@@ -564,11 +599,11 @@ export class ApolloGateway implements GraphQLService {
|
|
|
564
599
|
} else {
|
|
565
600
|
this.updateWithSchemaAndNotify(schema, supergraphSdl);
|
|
566
601
|
|
|
567
|
-
if (this.
|
|
568
|
-
this.
|
|
602
|
+
if (this.experimental_didUpdateSupergraph) {
|
|
603
|
+
this.experimental_didUpdateSupergraph(
|
|
569
604
|
{
|
|
570
605
|
compositionId: result.id,
|
|
571
|
-
supergraphSdl
|
|
606
|
+
supergraphSdl,
|
|
572
607
|
schema: schema.toGraphQLJSSchema(),
|
|
573
608
|
},
|
|
574
609
|
previousCompositionId && previousSupergraphSdl && previousSchema
|
|
@@ -594,7 +629,9 @@ export class ApolloGateway implements GraphQLService {
|
|
|
594
629
|
): void {
|
|
595
630
|
if (this.queryPlanStore) this.queryPlanStore.flush();
|
|
596
631
|
this.apiSchema = coreSchema.toAPISchema();
|
|
597
|
-
this.schema = wrapSchemaWithAliasResolver(
|
|
632
|
+
this.schema = wrapSchemaWithAliasResolver(
|
|
633
|
+
this.apiSchema.toGraphQLJSSchema(),
|
|
634
|
+
);
|
|
598
635
|
this.queryPlanner = new QueryPlanner(coreSchema);
|
|
599
636
|
|
|
600
637
|
// Notify onSchemaChange listeners of the updated schema
|
|
@@ -629,39 +666,6 @@ export class ApolloGateway implements GraphQLService {
|
|
|
629
666
|
});
|
|
630
667
|
}
|
|
631
668
|
|
|
632
|
-
private async maybePerformServiceHealthCheck(update: CompositionUpdate) {
|
|
633
|
-
// Run service health checks before we commit and update the new schema.
|
|
634
|
-
// This is the last chance to bail out of a schema update.
|
|
635
|
-
if (this.config.serviceHealthCheck) {
|
|
636
|
-
const serviceList = isSupergraphSdlUpdate(update)
|
|
637
|
-
? // Parsing of the supergraph SDL could technically fail and throw here, but parseability has
|
|
638
|
-
// already been confirmed slightly earlier in the code path
|
|
639
|
-
this.serviceListFromSupergraphSdl(update.supergraphSdl)
|
|
640
|
-
: // Existence of this is determined in advance with an early return otherwise
|
|
641
|
-
update.serviceDefinitions!;
|
|
642
|
-
// Here we need to construct new datasources based on the new schema info
|
|
643
|
-
// so we can check the health of the services we're _updating to_.
|
|
644
|
-
const serviceMap = serviceList.reduce((serviceMap, serviceDef) => {
|
|
645
|
-
serviceMap[serviceDef.name] = {
|
|
646
|
-
url: serviceDef.url,
|
|
647
|
-
dataSource: this.createDataSource(serviceDef),
|
|
648
|
-
};
|
|
649
|
-
return serviceMap;
|
|
650
|
-
}, Object.create(null) as DataSourceMap);
|
|
651
|
-
|
|
652
|
-
try {
|
|
653
|
-
await this.serviceHealthCheck(serviceMap);
|
|
654
|
-
} catch (e) {
|
|
655
|
-
throw new Error(
|
|
656
|
-
'The gateway did not update its schema due to failed service health checks. ' +
|
|
657
|
-
'The gateway will continue to operate with the previous schema and reattempt updates. ' +
|
|
658
|
-
'The following error occurred during the health check:\n' +
|
|
659
|
-
e.message,
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
669
|
/**
|
|
666
670
|
* This can be used without an argument in order to perform an ad-hoc health check
|
|
667
671
|
* of the downstream services like so:
|
|
@@ -694,40 +698,9 @@ export class ApolloGateway implements GraphQLService {
|
|
|
694
698
|
);
|
|
695
699
|
}
|
|
696
700
|
|
|
697
|
-
private
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
.map(({ name, url }) => ` ${url || 'local'}: ${name}`)
|
|
701
|
-
.join('\n')}`,
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
const compositionResult = composeServices(serviceList);
|
|
705
|
-
const errors = compositionResult.errors;
|
|
706
|
-
if (errors) {
|
|
707
|
-
if (this.experimental_didFailComposition) {
|
|
708
|
-
this.experimental_didFailComposition({
|
|
709
|
-
errors,
|
|
710
|
-
serviceList,
|
|
711
|
-
...(this.compositionMetadata && {
|
|
712
|
-
compositionMetadata: this.compositionMetadata,
|
|
713
|
-
}),
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
throw Error(
|
|
717
|
-
"A valid schema couldn't be composed. The following composition errors were found:\n" +
|
|
718
|
-
errors.map((e) => '\t' + e.message).join('\n'),
|
|
719
|
-
);
|
|
720
|
-
} else {
|
|
721
|
-
this.createServices(serviceList);
|
|
722
|
-
this.logger.debug('Schema loaded and ready for execution');
|
|
723
|
-
return {
|
|
724
|
-
schema: compositionResult.schema,
|
|
725
|
-
supergraphSdl: compositionResult.supergraphSdl,
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
private serviceListFromSupergraphSdl(supergraphSdl: string): Omit<ServiceDefinition, 'typeDefs'>[] {
|
|
701
|
+
private serviceListFromSupergraphSdl(
|
|
702
|
+
supergraphSdl: string,
|
|
703
|
+
): Omit<ServiceDefinition, 'typeDefs'>[] {
|
|
731
704
|
return buildSupergraphSchema(supergraphSdl)[1];
|
|
732
705
|
}
|
|
733
706
|
|
|
@@ -767,93 +740,16 @@ export class ApolloGateway implements GraphQLService {
|
|
|
767
740
|
};
|
|
768
741
|
}
|
|
769
742
|
|
|
770
|
-
|
|
771
|
-
// again. Note that it is an async function whose Promise is not actually awaited;
|
|
772
|
-
// it should never throw itself other than due to a bug in its state machine.
|
|
773
|
-
private async pollServices(unrefTimer: boolean) {
|
|
774
|
-
switch (this.state.phase) {
|
|
775
|
-
case 'stopping':
|
|
776
|
-
case 'stopped':
|
|
777
|
-
case 'failed to load':
|
|
778
|
-
return;
|
|
779
|
-
case 'initialized':
|
|
780
|
-
throw Error('pollServices should not be called before load!');
|
|
781
|
-
case 'polling':
|
|
782
|
-
throw Error(
|
|
783
|
-
'pollServices should not be called while in the middle of polling!',
|
|
784
|
-
);
|
|
785
|
-
case 'waiting to poll':
|
|
786
|
-
throw Error(
|
|
787
|
-
'pollServices should not be called while already waiting to poll!',
|
|
788
|
-
);
|
|
789
|
-
case 'loaded':
|
|
790
|
-
// This is the normal case.
|
|
791
|
-
break;
|
|
792
|
-
default:
|
|
793
|
-
throw new UnreachableCaseError(this.state);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Transition into 'waiting to poll' and set a timer. The timer resolves the
|
|
797
|
-
// Promise we're awaiting here; note that calling stop() also can resolve
|
|
798
|
-
// that Promise.
|
|
799
|
-
await new Promise<void>((doneWaiting) => {
|
|
800
|
-
this.state = {
|
|
801
|
-
phase: 'waiting to poll',
|
|
802
|
-
doneWaiting,
|
|
803
|
-
pollWaitTimer: setTimeout(() => {
|
|
804
|
-
// Note that we might be in 'stopped', in which case we just do
|
|
805
|
-
// nothing.
|
|
806
|
-
if (this.state.phase == 'waiting to poll') {
|
|
807
|
-
this.state.doneWaiting();
|
|
808
|
-
}
|
|
809
|
-
}, this.experimental_pollInterval || 10000),
|
|
810
|
-
};
|
|
811
|
-
if (unrefTimer) {
|
|
812
|
-
this.state.pollWaitTimer.unref();
|
|
813
|
-
}
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
// If we've been stopped, then we're done. The cast here is because TS
|
|
817
|
-
// doesn't understand that this.state can change during the await
|
|
818
|
-
// (https://github.com/microsoft/TypeScript/issues/9998).
|
|
819
|
-
if ((this.state as GatewayState).phase !== 'waiting to poll') {
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
let pollingDone: () => void;
|
|
824
|
-
this.state = {
|
|
825
|
-
phase: 'polling',
|
|
826
|
-
pollingDonePromise: new Promise<void>((res) => {
|
|
827
|
-
pollingDone = res;
|
|
828
|
-
}),
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
try {
|
|
832
|
-
await this.updateSchema();
|
|
833
|
-
} catch (err) {
|
|
834
|
-
this.logger.error((err && err.message) || err);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (this.state.phase === 'polling') {
|
|
838
|
-
// If we weren't stopped, we should transition back to the initial 'loading' state and trigger
|
|
839
|
-
// another call to itself. (Do that in a setImmediate to avoid unbounded stack sizes.)
|
|
840
|
-
this.state = { phase: 'loaded' };
|
|
841
|
-
setImmediate(() => this.pollServices(unrefTimer));
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Whether we were stopped or not, let any concurrent stop() call finish.
|
|
845
|
-
pollingDone!();
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
private createAndCacheDataSource(
|
|
743
|
+
private getOrCreateDataSource(
|
|
849
744
|
serviceDef: ServiceEndpointDefinition,
|
|
850
745
|
): GraphQLDataSource {
|
|
851
746
|
// If the DataSource has already been created, early return
|
|
852
747
|
if (
|
|
853
748
|
this.serviceMap[serviceDef.name] &&
|
|
854
749
|
serviceDef.url === this.serviceMap[serviceDef.name].url
|
|
855
|
-
)
|
|
750
|
+
) {
|
|
856
751
|
return this.serviceMap[serviceDef.name].dataSource;
|
|
752
|
+
}
|
|
857
753
|
|
|
858
754
|
const dataSource = this.createDataSource(serviceDef);
|
|
859
755
|
|
|
@@ -881,55 +777,8 @@ export class ApolloGateway implements GraphQLService {
|
|
|
881
777
|
|
|
882
778
|
private createServices(services: ServiceEndpointDefinition[]) {
|
|
883
779
|
for (const serviceDef of services) {
|
|
884
|
-
this.
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
protected async loadServiceDefinitions(
|
|
889
|
-
config: RemoteGatewayConfig | ManagedGatewayConfig,
|
|
890
|
-
): Promise<CompositionUpdate> {
|
|
891
|
-
if (isRemoteConfig(config)) {
|
|
892
|
-
const serviceList = config.serviceList.map((serviceDefinition) => ({
|
|
893
|
-
...serviceDefinition,
|
|
894
|
-
dataSource: this.createAndCacheDataSource(serviceDefinition),
|
|
895
|
-
}));
|
|
896
|
-
|
|
897
|
-
return getServiceDefinitionsFromRemoteEndpoint({
|
|
898
|
-
serviceList,
|
|
899
|
-
async getServiceIntrospectionHeaders(service) {
|
|
900
|
-
return typeof config.introspectionHeaders === 'function'
|
|
901
|
-
? await config.introspectionHeaders(service)
|
|
902
|
-
: config.introspectionHeaders;
|
|
903
|
-
},
|
|
904
|
-
serviceSdlCache: this.serviceSdlCache,
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
const canUseManagedConfig =
|
|
909
|
-
this.apolloConfig?.graphRef && this.apolloConfig?.keyHash;
|
|
910
|
-
if (!canUseManagedConfig) {
|
|
911
|
-
throw new Error(
|
|
912
|
-
'When a manual configuration is not provided, gateway requires an Apollo ' +
|
|
913
|
-
'configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ ' +
|
|
914
|
-
'for more information. Manual configuration options include: ' +
|
|
915
|
-
'`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
|
|
916
|
-
);
|
|
780
|
+
this.getOrCreateDataSource(serviceDef);
|
|
917
781
|
}
|
|
918
|
-
|
|
919
|
-
const result = await loadSupergraphSdlFromStorage({
|
|
920
|
-
graphRef: this.apolloConfig!.graphRef!,
|
|
921
|
-
apiKey: this.apolloConfig!.key!,
|
|
922
|
-
endpoint: this.schemaConfigDeliveryEndpoint!,
|
|
923
|
-
fetcher: this.fetcher,
|
|
924
|
-
compositionId: this.compositionId ?? null,
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
return (
|
|
928
|
-
result ?? {
|
|
929
|
-
id: this.compositionId!,
|
|
930
|
-
supergraphSdl: this.supergraphSdl!,
|
|
931
|
-
}
|
|
932
|
-
);
|
|
933
782
|
}
|
|
934
783
|
|
|
935
784
|
private maybeWarnOnConflictingConfig() {
|
|
@@ -1004,7 +853,11 @@ export class ApolloGateway implements GraphQLService {
|
|
|
1004
853
|
OpenTelemetrySpanNames.PLAN,
|
|
1005
854
|
(span) => {
|
|
1006
855
|
try {
|
|
1007
|
-
const operation = operationFromDocument(
|
|
856
|
+
const operation = operationFromDocument(
|
|
857
|
+
this.apiSchema!,
|
|
858
|
+
document,
|
|
859
|
+
request.operationName,
|
|
860
|
+
);
|
|
1008
861
|
// TODO(#631): Can we be sure the query planner has been initialized here?
|
|
1009
862
|
return this.queryPlanner!.buildQueryPlan(operation);
|
|
1010
863
|
} catch (err) {
|
|
@@ -1144,9 +997,24 @@ export class ApolloGateway implements GraphQLService {
|
|
|
1144
997
|
});
|
|
1145
998
|
}
|
|
1146
999
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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.
|
|
1150
1018
|
public async stop() {
|
|
1151
1019
|
switch (this.state.phase) {
|
|
1152
1020
|
case 'initialized':
|
|
@@ -1169,40 +1037,31 @@ export class ApolloGateway implements GraphQLService {
|
|
|
1169
1037
|
}
|
|
1170
1038
|
return;
|
|
1171
1039
|
case 'loaded':
|
|
1172
|
-
|
|
1173
|
-
return;
|
|
1174
|
-
case 'waiting to poll': {
|
|
1175
|
-
// If we're waiting to poll, we can synchronously transition to fully stopped.
|
|
1176
|
-
// We will terminate the current pollServices call and it will succeed quickly.
|
|
1177
|
-
const doneWaiting = this.state.doneWaiting;
|
|
1178
|
-
clearTimeout(this.state.pollWaitTimer);
|
|
1179
|
-
this.state = { phase: 'stopped' };
|
|
1180
|
-
doneWaiting();
|
|
1181
|
-
return;
|
|
1182
|
-
}
|
|
1183
|
-
case 'polling': {
|
|
1184
|
-
// We're in the middle of running updateSchema. We need to go into 'stopping'
|
|
1185
|
-
// mode and let this run complete. First we set things up so that any concurrent
|
|
1186
|
-
// calls to stop() will wait until we let them finish. (Those concurrent calls shouldn't
|
|
1187
|
-
// just wait on pollingDonePromise themselves because we want to make sure we fully
|
|
1188
|
-
// transition to state='stopped' before the other call returns.)
|
|
1189
|
-
const pollingDonePromise = this.state.pollingDonePromise;
|
|
1190
|
-
let stoppingDone: () => void;
|
|
1040
|
+
const stoppingDonePromise = this.performCleanupAndLogErrors();
|
|
1191
1041
|
this.state = {
|
|
1192
1042
|
phase: 'stopping',
|
|
1193
|
-
stoppingDonePromise
|
|
1194
|
-
stoppingDone = res;
|
|
1195
|
-
}),
|
|
1043
|
+
stoppingDonePromise,
|
|
1196
1044
|
};
|
|
1197
|
-
await
|
|
1045
|
+
await stoppingDonePromise;
|
|
1198
1046
|
this.state = { phase: 'stopped' };
|
|
1199
|
-
stoppingDone!();
|
|
1200
1047
|
return;
|
|
1048
|
+
case 'updating schema': {
|
|
1049
|
+
throw Error(
|
|
1050
|
+
"`ApolloGateway.stop` shouldn't be called from inside a schema change listener",
|
|
1051
|
+
);
|
|
1201
1052
|
}
|
|
1202
1053
|
default:
|
|
1203
1054
|
throw new UnreachableCaseError(this.state);
|
|
1204
1055
|
}
|
|
1205
1056
|
}
|
|
1057
|
+
|
|
1058
|
+
public __testing() {
|
|
1059
|
+
return {
|
|
1060
|
+
state: this.state,
|
|
1061
|
+
compositionId: this.compositionId,
|
|
1062
|
+
supergraphSdl: this.supergraphSdl,
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1206
1065
|
}
|
|
1207
1066
|
|
|
1208
1067
|
ApolloGateway.prototype.onSchemaChange = deprecate(
|
|
@@ -1249,11 +1108,25 @@ export {
|
|
|
1249
1108
|
ServiceMap,
|
|
1250
1109
|
Experimental_DidFailCompositionCallback,
|
|
1251
1110
|
Experimental_DidResolveQueryPlanCallback,
|
|
1252
|
-
|
|
1111
|
+
Experimental_DidUpdateSupergraphCallback,
|
|
1253
1112
|
Experimental_UpdateComposition,
|
|
1254
1113
|
GatewayConfig,
|
|
1255
1114
|
ServiceEndpointDefinition,
|
|
1115
|
+
ServiceDefinition,
|
|
1256
1116
|
CompositionInfo,
|
|
1117
|
+
IntrospectAndCompose,
|
|
1118
|
+
LocalCompose,
|
|
1257
1119
|
};
|
|
1258
1120
|
|
|
1259
1121
|
export * from './datasources';
|
|
1122
|
+
|
|
1123
|
+
export {
|
|
1124
|
+
SupergraphSdlUpdateFunction,
|
|
1125
|
+
SubgraphHealthCheckFunction,
|
|
1126
|
+
GetDataSourceFunction,
|
|
1127
|
+
SupergraphSdlHook,
|
|
1128
|
+
SupergraphManager
|
|
1129
|
+
} from './config';
|
|
1130
|
+
|
|
1131
|
+
export { UplinkFetcherError } from "./supergraphManagers"
|
|
1132
|
+
|