@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.
- package/dist/config.d.ts +4 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.d.ts +2 -2
- package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.js +0 -2
- package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts +11 -10
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +30 -31
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/datasources/types.d.ts +5 -5
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/executeQueryPlan.d.ts +3 -3
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +2 -2
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +7 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -35
- package/dist/index.js.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts +2 -2
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +3 -3
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js +14 -13
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +6 -5
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +3 -3
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -1
- package/dist/typings/graphql.d.ts +24 -0
- package/dist/typings/graphql.d.ts.map +1 -0
- package/dist/{schema-helper/resolverMap.js → typings/graphql.js} +1 -1
- package/dist/typings/graphql.js.map +1 -0
- package/package.json +11 -13
- package/src/__generated__/graphqlTypes.ts +1 -1
- package/src/__tests__/executeQueryPlan.test.ts +15 -6
- package/src/__tests__/execution-utils.ts +4 -4
- package/src/__tests__/gateway/buildService.test.ts +81 -83
- package/src/__tests__/gateway/endToEnd.test.ts +1 -1
- package/src/__tests__/gateway/executor.test.ts +20 -17
- package/src/__tests__/gateway/opentelemetry.test.ts +3 -7
- package/src/__tests__/gateway/reporting.test.ts +1 -1
- package/src/__tests__/gateway/supergraphSdl.test.ts +11 -13
- package/src/__tests__/integration/complex-key.test.ts +2 -2
- package/src/config.ts +4 -6
- package/src/datasources/LocalGraphQLDataSource.ts +2 -4
- package/src/datasources/RemoteGraphQLDataSource.ts +72 -60
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +3 -3
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +120 -159
- package/src/datasources/types.ts +12 -5
- package/src/executeQueryPlan.ts +18 -18
- package/src/index.ts +25 -71
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -6
- package/src/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts +2 -2
- package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +70 -74
- package/src/supergraphManagers/UplinkFetcher/index.ts +2 -2
- package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +23 -17
- package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +9 -7
- package/src/typings/graphql.ts +31 -0
- package/dist/cache.d.ts +0 -18
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js +0 -46
- package/dist/cache.js.map +0 -1
- package/dist/schema-helper/addResolversToSchema.d.ts +0 -4
- package/dist/schema-helper/addResolversToSchema.d.ts.map +0 -1
- package/dist/schema-helper/addResolversToSchema.js +0 -62
- package/dist/schema-helper/addResolversToSchema.js.map +0 -1
- package/dist/schema-helper/index.d.ts +0 -3
- package/dist/schema-helper/index.d.ts.map +0 -1
- package/dist/schema-helper/index.js +0 -19
- package/dist/schema-helper/index.js.map +0 -1
- package/dist/schema-helper/resolverMap.d.ts +0 -16
- package/dist/schema-helper/resolverMap.d.ts.map +0 -1
- package/dist/schema-helper/resolverMap.js.map +0 -1
- package/dist/utilities/createHash.d.ts +0 -2
- package/dist/utilities/createHash.d.ts.map +0 -1
- package/dist/utilities/createHash.js +0 -15
- package/dist/utilities/createHash.js.map +0 -1
- package/src/__mocks__/apollo-server-env.ts +0 -56
- package/src/__mocks__/make-fetch-happen-fetcher.ts +0 -57
- package/src/cache.ts +0 -66
- package/src/make-fetch-happen.d.ts +0 -59
- package/src/schema-helper/addResolversToSchema.ts +0 -83
- package/src/schema-helper/index.ts +0 -2
- package/src/schema-helper/resolverMap.ts +0 -23
- 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 '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
455
|
+
} as GatewayGraphQLRequestContext<MyContext>,
|
|
497
456
|
context,
|
|
498
457
|
});
|
|
499
458
|
|
|
500
|
-
await expect(result).rejects.toThrow(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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(
|
|
558
|
+
await expect(result).rejects.toThrow(GraphQLError);
|
|
598
559
|
await expect(result).rejects.toMatchObject({
|
|
599
560
|
extensions: {
|
|
600
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;
|
|
@@ -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:
|
|
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:
|
|
48
|
+
context: GatewayGraphQLRequestContext<TContext>['context'];
|
|
42
49
|
}
|
|
43
50
|
| {
|
|
44
51
|
kind:
|
package/src/executeQueryPlan.ts
CHANGED
|
@@ -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
|
|
40
|
+
interface ExecutionContext {
|
|
45
41
|
queryPlan: QueryPlan;
|
|
46
42
|
operationContext: OperationContext;
|
|
47
43
|
serviceMap: ServiceMap;
|
|
48
|
-
requestContext:
|
|
44
|
+
requestContext: GatewayGraphQLRequestContext;
|
|
49
45
|
errors: GraphQLError[];
|
|
50
46
|
}
|
|
51
47
|
|
|
52
|
-
export async function executeQueryPlan
|
|
48
|
+
export async function executeQueryPlan(
|
|
53
49
|
queryPlan: QueryPlan,
|
|
54
50
|
serviceMap: ServiceMap,
|
|
55
|
-
requestContext:
|
|
51
|
+
requestContext: GatewayGraphQLRequestContext,
|
|
56
52
|
operationContext: OperationContext,
|
|
57
|
-
): Promise<
|
|
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
|
|
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
|
|
175
|
-
context: ExecutionContext
|
|
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:
|
|
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
|
|
288
|
-
context: ExecutionContext
|
|
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
|
|
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
|