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