@apollo/gateway 0.50.2 → 0.52.1
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 +4 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.d.ts +2 -2
- package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.js +0 -2
- package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts +11 -10
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +28 -28
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/datasources/types.d.ts +5 -5
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/executeQueryPlan.d.ts +3 -3
- 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 +7 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -33
- package/dist/index.js.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts +2 -2
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +3 -3
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js +14 -13
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +6 -5
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +3 -3
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -1
- package/package.json +10 -12
- package/src/__generated__/graphqlTypes.ts +1 -1
- package/src/__tests__/executeQueryPlan.test.ts +14 -5
- package/src/__tests__/execution-utils.ts +3 -3
- package/src/__tests__/gateway/buildService.test.ts +81 -83
- package/src/__tests__/gateway/executor.test.ts +20 -17
- package/src/__tests__/gateway/opentelemetry.test.ts +3 -7
- package/src/__tests__/gateway/supergraphSdl.test.ts +10 -12
- package/src/config.ts +4 -6
- package/src/datasources/LocalGraphQLDataSource.ts +2 -4
- package/src/datasources/RemoteGraphQLDataSource.ts +72 -59
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +2 -2
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +120 -159
- package/src/datasources/types.ts +5 -5
- package/src/executeQueryPlan.ts +18 -18
- package/src/index.ts +24 -70
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -6
- package/src/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts +2 -2
- package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +70 -74
- package/src/supergraphManagers/UplinkFetcher/index.ts +2 -2
- package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +23 -17
- package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +9 -7
- package/dist/cache.d.ts +0 -18
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js +0 -46
- package/dist/cache.js.map +0 -1
- package/src/__mocks__/apollo-server-env.ts +0 -56
- package/src/__mocks__/make-fetch-happen-fetcher.ts +0 -57
- package/src/cache.ts +0 -66
- package/src/make-fetch-happen.d.ts +0 -59
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
GraphQLRequestContext,
|
|
3
|
-
GraphQLResponse,
|
|
4
|
-
ValueOrPromise,
|
|
5
|
-
GraphQLRequest,
|
|
6
|
-
CacheHint,
|
|
7
|
-
CacheScope,
|
|
8
|
-
CachePolicy,
|
|
9
|
-
} from 'apollo-server-types';
|
|
10
|
-
import {
|
|
11
|
-
ApolloError,
|
|
12
|
-
AuthenticationError,
|
|
13
|
-
ForbiddenError,
|
|
14
|
-
} from 'apollo-server-errors';
|
|
15
|
-
import { fetch, Request, Headers, Response } from 'apollo-server-env';
|
|
16
1
|
import { isObject } from '../utilities/predicates';
|
|
17
2
|
import { GraphQLDataSource, GraphQLDataSourceProcessOptions, GraphQLDataSourceRequestKind } from './types';
|
|
18
3
|
import { createHash } from '@apollo/utils.createhash';
|
|
19
4
|
import { parseCacheControlHeader } from './parseCacheControlHeader';
|
|
20
5
|
import fetcher from 'make-fetch-happen';
|
|
6
|
+
import { Headers as NodeFetchHeaders, Request as NodeFetchRequest } from 'node-fetch';
|
|
7
|
+
import { Fetcher, FetcherRequestInit, FetcherResponse } from '@apollo/utils.fetcher';
|
|
8
|
+
import { GraphQLError, GraphQLErrorExtensions } from 'graphql';
|
|
9
|
+
import { GatewayCacheHint, GatewayCachePolicy, GatewayGraphQLRequest, GatewayGraphQLRequestContext, GatewayGraphQLResponse } from '@apollo/server-gateway-interface';
|
|
10
|
+
|
|
21
11
|
export class RemoteGraphQLDataSource<
|
|
22
12
|
TContext extends Record<string, any> = Record<string, any>,
|
|
23
13
|
> implements GraphQLDataSource<TContext>
|
|
24
14
|
{
|
|
25
|
-
fetcher:
|
|
15
|
+
fetcher: Fetcher;
|
|
26
16
|
|
|
27
17
|
constructor(
|
|
28
18
|
config?: Partial<RemoteGraphQLDataSource<TContext>> &
|
|
@@ -30,6 +20,11 @@ export class RemoteGraphQLDataSource<
|
|
|
30
20
|
ThisType<RemoteGraphQLDataSource<TContext>>,
|
|
31
21
|
) {
|
|
32
22
|
this.fetcher = fetcher.defaults({
|
|
23
|
+
// Allow an arbitrary number of sockets per subgraph. This is the default
|
|
24
|
+
// behavior of Node's http.Agent as well as the npm package agentkeepalive
|
|
25
|
+
// which wraps it, but is not the default behavior of make-fetch-happen
|
|
26
|
+
// which wraps agentkeepalive (that package sets this to 15 by default).
|
|
27
|
+
maxSockets: Infinity,
|
|
33
28
|
// although this is the default, we want to take extra care and be very
|
|
34
29
|
// explicity to ensure that mutations cannot be retried. please leave this
|
|
35
30
|
// intact.
|
|
@@ -70,7 +65,7 @@ export class RemoteGraphQLDataSource<
|
|
|
70
65
|
|
|
71
66
|
async process(
|
|
72
67
|
options: GraphQLDataSourceProcessOptions<TContext>,
|
|
73
|
-
): Promise<
|
|
68
|
+
): Promise<GatewayGraphQLResponse> {
|
|
74
69
|
const { request, context: originalContext } = options;
|
|
75
70
|
// Deal with a bit of a hairy situation in typings: when doing health checks
|
|
76
71
|
// and schema checks we always pass in `{}` as the context even though it's
|
|
@@ -82,7 +77,12 @@ export class RemoteGraphQLDataSource<
|
|
|
82
77
|
const context = originalContext as TContext;
|
|
83
78
|
|
|
84
79
|
// Respect incoming http headers (eg, apollo-federation-include-trace).
|
|
85
|
-
const headers =
|
|
80
|
+
const headers = new NodeFetchHeaders();
|
|
81
|
+
if (request.http?.headers) {
|
|
82
|
+
for (const [name, value] of request.http.headers) {
|
|
83
|
+
headers.append(name, value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
86
|
headers.set('Content-Type', 'application/json');
|
|
87
87
|
|
|
88
88
|
request.http = {
|
|
@@ -107,7 +107,8 @@ export class RemoteGraphQLDataSource<
|
|
|
107
107
|
const overallCachePolicy =
|
|
108
108
|
this.honorSubgraphCacheControlHeader &&
|
|
109
109
|
options.kind === GraphQLDataSourceRequestKind.INCOMING_OPERATION &&
|
|
110
|
-
options.incomingRequestContext.overallCachePolicy
|
|
110
|
+
options.incomingRequestContext.overallCachePolicy &&
|
|
111
|
+
'restrict' in options.incomingRequestContext.overallCachePolicy
|
|
111
112
|
? options.incomingRequestContext.overallCachePolicy
|
|
112
113
|
: null;
|
|
113
114
|
|
|
@@ -149,7 +150,7 @@ export class RemoteGraphQLDataSource<
|
|
|
149
150
|
// If APQ was enabled, we'll run the same request again, but add in the
|
|
150
151
|
// previously omitted `query`. If APQ was NOT enabled, this is the first
|
|
151
152
|
// request (non-APQ, all the way).
|
|
152
|
-
const requestWithQuery:
|
|
153
|
+
const requestWithQuery: GatewayGraphQLRequest = {
|
|
153
154
|
query,
|
|
154
155
|
...requestWithoutQuery,
|
|
155
156
|
};
|
|
@@ -163,9 +164,9 @@ export class RemoteGraphQLDataSource<
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
private async sendRequest(
|
|
166
|
-
request:
|
|
167
|
+
request: GatewayGraphQLRequest,
|
|
167
168
|
context: TContext,
|
|
168
|
-
): Promise<
|
|
169
|
+
): Promise<GatewayGraphQLResponse> {
|
|
169
170
|
// This would represent an internal programming error since this shouldn't
|
|
170
171
|
// be possible in the way that this method is invoked right now.
|
|
171
172
|
if (!request.http) {
|
|
@@ -177,20 +178,24 @@ export class RemoteGraphQLDataSource<
|
|
|
177
178
|
// we're accessing (e.g. url) and what we access it with (e.g. headers).
|
|
178
179
|
const { http, ...requestWithoutHttp } = request;
|
|
179
180
|
const stringifiedRequestWithoutHttp = JSON.stringify(requestWithoutHttp);
|
|
180
|
-
const
|
|
181
|
-
|
|
181
|
+
const requestInit: FetcherRequestInit = {
|
|
182
|
+
method: http.method,
|
|
183
|
+
headers: Object.fromEntries(http.headers),
|
|
182
184
|
body: stringifiedRequestWithoutHttp,
|
|
183
|
-
}
|
|
185
|
+
};
|
|
186
|
+
// Note that we don't actually send this Request object to the fetcher; it
|
|
187
|
+
// is merely sent to methods on this object that might be overridden by users.
|
|
188
|
+
// We are careful to only send data to the overridable fetcher function that uses
|
|
189
|
+
// plain JS objects --- some fetch implementations don't know how to handle
|
|
190
|
+
// Request or Headers objects created by other fetch implementations.
|
|
191
|
+
const fetchRequest = new NodeFetchRequest(http.url, requestInit);
|
|
184
192
|
|
|
185
|
-
let fetchResponse:
|
|
193
|
+
let fetchResponse: FetcherResponse | undefined;
|
|
186
194
|
|
|
187
195
|
try {
|
|
188
196
|
// Use our local `fetcher` to allow for fetch injection
|
|
189
197
|
// Use the fetcher's `Request` implementation for compatibility
|
|
190
|
-
fetchResponse = await this.fetcher(http.url,
|
|
191
|
-
...http,
|
|
192
|
-
body: stringifiedRequestWithoutHttp,
|
|
193
|
-
});
|
|
198
|
+
fetchResponse = await this.fetcher(http.url, requestInit);
|
|
194
199
|
|
|
195
200
|
if (!fetchResponse.ok) {
|
|
196
201
|
throw await this.errorFromResponse(fetchResponse);
|
|
@@ -214,7 +219,7 @@ export class RemoteGraphQLDataSource<
|
|
|
214
219
|
|
|
215
220
|
public willSendRequest?(
|
|
216
221
|
options: GraphQLDataSourceProcessOptions<TContext>,
|
|
217
|
-
):
|
|
222
|
+
): void | Promise<void>;
|
|
218
223
|
|
|
219
224
|
private async respond({
|
|
220
225
|
response,
|
|
@@ -222,11 +227,11 @@ export class RemoteGraphQLDataSource<
|
|
|
222
227
|
context,
|
|
223
228
|
overallCachePolicy,
|
|
224
229
|
}: {
|
|
225
|
-
response:
|
|
226
|
-
request:
|
|
230
|
+
response: GatewayGraphQLResponse;
|
|
231
|
+
request: GatewayGraphQLRequest;
|
|
227
232
|
context: TContext;
|
|
228
|
-
overallCachePolicy:
|
|
229
|
-
}): Promise<
|
|
233
|
+
overallCachePolicy: GatewayCachePolicy | null;
|
|
234
|
+
}): Promise<GatewayGraphQLResponse> {
|
|
230
235
|
const processedResponse =
|
|
231
236
|
typeof this.didReceiveResponse === 'function'
|
|
232
237
|
? await this.didReceiveResponse({ response, request, context })
|
|
@@ -241,16 +246,16 @@ export class RemoteGraphQLDataSource<
|
|
|
241
246
|
// thus the overall response) is uncacheable. (If you don't like this, you
|
|
242
247
|
// can tweak the `cache-control` header in your `didReceiveResponse`
|
|
243
248
|
// method.)
|
|
244
|
-
const hint:
|
|
249
|
+
const hint: GatewayCacheHint = { maxAge: 0 };
|
|
245
250
|
const maxAge = parsed['max-age'];
|
|
246
251
|
if (typeof maxAge === 'string' && maxAge.match(/^[0-9]+$/)) {
|
|
247
252
|
hint.maxAge = +maxAge;
|
|
248
253
|
}
|
|
249
254
|
if (parsed['private'] === true) {
|
|
250
|
-
hint.scope =
|
|
255
|
+
hint.scope = 'PRIVATE';
|
|
251
256
|
}
|
|
252
257
|
if (parsed['public'] === true) {
|
|
253
|
-
hint.scope =
|
|
258
|
+
hint.scope = 'PUBLIC';
|
|
254
259
|
}
|
|
255
260
|
overallCachePolicy.restrict(hint);
|
|
256
261
|
}
|
|
@@ -260,22 +265,22 @@ export class RemoteGraphQLDataSource<
|
|
|
260
265
|
|
|
261
266
|
public didReceiveResponse?(
|
|
262
267
|
requestContext: Required<
|
|
263
|
-
Pick<
|
|
268
|
+
Pick<GatewayGraphQLRequestContext<TContext>, 'request' | 'response' | 'context'>
|
|
264
269
|
>,
|
|
265
|
-
):
|
|
270
|
+
): GatewayGraphQLResponse | Promise<GatewayGraphQLResponse>;
|
|
266
271
|
|
|
267
272
|
public didEncounterError(
|
|
268
273
|
error: Error,
|
|
269
|
-
_fetchRequest:
|
|
270
|
-
_fetchResponse?:
|
|
274
|
+
_fetchRequest: NodeFetchRequest,
|
|
275
|
+
_fetchResponse?: FetcherResponse,
|
|
271
276
|
_context?: TContext,
|
|
272
277
|
) {
|
|
273
278
|
throw error;
|
|
274
279
|
}
|
|
275
280
|
|
|
276
281
|
public parseBody(
|
|
277
|
-
fetchResponse:
|
|
278
|
-
_fetchRequest?:
|
|
282
|
+
fetchResponse: FetcherResponse,
|
|
283
|
+
_fetchRequest?: NodeFetchRequest,
|
|
279
284
|
_context?: TContext,
|
|
280
285
|
): Promise<object | string> {
|
|
281
286
|
const contentType = fetchResponse.headers.get('Content-Type');
|
|
@@ -286,29 +291,37 @@ export class RemoteGraphQLDataSource<
|
|
|
286
291
|
}
|
|
287
292
|
}
|
|
288
293
|
|
|
289
|
-
public async errorFromResponse(response:
|
|
290
|
-
const message = `${response.status}: ${response.statusText}`;
|
|
291
|
-
|
|
292
|
-
let error: ApolloError;
|
|
293
|
-
if (response.status === 401) {
|
|
294
|
-
error = new AuthenticationError(message);
|
|
295
|
-
} else if (response.status === 403) {
|
|
296
|
-
error = new ForbiddenError(message);
|
|
297
|
-
} else {
|
|
298
|
-
error = new ApolloError(message);
|
|
299
|
-
}
|
|
300
|
-
|
|
294
|
+
public async errorFromResponse(response: FetcherResponse) {
|
|
301
295
|
const body = await this.parseBody(response);
|
|
302
296
|
|
|
303
|
-
|
|
297
|
+
const extensions: GraphQLErrorExtensions = {
|
|
304
298
|
response: {
|
|
305
299
|
url: response.url,
|
|
306
300
|
status: response.status,
|
|
307
301
|
statusText: response.statusText,
|
|
308
302
|
body,
|
|
309
303
|
},
|
|
310
|
-
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
if (response.status === 401) {
|
|
307
|
+
extensions.code = 'UNAUTHENTICATED';
|
|
308
|
+
} else if (response.status === 403) {
|
|
309
|
+
extensions.code = 'FORBIDDEN';
|
|
310
|
+
}
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
// Note: gateway 0.x still supports graphql-js v15.8, which does
|
|
313
|
+
// not have the options-based GraphQLError constructor. Note that
|
|
314
|
+
// the constructor used here is dropped in graphql-js v17, so this
|
|
315
|
+
// will have to be adjusted if we try to make gateway 0.x support
|
|
316
|
+
// graphql-js v17.
|
|
317
|
+
return new GraphQLError(
|
|
318
|
+
`${response.status}: ${response.statusText}`,
|
|
319
|
+
null,
|
|
320
|
+
null,
|
|
321
|
+
null,
|
|
322
|
+
null,
|
|
323
|
+
null,
|
|
324
|
+
extensions,
|
|
325
|
+
);
|
|
313
326
|
}
|
|
314
327
|
}
|
|
@@ -2,8 +2,8 @@ import { LocalGraphQLDataSource } from '../LocalGraphQLDataSource';
|
|
|
2
2
|
import { buildSubgraphSchema } from '@apollo/subgraph';
|
|
3
3
|
import gql from 'graphql-tag';
|
|
4
4
|
import { GraphQLResolverMap } from '@apollo/subgraph/src/schema-helper';
|
|
5
|
-
import { GraphQLRequestContext } from 'apollo-server-types';
|
|
6
5
|
import { GraphQLDataSourceRequestKind } from '../types';
|
|
6
|
+
import { GatewayGraphQLRequestContext } from '@apollo/server-gateway-interface';
|
|
7
7
|
|
|
8
8
|
describe('constructing requests', () => {
|
|
9
9
|
it('accepts context', async () => {
|
|
@@ -42,7 +42,7 @@ describe('constructing requests', () => {
|
|
|
42
42
|
},
|
|
43
43
|
incomingRequestContext: {
|
|
44
44
|
context: { userId: 2 },
|
|
45
|
-
} as
|
|
45
|
+
} as GatewayGraphQLRequestContext<{userId: number}>,
|
|
46
46
|
context: { userId: 2 },
|
|
47
47
|
});
|
|
48
48
|
|