@apollo/gateway 0.300.0-alpha.2 → 2.0.0-alpha.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.
- package/LICENSE +95 -0
- package/README.md +1 -1
- package/dist/__generated__/graphqlTypes.d.ts +130 -0
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -0
- package/dist/__generated__/graphqlTypes.js +25 -0
- package/dist/__generated__/graphqlTypes.js.map +1 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +47 -0
- package/dist/config.js.map +1 -0
- package/dist/datasources/LocalGraphQLDataSource.d.ts +3 -3
- package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.js +5 -5
- package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts +6 -4
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +60 -17
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/datasources/index.d.ts +1 -1
- package/dist/datasources/index.d.ts.map +1 -1
- package/dist/datasources/index.js +1 -0
- package/dist/datasources/index.js.map +1 -1
- package/dist/datasources/parseCacheControlHeader.d.ts +2 -0
- package/dist/datasources/parseCacheControlHeader.d.ts.map +1 -0
- package/dist/datasources/parseCacheControlHeader.js +16 -0
- package/dist/datasources/parseCacheControlHeader.js.map +1 -0
- package/dist/datasources/types.d.ts +16 -1
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/datasources/types.js +7 -0
- package/dist/datasources/types.js.map +1 -1
- package/dist/executeQueryPlan.d.ts +2 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +199 -112
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +62 -80
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +543 -234
- package/dist/index.js.map +1 -1
- package/dist/loadServicesFromRemoteEndpoint.d.ts +9 -9
- package/dist/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
- package/dist/loadServicesFromRemoteEndpoint.js +13 -8
- package/dist/loadServicesFromRemoteEndpoint.js.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +13 -0
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/loadSupergraphSdlFromStorage.js +101 -0
- package/dist/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/operationContext.d.ts +17 -0
- package/dist/operationContext.d.ts.map +1 -0
- package/dist/operationContext.js +42 -0
- package/dist/operationContext.js.map +1 -0
- package/dist/outOfBandReporter.d.ts +15 -0
- package/dist/outOfBandReporter.d.ts.map +1 -0
- package/dist/outOfBandReporter.js +88 -0
- package/dist/outOfBandReporter.js.map +1 -0
- package/dist/utilities/array.d.ts +1 -2
- package/dist/utilities/array.d.ts.map +1 -1
- package/dist/utilities/array.js +7 -14
- package/dist/utilities/array.js.map +1 -1
- package/dist/utilities/assert.d.ts +2 -0
- package/dist/utilities/assert.d.ts.map +1 -0
- package/dist/utilities/assert.js +10 -0
- package/dist/utilities/assert.js.map +1 -0
- package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts +3 -0
- package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts.map +1 -0
- package/dist/utilities/cleanErrorOfInaccessibleNames.js +27 -0
- package/dist/utilities/cleanErrorOfInaccessibleNames.js.map +1 -0
- package/dist/utilities/deepMerge.js +2 -2
- package/dist/utilities/deepMerge.js.map +1 -1
- package/dist/utilities/graphql.d.ts +1 -4
- package/dist/utilities/graphql.d.ts.map +1 -1
- package/dist/utilities/graphql.js +3 -36
- package/dist/utilities/graphql.js.map +1 -1
- package/dist/utilities/opentelemetry.d.ts +10 -0
- package/dist/utilities/opentelemetry.d.ts.map +1 -0
- package/dist/utilities/opentelemetry.js +19 -0
- package/dist/utilities/opentelemetry.js.map +1 -0
- package/package.json +30 -21
- package/src/__generated__/graphqlTypes.ts +140 -0
- package/src/__mocks__/apollo-server-env.ts +56 -0
- package/src/__mocks__/make-fetch-happen-fetcher.ts +55 -0
- package/src/__mocks__/tsconfig.json +7 -0
- package/src/__tests__/build-query-plan.feature +40 -311
- package/src/__tests__/buildQueryPlan.test.ts +246 -426
- package/src/__tests__/executeQueryPlan.test.ts +1691 -194
- package/src/__tests__/execution-utils.ts +33 -26
- package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +195 -0
- package/src/__tests__/gateway/buildService.test.ts +16 -19
- package/src/__tests__/gateway/composedSdl.test.ts +44 -0
- package/src/__tests__/gateway/endToEnd.test.ts +166 -0
- package/src/__tests__/gateway/executor.test.ts +49 -43
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -29
- package/src/__tests__/gateway/opentelemetry.test.ts +123 -0
- package/src/__tests__/gateway/queryPlanCache.test.ts +19 -20
- package/src/__tests__/gateway/reporting.test.ts +76 -55
- package/src/__tests__/integration/abstract-types.test.ts +1086 -22
- package/src/__tests__/integration/aliases.test.ts +5 -6
- package/src/__tests__/integration/boolean.test.ts +40 -38
- package/src/__tests__/integration/complex-key.test.ts +41 -56
- package/src/__tests__/integration/configuration.test.ts +321 -0
- package/src/__tests__/integration/custom-directives.test.ts +61 -46
- package/src/__tests__/integration/fragments.test.ts +8 -2
- package/src/__tests__/integration/list-key.test.ts +2 -2
- package/src/__tests__/integration/logger.test.ts +2 -2
- package/src/__tests__/integration/multiple-key.test.ts +11 -12
- package/src/__tests__/integration/mutations.test.ts +8 -5
- package/src/__tests__/integration/networkRequests.test.ts +447 -289
- package/src/__tests__/integration/nockMocks.ts +95 -66
- package/src/__tests__/integration/provides.test.ts +9 -6
- package/src/__tests__/integration/requires.test.ts +17 -15
- package/src/__tests__/integration/scope.test.ts +557 -0
- package/src/__tests__/integration/unions.test.ts +1 -1
- package/src/__tests__/integration/value-types.test.ts +35 -32
- package/src/__tests__/integration/variables.test.ts +8 -2
- package/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +6 -2
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +694 -0
- package/src/__tests__/queryPlanCucumber.test.ts +11 -61
- package/src/__tests__/testSetup.ts +1 -4
- package/src/__tests__/tsconfig.json +2 -1
- package/src/config.ts +225 -0
- package/src/core/__tests__/core.test.ts +412 -0
- package/src/datasources/LocalGraphQLDataSource.ts +9 -10
- package/src/datasources/RemoteGraphQLDataSource.ts +117 -43
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +11 -4
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +148 -79
- package/src/datasources/__tests__/tsconfig.json +4 -2
- package/src/datasources/index.ts +1 -1
- package/src/datasources/parseCacheControlHeader.ts +43 -0
- package/src/datasources/types.ts +47 -2
- package/src/executeQueryPlan.ts +264 -153
- package/src/index.ts +925 -480
- package/src/loadServicesFromRemoteEndpoint.ts +24 -17
- package/src/loadSupergraphSdlFromStorage.ts +140 -0
- package/src/make-fetch-happen.d.ts +2 -2
- package/src/operationContext.ts +70 -0
- package/src/outOfBandReporter.ts +128 -0
- package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +104 -0
- package/src/utilities/__tests__/tsconfig.json +8 -0
- package/src/utilities/array.ts +6 -28
- package/src/utilities/assert.ts +14 -0
- package/src/utilities/cleanErrorOfInaccessibleNames.ts +29 -0
- package/src/utilities/graphql.ts +0 -64
- package/src/utilities/opentelemetry.ts +13 -0
- package/CHANGELOG.md +0 -226
- package/LICENSE.md +0 -20
- package/dist/FieldSet.d.ts +0 -18
- package/dist/FieldSet.d.ts.map +0 -1
- package/dist/FieldSet.js +0 -96
- package/dist/FieldSet.js.map +0 -1
- package/dist/QueryPlan.d.ts +0 -41
- package/dist/QueryPlan.d.ts.map +0 -1
- package/dist/QueryPlan.js +0 -15
- package/dist/QueryPlan.js.map +0 -1
- package/dist/buildQueryPlan.d.ts +0 -44
- package/dist/buildQueryPlan.d.ts.map +0 -1
- package/dist/buildQueryPlan.js +0 -670
- package/dist/buildQueryPlan.js.map +0 -1
- package/dist/loadServicesFromStorage.d.ts +0 -21
- package/dist/loadServicesFromStorage.d.ts.map +0 -1
- package/dist/loadServicesFromStorage.js +0 -64
- package/dist/loadServicesFromStorage.js.map +0 -1
- package/dist/snapshotSerializers/astSerializer.d.ts +0 -3
- package/dist/snapshotSerializers/astSerializer.d.ts.map +0 -1
- package/dist/snapshotSerializers/astSerializer.js +0 -14
- package/dist/snapshotSerializers/astSerializer.js.map +0 -1
- package/dist/snapshotSerializers/index.d.ts +0 -13
- package/dist/snapshotSerializers/index.d.ts.map +0 -1
- package/dist/snapshotSerializers/index.js +0 -15
- package/dist/snapshotSerializers/index.js.map +0 -1
- package/dist/snapshotSerializers/queryPlanSerializer.d.ts +0 -3
- package/dist/snapshotSerializers/queryPlanSerializer.d.ts.map +0 -1
- package/dist/snapshotSerializers/queryPlanSerializer.js +0 -78
- package/dist/snapshotSerializers/queryPlanSerializer.js.map +0 -1
- package/dist/snapshotSerializers/selectionSetSerializer.d.ts +0 -3
- package/dist/snapshotSerializers/selectionSetSerializer.d.ts.map +0 -1
- package/dist/snapshotSerializers/selectionSetSerializer.js +0 -12
- package/dist/snapshotSerializers/selectionSetSerializer.js.map +0 -1
- package/dist/snapshotSerializers/typeSerializer.d.ts +0 -3
- package/dist/snapshotSerializers/typeSerializer.d.ts.map +0 -1
- package/dist/snapshotSerializers/typeSerializer.js +0 -12
- package/dist/snapshotSerializers/typeSerializer.js.map +0 -1
- package/dist/utilities/MultiMap.d.ts +0 -4
- package/dist/utilities/MultiMap.d.ts.map +0 -1
- package/dist/utilities/MultiMap.js +0 -17
- package/dist/utilities/MultiMap.js.map +0 -1
- package/src/FieldSet.ts +0 -169
- package/src/QueryPlan.ts +0 -57
- package/src/__tests__/matchers/toCallService.ts +0 -105
- package/src/__tests__/matchers/toHaveBeenCalledBefore.ts +0 -40
- package/src/__tests__/matchers/toHaveFetched.ts +0 -81
- package/src/__tests__/matchers/toMatchAST.ts +0 -64
- package/src/buildQueryPlan.ts +0 -1190
- package/src/loadServicesFromStorage.ts +0 -170
- package/src/snapshotSerializers/astSerializer.ts +0 -21
- package/src/snapshotSerializers/index.ts +0 -21
- package/src/snapshotSerializers/queryPlanSerializer.ts +0 -144
- package/src/snapshotSerializers/selectionSetSerializer.ts +0 -13
- package/src/snapshotSerializers/typeSerializer.ts +0 -11
- package/src/utilities/MultiMap.ts +0 -11
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { fetch } from '__mocks__/apollo-server-env';
|
|
1
|
+
import { fetch } from '../../__mocks__/apollo-server-env';
|
|
2
|
+
import { makeFetchHappenFetcher } from '../../__mocks__/make-fetch-happen-fetcher';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
ApolloError,
|
|
@@ -9,12 +10,20 @@ import {
|
|
|
9
10
|
import { RemoteGraphQLDataSource } from '../RemoteGraphQLDataSource';
|
|
10
11
|
import { Headers } from 'apollo-server-env';
|
|
11
12
|
import { GraphQLRequestContext } from 'apollo-server-types';
|
|
12
|
-
import {
|
|
13
|
+
import { GraphQLDataSourceRequestKind } from '../types';
|
|
13
14
|
|
|
14
15
|
beforeEach(() => {
|
|
15
16
|
fetch.mockReset();
|
|
16
17
|
});
|
|
17
18
|
|
|
19
|
+
// Right now, none of these tests care what's on incomingRequestContext, so we
|
|
20
|
+
// pass this fake one in.
|
|
21
|
+
const defaultProcessOptions = {
|
|
22
|
+
kind: GraphQLDataSourceRequestKind.INCOMING_OPERATION,
|
|
23
|
+
incomingRequestContext: {} as any,
|
|
24
|
+
context: {},
|
|
25
|
+
};
|
|
26
|
+
|
|
18
27
|
describe('constructing requests', () => {
|
|
19
28
|
describe('without APQ', () => {
|
|
20
29
|
it('stringifies a request with a query', async () => {
|
|
@@ -26,14 +35,13 @@ describe('constructing requests', () => {
|
|
|
26
35
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
27
36
|
|
|
28
37
|
const { data } = await DataSource.process({
|
|
38
|
+
...defaultProcessOptions,
|
|
29
39
|
request: { query: '{ me { name } }' },
|
|
30
|
-
context: {},
|
|
31
40
|
});
|
|
32
41
|
|
|
33
42
|
expect(data).toEqual({ me: 'james' });
|
|
34
43
|
expect(fetch).toBeCalledTimes(1);
|
|
35
|
-
expect(fetch).toHaveFetched({
|
|
36
|
-
url: 'https://api.example.com/foo',
|
|
44
|
+
expect(fetch).toHaveFetched('https://api.example.com/foo', {
|
|
37
45
|
body: { query: '{ me { name } }' },
|
|
38
46
|
});
|
|
39
47
|
});
|
|
@@ -47,17 +55,16 @@ describe('constructing requests', () => {
|
|
|
47
55
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
48
56
|
|
|
49
57
|
const { data } = await DataSource.process({
|
|
58
|
+
...defaultProcessOptions,
|
|
50
59
|
request: {
|
|
51
60
|
query: '{ me { name } }',
|
|
52
61
|
variables: { id: '1' },
|
|
53
62
|
},
|
|
54
|
-
context: {},
|
|
55
63
|
});
|
|
56
64
|
|
|
57
65
|
expect(data).toEqual({ me: 'james' });
|
|
58
66
|
expect(fetch).toBeCalledTimes(1);
|
|
59
|
-
expect(fetch).toHaveFetched({
|
|
60
|
-
url: 'https://api.example.com/foo',
|
|
67
|
+
expect(fetch).toHaveFetched('https://api.example.com/foo', {
|
|
61
68
|
body: { query: '{ me { name } }', variables: { id: '1' } },
|
|
62
69
|
});
|
|
63
70
|
});
|
|
@@ -69,21 +76,23 @@ describe('constructing requests', () => {
|
|
|
69
76
|
|
|
70
77
|
// This is a SHA-256 hash of `query` above.
|
|
71
78
|
const sha256Hash =
|
|
72
|
-
|
|
79
|
+
'b8d9506e34c83b0e53c2aa463624fcea354713bc38f95276e6f0bd893ffb5b88';
|
|
73
80
|
|
|
74
81
|
describe('miss', () => {
|
|
75
82
|
const apqNotFoundResponse = {
|
|
76
|
-
|
|
83
|
+
errors: [
|
|
77
84
|
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
message: 'PersistedQueryNotFound',
|
|
86
|
+
extensions: {
|
|
87
|
+
code: 'PERSISTED_QUERY_NOT_FOUND',
|
|
88
|
+
exception: {
|
|
89
|
+
stacktrace: [
|
|
90
|
+
'PersistedQueryNotFoundError: PersistedQueryNotFound',
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
],
|
|
87
96
|
};
|
|
88
97
|
|
|
89
98
|
it('stringifies a request with a query', async () => {
|
|
@@ -96,33 +105,31 @@ describe('constructing requests', () => {
|
|
|
96
105
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
97
106
|
|
|
98
107
|
const { data } = await DataSource.process({
|
|
108
|
+
...defaultProcessOptions,
|
|
99
109
|
request: { query },
|
|
100
|
-
context: {},
|
|
101
110
|
});
|
|
102
111
|
|
|
103
112
|
expect(data).toEqual({ me: 'james' });
|
|
104
113
|
expect(fetch).toBeCalledTimes(2);
|
|
105
|
-
expect(fetch).toHaveFetchedNth(1, {
|
|
106
|
-
url: 'https://api.example.com/foo',
|
|
114
|
+
expect(fetch).toHaveFetchedNth(1, 'https://api.example.com/foo', {
|
|
107
115
|
body: {
|
|
108
116
|
extensions: {
|
|
109
117
|
persistedQuery: {
|
|
110
118
|
version: 1,
|
|
111
119
|
sha256Hash,
|
|
112
|
-
}
|
|
113
|
-
}
|
|
120
|
+
},
|
|
121
|
+
},
|
|
114
122
|
},
|
|
115
123
|
});
|
|
116
|
-
expect(fetch).toHaveFetchedNth(2, {
|
|
117
|
-
url: 'https://api.example.com/foo',
|
|
124
|
+
expect(fetch).toHaveFetchedNth(2, 'https://api.example.com/foo', {
|
|
118
125
|
body: {
|
|
119
126
|
query,
|
|
120
127
|
extensions: {
|
|
121
128
|
persistedQuery: {
|
|
122
129
|
version: 1,
|
|
123
130
|
sha256Hash,
|
|
124
|
-
}
|
|
125
|
-
}
|
|
131
|
+
},
|
|
132
|
+
},
|
|
126
133
|
},
|
|
127
134
|
});
|
|
128
135
|
});
|
|
@@ -137,30 +144,27 @@ describe('constructing requests', () => {
|
|
|
137
144
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
138
145
|
|
|
139
146
|
const { data } = await DataSource.process({
|
|
147
|
+
...defaultProcessOptions,
|
|
140
148
|
request: {
|
|
141
149
|
query,
|
|
142
150
|
variables: { id: '1' },
|
|
143
151
|
},
|
|
144
|
-
context: {},
|
|
145
152
|
});
|
|
146
153
|
|
|
147
154
|
expect(data).toEqual({ me: 'james' });
|
|
148
155
|
expect(fetch).toBeCalledTimes(2);
|
|
149
|
-
expect(fetch).toHaveFetchedNth(1, {
|
|
150
|
-
url: 'https://api.example.com/foo',
|
|
156
|
+
expect(fetch).toHaveFetchedNth(1, 'https://api.example.com/foo', {
|
|
151
157
|
body: {
|
|
152
158
|
variables: { id: '1' },
|
|
153
159
|
extensions: {
|
|
154
160
|
persistedQuery: {
|
|
155
161
|
version: 1,
|
|
156
162
|
sha256Hash,
|
|
157
|
-
}
|
|
158
|
-
}
|
|
163
|
+
},
|
|
164
|
+
},
|
|
159
165
|
},
|
|
160
166
|
});
|
|
161
|
-
|
|
162
|
-
expect(fetch).toHaveFetchedNth(2, {
|
|
163
|
-
url: 'https://api.example.com/foo',
|
|
167
|
+
expect(fetch).toHaveFetchedNth(2, 'https://api.example.com/foo', {
|
|
164
168
|
body: {
|
|
165
169
|
query,
|
|
166
170
|
variables: { id: '1' },
|
|
@@ -168,8 +172,8 @@ describe('constructing requests', () => {
|
|
|
168
172
|
persistedQuery: {
|
|
169
173
|
version: 1,
|
|
170
174
|
sha256Hash,
|
|
171
|
-
}
|
|
172
|
-
}
|
|
175
|
+
},
|
|
176
|
+
},
|
|
173
177
|
},
|
|
174
178
|
});
|
|
175
179
|
});
|
|
@@ -185,21 +189,20 @@ describe('constructing requests', () => {
|
|
|
185
189
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
186
190
|
|
|
187
191
|
const { data } = await DataSource.process({
|
|
192
|
+
...defaultProcessOptions,
|
|
188
193
|
request: { query },
|
|
189
|
-
context: {},
|
|
190
194
|
});
|
|
191
195
|
|
|
192
196
|
expect(data).toEqual({ me: 'james' });
|
|
193
197
|
expect(fetch).toBeCalledTimes(1);
|
|
194
|
-
expect(fetch).toHaveFetched({
|
|
195
|
-
url: 'https://api.example.com/foo',
|
|
198
|
+
expect(fetch).toHaveFetched('https://api.example.com/foo', {
|
|
196
199
|
body: {
|
|
197
200
|
extensions: {
|
|
198
201
|
persistedQuery: {
|
|
199
202
|
version: 1,
|
|
200
203
|
sha256Hash,
|
|
201
|
-
}
|
|
202
|
-
}
|
|
204
|
+
},
|
|
205
|
+
},
|
|
203
206
|
},
|
|
204
207
|
});
|
|
205
208
|
});
|
|
@@ -213,25 +216,24 @@ describe('constructing requests', () => {
|
|
|
213
216
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
214
217
|
|
|
215
218
|
const { data } = await DataSource.process({
|
|
219
|
+
...defaultProcessOptions,
|
|
216
220
|
request: {
|
|
217
221
|
query,
|
|
218
222
|
variables: { id: '1' },
|
|
219
223
|
},
|
|
220
|
-
context: {},
|
|
221
224
|
});
|
|
222
225
|
|
|
223
226
|
expect(data).toEqual({ me: 'james' });
|
|
224
227
|
expect(fetch).toBeCalledTimes(1);
|
|
225
|
-
expect(fetch).toHaveFetched({
|
|
226
|
-
url: 'https://api.example.com/foo',
|
|
228
|
+
expect(fetch).toHaveFetched('https://api.example.com/foo', {
|
|
227
229
|
body: {
|
|
228
230
|
variables: { id: '1' },
|
|
229
231
|
extensions: {
|
|
230
232
|
persistedQuery: {
|
|
231
233
|
version: 1,
|
|
232
234
|
sha256Hash,
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
+
},
|
|
236
|
+
},
|
|
235
237
|
},
|
|
236
238
|
});
|
|
237
239
|
});
|
|
@@ -241,25 +243,46 @@ describe('constructing requests', () => {
|
|
|
241
243
|
|
|
242
244
|
describe('fetcher', () => {
|
|
243
245
|
it('uses a custom provided `fetcher`', async () => {
|
|
244
|
-
const injectedFetch = fetch.mockJSONResponseOnce({
|
|
246
|
+
const injectedFetch = fetch.mockJSONResponseOnce({
|
|
247
|
+
data: { injected: true },
|
|
248
|
+
});
|
|
245
249
|
const DataSource = new RemoteGraphQLDataSource({
|
|
246
250
|
url: 'https://api.example.com/foo',
|
|
247
251
|
fetcher: injectedFetch,
|
|
248
252
|
});
|
|
249
253
|
|
|
250
254
|
const { data } = await DataSource.process({
|
|
255
|
+
...defaultProcessOptions,
|
|
251
256
|
request: {
|
|
252
257
|
query: '{ me { name } }',
|
|
253
258
|
variables: { id: '1' },
|
|
254
259
|
},
|
|
255
|
-
context: {},
|
|
256
260
|
});
|
|
257
261
|
|
|
258
262
|
expect(injectedFetch).toHaveBeenCalled();
|
|
259
|
-
expect(data).toEqual({injected: true});
|
|
260
|
-
|
|
263
|
+
expect(data).toEqual({ injected: true });
|
|
261
264
|
});
|
|
262
265
|
|
|
266
|
+
it('supports a custom fetcher, like `make-fetch-happen`', async () => {
|
|
267
|
+
const injectedFetch = makeFetchHappenFetcher.mockJSONResponseOnce({
|
|
268
|
+
data: { me: 'james' },
|
|
269
|
+
});
|
|
270
|
+
const DataSource = new RemoteGraphQLDataSource({
|
|
271
|
+
url: 'https://api.example.com/foo',
|
|
272
|
+
fetcher: injectedFetch,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const { data } = await DataSource.process({
|
|
276
|
+
...defaultProcessOptions,
|
|
277
|
+
request: {
|
|
278
|
+
query: '{ me { name } }',
|
|
279
|
+
variables: { id: '1' },
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(injectedFetch).toHaveBeenCalled();
|
|
284
|
+
expect(data).toEqual({ me: 'james' });
|
|
285
|
+
});
|
|
263
286
|
});
|
|
264
287
|
|
|
265
288
|
describe('willSendRequest', () => {
|
|
@@ -267,26 +290,25 @@ describe('willSendRequest', () => {
|
|
|
267
290
|
const DataSource = new RemoteGraphQLDataSource({
|
|
268
291
|
url: 'https://api.example.com/foo',
|
|
269
292
|
willSendRequest: ({ request }) => {
|
|
270
|
-
request.variables =
|
|
293
|
+
request.variables = { id: '2' };
|
|
271
294
|
},
|
|
272
295
|
});
|
|
273
296
|
|
|
274
297
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
275
298
|
|
|
276
299
|
const { data } = await DataSource.process({
|
|
300
|
+
...defaultProcessOptions,
|
|
277
301
|
request: {
|
|
278
302
|
query: '{ me { name } }',
|
|
279
303
|
variables: { id: '1' },
|
|
280
304
|
},
|
|
281
|
-
context: {},
|
|
282
305
|
});
|
|
283
306
|
|
|
284
307
|
expect(data).toEqual({ me: 'james' });
|
|
285
|
-
expect(fetch).toHaveFetched({
|
|
286
|
-
url: 'https://api.example.com/foo',
|
|
308
|
+
expect(fetch).toHaveFetched('https://api.example.com/foo', {
|
|
287
309
|
body: {
|
|
288
310
|
query: '{ me { name } }',
|
|
289
|
-
variables:
|
|
311
|
+
variables: { id: '2' },
|
|
290
312
|
},
|
|
291
313
|
});
|
|
292
314
|
});
|
|
@@ -294,14 +316,20 @@ describe('willSendRequest', () => {
|
|
|
294
316
|
it('accepts context', async () => {
|
|
295
317
|
const DataSource = new RemoteGraphQLDataSource({
|
|
296
318
|
url: 'https://api.example.com/foo',
|
|
297
|
-
willSendRequest: (
|
|
298
|
-
|
|
319
|
+
willSendRequest: (options) => {
|
|
320
|
+
if (options.kind === GraphQLDataSourceRequestKind.INCOMING_OPERATION) {
|
|
321
|
+
options.request.http?.headers.set(
|
|
322
|
+
'x-user-id',
|
|
323
|
+
options.context.userId,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
299
326
|
},
|
|
300
327
|
});
|
|
301
328
|
|
|
302
329
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
303
330
|
|
|
304
331
|
const { data } = await DataSource.process({
|
|
332
|
+
...defaultProcessOptions,
|
|
305
333
|
request: {
|
|
306
334
|
query: '{ me { name } }',
|
|
307
335
|
variables: { id: '1' },
|
|
@@ -310,8 +338,7 @@ describe('willSendRequest', () => {
|
|
|
310
338
|
});
|
|
311
339
|
|
|
312
340
|
expect(data).toEqual({ me: 'james' });
|
|
313
|
-
expect(fetch).toHaveFetched({
|
|
314
|
-
url: 'https://api.example.com/foo',
|
|
341
|
+
expect(fetch).toHaveFetched('https://api.example.com/foo', {
|
|
315
342
|
body: {
|
|
316
343
|
query: '{ me { name } }',
|
|
317
344
|
variables: { id: '1' },
|
|
@@ -335,10 +362,12 @@ describe('didReceiveResponse', () => {
|
|
|
335
362
|
didReceiveResponse<MyContext>({
|
|
336
363
|
request,
|
|
337
364
|
response,
|
|
338
|
-
}: Required<
|
|
339
|
-
|
|
365
|
+
}: Required<
|
|
366
|
+
Pick<
|
|
367
|
+
GraphQLRequestContext<MyContext>,
|
|
340
368
|
'request' | 'response' | 'context'
|
|
341
|
-
|
|
369
|
+
>
|
|
370
|
+
>) {
|
|
342
371
|
const surrogateKeys =
|
|
343
372
|
request.http && request.http.headers.get('surrogate-keys');
|
|
344
373
|
if (surrogateKeys) {
|
|
@@ -354,6 +383,7 @@ describe('didReceiveResponse', () => {
|
|
|
354
383
|
|
|
355
384
|
const context: MyContext = { surrogateKeys: [] };
|
|
356
385
|
await DataSource.process({
|
|
386
|
+
...defaultProcessOptions,
|
|
357
387
|
request: {
|
|
358
388
|
query: '{ me { name } }',
|
|
359
389
|
variables: { id: '1' },
|
|
@@ -375,30 +405,30 @@ describe('didReceiveResponse', () => {
|
|
|
375
405
|
|
|
376
406
|
didReceiveResponse<MyContext>({
|
|
377
407
|
response,
|
|
378
|
-
}: Required<
|
|
379
|
-
|
|
408
|
+
}: Required<
|
|
409
|
+
Pick<
|
|
410
|
+
GraphQLRequestContext<MyContext>,
|
|
380
411
|
'request' | 'response' | 'context'
|
|
381
|
-
|
|
412
|
+
>
|
|
413
|
+
>) {
|
|
382
414
|
return response;
|
|
383
415
|
}
|
|
384
416
|
}
|
|
385
417
|
|
|
386
418
|
const DataSource = new MyDataSource();
|
|
387
|
-
const spyDidReceiveResponse =
|
|
388
|
-
jest.spyOn(DataSource, 'didReceiveResponse');
|
|
419
|
+
const spyDidReceiveResponse = jest.spyOn(DataSource, 'didReceiveResponse');
|
|
389
420
|
|
|
390
421
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
391
422
|
|
|
392
423
|
await DataSource.process({
|
|
424
|
+
...defaultProcessOptions,
|
|
393
425
|
request: {
|
|
394
426
|
query: '{ me { name } }',
|
|
395
427
|
variables: { id: '1' },
|
|
396
428
|
},
|
|
397
|
-
context: {},
|
|
398
429
|
});
|
|
399
430
|
|
|
400
431
|
expect(spyDidReceiveResponse).toHaveBeenCalledTimes(1);
|
|
401
|
-
|
|
402
432
|
});
|
|
403
433
|
|
|
404
434
|
// APQ makes two requests, so make sure only one calls the response hook.
|
|
@@ -409,10 +439,12 @@ describe('didReceiveResponse', () => {
|
|
|
409
439
|
|
|
410
440
|
didReceiveResponse<MyContext>({
|
|
411
441
|
response,
|
|
412
|
-
}: Required<
|
|
413
|
-
|
|
442
|
+
}: Required<
|
|
443
|
+
Pick<
|
|
444
|
+
GraphQLRequestContext<MyContext>,
|
|
414
445
|
'request' | 'response' | 'context'
|
|
415
|
-
|
|
446
|
+
>
|
|
447
|
+
>) {
|
|
416
448
|
return response;
|
|
417
449
|
}
|
|
418
450
|
}
|
|
@@ -423,15 +455,52 @@ describe('didReceiveResponse', () => {
|
|
|
423
455
|
fetch.mockJSONResponseOnce({ data: { me: 'james' } });
|
|
424
456
|
|
|
425
457
|
await DataSource.process({
|
|
458
|
+
...defaultProcessOptions,
|
|
426
459
|
request: {
|
|
427
460
|
query: '{ me { name } }',
|
|
428
461
|
variables: { id: '1' },
|
|
429
462
|
},
|
|
430
|
-
context: {},
|
|
431
463
|
});
|
|
432
464
|
|
|
433
465
|
expect(spyDidReceiveResponse).toHaveBeenCalledTimes(1);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe('didEncounterError', () => {
|
|
470
|
+
it('can accept and modify context', async () => {
|
|
471
|
+
interface MyContext {
|
|
472
|
+
timingData: { time: number }[];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
class MyDataSource extends RemoteGraphQLDataSource<MyContext> {
|
|
476
|
+
url = 'https://api.example.com/foo';
|
|
434
477
|
|
|
478
|
+
didEncounterError() {
|
|
479
|
+
// a timestamp a la `Date.now()`
|
|
480
|
+
context.timingData.push({ time: 1616446845234 });
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const DataSource = new MyDataSource();
|
|
485
|
+
|
|
486
|
+
fetch.mockResponseOnce('Invalid token', undefined, 401);
|
|
487
|
+
|
|
488
|
+
const context: MyContext = { timingData: [] };
|
|
489
|
+
const result = DataSource.process({
|
|
490
|
+
...defaultProcessOptions,
|
|
491
|
+
request: {
|
|
492
|
+
query: '{ me { name } }',
|
|
493
|
+
},
|
|
494
|
+
incomingRequestContext: {
|
|
495
|
+
context,
|
|
496
|
+
} as GraphQLRequestContext<MyContext>,
|
|
497
|
+
context,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
await expect(result).rejects.toThrow(AuthenticationError);
|
|
501
|
+
expect(context).toMatchObject({
|
|
502
|
+
timingData: [{ time: 1616446845234 }],
|
|
503
|
+
});
|
|
435
504
|
});
|
|
436
505
|
});
|
|
437
506
|
|
|
@@ -444,8 +513,8 @@ describe('error handling', () => {
|
|
|
444
513
|
fetch.mockResponseOnce('Invalid token', undefined, 401);
|
|
445
514
|
|
|
446
515
|
const result = DataSource.process({
|
|
516
|
+
...defaultProcessOptions,
|
|
447
517
|
request: { query: '{ me { name } }' },
|
|
448
|
-
context: {},
|
|
449
518
|
});
|
|
450
519
|
await expect(result).rejects.toThrow(AuthenticationError);
|
|
451
520
|
await expect(result).rejects.toMatchObject({
|
|
@@ -467,8 +536,8 @@ describe('error handling', () => {
|
|
|
467
536
|
fetch.mockResponseOnce('No access', undefined, 403);
|
|
468
537
|
|
|
469
538
|
const result = DataSource.process({
|
|
539
|
+
...defaultProcessOptions,
|
|
470
540
|
request: { query: '{ me { name } }' },
|
|
471
|
-
context: {},
|
|
472
541
|
});
|
|
473
542
|
await expect(result).rejects.toThrow(ForbiddenError);
|
|
474
543
|
await expect(result).rejects.toMatchObject({
|
|
@@ -490,8 +559,8 @@ describe('error handling', () => {
|
|
|
490
559
|
fetch.mockResponseOnce('Oops', undefined, 500);
|
|
491
560
|
|
|
492
561
|
const result = DataSource.process({
|
|
562
|
+
...defaultProcessOptions,
|
|
493
563
|
request: { query: '{ me { name } }' },
|
|
494
|
-
context: {},
|
|
495
564
|
});
|
|
496
565
|
await expect(result).rejects.toThrow(ApolloError);
|
|
497
566
|
await expect(result).rejects.toMatchObject({
|
|
@@ -522,8 +591,8 @@ describe('error handling', () => {
|
|
|
522
591
|
);
|
|
523
592
|
|
|
524
593
|
const result = DataSource.process({
|
|
594
|
+
...defaultProcessOptions,
|
|
525
595
|
request: { query: '{ me { name } }' },
|
|
526
|
-
context: {},
|
|
527
596
|
});
|
|
528
597
|
await expect(result).rejects.toThrow(ApolloError);
|
|
529
598
|
await expect(result).rejects.toMatchObject({
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "
|
|
2
|
+
"extends": "../../../tsconfig.test",
|
|
3
3
|
"include": ["**/*"],
|
|
4
4
|
"references": [
|
|
5
|
-
{ "path": "
|
|
5
|
+
{ "path": "../../.." },
|
|
6
|
+
{ "path": "../../__mocks__" },
|
|
7
|
+
{ "path": "../../../../federation-integration-testsuite-js" },
|
|
6
8
|
]
|
|
7
9
|
}
|
package/src/datasources/index.ts
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Adapted from https://github.com/kornelski/http-cache-semantics
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2016-2018 Kornel Lesiński
|
|
4
|
+
//
|
|
5
|
+
// Redistribution and use in source and binary forms, with or without
|
|
6
|
+
// modification, are permitted provided that the following conditions
|
|
7
|
+
// are met:
|
|
8
|
+
//
|
|
9
|
+
// 1. Redistributions of source code must retain the above copyright
|
|
10
|
+
// notice, this list of conditions and the following disclaimer.
|
|
11
|
+
//
|
|
12
|
+
// 2. Redistributions in binary form must reproduce the above copyright
|
|
13
|
+
// notice, this list of conditions and the following disclaimer in
|
|
14
|
+
// the documentation and/or other materials provided with the
|
|
15
|
+
// distribution.
|
|
16
|
+
//
|
|
17
|
+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
18
|
+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
19
|
+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
20
|
+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
21
|
+
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
22
|
+
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
23
|
+
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
24
|
+
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
25
|
+
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
26
|
+
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
|
27
|
+
// WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
28
|
+
// POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
30
|
+
export function parseCacheControlHeader(
|
|
31
|
+
header: string | null | undefined,
|
|
32
|
+
): Record<string, string | true> {
|
|
33
|
+
const cc: Record<string, string | true> = {};
|
|
34
|
+
if (!header) return cc;
|
|
35
|
+
|
|
36
|
+
const parts = header.trim().split(/\s*,\s*/);
|
|
37
|
+
for (const part of parts) {
|
|
38
|
+
const [k, v] = part.split(/\s*=\s*/, 2);
|
|
39
|
+
cc[k] = v === undefined ? true : v.replace(/^"|"$/g, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return cc;
|
|
43
|
+
}
|
package/src/datasources/types.ts
CHANGED
|
@@ -1,7 +1,52 @@
|
|
|
1
1
|
import { GraphQLResponse, GraphQLRequestContext } from 'apollo-server-types';
|
|
2
2
|
|
|
3
|
-
export interface GraphQLDataSource<
|
|
3
|
+
export interface GraphQLDataSource<
|
|
4
|
+
TContext extends Record<string, any> = Record<string, any>,
|
|
5
|
+
> {
|
|
4
6
|
process(
|
|
5
|
-
|
|
7
|
+
options: GraphQLDataSourceProcessOptions<TContext>,
|
|
6
8
|
): Promise<GraphQLResponse>;
|
|
7
9
|
}
|
|
10
|
+
|
|
11
|
+
export enum GraphQLDataSourceRequestKind {
|
|
12
|
+
INCOMING_OPERATION = 'incoming operation',
|
|
13
|
+
HEALTH_CHECK = 'health check',
|
|
14
|
+
LOADING_SCHEMA = 'loading schema',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type GraphQLDataSourceProcessOptions<
|
|
18
|
+
TContext extends Record<string, any> = Record<string, any>,
|
|
19
|
+
> = {
|
|
20
|
+
/**
|
|
21
|
+
* The request to send to the subgraph.
|
|
22
|
+
*/
|
|
23
|
+
request: GraphQLRequestContext<TContext>['request'];
|
|
24
|
+
} & (
|
|
25
|
+
| {
|
|
26
|
+
kind: GraphQLDataSourceRequestKind.INCOMING_OPERATION;
|
|
27
|
+
/**
|
|
28
|
+
* The GraphQLRequestContext for the operation received by the gateway, or
|
|
29
|
+
* one of the strings if this operation is generated by the gateway without an
|
|
30
|
+
* incoming request.
|
|
31
|
+
*/
|
|
32
|
+
incomingRequestContext: GraphQLRequestContext<TContext>;
|
|
33
|
+
/**
|
|
34
|
+
* Equivalent to incomingRequestContext.context (provided here for
|
|
35
|
+
* backwards compatibility): the object created by the Apollo Server
|
|
36
|
+
* `context` function.
|
|
37
|
+
*
|
|
38
|
+
* @deprecated Use `incomingRequestContext.context` instead (after
|
|
39
|
+
* checking `kind`).
|
|
40
|
+
*/
|
|
41
|
+
context: GraphQLRequestContext<TContext>['context'];
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
kind:
|
|
45
|
+
| GraphQLDataSourceRequestKind.HEALTH_CHECK
|
|
46
|
+
| GraphQLDataSourceRequestKind.LOADING_SCHEMA;
|
|
47
|
+
/**
|
|
48
|
+
* Mostly provided for historical reasons.
|
|
49
|
+
*/
|
|
50
|
+
context: Record<string, any>;
|
|
51
|
+
}
|
|
52
|
+
);
|