@apollo/gateway 2.0.2-alpha.1 → 2.0.3
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 +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts +6 -5
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +16 -11
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- 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 +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -20
- package/dist/index.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 +7 -7
- package/src/__generated__/graphqlTypes.ts +1 -1
- package/src/__tests__/executeQueryPlan.test.ts +0 -1
- package/src/__tests__/gateway/buildService.test.ts +81 -83
- package/src/__tests__/gateway/executor.test.ts +20 -17
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +1 -1
- package/src/__tests__/gateway/opentelemetry.test.ts +3 -7
- package/src/__tests__/gateway/supergraphSdl.test.ts +6 -11
- package/src/config.ts +2 -2
- package/src/datasources/RemoteGraphQLDataSource.ts +32 -16
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +106 -140
- package/src/executeQueryPlan.ts +5 -1
- package/src/index.ts +3 -26
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -6
- 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/src/__mocks__/apollo-server-env.ts +0 -56
- package/src/__mocks__/make-fetch-happen-fetcher.ts +0 -57
- 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:
|
|
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 =
|
|
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
|
|
181
|
-
|
|
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:
|
|
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:
|
|
270
|
-
_fetchResponse?:
|
|
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:
|
|
278
|
-
_fetchRequest?:
|
|
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:
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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,
|
package/src/executeQueryPlan.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
GraphQLExecutionResult,
|
|
3
3
|
GraphQLRequestContext,
|
|
4
4
|
} from 'apollo-server-types';
|
|
5
|
-
import { Headers } from '
|
|
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,7 +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 { fetch } from 'apollo-server-env';
|
|
31
30
|
import {
|
|
32
31
|
QueryPlanner,
|
|
33
32
|
QueryPlan,
|
|
@@ -66,6 +65,7 @@ import {
|
|
|
66
65
|
Schema,
|
|
67
66
|
ServiceDefinition,
|
|
68
67
|
} from '@apollo/federation-internals';
|
|
68
|
+
import { Fetcher } from '@apollo/utils.fetcher';
|
|
69
69
|
|
|
70
70
|
type DataSourceMap = {
|
|
71
71
|
[serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
|
|
@@ -80,29 +80,6 @@ type WarnedStates = {
|
|
|
80
80
|
remoteWithLocalConfig?: boolean;
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
export function getDefaultFetcher() {
|
|
84
|
-
const { name, version } = require('../package.json');
|
|
85
|
-
return fetcher.defaults({
|
|
86
|
-
// All headers should be lower-cased here, as `make-fetch-happen`
|
|
87
|
-
// treats differently cased headers as unique (unlike the `Headers` object).
|
|
88
|
-
// @see: https://git.io/JvRUa
|
|
89
|
-
headers: {
|
|
90
|
-
'apollographql-client-name': name,
|
|
91
|
-
'apollographql-client-version': version,
|
|
92
|
-
'user-agent': `${name}/${version}`,
|
|
93
|
-
'content-type': 'application/json',
|
|
94
|
-
},
|
|
95
|
-
retry: {
|
|
96
|
-
retries: 5,
|
|
97
|
-
// The default factor: expected attempts at 0, 1, 3, 7, 15, and 31 seconds elapsed
|
|
98
|
-
factor: 2,
|
|
99
|
-
// 1 second
|
|
100
|
-
minTimeout: 1000,
|
|
101
|
-
randomize: true,
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
83
|
export const HEALTH_CHECK_QUERY =
|
|
107
84
|
'query __ApolloServiceHealthCheck__ { __typename }';
|
|
108
85
|
export const SERVICE_DEFINITION_QUERY =
|
|
@@ -168,7 +145,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
168
145
|
private warnedStates: WarnedStates = Object.create(null);
|
|
169
146
|
private queryPlanner?: QueryPlanner;
|
|
170
147
|
private supergraphSdl?: string;
|
|
171
|
-
private fetcher:
|
|
148
|
+
private fetcher: Fetcher;
|
|
172
149
|
private compositionId?: string;
|
|
173
150
|
private state: GatewayState;
|
|
174
151
|
|
|
@@ -196,7 +173,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
196
173
|
this.queryPlanStore = this.initQueryPlanStore(
|
|
197
174
|
config?.experimental_approximateQueryPlanStoreMiB,
|
|
198
175
|
);
|
|
199
|
-
this.fetcher = config?.fetcher ||
|
|
176
|
+
this.fetcher = config?.fetcher || fetcher;
|
|
200
177
|
|
|
201
178
|
// set up experimental observability callbacks and config settings
|
|
202
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
|
});
|