@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.
Files changed (198) hide show
  1. package/LICENSE +95 -0
  2. package/README.md +1 -1
  3. package/dist/__generated__/graphqlTypes.d.ts +130 -0
  4. package/dist/__generated__/graphqlTypes.d.ts.map +1 -0
  5. package/dist/__generated__/graphqlTypes.js +25 -0
  6. package/dist/__generated__/graphqlTypes.js.map +1 -0
  7. package/dist/config.d.ts +104 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +47 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/datasources/LocalGraphQLDataSource.d.ts +3 -3
  12. package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
  13. package/dist/datasources/LocalGraphQLDataSource.js +5 -5
  14. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  15. package/dist/datasources/RemoteGraphQLDataSource.d.ts +6 -4
  16. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  17. package/dist/datasources/RemoteGraphQLDataSource.js +60 -17
  18. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  19. package/dist/datasources/index.d.ts +1 -1
  20. package/dist/datasources/index.d.ts.map +1 -1
  21. package/dist/datasources/index.js +1 -0
  22. package/dist/datasources/index.js.map +1 -1
  23. package/dist/datasources/parseCacheControlHeader.d.ts +2 -0
  24. package/dist/datasources/parseCacheControlHeader.d.ts.map +1 -0
  25. package/dist/datasources/parseCacheControlHeader.js +16 -0
  26. package/dist/datasources/parseCacheControlHeader.js.map +1 -0
  27. package/dist/datasources/types.d.ts +16 -1
  28. package/dist/datasources/types.d.ts.map +1 -1
  29. package/dist/datasources/types.js +7 -0
  30. package/dist/datasources/types.js.map +1 -1
  31. package/dist/executeQueryPlan.d.ts +2 -1
  32. package/dist/executeQueryPlan.d.ts.map +1 -1
  33. package/dist/executeQueryPlan.js +199 -112
  34. package/dist/executeQueryPlan.js.map +1 -1
  35. package/dist/index.d.ts +62 -80
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +543 -234
  38. package/dist/index.js.map +1 -1
  39. package/dist/loadServicesFromRemoteEndpoint.d.ts +9 -9
  40. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
  41. package/dist/loadServicesFromRemoteEndpoint.js +13 -8
  42. package/dist/loadServicesFromRemoteEndpoint.js.map +1 -1
  43. package/dist/loadSupergraphSdlFromStorage.d.ts +13 -0
  44. package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  45. package/dist/loadSupergraphSdlFromStorage.js +101 -0
  46. package/dist/loadSupergraphSdlFromStorage.js.map +1 -0
  47. package/dist/operationContext.d.ts +17 -0
  48. package/dist/operationContext.d.ts.map +1 -0
  49. package/dist/operationContext.js +42 -0
  50. package/dist/operationContext.js.map +1 -0
  51. package/dist/outOfBandReporter.d.ts +15 -0
  52. package/dist/outOfBandReporter.d.ts.map +1 -0
  53. package/dist/outOfBandReporter.js +88 -0
  54. package/dist/outOfBandReporter.js.map +1 -0
  55. package/dist/utilities/array.d.ts +1 -2
  56. package/dist/utilities/array.d.ts.map +1 -1
  57. package/dist/utilities/array.js +7 -14
  58. package/dist/utilities/array.js.map +1 -1
  59. package/dist/utilities/assert.d.ts +2 -0
  60. package/dist/utilities/assert.d.ts.map +1 -0
  61. package/dist/utilities/assert.js +10 -0
  62. package/dist/utilities/assert.js.map +1 -0
  63. package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts +3 -0
  64. package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts.map +1 -0
  65. package/dist/utilities/cleanErrorOfInaccessibleNames.js +27 -0
  66. package/dist/utilities/cleanErrorOfInaccessibleNames.js.map +1 -0
  67. package/dist/utilities/deepMerge.js +2 -2
  68. package/dist/utilities/deepMerge.js.map +1 -1
  69. package/dist/utilities/graphql.d.ts +1 -4
  70. package/dist/utilities/graphql.d.ts.map +1 -1
  71. package/dist/utilities/graphql.js +3 -36
  72. package/dist/utilities/graphql.js.map +1 -1
  73. package/dist/utilities/opentelemetry.d.ts +10 -0
  74. package/dist/utilities/opentelemetry.d.ts.map +1 -0
  75. package/dist/utilities/opentelemetry.js +19 -0
  76. package/dist/utilities/opentelemetry.js.map +1 -0
  77. package/package.json +30 -21
  78. package/src/__generated__/graphqlTypes.ts +140 -0
  79. package/src/__mocks__/apollo-server-env.ts +56 -0
  80. package/src/__mocks__/make-fetch-happen-fetcher.ts +55 -0
  81. package/src/__mocks__/tsconfig.json +7 -0
  82. package/src/__tests__/build-query-plan.feature +40 -311
  83. package/src/__tests__/buildQueryPlan.test.ts +246 -426
  84. package/src/__tests__/executeQueryPlan.test.ts +1691 -194
  85. package/src/__tests__/execution-utils.ts +33 -26
  86. package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +195 -0
  87. package/src/__tests__/gateway/buildService.test.ts +16 -19
  88. package/src/__tests__/gateway/composedSdl.test.ts +44 -0
  89. package/src/__tests__/gateway/endToEnd.test.ts +166 -0
  90. package/src/__tests__/gateway/executor.test.ts +49 -43
  91. package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -29
  92. package/src/__tests__/gateway/opentelemetry.test.ts +123 -0
  93. package/src/__tests__/gateway/queryPlanCache.test.ts +19 -20
  94. package/src/__tests__/gateway/reporting.test.ts +76 -55
  95. package/src/__tests__/integration/abstract-types.test.ts +1086 -22
  96. package/src/__tests__/integration/aliases.test.ts +5 -6
  97. package/src/__tests__/integration/boolean.test.ts +40 -38
  98. package/src/__tests__/integration/complex-key.test.ts +41 -56
  99. package/src/__tests__/integration/configuration.test.ts +321 -0
  100. package/src/__tests__/integration/custom-directives.test.ts +61 -46
  101. package/src/__tests__/integration/fragments.test.ts +8 -2
  102. package/src/__tests__/integration/list-key.test.ts +2 -2
  103. package/src/__tests__/integration/logger.test.ts +2 -2
  104. package/src/__tests__/integration/multiple-key.test.ts +11 -12
  105. package/src/__tests__/integration/mutations.test.ts +8 -5
  106. package/src/__tests__/integration/networkRequests.test.ts +447 -289
  107. package/src/__tests__/integration/nockMocks.ts +95 -66
  108. package/src/__tests__/integration/provides.test.ts +9 -6
  109. package/src/__tests__/integration/requires.test.ts +17 -15
  110. package/src/__tests__/integration/scope.test.ts +557 -0
  111. package/src/__tests__/integration/unions.test.ts +1 -1
  112. package/src/__tests__/integration/value-types.test.ts +35 -32
  113. package/src/__tests__/integration/variables.test.ts +8 -2
  114. package/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +6 -2
  115. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +694 -0
  116. package/src/__tests__/queryPlanCucumber.test.ts +11 -61
  117. package/src/__tests__/testSetup.ts +1 -4
  118. package/src/__tests__/tsconfig.json +2 -1
  119. package/src/config.ts +225 -0
  120. package/src/core/__tests__/core.test.ts +412 -0
  121. package/src/datasources/LocalGraphQLDataSource.ts +9 -10
  122. package/src/datasources/RemoteGraphQLDataSource.ts +117 -43
  123. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +11 -4
  124. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +148 -79
  125. package/src/datasources/__tests__/tsconfig.json +4 -2
  126. package/src/datasources/index.ts +1 -1
  127. package/src/datasources/parseCacheControlHeader.ts +43 -0
  128. package/src/datasources/types.ts +47 -2
  129. package/src/executeQueryPlan.ts +264 -153
  130. package/src/index.ts +925 -480
  131. package/src/loadServicesFromRemoteEndpoint.ts +24 -17
  132. package/src/loadSupergraphSdlFromStorage.ts +140 -0
  133. package/src/make-fetch-happen.d.ts +2 -2
  134. package/src/operationContext.ts +70 -0
  135. package/src/outOfBandReporter.ts +128 -0
  136. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +104 -0
  137. package/src/utilities/__tests__/tsconfig.json +8 -0
  138. package/src/utilities/array.ts +6 -28
  139. package/src/utilities/assert.ts +14 -0
  140. package/src/utilities/cleanErrorOfInaccessibleNames.ts +29 -0
  141. package/src/utilities/graphql.ts +0 -64
  142. package/src/utilities/opentelemetry.ts +13 -0
  143. package/CHANGELOG.md +0 -226
  144. package/LICENSE.md +0 -20
  145. package/dist/FieldSet.d.ts +0 -18
  146. package/dist/FieldSet.d.ts.map +0 -1
  147. package/dist/FieldSet.js +0 -96
  148. package/dist/FieldSet.js.map +0 -1
  149. package/dist/QueryPlan.d.ts +0 -41
  150. package/dist/QueryPlan.d.ts.map +0 -1
  151. package/dist/QueryPlan.js +0 -15
  152. package/dist/QueryPlan.js.map +0 -1
  153. package/dist/buildQueryPlan.d.ts +0 -44
  154. package/dist/buildQueryPlan.d.ts.map +0 -1
  155. package/dist/buildQueryPlan.js +0 -670
  156. package/dist/buildQueryPlan.js.map +0 -1
  157. package/dist/loadServicesFromStorage.d.ts +0 -21
  158. package/dist/loadServicesFromStorage.d.ts.map +0 -1
  159. package/dist/loadServicesFromStorage.js +0 -64
  160. package/dist/loadServicesFromStorage.js.map +0 -1
  161. package/dist/snapshotSerializers/astSerializer.d.ts +0 -3
  162. package/dist/snapshotSerializers/astSerializer.d.ts.map +0 -1
  163. package/dist/snapshotSerializers/astSerializer.js +0 -14
  164. package/dist/snapshotSerializers/astSerializer.js.map +0 -1
  165. package/dist/snapshotSerializers/index.d.ts +0 -13
  166. package/dist/snapshotSerializers/index.d.ts.map +0 -1
  167. package/dist/snapshotSerializers/index.js +0 -15
  168. package/dist/snapshotSerializers/index.js.map +0 -1
  169. package/dist/snapshotSerializers/queryPlanSerializer.d.ts +0 -3
  170. package/dist/snapshotSerializers/queryPlanSerializer.d.ts.map +0 -1
  171. package/dist/snapshotSerializers/queryPlanSerializer.js +0 -78
  172. package/dist/snapshotSerializers/queryPlanSerializer.js.map +0 -1
  173. package/dist/snapshotSerializers/selectionSetSerializer.d.ts +0 -3
  174. package/dist/snapshotSerializers/selectionSetSerializer.d.ts.map +0 -1
  175. package/dist/snapshotSerializers/selectionSetSerializer.js +0 -12
  176. package/dist/snapshotSerializers/selectionSetSerializer.js.map +0 -1
  177. package/dist/snapshotSerializers/typeSerializer.d.ts +0 -3
  178. package/dist/snapshotSerializers/typeSerializer.d.ts.map +0 -1
  179. package/dist/snapshotSerializers/typeSerializer.js +0 -12
  180. package/dist/snapshotSerializers/typeSerializer.js.map +0 -1
  181. package/dist/utilities/MultiMap.d.ts +0 -4
  182. package/dist/utilities/MultiMap.d.ts.map +0 -1
  183. package/dist/utilities/MultiMap.js +0 -17
  184. package/dist/utilities/MultiMap.js.map +0 -1
  185. package/src/FieldSet.ts +0 -169
  186. package/src/QueryPlan.ts +0 -57
  187. package/src/__tests__/matchers/toCallService.ts +0 -105
  188. package/src/__tests__/matchers/toHaveBeenCalledBefore.ts +0 -40
  189. package/src/__tests__/matchers/toHaveFetched.ts +0 -81
  190. package/src/__tests__/matchers/toMatchAST.ts +0 -64
  191. package/src/buildQueryPlan.ts +0 -1190
  192. package/src/loadServicesFromStorage.ts +0 -170
  193. package/src/snapshotSerializers/astSerializer.ts +0 -21
  194. package/src/snapshotSerializers/index.ts +0 -21
  195. package/src/snapshotSerializers/queryPlanSerializer.ts +0 -144
  196. package/src/snapshotSerializers/selectionSetSerializer.ts +0 -13
  197. package/src/snapshotSerializers/typeSerializer.ts +0 -11
  198. 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 { Response } from '../../../../../../apollo-tooling/packages/apollo-env/lib';
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
- "b8d9506e34c83b0e53c2aa463624fcea354713bc38f95276e6f0bd893ffb5b88";
79
+ 'b8d9506e34c83b0e53c2aa463624fcea354713bc38f95276e6f0bd893ffb5b88';
73
80
 
74
81
  describe('miss', () => {
75
82
  const apqNotFoundResponse = {
76
- "errors": [
83
+ errors: [
77
84
  {
78
- "message": "PersistedQueryNotFound",
79
- "extensions": {
80
- "code": "PERSISTED_QUERY_NOT_FOUND",
81
- "exception": {
82
- "stacktrace": ["PersistedQueryNotFoundError: PersistedQueryNotFound"]
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({ data: { injected: true } });
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 = JSON.stringify(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: JSON.stringify({ id: '1' }),
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: ({ request, context }) => {
298
- request.http.headers.set('x-user-id', context.userId);
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<Pick<
339
- GraphQLRequestContext<MyContext>,
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<Pick<
379
- GraphQLRequestContext<MyContext>,
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<Pick<
413
- GraphQLRequestContext<MyContext>,
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": "../../../../../tsconfig.test.base",
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
  }
@@ -1,3 +1,3 @@
1
1
  export { LocalGraphQLDataSource } from './LocalGraphQLDataSource';
2
2
  export { RemoteGraphQLDataSource } from './RemoteGraphQLDataSource';
3
- export { GraphQLDataSource } from './types';
3
+ export { GraphQLDataSource, GraphQLDataSourceProcessOptions } from './types';
@@ -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
+ }
@@ -1,7 +1,52 @@
1
1
  import { GraphQLResponse, GraphQLRequestContext } from 'apollo-server-types';
2
2
 
3
- export interface GraphQLDataSource<TContext extends Record<string, any> = Record<string, any>> {
3
+ export interface GraphQLDataSource<
4
+ TContext extends Record<string, any> = Record<string, any>,
5
+ > {
4
6
  process(
5
- request: Pick<GraphQLRequestContext<TContext>, 'request' | 'context'>,
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
+ );