@apollo/gateway 2.1.0-alpha.1 → 2.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +4 -3
- 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 +6 -6
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +13 -17
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/datasources/types.d.ts +6 -6
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/executeQueryPlan.d.ts +2 -2
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +6 -0
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -12
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.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/UplinkSupergraphManager/index.d.ts +3 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/index.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/index.js +2 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/index.js.map +1 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage.d.ts +1 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.d.ts +1 -2
- package/dist/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.js.map +1 -1
- package/package.json +9 -11
- package/src/__tests__/buildQueryPlan.test.ts +6 -2
- package/src/__tests__/executeQueryPlan.test.ts +9 -7
- package/src/__tests__/execution-utils.ts +3 -3
- package/src/__tests__/gateway/executor.test.ts +2 -2
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +1 -1
- package/src/__tests__/integration/abstract-types.test.ts +8 -6
- package/src/__tests__/integration/managed.test.ts +26 -0
- package/src/config.ts +5 -5
- package/src/datasources/LocalGraphQLDataSource.ts +2 -4
- package/src/datasources/RemoteGraphQLDataSource.ts +27 -42
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +2 -2
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +14 -19
- package/src/datasources/types.ts +6 -6
- package/src/executeQueryPlan.ts +19 -16
- package/src/index.ts +23 -43
- package/src/logger.ts +1 -1
- package/src/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts +2 -2
- package/src/supergraphManagers/UplinkSupergraphManager/__tests__/loadSupergraphSdlFromStorage.test.ts +46 -2
- package/src/supergraphManagers/UplinkSupergraphManager/index.ts +6 -2
- package/src/supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage.ts +5 -5
- package/src/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.ts +1 -2
- package/dist/supergraphManagers/UplinkSupergraphManager/types.d.ts +0 -9
- package/dist/supergraphManagers/UplinkSupergraphManager/types.d.ts.map +0 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/types.js +0 -5
- package/dist/supergraphManagers/UplinkSupergraphManager/types.js.map +0 -1
- package/src/supergraphManagers/UplinkSupergraphManager/types.ts +0 -10
|
@@ -1,17 +1,3 @@
|
|
|
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
1
|
import { isObject } from '../utilities/predicates';
|
|
16
2
|
import { GraphQLDataSource, GraphQLDataSourceProcessOptions, GraphQLDataSourceRequestKind } from './types';
|
|
17
3
|
import { createHash } from '@apollo/utils.createhash';
|
|
@@ -19,6 +5,8 @@ import { parseCacheControlHeader } from './parseCacheControlHeader';
|
|
|
19
5
|
import fetcher from 'make-fetch-happen';
|
|
20
6
|
import { Headers as NodeFetchHeaders, Request as NodeFetchRequest } from 'node-fetch';
|
|
21
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';
|
|
22
10
|
|
|
23
11
|
export class RemoteGraphQLDataSource<
|
|
24
12
|
TContext extends Record<string, any> = Record<string, any>,
|
|
@@ -77,7 +65,7 @@ export class RemoteGraphQLDataSource<
|
|
|
77
65
|
|
|
78
66
|
async process(
|
|
79
67
|
options: GraphQLDataSourceProcessOptions<TContext>,
|
|
80
|
-
): Promise<
|
|
68
|
+
): Promise<GatewayGraphQLResponse> {
|
|
81
69
|
const { request, context: originalContext } = options;
|
|
82
70
|
// Deal with a bit of a hairy situation in typings: when doing health checks
|
|
83
71
|
// and schema checks we always pass in `{}` as the context even though it's
|
|
@@ -160,7 +148,7 @@ export class RemoteGraphQLDataSource<
|
|
|
160
148
|
// If APQ was enabled, we'll run the same request again, but add in the
|
|
161
149
|
// previously omitted `query`. If APQ was NOT enabled, this is the first
|
|
162
150
|
// request (non-APQ, all the way).
|
|
163
|
-
const requestWithQuery:
|
|
151
|
+
const requestWithQuery: GatewayGraphQLRequest = {
|
|
164
152
|
query,
|
|
165
153
|
...requestWithoutQuery,
|
|
166
154
|
};
|
|
@@ -174,9 +162,9 @@ export class RemoteGraphQLDataSource<
|
|
|
174
162
|
}
|
|
175
163
|
|
|
176
164
|
private async sendRequest(
|
|
177
|
-
request:
|
|
165
|
+
request: GatewayGraphQLRequest,
|
|
178
166
|
context: TContext,
|
|
179
|
-
): Promise<
|
|
167
|
+
): Promise<GatewayGraphQLResponse> {
|
|
180
168
|
// This would represent an internal programming error since this shouldn't
|
|
181
169
|
// be possible in the way that this method is invoked right now.
|
|
182
170
|
if (!request.http) {
|
|
@@ -229,7 +217,7 @@ export class RemoteGraphQLDataSource<
|
|
|
229
217
|
|
|
230
218
|
public willSendRequest?(
|
|
231
219
|
options: GraphQLDataSourceProcessOptions<TContext>,
|
|
232
|
-
):
|
|
220
|
+
): void | Promise<void>;
|
|
233
221
|
|
|
234
222
|
private async respond({
|
|
235
223
|
response,
|
|
@@ -237,11 +225,11 @@ export class RemoteGraphQLDataSource<
|
|
|
237
225
|
context,
|
|
238
226
|
overallCachePolicy,
|
|
239
227
|
}: {
|
|
240
|
-
response:
|
|
241
|
-
request:
|
|
228
|
+
response: GatewayGraphQLResponse;
|
|
229
|
+
request: GatewayGraphQLRequest;
|
|
242
230
|
context: TContext;
|
|
243
|
-
overallCachePolicy:
|
|
244
|
-
}): Promise<
|
|
231
|
+
overallCachePolicy: GatewayCachePolicy | null;
|
|
232
|
+
}): Promise<GatewayGraphQLResponse> {
|
|
245
233
|
const processedResponse =
|
|
246
234
|
typeof this.didReceiveResponse === 'function'
|
|
247
235
|
? await this.didReceiveResponse({ response, request, context })
|
|
@@ -256,16 +244,16 @@ export class RemoteGraphQLDataSource<
|
|
|
256
244
|
// thus the overall response) is uncacheable. (If you don't like this, you
|
|
257
245
|
// can tweak the `cache-control` header in your `didReceiveResponse`
|
|
258
246
|
// method.)
|
|
259
|
-
const hint:
|
|
247
|
+
const hint: GatewayCacheHint = { maxAge: 0 };
|
|
260
248
|
const maxAge = parsed['max-age'];
|
|
261
249
|
if (typeof maxAge === 'string' && maxAge.match(/^[0-9]+$/)) {
|
|
262
250
|
hint.maxAge = +maxAge;
|
|
263
251
|
}
|
|
264
252
|
if (parsed['private'] === true) {
|
|
265
|
-
hint.scope =
|
|
253
|
+
hint.scope = 'PRIVATE';
|
|
266
254
|
}
|
|
267
255
|
if (parsed['public'] === true) {
|
|
268
|
-
hint.scope =
|
|
256
|
+
hint.scope = 'PUBLIC';
|
|
269
257
|
}
|
|
270
258
|
overallCachePolicy.restrict(hint);
|
|
271
259
|
}
|
|
@@ -275,9 +263,9 @@ export class RemoteGraphQLDataSource<
|
|
|
275
263
|
|
|
276
264
|
public didReceiveResponse?(
|
|
277
265
|
requestContext: Required<
|
|
278
|
-
Pick<
|
|
266
|
+
Pick<GatewayGraphQLRequestContext<TContext>, 'request' | 'response' | 'context'>
|
|
279
267
|
>,
|
|
280
|
-
):
|
|
268
|
+
): GatewayGraphQLResponse | Promise<GatewayGraphQLResponse>;
|
|
281
269
|
|
|
282
270
|
public didEncounterError(
|
|
283
271
|
error: Error,
|
|
@@ -302,28 +290,25 @@ export class RemoteGraphQLDataSource<
|
|
|
302
290
|
}
|
|
303
291
|
|
|
304
292
|
public async errorFromResponse(response: FetcherResponse) {
|
|
305
|
-
const message = `${response.status}: ${response.statusText}`;
|
|
306
|
-
|
|
307
|
-
let error: ApolloError;
|
|
308
|
-
if (response.status === 401) {
|
|
309
|
-
error = new AuthenticationError(message);
|
|
310
|
-
} else if (response.status === 403) {
|
|
311
|
-
error = new ForbiddenError(message);
|
|
312
|
-
} else {
|
|
313
|
-
error = new ApolloError(message);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
293
|
const body = await this.parseBody(response);
|
|
317
294
|
|
|
318
|
-
|
|
295
|
+
const extensions: GraphQLErrorExtensions = {
|
|
319
296
|
response: {
|
|
320
297
|
url: response.url,
|
|
321
298
|
status: response.status,
|
|
322
299
|
statusText: response.statusText,
|
|
323
300
|
body,
|
|
324
301
|
},
|
|
325
|
-
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
if (response.status === 401) {
|
|
305
|
+
extensions.code = 'UNAUTHENTICATED';
|
|
306
|
+
} else if (response.status === 403) {
|
|
307
|
+
extensions.code = 'FORBIDDEN';
|
|
308
|
+
}
|
|
326
309
|
|
|
327
|
-
return
|
|
310
|
+
return new GraphQLError(`${response.status}: ${response.statusText}`, {
|
|
311
|
+
extensions,
|
|
312
|
+
});
|
|
328
313
|
}
|
|
329
314
|
}
|
|
@@ -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
|
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ApolloError,
|
|
3
|
-
AuthenticationError,
|
|
4
|
-
ForbiddenError,
|
|
5
|
-
} from 'apollo-server-errors';
|
|
6
|
-
|
|
7
1
|
import { RemoteGraphQLDataSource } from '../RemoteGraphQLDataSource';
|
|
8
2
|
import { Response, Headers } from 'node-fetch';
|
|
9
|
-
import { GraphQLRequestContext } from 'apollo-server-types';
|
|
10
3
|
import { GraphQLDataSourceRequestKind } from '../types';
|
|
11
4
|
import { nockBeforeEach, nockAfterEach } from '../../__tests__/nockAssertions';
|
|
12
5
|
import nock from 'nock';
|
|
6
|
+
import { GraphQLError } from 'graphql';
|
|
7
|
+
import { GatewayGraphQLRequestContext } from '@apollo/server-gateway-interface';
|
|
13
8
|
|
|
14
9
|
beforeEach(nockBeforeEach);
|
|
15
10
|
afterEach(nockAfterEach);
|
|
@@ -322,7 +317,7 @@ describe('didReceiveResponse', () => {
|
|
|
322
317
|
response,
|
|
323
318
|
}: Required<
|
|
324
319
|
Pick<
|
|
325
|
-
|
|
320
|
+
GatewayGraphQLRequestContext<MyContext>,
|
|
326
321
|
'request' | 'response' | 'context'
|
|
327
322
|
>
|
|
328
323
|
>) {
|
|
@@ -367,7 +362,7 @@ describe('didReceiveResponse', () => {
|
|
|
367
362
|
response,
|
|
368
363
|
}: Required<
|
|
369
364
|
Pick<
|
|
370
|
-
|
|
365
|
+
GatewayGraphQLRequestContext<MyContext>,
|
|
371
366
|
'request' | 'response' | 'context'
|
|
372
367
|
>
|
|
373
368
|
>) {
|
|
@@ -403,7 +398,7 @@ describe('didReceiveResponse', () => {
|
|
|
403
398
|
response,
|
|
404
399
|
}: Required<
|
|
405
400
|
Pick<
|
|
406
|
-
|
|
401
|
+
GatewayGraphQLRequestContext<MyContext>,
|
|
407
402
|
'request' | 'response' | 'context'
|
|
408
403
|
>
|
|
409
404
|
>) {
|
|
@@ -457,11 +452,11 @@ describe('didEncounterError', () => {
|
|
|
457
452
|
},
|
|
458
453
|
incomingRequestContext: {
|
|
459
454
|
context,
|
|
460
|
-
} as
|
|
455
|
+
} as GatewayGraphQLRequestContext<MyContext>,
|
|
461
456
|
context,
|
|
462
457
|
});
|
|
463
458
|
|
|
464
|
-
await expect(result).rejects.toThrow(
|
|
459
|
+
await expect(result).rejects.toThrow(GraphQLError);
|
|
465
460
|
expect(context).toMatchObject({
|
|
466
461
|
timingData: [{ time: 1616446845234 }],
|
|
467
462
|
});
|
|
@@ -469,7 +464,7 @@ describe('didEncounterError', () => {
|
|
|
469
464
|
});
|
|
470
465
|
|
|
471
466
|
describe('error handling', () => {
|
|
472
|
-
it('throws
|
|
467
|
+
it('throws error with code UNAUTHENTICATED when the response status is 401', async () => {
|
|
473
468
|
const DataSource = new RemoteGraphQLDataSource({
|
|
474
469
|
url: 'https://api.example.com/foo',
|
|
475
470
|
});
|
|
@@ -480,7 +475,7 @@ describe('error handling', () => {
|
|
|
480
475
|
...defaultProcessOptions,
|
|
481
476
|
request: { query: '{ me { name } }' },
|
|
482
477
|
});
|
|
483
|
-
await expect(result).rejects.toThrow(
|
|
478
|
+
await expect(result).rejects.toThrow(GraphQLError);
|
|
484
479
|
await expect(result).rejects.toMatchObject({
|
|
485
480
|
extensions: {
|
|
486
481
|
code: 'UNAUTHENTICATED',
|
|
@@ -492,7 +487,7 @@ describe('error handling', () => {
|
|
|
492
487
|
});
|
|
493
488
|
});
|
|
494
489
|
|
|
495
|
-
it('throws
|
|
490
|
+
it('throws an error with code FORBIDDEN when the response status is 403', async () => {
|
|
496
491
|
const DataSource = new RemoteGraphQLDataSource({
|
|
497
492
|
url: 'https://api.example.com/foo',
|
|
498
493
|
});
|
|
@@ -503,7 +498,7 @@ describe('error handling', () => {
|
|
|
503
498
|
...defaultProcessOptions,
|
|
504
499
|
request: { query: '{ me { name } }' },
|
|
505
500
|
});
|
|
506
|
-
await expect(result).rejects.toThrow(
|
|
501
|
+
await expect(result).rejects.toThrow(GraphQLError);
|
|
507
502
|
await expect(result).rejects.toMatchObject({
|
|
508
503
|
extensions: {
|
|
509
504
|
code: 'FORBIDDEN',
|
|
@@ -515,7 +510,7 @@ describe('error handling', () => {
|
|
|
515
510
|
});
|
|
516
511
|
});
|
|
517
512
|
|
|
518
|
-
it('throws
|
|
513
|
+
it('throws a GraphQLError when the response status is 500', async () => {
|
|
519
514
|
const DataSource = new RemoteGraphQLDataSource({
|
|
520
515
|
url: 'https://api.example.com/foo',
|
|
521
516
|
});
|
|
@@ -526,7 +521,7 @@ describe('error handling', () => {
|
|
|
526
521
|
...defaultProcessOptions,
|
|
527
522
|
request: { query: '{ me { name } }' },
|
|
528
523
|
});
|
|
529
|
-
await expect(result).rejects.toThrow(
|
|
524
|
+
await expect(result).rejects.toThrow(GraphQLError);
|
|
530
525
|
await expect(result).rejects.toMatchObject({
|
|
531
526
|
extensions: {
|
|
532
527
|
response: {
|
|
@@ -560,7 +555,7 @@ describe('error handling', () => {
|
|
|
560
555
|
...defaultProcessOptions,
|
|
561
556
|
request: { query: '{ me { name } }' },
|
|
562
557
|
});
|
|
563
|
-
await expect(result).rejects.toThrow(
|
|
558
|
+
await expect(result).rejects.toThrow(GraphQLError);
|
|
564
559
|
await expect(result).rejects.toMatchObject({
|
|
565
560
|
extensions: {
|
|
566
561
|
response: {
|
package/src/datasources/types.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GatewayGraphQLResponse, GatewayGraphQLRequestContext } from '@apollo/server-gateway-interface';
|
|
2
2
|
|
|
3
3
|
export interface GraphQLDataSource<
|
|
4
4
|
TContext extends Record<string, any> = Record<string, any>,
|
|
5
5
|
> {
|
|
6
6
|
process(
|
|
7
7
|
options: GraphQLDataSourceProcessOptions<TContext>,
|
|
8
|
-
): Promise<
|
|
8
|
+
): Promise<GatewayGraphQLResponse>;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export enum GraphQLDataSourceRequestKind {
|
|
@@ -20,7 +20,7 @@ export type GraphQLDataSourceProcessOptions<
|
|
|
20
20
|
/**
|
|
21
21
|
* The request to send to the subgraph.
|
|
22
22
|
*/
|
|
23
|
-
request:
|
|
23
|
+
request: GatewayGraphQLRequestContext<TContext>['request'];
|
|
24
24
|
} & (
|
|
25
25
|
| {
|
|
26
26
|
kind: GraphQLDataSourceRequestKind.INCOMING_OPERATION;
|
|
@@ -29,7 +29,7 @@ export type GraphQLDataSourceProcessOptions<
|
|
|
29
29
|
* one of the strings if this operation is generated by the gateway without an
|
|
30
30
|
* incoming request.
|
|
31
31
|
*/
|
|
32
|
-
incomingRequestContext:
|
|
32
|
+
incomingRequestContext: GatewayGraphQLRequestContext<TContext>;
|
|
33
33
|
/**
|
|
34
34
|
* Equivalent to incomingRequestContext.context (provided here for
|
|
35
35
|
* backwards compatibility): the object created by the Apollo Server
|
|
@@ -38,11 +38,11 @@ export type GraphQLDataSourceProcessOptions<
|
|
|
38
38
|
* @deprecated Use `incomingRequestContext.context` instead (after
|
|
39
39
|
* checking `kind`).
|
|
40
40
|
*/
|
|
41
|
-
context:
|
|
41
|
+
context: GatewayGraphQLRequestContext<TContext>['context'];
|
|
42
42
|
/**
|
|
43
43
|
* The document representation of the request's query being sent to the subgraph, if available.
|
|
44
44
|
*/
|
|
45
|
-
document?:
|
|
45
|
+
document?: GatewayGraphQLRequestContext<TContext>['document'];
|
|
46
46
|
}
|
|
47
47
|
| {
|
|
48
48
|
kind:
|
package/src/executeQueryPlan.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
GraphQLExecutionResult,
|
|
3
|
-
GraphQLRequestContext,
|
|
4
|
-
} from 'apollo-server-types';
|
|
5
1
|
import { Headers } from 'node-fetch';
|
|
6
2
|
import {
|
|
7
3
|
execute,
|
|
@@ -33,7 +29,8 @@ import { deepMerge } from './utilities/deepMerge';
|
|
|
33
29
|
import { isNotNullOrUndefined } from './utilities/array';
|
|
34
30
|
import { SpanStatusCode } from "@opentelemetry/api";
|
|
35
31
|
import { OpenTelemetrySpanNames, tracer } from "./utilities/opentelemetry";
|
|
36
|
-
import { defaultRootName, errorCodeDef, ERRORS } from '@apollo/federation-internals';
|
|
32
|
+
import { assert, defaultRootName, errorCodeDef, ERRORS } from '@apollo/federation-internals';
|
|
33
|
+
import { GatewayGraphQLRequestContext, GatewayExecutionResult } from '@apollo/server-gateway-interface';
|
|
37
34
|
|
|
38
35
|
export type ServiceMap = {
|
|
39
36
|
[serviceName: string]: GraphQLDataSource;
|
|
@@ -41,22 +38,22 @@ export type ServiceMap = {
|
|
|
41
38
|
|
|
42
39
|
type ResultMap = Record<string, any>;
|
|
43
40
|
|
|
44
|
-
interface ExecutionContext
|
|
41
|
+
interface ExecutionContext {
|
|
45
42
|
queryPlan: QueryPlan;
|
|
46
43
|
operationContext: OperationContext;
|
|
47
44
|
serviceMap: ServiceMap;
|
|
48
|
-
requestContext:
|
|
45
|
+
requestContext: GatewayGraphQLRequestContext;
|
|
49
46
|
supergraphSchema: GraphQLSchema;
|
|
50
47
|
errors: GraphQLError[];
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
export async function executeQueryPlan
|
|
50
|
+
export async function executeQueryPlan(
|
|
54
51
|
queryPlan: QueryPlan,
|
|
55
52
|
serviceMap: ServiceMap,
|
|
56
|
-
requestContext:
|
|
53
|
+
requestContext: GatewayGraphQLRequestContext,
|
|
57
54
|
operationContext: OperationContext,
|
|
58
55
|
supergraphSchema: GraphQLSchema,
|
|
59
|
-
): Promise<
|
|
56
|
+
): Promise<GatewayExecutionResult> {
|
|
60
57
|
|
|
61
58
|
const logger = requestContext.logger || console;
|
|
62
59
|
|
|
@@ -64,7 +61,7 @@ export async function executeQueryPlan<TContext>(
|
|
|
64
61
|
try {
|
|
65
62
|
const errors: GraphQLError[] = [];
|
|
66
63
|
|
|
67
|
-
const context: ExecutionContext
|
|
64
|
+
const context: ExecutionContext = {
|
|
68
65
|
queryPlan,
|
|
69
66
|
operationContext,
|
|
70
67
|
serviceMap,
|
|
@@ -170,8 +167,8 @@ export async function executeQueryPlan<TContext>(
|
|
|
170
167
|
// we're going to ignore it, because it makes the code much simpler and more
|
|
171
168
|
// typesafe. However, it doesn't actually ask for traces from the backend
|
|
172
169
|
// service unless we are capturing traces for Studio.
|
|
173
|
-
async function executeNode
|
|
174
|
-
context: ExecutionContext
|
|
170
|
+
async function executeNode(
|
|
171
|
+
context: ExecutionContext,
|
|
175
172
|
node: PlanNode,
|
|
176
173
|
results: ResultMap | ResultMap[],
|
|
177
174
|
path: ResponsePath,
|
|
@@ -251,11 +248,17 @@ async function executeNode<TContext>(
|
|
|
251
248
|
}
|
|
252
249
|
return new Trace.QueryPlanNode({ fetch: traceNode });
|
|
253
250
|
}
|
|
251
|
+
case 'Defer': {
|
|
252
|
+
assert(false, `@defer support is not available in the gateway`);
|
|
253
|
+
}
|
|
254
|
+
case 'Condition': {
|
|
255
|
+
assert(false, `Condition nodes are not available in the gateway`);
|
|
256
|
+
}
|
|
254
257
|
}
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
async function executeFetch
|
|
258
|
-
context: ExecutionContext
|
|
260
|
+
async function executeFetch(
|
|
261
|
+
context: ExecutionContext,
|
|
259
262
|
fetch: FetchNode,
|
|
260
263
|
results: ResultMap | (ResultMap | null | undefined)[],
|
|
261
264
|
_path: ResponsePath,
|
|
@@ -379,7 +382,7 @@ async function executeFetch<TContext>(
|
|
|
379
382
|
}
|
|
380
383
|
});
|
|
381
384
|
async function sendOperation(
|
|
382
|
-
context: ExecutionContext
|
|
385
|
+
context: ExecutionContext,
|
|
383
386
|
source: string,
|
|
384
387
|
variables: Record<string, any>,
|
|
385
388
|
operationName: string | undefined,
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { deprecate } from 'util';
|
|
2
|
-
import { GraphQLService, Unsubscriber } from 'apollo-server-core';
|
|
3
|
-
import {
|
|
4
|
-
GraphQLExecutionResult,
|
|
5
|
-
GraphQLRequestContextExecutionDidStart,
|
|
6
|
-
} from 'apollo-server-types';
|
|
7
2
|
import { createHash } from '@apollo/utils.createhash';
|
|
8
3
|
import type { Logger } from '@apollo/utils.logger';
|
|
9
|
-
import
|
|
4
|
+
import LRUCache from 'lru-cache';
|
|
10
5
|
import {
|
|
11
6
|
isObjectType,
|
|
12
7
|
isIntrospectionType,
|
|
@@ -63,6 +58,7 @@ import {
|
|
|
63
58
|
ServiceDefinition,
|
|
64
59
|
} from '@apollo/federation-internals';
|
|
65
60
|
import { getDefaultLogger } from './logger';
|
|
61
|
+
import {GatewayInterface, GatewayUnsubscriber, GatewayGraphQLRequestContext, GatewayExecutionResult} from '@apollo/server-gateway-interface';
|
|
66
62
|
|
|
67
63
|
type DataSourceMap = {
|
|
68
64
|
[serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
|
|
@@ -118,7 +114,7 @@ interface GraphQLServiceEngineConfig {
|
|
|
118
114
|
graphVariant?: string;
|
|
119
115
|
}
|
|
120
116
|
|
|
121
|
-
export class ApolloGateway implements
|
|
117
|
+
export class ApolloGateway implements GatewayInterface {
|
|
122
118
|
public schema?: GraphQLSchema;
|
|
123
119
|
// Same as a `schema` but as a `Schema` to avoid reconverting when we need it.
|
|
124
120
|
// TODO(sylvain): if we add caching in `Schema.toGraphQLJSSchema`, we could maybe only keep `apiSchema`
|
|
@@ -130,7 +126,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
130
126
|
private serviceMap: DataSourceMap = Object.create(null);
|
|
131
127
|
private config: GatewayConfig;
|
|
132
128
|
private logger: Logger;
|
|
133
|
-
private queryPlanStore:
|
|
129
|
+
private queryPlanStore: LRUCache<string, QueryPlan>;
|
|
134
130
|
private apolloConfig?: ApolloConfigFromAS3;
|
|
135
131
|
private onSchemaChangeListeners = new Set<(schema: GraphQLSchema) => void>();
|
|
136
132
|
private onSchemaLoadOrUpdateListeners = new Set<
|
|
@@ -196,14 +192,14 @@ export class ApolloGateway implements GraphQLService {
|
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
private initQueryPlanStore(approximateQueryPlanStoreMiB?: number) {
|
|
199
|
-
return new
|
|
195
|
+
return new LRUCache<string, QueryPlan>({
|
|
200
196
|
// Create ~about~ a 30MiB InMemoryLRUCache. This is less than precise
|
|
201
197
|
// since the technique to calculate the size of a DocumentNode is
|
|
202
198
|
// only using JSON.stringify on the DocumentNode (and thus doesn't account
|
|
203
199
|
// for unicode characters, etc.), but it should do a reasonable job at
|
|
204
200
|
// providing a caching document store for most operations.
|
|
205
201
|
maxSize: Math.pow(2, 20) * (approximateQueryPlanStoreMiB || 30),
|
|
206
|
-
|
|
202
|
+
sizeCalculation: approximateObjectSize,
|
|
207
203
|
});
|
|
208
204
|
}
|
|
209
205
|
|
|
@@ -360,6 +356,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
360
356
|
uplinkEndpoints:
|
|
361
357
|
this.config.uplinkEndpoints ?? schemaDeliveryEndpoints,
|
|
362
358
|
maxRetries: this.config.uplinkMaxRetries,
|
|
359
|
+
fetcher: this.config.fetcher,
|
|
363
360
|
logger: this.logger,
|
|
364
361
|
fallbackPollIntervalInMs: this.pollIntervalInMs,
|
|
365
362
|
}),
|
|
@@ -558,12 +555,12 @@ export class ApolloGateway implements GraphQLService {
|
|
|
558
555
|
// Once we remove the deprecated onSchemaChange() method, we can remove this.
|
|
559
556
|
legacyDontNotifyOnSchemaChangeListeners: boolean = false,
|
|
560
557
|
): void {
|
|
561
|
-
|
|
558
|
+
this.queryPlanStore.clear();
|
|
562
559
|
this.apiSchema = coreSchema.toAPISchema();
|
|
563
560
|
this.schema = addExtensions(
|
|
564
561
|
wrapSchemaWithAliasResolver(this.apiSchema.toGraphQLJSSchema()),
|
|
565
562
|
);
|
|
566
|
-
this.queryPlanner = new QueryPlanner(coreSchema);
|
|
563
|
+
this.queryPlanner = new QueryPlanner(coreSchema, this.config.queryPlannerConfig);
|
|
567
564
|
|
|
568
565
|
// Notify onSchemaChange listeners of the updated schema
|
|
569
566
|
if (!legacyDontNotifyOnSchemaChangeListeners) {
|
|
@@ -650,7 +647,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
650
647
|
*/
|
|
651
648
|
public onSchemaChange(
|
|
652
649
|
callback: (schema: GraphQLSchema) => void,
|
|
653
|
-
):
|
|
650
|
+
): GatewayUnsubscriber {
|
|
654
651
|
this.onSchemaChangeListeners.add(callback);
|
|
655
652
|
|
|
656
653
|
return () => {
|
|
@@ -663,7 +660,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
663
660
|
apiSchema: GraphQLSchema;
|
|
664
661
|
coreSupergraphSdl: string;
|
|
665
662
|
}) => void,
|
|
666
|
-
):
|
|
663
|
+
): GatewayUnsubscriber {
|
|
667
664
|
this.onSchemaLoadOrUpdateListeners.add(callback);
|
|
668
665
|
|
|
669
666
|
return () => {
|
|
@@ -743,9 +740,9 @@ export class ApolloGateway implements GraphQLService {
|
|
|
743
740
|
// ApolloServerPluginUsageReporting) assumes that. In fact, errors talking to backends
|
|
744
741
|
// are unlikely to show up as GraphQLErrors. Do we need to use
|
|
745
742
|
// formatApolloErrors or something?
|
|
746
|
-
public executor = async
|
|
747
|
-
requestContext:
|
|
748
|
-
): Promise<
|
|
743
|
+
public executor = async (
|
|
744
|
+
requestContext: GatewayGraphQLRequestContext,
|
|
745
|
+
): Promise<GatewayExecutionResult> => {
|
|
749
746
|
const spanAttributes = requestContext.operationName
|
|
750
747
|
? { operationName: requestContext.operationName }
|
|
751
748
|
: {};
|
|
@@ -774,10 +771,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
774
771
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
775
772
|
return { errors: validationErrors };
|
|
776
773
|
}
|
|
777
|
-
let queryPlan
|
|
778
|
-
if (this.queryPlanStore) {
|
|
779
|
-
queryPlan = await this.queryPlanStore.get(queryPlanStoreKey);
|
|
780
|
-
}
|
|
774
|
+
let queryPlan = this.queryPlanStore.get(queryPlanStoreKey);
|
|
781
775
|
|
|
782
776
|
if (!queryPlan) {
|
|
783
777
|
queryPlan = tracer.startActiveSpan(
|
|
@@ -800,25 +794,11 @@ export class ApolloGateway implements GraphQLService {
|
|
|
800
794
|
},
|
|
801
795
|
);
|
|
802
796
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
// whether or not it's successful at this point. We'll instead proceed
|
|
809
|
-
// to serve the rest of the request and just hope that this works out.
|
|
810
|
-
// If it doesn't work, the next request will have another opportunity to
|
|
811
|
-
// try again. Errors will surface as warnings, as appropriate.
|
|
812
|
-
//
|
|
813
|
-
// While it shouldn't normally be necessary to wrap this `Promise` in a
|
|
814
|
-
// `Promise.resolve` invocation, it seems that the underlying cache store
|
|
815
|
-
// is returning a non-native `Promise` (e.g. Bluebird, etc.).
|
|
816
|
-
Promise.resolve(
|
|
817
|
-
this.queryPlanStore.set(queryPlanStoreKey, queryPlan),
|
|
818
|
-
).catch((err) =>
|
|
819
|
-
this.logger.warn(
|
|
820
|
-
'Could not store queryPlan' + ((err && err.message) || err),
|
|
821
|
-
),
|
|
797
|
+
try {
|
|
798
|
+
this.queryPlanStore.set(queryPlanStoreKey, queryPlan);
|
|
799
|
+
} catch (err) {
|
|
800
|
+
this.logger.warn(
|
|
801
|
+
'Could not store queryPlan' + ((err && err.message) || err),
|
|
822
802
|
);
|
|
823
803
|
}
|
|
824
804
|
}
|
|
@@ -840,7 +820,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
840
820
|
});
|
|
841
821
|
}
|
|
842
822
|
|
|
843
|
-
const response = await executeQueryPlan
|
|
823
|
+
const response = await executeQueryPlan(
|
|
844
824
|
queryPlan,
|
|
845
825
|
serviceMap,
|
|
846
826
|
requestContext,
|
|
@@ -898,8 +878,8 @@ export class ApolloGateway implements GraphQLService {
|
|
|
898
878
|
);
|
|
899
879
|
};
|
|
900
880
|
|
|
901
|
-
private validateIncomingRequest
|
|
902
|
-
requestContext:
|
|
881
|
+
private validateIncomingRequest(
|
|
882
|
+
requestContext: GatewayGraphQLRequestContext,
|
|
903
883
|
operationContext: OperationContext,
|
|
904
884
|
) {
|
|
905
885
|
return tracer.startActiveSpan(OpenTelemetrySpanNames.VALIDATE, (span) => {
|
package/src/logger.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import loglevel from 'loglevel';
|
|
2
2
|
import type { Logger } from '@apollo/utils.logger';
|
|
3
3
|
|
|
4
|
-
export function getDefaultLogger(debug: boolean =
|
|
4
|
+
export function getDefaultLogger(debug: boolean = false): Logger {
|
|
5
5
|
const logger = loglevel.getLogger('apollo-gateway');
|
|
6
6
|
|
|
7
7
|
const level = debug === true ? loglevel.levels.DEBUG : loglevel.levels.WARN;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { GraphQLRequest } from 'apollo-server-types';
|
|
2
1
|
import { parse } from 'graphql';
|
|
3
2
|
import { Headers, type HeadersInit } from 'node-fetch';
|
|
4
3
|
import { ServiceDefinition } from '@apollo/federation-internals';
|
|
5
4
|
import { GraphQLDataSource, GraphQLDataSourceRequestKind } from '../../datasources/types';
|
|
6
5
|
import { SERVICE_DEFINITION_QUERY } from '../..';
|
|
7
6
|
import { ServiceDefinitionUpdate, ServiceEndpointDefinition } from '../../config';
|
|
7
|
+
import { GatewayGraphQLRequest } from '@apollo/server-gateway-interface';
|
|
8
8
|
|
|
9
9
|
export type Service = ServiceEndpointDefinition & {
|
|
10
10
|
dataSource: GraphQLDataSource;
|
|
@@ -35,7 +35,7 @@ export async function loadServicesFromRemoteEndpoint({
|
|
|
35
35
|
`Tried to load schema for '${name}' but no 'url' was specified.`);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const request:
|
|
38
|
+
const request: GatewayGraphQLRequest = {
|
|
39
39
|
query: SERVICE_DEFINITION_QUERY,
|
|
40
40
|
http: {
|
|
41
41
|
url,
|