@apollo/gateway 2.0.2-alpha.0 → 2.0.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.
Files changed (47) hide show
  1. package/dist/config.d.ts +2 -2
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/datasources/RemoteGraphQLDataSource.d.ts +6 -5
  4. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  5. package/dist/datasources/RemoteGraphQLDataSource.js +16 -11
  6. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  7. package/dist/executeQueryPlan.d.ts.map +1 -1
  8. package/dist/executeQueryPlan.js +2 -2
  9. package/dist/executeQueryPlan.js.map +1 -1
  10. package/dist/index.d.ts +0 -4
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -22
  13. package/dist/index.js.map +1 -1
  14. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +2 -2
  15. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -1
  16. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -1
  17. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +3 -3
  18. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -1
  19. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js +14 -13
  20. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -1
  21. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +6 -5
  22. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -1
  23. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +3 -3
  24. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -1
  25. package/package.json +7 -7
  26. package/src/__tests__/gateway/buildService.test.ts +81 -83
  27. package/src/__tests__/gateway/executor.test.ts +20 -17
  28. package/src/__tests__/gateway/opentelemetry.test.ts +3 -7
  29. package/src/__tests__/gateway/supergraphSdl.test.ts +6 -11
  30. package/src/config.ts +2 -2
  31. package/src/datasources/RemoteGraphQLDataSource.ts +32 -16
  32. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +106 -140
  33. package/src/executeQueryPlan.ts +5 -1
  34. package/src/index.ts +3 -28
  35. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -6
  36. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +70 -74
  37. package/src/supergraphManagers/UplinkFetcher/index.ts +2 -2
  38. package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +23 -17
  39. package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +9 -7
  40. package/dist/cache.d.ts +0 -18
  41. package/dist/cache.d.ts.map +0 -1
  42. package/dist/cache.js +0 -46
  43. package/dist/cache.js.map +0 -1
  44. package/src/__mocks__/apollo-server-env.ts +0 -56
  45. package/src/__mocks__/make-fetch-happen-fetcher.ts +0 -57
  46. package/src/cache.ts +0 -66
  47. package/src/make-fetch-happen.d.ts +0 -59
@@ -12,17 +12,19 @@ import {
12
12
  AuthenticationError,
13
13
  ForbiddenError,
14
14
  } from 'apollo-server-errors';
15
- import { fetch, Request, Headers, Response } from 'apollo-server-env';
16
15
  import { isObject } from '../utilities/predicates';
17
16
  import { GraphQLDataSource, GraphQLDataSourceProcessOptions, GraphQLDataSourceRequestKind } from './types';
18
17
  import { createHash } from '@apollo/utils.createhash';
19
18
  import { parseCacheControlHeader } from './parseCacheControlHeader';
20
19
  import fetcher from 'make-fetch-happen';
20
+ import { Headers as NodeFetchHeaders, Request as NodeFetchRequest } from 'node-fetch';
21
+ import { Fetcher, FetcherRequestInit, FetcherResponse } from '@apollo/utils.fetcher';
22
+
21
23
  export class RemoteGraphQLDataSource<
22
24
  TContext extends Record<string, any> = Record<string, any>,
23
25
  > implements GraphQLDataSource<TContext>
24
26
  {
25
- fetcher: typeof fetch;
27
+ fetcher: Fetcher;
26
28
 
27
29
  constructor(
28
30
  config?: Partial<RemoteGraphQLDataSource<TContext>> &
@@ -30,6 +32,11 @@ export class RemoteGraphQLDataSource<
30
32
  ThisType<RemoteGraphQLDataSource<TContext>>,
31
33
  ) {
32
34
  this.fetcher = fetcher.defaults({
35
+ // Allow an arbitrary number of sockets per subgraph. This is the default
36
+ // behavior of Node's http.Agent as well as the npm package agentkeepalive
37
+ // which wraps it, but is not the default behavior of make-fetch-happen
38
+ // which wraps agentkeepalive (that package sets this to 15 by default).
39
+ maxSockets: Infinity,
33
40
  // although this is the default, we want to take extra care and be very
34
41
  // explicity to ensure that mutations cannot be retried. please leave this
35
42
  // intact.
@@ -82,7 +89,12 @@ export class RemoteGraphQLDataSource<
82
89
  const context = originalContext as TContext;
83
90
 
84
91
  // Respect incoming http headers (eg, apollo-federation-include-trace).
85
- const headers = (request.http && request.http.headers) || new Headers();
92
+ const headers = new NodeFetchHeaders();
93
+ if (request.http?.headers) {
94
+ for (const [name, value] of request.http.headers) {
95
+ headers.append(name, value);
96
+ }
97
+ }
86
98
  headers.set('Content-Type', 'application/json');
87
99
 
88
100
  request.http = {
@@ -177,20 +189,24 @@ export class RemoteGraphQLDataSource<
177
189
  // we're accessing (e.g. url) and what we access it with (e.g. headers).
178
190
  const { http, ...requestWithoutHttp } = request;
179
191
  const stringifiedRequestWithoutHttp = JSON.stringify(requestWithoutHttp);
180
- const fetchRequest = new Request(http.url, {
181
- ...http,
192
+ const requestInit: FetcherRequestInit = {
193
+ method: http.method,
194
+ headers: Object.fromEntries(http.headers),
182
195
  body: stringifiedRequestWithoutHttp,
183
- });
196
+ };
197
+ // Note that we don't actually send this Request object to the fetcher; it
198
+ // is merely sent to methods on this object that might be overridden by users.
199
+ // We are careful to only send data to the overridable fetcher function that uses
200
+ // plain JS objects --- some fetch implementations don't know how to handle
201
+ // Request or Headers objects created by other fetch implementations.
202
+ const fetchRequest = new NodeFetchRequest(http.url, requestInit);
184
203
 
185
- let fetchResponse: Response | undefined;
204
+ let fetchResponse: FetcherResponse | undefined;
186
205
 
187
206
  try {
188
207
  // Use our local `fetcher` to allow for fetch injection
189
208
  // Use the fetcher's `Request` implementation for compatibility
190
- fetchResponse = await this.fetcher(http.url, {
191
- ...http,
192
- body: stringifiedRequestWithoutHttp,
193
- });
209
+ fetchResponse = await this.fetcher(http.url, requestInit);
194
210
 
195
211
  if (!fetchResponse.ok) {
196
212
  throw await this.errorFromResponse(fetchResponse);
@@ -266,16 +282,16 @@ export class RemoteGraphQLDataSource<
266
282
 
267
283
  public didEncounterError(
268
284
  error: Error,
269
- _fetchRequest: Request,
270
- _fetchResponse?: Response,
285
+ _fetchRequest: NodeFetchRequest,
286
+ _fetchResponse?: FetcherResponse,
271
287
  _context?: TContext,
272
288
  ) {
273
289
  throw error;
274
290
  }
275
291
 
276
292
  public parseBody(
277
- fetchResponse: Response,
278
- _fetchRequest?: Request,
293
+ fetchResponse: FetcherResponse,
294
+ _fetchRequest?: NodeFetchRequest,
279
295
  _context?: TContext,
280
296
  ): Promise<object | string> {
281
297
  const contentType = fetchResponse.headers.get('Content-Type');
@@ -286,7 +302,7 @@ export class RemoteGraphQLDataSource<
286
302
  }
287
303
  }
288
304
 
289
- public async errorFromResponse(response: Response) {
305
+ public async errorFromResponse(response: FetcherResponse) {
290
306
  const message = `${response.status}: ${response.statusText}`;
291
307
 
292
308
  let error: ApolloError;
@@ -1,6 +1,3 @@
1
- import { fetch as customFetcher } from '../../__mocks__/apollo-server-env';
2
- import { fetch } from '../../__mocks__/make-fetch-happen-fetcher';
3
-
4
1
  import {
5
2
  ApolloError,
6
3
  AuthenticationError,
@@ -8,13 +5,18 @@ import {
8
5
  } from 'apollo-server-errors';
9
6
 
10
7
  import { RemoteGraphQLDataSource } from '../RemoteGraphQLDataSource';
11
- import { Headers } from 'apollo-server-env';
8
+ import { Response, Headers } from 'node-fetch';
12
9
  import { GraphQLRequestContext } from 'apollo-server-types';
13
10
  import { GraphQLDataSourceRequestKind } from '../types';
11
+ import { nockBeforeEach, nockAfterEach } from '../../__tests__/nockAssertions';
12
+ import nock from 'nock';
14
13
 
15
- beforeEach(() => {
16
- fetch.mockReset();
17
- });
14
+ beforeEach(nockBeforeEach);
15
+ afterEach(nockAfterEach);
16
+
17
+ const replyHeaders = {
18
+ 'content-type': 'application/json',
19
+ };
18
20
 
19
21
  // Right now, none of these tests care what's on incomingRequestContext, so we
20
22
  // pass this fake one in.
@@ -32,7 +34,9 @@ describe('constructing requests', () => {
32
34
  apq: false,
33
35
  });
34
36
 
35
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
37
+ nock('https://api.example.com')
38
+ .post('/foo', { query: '{ me { name } }' })
39
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
36
40
 
37
41
  const { data } = await DataSource.process({
38
42
  ...defaultProcessOptions,
@@ -40,10 +44,6 @@ describe('constructing requests', () => {
40
44
  });
41
45
 
42
46
  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
47
  });
48
48
 
49
49
  it('passes variables', async () => {
@@ -52,7 +52,9 @@ describe('constructing requests', () => {
52
52
  apq: false,
53
53
  });
54
54
 
55
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
55
+ nock('https://api.example.com')
56
+ .post('/foo', { query: '{ me { name } }', variables: { id: '1' } })
57
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
56
58
 
57
59
  const { data } = await DataSource.process({
58
60
  ...defaultProcessOptions,
@@ -63,10 +65,6 @@ describe('constructing requests', () => {
63
65
  });
64
66
 
65
67
  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
68
  });
71
69
  });
72
70
 
@@ -101,28 +99,18 @@ describe('constructing requests', () => {
101
99
  apq: true,
102
100
  });
103
101
 
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: {
102
+ nock('https://api.example.com')
103
+ .post('/foo', {
116
104
  extensions: {
117
105
  persistedQuery: {
118
106
  version: 1,
119
107
  sha256Hash,
120
108
  },
121
109
  },
122
- },
123
- });
124
- expect(fetch).toHaveFetchedNth(2, 'https://api.example.com/foo', {
125
- body: {
110
+ })
111
+ .reply(200, apqNotFoundResponse, replyHeaders);
112
+ nock('https://api.example.com')
113
+ .post('/foo', {
126
114
  query,
127
115
  extensions: {
128
116
  persistedQuery: {
@@ -130,8 +118,15 @@ describe('constructing requests', () => {
130
118
  sha256Hash,
131
119
  },
132
120
  },
133
- },
121
+ })
122
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
123
+
124
+ const { data } = await DataSource.process({
125
+ ...defaultProcessOptions,
126
+ request: { query },
134
127
  });
128
+
129
+ expect(data).toEqual({ me: 'james' });
135
130
  });
136
131
 
137
132
  it('passes variables', async () => {
@@ -140,21 +135,8 @@ describe('constructing requests', () => {
140
135
  apq: true,
141
136
  });
142
137
 
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: {
138
+ nock('https://api.example.com')
139
+ .post('/foo', {
158
140
  variables: { id: '1' },
159
141
  extensions: {
160
142
  persistedQuery: {
@@ -162,10 +144,10 @@ describe('constructing requests', () => {
162
144
  sha256Hash,
163
145
  },
164
146
  },
165
- },
166
- });
167
- expect(fetch).toHaveFetchedNth(2, 'https://api.example.com/foo', {
168
- body: {
147
+ })
148
+ .reply(200, apqNotFoundResponse, replyHeaders);
149
+ nock('https://api.example.com')
150
+ .post('/foo', {
169
151
  query,
170
152
  variables: { id: '1' },
171
153
  extensions: {
@@ -174,8 +156,18 @@ describe('constructing requests', () => {
174
156
  sha256Hash,
175
157
  },
176
158
  },
159
+ })
160
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
161
+
162
+ const { data } = await DataSource.process({
163
+ ...defaultProcessOptions,
164
+ request: {
165
+ query,
166
+ variables: { id: '1' },
177
167
  },
178
168
  });
169
+
170
+ expect(data).toEqual({ me: 'james' });
179
171
  });
180
172
  });
181
173
 
@@ -186,25 +178,23 @@ describe('constructing requests', () => {
186
178
  apq: true,
187
179
  });
188
180
 
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: {
181
+ nock('https://api.example.com')
182
+ .post('/foo', {
200
183
  extensions: {
201
184
  persistedQuery: {
202
185
  version: 1,
203
186
  sha256Hash,
204
187
  },
205
188
  },
206
- },
189
+ })
190
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
191
+
192
+ const { data } = await DataSource.process({
193
+ ...defaultProcessOptions,
194
+ request: { query },
207
195
  });
196
+
197
+ expect(data).toEqual({ me: 'james' });
208
198
  });
209
199
 
210
200
  it('passes variables', async () => {
@@ -213,7 +203,17 @@ describe('constructing requests', () => {
213
203
  apq: true,
214
204
  });
215
205
 
216
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
206
+ nock('https://api.example.com')
207
+ .post('/foo', {
208
+ variables: { id: '1' },
209
+ extensions: {
210
+ persistedQuery: {
211
+ version: 1,
212
+ sha256Hash,
213
+ },
214
+ },
215
+ })
216
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
217
217
 
218
218
  const { data } = await DataSource.process({
219
219
  ...defaultProcessOptions,
@@ -224,52 +224,20 @@ describe('constructing requests', () => {
224
224
  });
225
225
 
226
226
  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
227
  });
240
228
  });
241
229
  });
242
230
  });
243
231
 
244
232
  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
- });
233
+ it('supports a custom fetcher', async () => {
270
234
  const DataSource = new RemoteGraphQLDataSource({
271
235
  url: 'https://api.example.com/foo',
272
- fetcher: injectedFetch,
236
+ fetcher: async () =>
237
+ new Response(JSON.stringify({ data: { me: 'james' } }), {
238
+ status: 200,
239
+ headers: { 'content-type': 'application/json' },
240
+ }),
273
241
  });
274
242
 
275
243
  const { data } = await DataSource.process({
@@ -280,7 +248,6 @@ describe('fetcher', () => {
280
248
  },
281
249
  });
282
250
 
283
- expect(injectedFetch).toHaveBeenCalled();
284
251
  expect(data).toEqual({ me: 'james' });
285
252
  });
286
253
  });
@@ -294,7 +261,9 @@ describe('willSendRequest', () => {
294
261
  },
295
262
  });
296
263
 
297
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
264
+ nock('https://api.example.com')
265
+ .post('/foo', { query: '{ me { name } }', variables: { id: '2' } })
266
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
298
267
 
299
268
  const { data } = await DataSource.process({
300
269
  ...defaultProcessOptions,
@@ -305,12 +274,6 @@ describe('willSendRequest', () => {
305
274
  });
306
275
 
307
276
  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
277
  });
315
278
 
316
279
  it('accepts context', async () => {
@@ -326,7 +289,11 @@ describe('willSendRequest', () => {
326
289
  },
327
290
  });
328
291
 
329
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
292
+ nock('https://api.example.com', {
293
+ reqheaders: { 'x-user-id': '1234' },
294
+ })
295
+ .post('/foo', { query: '{ me { name } }', variables: { id: '1' } })
296
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
330
297
 
331
298
  const { data } = await DataSource.process({
332
299
  ...defaultProcessOptions,
@@ -338,15 +305,6 @@ describe('willSendRequest', () => {
338
305
  });
339
306
 
340
307
  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
308
  });
351
309
  });
352
310
 
@@ -379,7 +337,9 @@ describe('didReceiveResponse', () => {
379
337
 
380
338
  const DataSource = new MyDataSource();
381
339
 
382
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
340
+ nock('https://api.example.com')
341
+ .post('/foo', { query: '{ me { name } }', variables: { id: '1' } })
342
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
383
343
 
384
344
  const context: MyContext = { surrogateKeys: [] };
385
345
  await DataSource.process({
@@ -418,7 +378,9 @@ describe('didReceiveResponse', () => {
418
378
  const DataSource = new MyDataSource();
419
379
  const spyDidReceiveResponse = jest.spyOn(DataSource, 'didReceiveResponse');
420
380
 
421
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
381
+ nock('https://api.example.com')
382
+ .post('/foo', { query: '{ me { name } }', variables: { id: '1' } })
383
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
422
384
 
423
385
  await DataSource.process({
424
386
  ...defaultProcessOptions,
@@ -452,7 +414,9 @@ describe('didReceiveResponse', () => {
452
414
  const DataSource = new MyDataSource();
453
415
  const spyDidReceiveResponse = jest.spyOn(DataSource, 'didReceiveResponse');
454
416
 
455
- fetch.mockJSONResponseOnce({ data: { me: 'james' } });
417
+ nock('https://api.example.com')
418
+ .post('/foo')
419
+ .reply(200, { data: { me: 'james' } }, replyHeaders);
456
420
 
457
421
  await DataSource.process({
458
422
  ...defaultProcessOptions,
@@ -483,7 +447,7 @@ describe('didEncounterError', () => {
483
447
 
484
448
  const DataSource = new MyDataSource();
485
449
 
486
- fetch.mockResponseOnce('Invalid token', undefined, 401);
450
+ nock('https://api.example.com').post('/foo').reply(401, 'Invalid token');
487
451
 
488
452
  const context: MyContext = { timingData: [] };
489
453
  const result = DataSource.process({
@@ -510,7 +474,7 @@ describe('error handling', () => {
510
474
  url: 'https://api.example.com/foo',
511
475
  });
512
476
 
513
- fetch.mockResponseOnce('Invalid token', undefined, 401);
477
+ nock('https://api.example.com').post('/foo').reply(401, 'Invalid token');
514
478
 
515
479
  const result = DataSource.process({
516
480
  ...defaultProcessOptions,
@@ -533,7 +497,7 @@ describe('error handling', () => {
533
497
  url: 'https://api.example.com/foo',
534
498
  });
535
499
 
536
- fetch.mockResponseOnce('No access', undefined, 403);
500
+ nock('https://api.example.com').post('/foo').reply(403, 'No access');
537
501
 
538
502
  const result = DataSource.process({
539
503
  ...defaultProcessOptions,
@@ -556,7 +520,7 @@ describe('error handling', () => {
556
520
  url: 'https://api.example.com/foo',
557
521
  });
558
522
 
559
- fetch.mockResponseOnce('Oops', undefined, 500);
523
+ nock('https://api.example.com').post('/foo').reply(500, 'Oops');
560
524
 
561
525
  const result = DataSource.process({
562
526
  ...defaultProcessOptions,
@@ -578,17 +542,19 @@ describe('error handling', () => {
578
542
  url: 'https://api.example.com/foo',
579
543
  });
580
544
 
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
- );
545
+ nock('https://api.example.com')
546
+ .post('/foo')
547
+ .reply(
548
+ 500,
549
+ {
550
+ errors: [
551
+ {
552
+ message: 'Houston, we have a problem.',
553
+ },
554
+ ],
555
+ },
556
+ { 'Content-Type': 'application/json' },
557
+ );
592
558
 
593
559
  const result = DataSource.process({
594
560
  ...defaultProcessOptions,
@@ -2,7 +2,7 @@ import {
2
2
  GraphQLExecutionResult,
3
3
  GraphQLRequestContext,
4
4
  } from 'apollo-server-types';
5
- import { Headers } from 'apollo-server-env';
5
+ import { Headers } from 'node-fetch';
6
6
  import {
7
7
  execute,
8
8
  GraphQLError,
@@ -380,6 +380,10 @@ async function executeFetch<TContext>(
380
380
  ): Promise<ResultMap | void | null> {
381
381
  // We declare this as 'any' because it is missing url and method, which
382
382
  // GraphQLRequest.http is supposed to have if it exists.
383
+ // (This is admittedly kinda weird, since we currently do pass url and
384
+ // method to `process` from the SDL fetching call site, but presumably
385
+ // existing implementation of the interface don't try to look for these
386
+ // fields. RemoteGraphQLDataSource just overwrites them.)
383
387
  let http: any;
384
388
 
385
389
  // If we're capturing a trace for Studio, then save the operation text to
package/src/index.ts CHANGED
@@ -27,8 +27,6 @@ import {
27
27
  import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource';
28
28
  import { getVariableValues } from 'graphql/execution/values';
29
29
  import fetcher from 'make-fetch-happen';
30
- import { HttpRequestCache } from './cache';
31
- import { fetch } from 'apollo-server-env';
32
30
  import {
33
31
  QueryPlanner,
34
32
  QueryPlan,
@@ -67,6 +65,7 @@ import {
67
65
  Schema,
68
66
  ServiceDefinition,
69
67
  } from '@apollo/federation-internals';
68
+ import { Fetcher } from '@apollo/utils.fetcher';
70
69
 
71
70
  type DataSourceMap = {
72
71
  [serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
@@ -81,30 +80,6 @@ type WarnedStates = {
81
80
  remoteWithLocalConfig?: boolean;
82
81
  };
83
82
 
84
- export function getDefaultFetcher() {
85
- const { name, version } = require('../package.json');
86
- return fetcher.defaults({
87
- cacheManager: new HttpRequestCache(),
88
- // All headers should be lower-cased here, as `make-fetch-happen`
89
- // treats differently cased headers as unique (unlike the `Headers` object).
90
- // @see: https://git.io/JvRUa
91
- headers: {
92
- 'apollographql-client-name': name,
93
- 'apollographql-client-version': version,
94
- 'user-agent': `${name}/${version}`,
95
- 'content-type': 'application/json',
96
- },
97
- retry: {
98
- retries: 5,
99
- // The default factor: expected attempts at 0, 1, 3, 7, 15, and 31 seconds elapsed
100
- factor: 2,
101
- // 1 second
102
- minTimeout: 1000,
103
- randomize: true,
104
- },
105
- });
106
- }
107
-
108
83
  export const HEALTH_CHECK_QUERY =
109
84
  'query __ApolloServiceHealthCheck__ { __typename }';
110
85
  export const SERVICE_DEFINITION_QUERY =
@@ -170,7 +145,7 @@ export class ApolloGateway implements GraphQLService {
170
145
  private warnedStates: WarnedStates = Object.create(null);
171
146
  private queryPlanner?: QueryPlanner;
172
147
  private supergraphSdl?: string;
173
- private fetcher: typeof fetch;
148
+ private fetcher: Fetcher;
174
149
  private compositionId?: string;
175
150
  private state: GatewayState;
176
151
 
@@ -198,7 +173,7 @@ export class ApolloGateway implements GraphQLService {
198
173
  this.queryPlanStore = this.initQueryPlanStore(
199
174
  config?.experimental_approximateQueryPlanStoreMiB,
200
175
  );
201
- this.fetcher = config?.fetcher || getDefaultFetcher();
176
+ this.fetcher = config?.fetcher || fetcher;
202
177
 
203
178
  // set up experimental observability callbacks and config settings
204
179
  this.experimental_didResolveQueryPlan =
@@ -119,11 +119,6 @@ describe('IntrospectAndCompose', () => {
119
119
 
120
120
  // TODO: useFakeTimers (though I'm struggling to get this to work as expected)
121
121
  it("doesn't call `update` when there's no change to the supergraph", async () => {
122
- const fetcher =
123
- jest.requireActual<typeof import('apollo-server-env')>(
124
- 'apollo-server-env',
125
- ).fetch;
126
-
127
122
  // mock for initial load and a few polls against an unchanging schema
128
123
  mockAllServicesSdlQuerySuccess();
129
124
  mockAllServicesSdlQuerySuccess();
@@ -144,7 +139,6 @@ describe('IntrospectAndCompose', () => {
144
139
  getDataSource({ url }) {
145
140
  return new RemoteGraphQLDataSource({
146
141
  url,
147
- fetcher,
148
142
  });
149
143
  },
150
144
  });