@apollo/gateway 0.50.1 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/config.d.ts +4 -4
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js.map +1 -1
  4. package/dist/datasources/LocalGraphQLDataSource.d.ts +2 -2
  5. package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
  6. package/dist/datasources/LocalGraphQLDataSource.js +0 -2
  7. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  8. package/dist/datasources/RemoteGraphQLDataSource.d.ts +11 -10
  9. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  10. package/dist/datasources/RemoteGraphQLDataSource.js +30 -31
  11. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  12. package/dist/datasources/types.d.ts +5 -5
  13. package/dist/datasources/types.d.ts.map +1 -1
  14. package/dist/executeQueryPlan.d.ts +3 -3
  15. package/dist/executeQueryPlan.d.ts.map +1 -1
  16. package/dist/executeQueryPlan.js +2 -2
  17. package/dist/executeQueryPlan.js.map +1 -1
  18. package/dist/index.d.ts +7 -12
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +14 -35
  21. package/dist/index.js.map +1 -1
  22. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
  23. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -1
  24. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +2 -2
  25. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -1
  26. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -1
  27. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +3 -3
  28. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -1
  29. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js +14 -13
  30. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -1
  31. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +6 -5
  32. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -1
  33. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +3 -3
  34. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -1
  35. package/dist/typings/graphql.d.ts +24 -0
  36. package/dist/typings/graphql.d.ts.map +1 -0
  37. package/dist/{schema-helper/resolverMap.js → typings/graphql.js} +1 -1
  38. package/dist/typings/graphql.js.map +1 -0
  39. package/package.json +11 -13
  40. package/src/__generated__/graphqlTypes.ts +1 -1
  41. package/src/__tests__/executeQueryPlan.test.ts +15 -6
  42. package/src/__tests__/execution-utils.ts +4 -4
  43. package/src/__tests__/gateway/buildService.test.ts +81 -83
  44. package/src/__tests__/gateway/endToEnd.test.ts +1 -1
  45. package/src/__tests__/gateway/executor.test.ts +20 -17
  46. package/src/__tests__/gateway/opentelemetry.test.ts +3 -7
  47. package/src/__tests__/gateway/reporting.test.ts +1 -1
  48. package/src/__tests__/gateway/supergraphSdl.test.ts +11 -13
  49. package/src/__tests__/integration/complex-key.test.ts +2 -2
  50. package/src/config.ts +4 -6
  51. package/src/datasources/LocalGraphQLDataSource.ts +2 -4
  52. package/src/datasources/RemoteGraphQLDataSource.ts +72 -60
  53. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +3 -3
  54. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +120 -159
  55. package/src/datasources/types.ts +12 -5
  56. package/src/executeQueryPlan.ts +18 -18
  57. package/src/index.ts +25 -71
  58. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -6
  59. package/src/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts +2 -2
  60. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +70 -74
  61. package/src/supergraphManagers/UplinkFetcher/index.ts +2 -2
  62. package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +23 -17
  63. package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +9 -7
  64. package/src/typings/graphql.ts +31 -0
  65. package/dist/cache.d.ts +0 -18
  66. package/dist/cache.d.ts.map +0 -1
  67. package/dist/cache.js +0 -46
  68. package/dist/cache.js.map +0 -1
  69. package/dist/schema-helper/addResolversToSchema.d.ts +0 -4
  70. package/dist/schema-helper/addResolversToSchema.d.ts.map +0 -1
  71. package/dist/schema-helper/addResolversToSchema.js +0 -62
  72. package/dist/schema-helper/addResolversToSchema.js.map +0 -1
  73. package/dist/schema-helper/index.d.ts +0 -3
  74. package/dist/schema-helper/index.d.ts.map +0 -1
  75. package/dist/schema-helper/index.js +0 -19
  76. package/dist/schema-helper/index.js.map +0 -1
  77. package/dist/schema-helper/resolverMap.d.ts +0 -16
  78. package/dist/schema-helper/resolverMap.d.ts.map +0 -1
  79. package/dist/schema-helper/resolverMap.js.map +0 -1
  80. package/dist/utilities/createHash.d.ts +0 -2
  81. package/dist/utilities/createHash.d.ts.map +0 -1
  82. package/dist/utilities/createHash.js +0 -15
  83. package/dist/utilities/createHash.js.map +0 -1
  84. package/src/__mocks__/apollo-server-env.ts +0 -56
  85. package/src/__mocks__/make-fetch-happen-fetcher.ts +0 -57
  86. package/src/cache.ts +0 -66
  87. package/src/make-fetch-happen.d.ts +0 -59
  88. package/src/schema-helper/addResolversToSchema.ts +0 -83
  89. package/src/schema-helper/index.ts +0 -2
  90. package/src/schema-helper/resolverMap.ts +0 -23
  91. package/src/utilities/createHash.ts +0 -10
@@ -1,20 +1,17 @@
1
- import { fetch as customFetcher } from '../../__mocks__/apollo-server-env';
2
- import { fetch } from '../../__mocks__/make-fetch-happen-fetcher';
3
-
4
- import {
5
- ApolloError,
6
- AuthenticationError,
7
- ForbiddenError,
8
- } from 'apollo-server-errors';
9
-
10
1
  import { RemoteGraphQLDataSource } from '../RemoteGraphQLDataSource';
11
- import { Headers } from 'apollo-server-env';
12
- import { GraphQLRequestContext } from 'apollo-server-types';
2
+ import { Response, Headers } from 'node-fetch';
13
3
  import { GraphQLDataSourceRequestKind } from '../types';
4
+ import { nockBeforeEach, nockAfterEach } from '../../__tests__/nockAssertions';
5
+ import nock from 'nock';
6
+ import { GraphQLError } from 'graphql';
7
+ import { GatewayGraphQLRequestContext } from '@apollo/server-gateway-interface';
14
8
 
15
- beforeEach(() => {
16
- fetch.mockReset();
17
- });
9
+ beforeEach(nockBeforeEach);
10
+ afterEach(nockAfterEach);
11
+
12
+ const replyHeaders = {
13
+ 'content-type': 'application/json',
14
+ };
18
15
 
19
16
  // Right now, none of these tests care what's on incomingRequestContext, so we
20
17
  // pass this fake one in.
@@ -32,7 +29,9 @@ describe('constructing requests', () => {
32
29
  apq: false,
33
30
  });
34
31
 
35
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
32
+ nock('https://api.example.com')
33
+ .post('/foo', { query: '{ me { name } }' })
34
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
36
35
 
37
36
  const { data } = await DataSource.process({
38
37
  ...defaultProcessOptions,
@@ -40,10 +39,6 @@ describe('constructing requests', () => {
40
39
  });
41
40
 
42
41
  expect(data).toEqual({ me: 'james' });
43
- expect(fetch).toBeCalledTimes(1);
44
- expect(fetch).toHaveFetched('https://api.example.com/foo', {
45
- body: { query: '{ me { name } }' },
46
- });
47
42
  });
48
43
 
49
44
  it('passes variables', async () => {
@@ -52,7 +47,9 @@ describe('constructing requests', () => {
52
47
  apq: false,
53
48
  });
54
49
 
55
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
50
+ nock('https://api.example.com')
51
+ .post('/foo', { query: '{ me { name } }', variables: { id: '1' } })
52
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
56
53
 
57
54
  const { data } = await DataSource.process({
58
55
  ...defaultProcessOptions,
@@ -63,10 +60,6 @@ describe('constructing requests', () => {
63
60
  });
64
61
 
65
62
  expect(data).toEqual({ me: 'james' });
66
- expect(fetch).toBeCalledTimes(1);
67
- expect(fetch).toHaveFetched('https://api.example.com/foo', {
68
- body: { query: '{ me { name } }', variables: { id: '1' } },
69
- });
70
63
  });
71
64
  });
72
65
 
@@ -101,28 +94,18 @@ describe('constructing requests', () => {
101
94
  apq: true,
102
95
  });
103
96
 
104
- fetch.mockJSONResponseOnce(apqNotFoundResponse);
105
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
106
-
107
- const { data } = await DataSource.process({
108
- ...defaultProcessOptions,
109
- request: { query },
110
- });
111
-
112
- expect(data).toEqual({ me: 'james' });
113
- expect(fetch).toBeCalledTimes(2);
114
- expect(fetch).toHaveFetchedNth(1, 'https://api.example.com/foo', {
115
- body: {
97
+ nock('https://api.example.com')
98
+ .post('/foo', {
116
99
  extensions: {
117
100
  persistedQuery: {
118
101
  version: 1,
119
102
  sha256Hash,
120
103
  },
121
104
  },
122
- },
123
- });
124
- expect(fetch).toHaveFetchedNth(2, 'https://api.example.com/foo', {
125
- body: {
105
+ })
106
+ .reply(200, apqNotFoundResponse, replyHeaders);
107
+ nock('https://api.example.com')
108
+ .post('/foo', {
126
109
  query,
127
110
  extensions: {
128
111
  persistedQuery: {
@@ -130,8 +113,15 @@ describe('constructing requests', () => {
130
113
  sha256Hash,
131
114
  },
132
115
  },
133
- },
116
+ })
117
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
118
+
119
+ const { data } = await DataSource.process({
120
+ ...defaultProcessOptions,
121
+ request: { query },
134
122
  });
123
+
124
+ expect(data).toEqual({ me: 'james' });
135
125
  });
136
126
 
137
127
  it('passes variables', async () => {
@@ -140,21 +130,8 @@ describe('constructing requests', () => {
140
130
  apq: true,
141
131
  });
142
132
 
143
- fetch.mockJSONResponseOnce(apqNotFoundResponse);
144
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
145
-
146
- const { data } = await DataSource.process({
147
- ...defaultProcessOptions,
148
- request: {
149
- query,
150
- variables: { id: '1' },
151
- },
152
- });
153
-
154
- expect(data).toEqual({ me: 'james' });
155
- expect(fetch).toBeCalledTimes(2);
156
- expect(fetch).toHaveFetchedNth(1, 'https://api.example.com/foo', {
157
- body: {
133
+ nock('https://api.example.com')
134
+ .post('/foo', {
158
135
  variables: { id: '1' },
159
136
  extensions: {
160
137
  persistedQuery: {
@@ -162,10 +139,10 @@ describe('constructing requests', () => {
162
139
  sha256Hash,
163
140
  },
164
141
  },
165
- },
166
- });
167
- expect(fetch).toHaveFetchedNth(2, 'https://api.example.com/foo', {
168
- body: {
142
+ })
143
+ .reply(200, apqNotFoundResponse, replyHeaders);
144
+ nock('https://api.example.com')
145
+ .post('/foo', {
169
146
  query,
170
147
  variables: { id: '1' },
171
148
  extensions: {
@@ -174,8 +151,18 @@ describe('constructing requests', () => {
174
151
  sha256Hash,
175
152
  },
176
153
  },
154
+ })
155
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
156
+
157
+ const { data } = await DataSource.process({
158
+ ...defaultProcessOptions,
159
+ request: {
160
+ query,
161
+ variables: { id: '1' },
177
162
  },
178
163
  });
164
+
165
+ expect(data).toEqual({ me: 'james' });
179
166
  });
180
167
  });
181
168
 
@@ -186,25 +173,23 @@ describe('constructing requests', () => {
186
173
  apq: true,
187
174
  });
188
175
 
189
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
190
-
191
- const { data } = await DataSource.process({
192
- ...defaultProcessOptions,
193
- request: { query },
194
- });
195
-
196
- expect(data).toEqual({ me: 'james' });
197
- expect(fetch).toBeCalledTimes(1);
198
- expect(fetch).toHaveFetched('https://api.example.com/foo', {
199
- body: {
176
+ nock('https://api.example.com')
177
+ .post('/foo', {
200
178
  extensions: {
201
179
  persistedQuery: {
202
180
  version: 1,
203
181
  sha256Hash,
204
182
  },
205
183
  },
206
- },
184
+ })
185
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
186
+
187
+ const { data } = await DataSource.process({
188
+ ...defaultProcessOptions,
189
+ request: { query },
207
190
  });
191
+
192
+ expect(data).toEqual({ me: 'james' });
208
193
  });
209
194
 
210
195
  it('passes variables', async () => {
@@ -213,7 +198,17 @@ describe('constructing requests', () => {
213
198
  apq: true,
214
199
  });
215
200
 
216
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
201
+ nock('https://api.example.com')
202
+ .post('/foo', {
203
+ variables: { id: '1' },
204
+ extensions: {
205
+ persistedQuery: {
206
+ version: 1,
207
+ sha256Hash,
208
+ },
209
+ },
210
+ })
211
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
217
212
 
218
213
  const { data } = await DataSource.process({
219
214
  ...defaultProcessOptions,
@@ -224,52 +219,20 @@ describe('constructing requests', () => {
224
219
  });
225
220
 
226
221
  expect(data).toEqual({ me: 'james' });
227
- expect(fetch).toBeCalledTimes(1);
228
- expect(fetch).toHaveFetched('https://api.example.com/foo', {
229
- body: {
230
- variables: { id: '1' },
231
- extensions: {
232
- persistedQuery: {
233
- version: 1,
234
- sha256Hash,
235
- },
236
- },
237
- },
238
- });
239
222
  });
240
223
  });
241
224
  });
242
225
  });
243
226
 
244
227
  describe('fetcher', () => {
245
- it('uses a custom provided `fetcher`', async () => {
246
- const injectedFetch = fetch.mockJSONResponseOnce({
247
- data: { injected: true },
248
- });
249
- const DataSource = new RemoteGraphQLDataSource({
250
- url: 'https://api.example.com/foo',
251
- fetcher: injectedFetch,
252
- });
253
-
254
- const { data } = await DataSource.process({
255
- ...defaultProcessOptions,
256
- request: {
257
- query: '{ me { name } }',
258
- variables: { id: '1' },
259
- },
260
- });
261
-
262
- expect(injectedFetch).toHaveBeenCalled();
263
- expect(data).toEqual({ injected: true });
264
- });
265
-
266
- it('supports a custom fetcher, like `node-fetch`', async () => {
267
- const injectedFetch = customFetcher.mockJSONResponseOnce({
268
- data: { me: 'james' },
269
- });
228
+ it('supports a custom fetcher', async () => {
270
229
  const DataSource = new RemoteGraphQLDataSource({
271
230
  url: 'https://api.example.com/foo',
272
- fetcher: injectedFetch,
231
+ fetcher: async () =>
232
+ new Response(JSON.stringify({ data: { me: 'james' } }), {
233
+ status: 200,
234
+ headers: { 'content-type': 'application/json' },
235
+ }),
273
236
  });
274
237
 
275
238
  const { data } = await DataSource.process({
@@ -280,7 +243,6 @@ describe('fetcher', () => {
280
243
  },
281
244
  });
282
245
 
283
- expect(injectedFetch).toHaveBeenCalled();
284
246
  expect(data).toEqual({ me: 'james' });
285
247
  });
286
248
  });
@@ -294,7 +256,9 @@ describe('willSendRequest', () => {
294
256
  },
295
257
  });
296
258
 
297
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
259
+ nock('https://api.example.com')
260
+ .post('/foo', { query: '{ me { name } }', variables: { id: '2' } })
261
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
298
262
 
299
263
  const { data } = await DataSource.process({
300
264
  ...defaultProcessOptions,
@@ -305,12 +269,6 @@ describe('willSendRequest', () => {
305
269
  });
306
270
 
307
271
  expect(data).toEqual({ me: 'james' });
308
- expect(fetch).toHaveFetched('https://api.example.com/foo', {
309
- body: {
310
- query: '{ me { name } }',
311
- variables: { id: '2' },
312
- },
313
- });
314
272
  });
315
273
 
316
274
  it('accepts context', async () => {
@@ -326,7 +284,11 @@ describe('willSendRequest', () => {
326
284
  },
327
285
  });
328
286
 
329
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
287
+ nock('https://api.example.com', {
288
+ reqheaders: { 'x-user-id': '1234' },
289
+ })
290
+ .post('/foo', { query: '{ me { name } }', variables: { id: '1' } })
291
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
330
292
 
331
293
  const { data } = await DataSource.process({
332
294
  ...defaultProcessOptions,
@@ -338,15 +300,6 @@ describe('willSendRequest', () => {
338
300
  });
339
301
 
340
302
  expect(data).toEqual({ me: 'james' });
341
- expect(fetch).toHaveFetched('https://api.example.com/foo', {
342
- body: {
343
- query: '{ me { name } }',
344
- variables: { id: '1' },
345
- },
346
- headers: {
347
- 'x-user-id': '1234',
348
- },
349
- });
350
303
  });
351
304
  });
352
305
 
@@ -364,7 +317,7 @@ describe('didReceiveResponse', () => {
364
317
  response,
365
318
  }: Required<
366
319
  Pick<
367
- GraphQLRequestContext<MyContext>,
320
+ GatewayGraphQLRequestContext<MyContext>,
368
321
  'request' | 'response' | 'context'
369
322
  >
370
323
  >) {
@@ -379,7 +332,9 @@ describe('didReceiveResponse', () => {
379
332
 
380
333
  const DataSource = new MyDataSource();
381
334
 
382
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
335
+ nock('https://api.example.com')
336
+ .post('/foo', { query: '{ me { name } }', variables: { id: '1' } })
337
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
383
338
 
384
339
  const context: MyContext = { surrogateKeys: [] };
385
340
  await DataSource.process({
@@ -407,7 +362,7 @@ describe('didReceiveResponse', () => {
407
362
  response,
408
363
  }: Required<
409
364
  Pick<
410
- GraphQLRequestContext<MyContext>,
365
+ GatewayGraphQLRequestContext<MyContext>,
411
366
  'request' | 'response' | 'context'
412
367
  >
413
368
  >) {
@@ -418,7 +373,9 @@ describe('didReceiveResponse', () => {
418
373
  const DataSource = new MyDataSource();
419
374
  const spyDidReceiveResponse = jest.spyOn(DataSource, 'didReceiveResponse');
420
375
 
421
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
376
+ nock('https://api.example.com')
377
+ .post('/foo', { query: '{ me { name } }', variables: { id: '1' } })
378
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
422
379
 
423
380
  await DataSource.process({
424
381
  ...defaultProcessOptions,
@@ -441,7 +398,7 @@ describe('didReceiveResponse', () => {
441
398
  response,
442
399
  }: Required<
443
400
  Pick<
444
- GraphQLRequestContext<MyContext>,
401
+ GatewayGraphQLRequestContext<MyContext>,
445
402
  'request' | 'response' | 'context'
446
403
  >
447
404
  >) {
@@ -452,7 +409,9 @@ describe('didReceiveResponse', () => {
452
409
  const DataSource = new MyDataSource();
453
410
  const spyDidReceiveResponse = jest.spyOn(DataSource, 'didReceiveResponse');
454
411
 
455
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
412
+ nock('https://api.example.com')
413
+ .post('/foo')
414
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
456
415
 
457
416
  await DataSource.process({
458
417
  ...defaultProcessOptions,
@@ -483,7 +442,7 @@ describe('didEncounterError', () => {
483
442
 
484
443
  const DataSource = new MyDataSource();
485
444
 
486
- fetch.mockResponseOnce('Invalid token', undefined, 401);
445
+ nock('https://api.example.com').post('/foo').reply(401, 'Invalid token');
487
446
 
488
447
  const context: MyContext = { timingData: [] };
489
448
  const result = DataSource.process({
@@ -493,11 +452,11 @@ describe('didEncounterError', () => {
493
452
  },
494
453
  incomingRequestContext: {
495
454
  context,
496
- } as GraphQLRequestContext<MyContext>,
455
+ } as GatewayGraphQLRequestContext<MyContext>,
497
456
  context,
498
457
  });
499
458
 
500
- await expect(result).rejects.toThrow(AuthenticationError);
459
+ await expect(result).rejects.toThrow(GraphQLError);
501
460
  expect(context).toMatchObject({
502
461
  timingData: [{ time: 1616446845234 }],
503
462
  });
@@ -505,18 +464,18 @@ describe('didEncounterError', () => {
505
464
  });
506
465
 
507
466
  describe('error handling', () => {
508
- it('throws an AuthenticationError when the response status is 401', async () => {
467
+ it('throws error with code UNAUTHENTICATED when the response status is 401', async () => {
509
468
  const DataSource = new RemoteGraphQLDataSource({
510
469
  url: 'https://api.example.com/foo',
511
470
  });
512
471
 
513
- fetch.mockResponseOnce('Invalid token', undefined, 401);
472
+ nock('https://api.example.com').post('/foo').reply(401, 'Invalid token');
514
473
 
515
474
  const result = DataSource.process({
516
475
  ...defaultProcessOptions,
517
476
  request: { query: '{ me { name } }' },
518
477
  });
519
- await expect(result).rejects.toThrow(AuthenticationError);
478
+ await expect(result).rejects.toThrow(GraphQLError);
520
479
  await expect(result).rejects.toMatchObject({
521
480
  extensions: {
522
481
  code: 'UNAUTHENTICATED',
@@ -528,18 +487,18 @@ describe('error handling', () => {
528
487
  });
529
488
  });
530
489
 
531
- it('throws a ForbiddenError when the response status is 403', async () => {
490
+ it('throws an error with code FORBIDDEN when the response status is 403', async () => {
532
491
  const DataSource = new RemoteGraphQLDataSource({
533
492
  url: 'https://api.example.com/foo',
534
493
  });
535
494
 
536
- fetch.mockResponseOnce('No access', undefined, 403);
495
+ nock('https://api.example.com').post('/foo').reply(403, 'No access');
537
496
 
538
497
  const result = DataSource.process({
539
498
  ...defaultProcessOptions,
540
499
  request: { query: '{ me { name } }' },
541
500
  });
542
- await expect(result).rejects.toThrow(ForbiddenError);
501
+ await expect(result).rejects.toThrow(GraphQLError);
543
502
  await expect(result).rejects.toMatchObject({
544
503
  extensions: {
545
504
  code: 'FORBIDDEN',
@@ -551,18 +510,18 @@ describe('error handling', () => {
551
510
  });
552
511
  });
553
512
 
554
- it('throws an ApolloError when the response status is 500', async () => {
513
+ it('throws a GraphQLError when the response status is 500', async () => {
555
514
  const DataSource = new RemoteGraphQLDataSource({
556
515
  url: 'https://api.example.com/foo',
557
516
  });
558
517
 
559
- fetch.mockResponseOnce('Oops', undefined, 500);
518
+ nock('https://api.example.com').post('/foo').reply(500, 'Oops');
560
519
 
561
520
  const result = DataSource.process({
562
521
  ...defaultProcessOptions,
563
522
  request: { query: '{ me { name } }' },
564
523
  });
565
- await expect(result).rejects.toThrow(ApolloError);
524
+ await expect(result).rejects.toThrow(GraphQLError);
566
525
  await expect(result).rejects.toMatchObject({
567
526
  extensions: {
568
527
  response: {
@@ -578,23 +537,25 @@ describe('error handling', () => {
578
537
  url: 'https://api.example.com/foo',
579
538
  });
580
539
 
581
- fetch.mockResponseOnce(
582
- JSON.stringify({
583
- errors: [
584
- {
585
- message: 'Houston, we have a problem.',
586
- },
587
- ],
588
- }),
589
- { 'Content-Type': 'application/json' },
590
- 500,
591
- );
540
+ nock('https://api.example.com')
541
+ .post('/foo')
542
+ .reply(
543
+ 500,
544
+ {
545
+ errors: [
546
+ {
547
+ message: 'Houston, we have a problem.',
548
+ },
549
+ ],
550
+ },
551
+ { 'Content-Type': 'application/json' },
552
+ );
592
553
 
593
554
  const result = DataSource.process({
594
555
  ...defaultProcessOptions,
595
556
  request: { query: '{ me { name } }' },
596
557
  });
597
- await expect(result).rejects.toThrow(ApolloError);
558
+ await expect(result).rejects.toThrow(GraphQLError);
598
559
  await expect(result).rejects.toMatchObject({
599
560
  extensions: {
600
561
  response: {
@@ -1,11 +1,11 @@
1
- import { GraphQLResponse, GraphQLRequestContext } from 'apollo-server-types';
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<GraphQLResponse>;
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: GraphQLRequestContext<TContext>['request'];
23
+ request: GatewayGraphQLRequestContext<TContext>['request'];
24
24
  } & (
25
25
  | {
26
26
  kind: GraphQLDataSourceRequestKind.INCOMING_OPERATION;
@@ -28,8 +28,15 @@ export type GraphQLDataSourceProcessOptions<
28
28
  * The GraphQLRequestContext for the operation received by the gateway, or
29
29
  * one of the strings if this operation is generated by the gateway without an
30
30
  * incoming request.
31
+ *
32
+ * For backwards compatibility with Apollo Server 2, `overallCachePolicy` needs
33
+ * to be treated as optional.
31
34
  */
32
- incomingRequestContext: GraphQLRequestContext<TContext>;
35
+ incomingRequestContext: Omit<
36
+ GatewayGraphQLRequestContext<TContext>,
37
+ 'overallCachePolicy'
38
+ > &
39
+ Pick<Partial<GatewayGraphQLRequestContext<TContext>>, 'overallCachePolicy'>;
33
40
  /**
34
41
  * Equivalent to incomingRequestContext.context (provided here for
35
42
  * backwards compatibility): the object created by the Apollo Server
@@ -38,7 +45,7 @@ export type GraphQLDataSourceProcessOptions<
38
45
  * @deprecated Use `incomingRequestContext.context` instead (after
39
46
  * checking `kind`).
40
47
  */
41
- context: GraphQLRequestContext<TContext>['context'];
48
+ context: GatewayGraphQLRequestContext<TContext>['context'];
42
49
  }
43
50
  | {
44
51
  kind:
@@ -1,9 +1,4 @@
1
- import {
2
- GraphQLExecutionResult,
3
- GraphQLRequestContext,
4
- VariableValues,
5
- } from 'apollo-server-types';
6
- import { Headers } from 'apollo-server-env';
1
+ import { Headers } from 'node-fetch';
7
2
  import {
8
3
  execute,
9
4
  GraphQLError,
@@ -34,6 +29,7 @@ import { deepMerge } from './utilities/deepMerge';
34
29
  import { isNotNullOrUndefined } from './utilities/array';
35
30
  import { SpanStatusCode } from "@opentelemetry/api";
36
31
  import { OpenTelemetrySpanNames, tracer } from "./utilities/opentelemetry";
32
+ import { GatewayGraphQLRequestContext, GatewayExecutionResult } from '@apollo/server-gateway-interface';
37
33
 
38
34
  export type ServiceMap = {
39
35
  [serviceName: string]: GraphQLDataSource;
@@ -41,20 +37,20 @@ export type ServiceMap = {
41
37
 
42
38
  type ResultMap = Record<string, any>;
43
39
 
44
- interface ExecutionContext<TContext> {
40
+ interface ExecutionContext {
45
41
  queryPlan: QueryPlan;
46
42
  operationContext: OperationContext;
47
43
  serviceMap: ServiceMap;
48
- requestContext: GraphQLRequestContext<TContext>;
44
+ requestContext: GatewayGraphQLRequestContext;
49
45
  errors: GraphQLError[];
50
46
  }
51
47
 
52
- export async function executeQueryPlan<TContext>(
48
+ export async function executeQueryPlan(
53
49
  queryPlan: QueryPlan,
54
50
  serviceMap: ServiceMap,
55
- requestContext: GraphQLRequestContext<TContext>,
51
+ requestContext: GatewayGraphQLRequestContext,
56
52
  operationContext: OperationContext,
57
- ): Promise<GraphQLExecutionResult> {
53
+ ): Promise<GatewayExecutionResult> {
58
54
 
59
55
  const logger = requestContext.logger || console;
60
56
 
@@ -62,7 +58,7 @@ export async function executeQueryPlan<TContext>(
62
58
  try {
63
59
  const errors: GraphQLError[] = [];
64
60
 
65
- const context: ExecutionContext<TContext> = {
61
+ const context: ExecutionContext = {
66
62
  queryPlan,
67
63
  operationContext,
68
64
  serviceMap,
@@ -171,8 +167,8 @@ export async function executeQueryPlan<TContext>(
171
167
  // we're going to ignore it, because it makes the code much simpler and more
172
168
  // typesafe. However, it doesn't actually ask for traces from the backend
173
169
  // service unless we are capturing traces for Studio.
174
- async function executeNode<TContext>(
175
- context: ExecutionContext<TContext>,
170
+ async function executeNode(
171
+ context: ExecutionContext,
176
172
  node: PlanNode,
177
173
  results: ResultMap | ResultMap[],
178
174
  path: ResponsePath,
@@ -261,7 +257,7 @@ async function executeNode<TContext>(
261
257
 
262
258
  export function shouldSkipFetchNode(
263
259
  node: FetchNode,
264
- variables: VariableValues = {},
260
+ variables: Record<string, any> = {},
265
261
  ) {
266
262
  if (!node.inclusionConditions) return false;
267
263
 
@@ -284,8 +280,8 @@ export function shouldSkipFetchNode(
284
280
  });
285
281
  }
286
282
 
287
- async function executeFetch<TContext>(
288
- context: ExecutionContext<TContext>,
283
+ async function executeFetch(
284
+ context: ExecutionContext,
289
285
  fetch: FetchNode,
290
286
  results: ResultMap | (ResultMap | null | undefined)[],
291
287
  _path: ResponsePath,
@@ -404,13 +400,17 @@ async function executeFetch<TContext>(
404
400
  }
405
401
  });
406
402
  async function sendOperation(
407
- context: ExecutionContext<TContext>,
403
+ context: ExecutionContext,
408
404
  source: string,
409
405
  variables: Record<string, any>,
410
406
  operationName: string | undefined,
411
407
  ): Promise<ResultMap | void | null> {
412
408
  // We declare this as 'any' because it is missing url and method, which
413
409
  // GraphQLRequest.http is supposed to have if it exists.
410
+ // (This is admittedly kinda weird, since we currently do pass url and
411
+ // method to `process` from the SDL fetching call site, but presumably
412
+ // existing implementation of the interface don't try to look for these
413
+ // fields. RemoteGraphQLDataSource just overwrites them.)
414
414
  let http: any;
415
415
 
416
416
  // If we're capturing a trace for Studio, then save the operation text to