@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.
Files changed (91) hide show
  1. package/dist/config.d.ts +4 -4
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js.map +1 -1
  4. package/dist/datasources/LocalGraphQLDataSource.d.ts +2 -2
  5. package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
  6. package/dist/datasources/LocalGraphQLDataSource.js +0 -2
  7. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  8. package/dist/datasources/RemoteGraphQLDataSource.d.ts +11 -10
  9. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  10. package/dist/datasources/RemoteGraphQLDataSource.js +30 -31
  11. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  12. package/dist/datasources/types.d.ts +5 -5
  13. package/dist/datasources/types.d.ts.map +1 -1
  14. package/dist/executeQueryPlan.d.ts +3 -3
  15. package/dist/executeQueryPlan.d.ts.map +1 -1
  16. package/dist/executeQueryPlan.js +2 -2
  17. package/dist/executeQueryPlan.js.map +1 -1
  18. package/dist/index.d.ts +7 -12
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +14 -35
  21. package/dist/index.js.map +1 -1
  22. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
  23. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -1
  24. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +2 -2
  25. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -1
  26. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -1
  27. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +3 -3
  28. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -1
  29. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js +14 -13
  30. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -1
  31. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +6 -5
  32. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -1
  33. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +3 -3
  34. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -1
  35. package/dist/typings/graphql.d.ts +24 -0
  36. package/dist/typings/graphql.d.ts.map +1 -0
  37. package/dist/{schema-helper/resolverMap.js → typings/graphql.js} +1 -1
  38. package/dist/typings/graphql.js.map +1 -0
  39. package/package.json +11 -13
  40. package/src/__generated__/graphqlTypes.ts +1 -1
  41. package/src/__tests__/executeQueryPlan.test.ts +15 -6
  42. package/src/__tests__/execution-utils.ts +4 -4
  43. package/src/__tests__/gateway/buildService.test.ts +81 -83
  44. package/src/__tests__/gateway/endToEnd.test.ts +1 -1
  45. package/src/__tests__/gateway/executor.test.ts +20 -17
  46. package/src/__tests__/gateway/opentelemetry.test.ts +3 -7
  47. package/src/__tests__/gateway/reporting.test.ts +1 -1
  48. package/src/__tests__/gateway/supergraphSdl.test.ts +11 -13
  49. package/src/__tests__/integration/complex-key.test.ts +2 -2
  50. package/src/config.ts +4 -6
  51. package/src/datasources/LocalGraphQLDataSource.ts +2 -4
  52. package/src/datasources/RemoteGraphQLDataSource.ts +72 -60
  53. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +3 -3
  54. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +120 -159
  55. package/src/datasources/types.ts +12 -5
  56. package/src/executeQueryPlan.ts +18 -18
  57. package/src/index.ts +25 -71
  58. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -6
  59. package/src/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts +2 -2
  60. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +70 -74
  61. package/src/supergraphManagers/UplinkFetcher/index.ts +2 -2
  62. package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +23 -17
  63. package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +9 -7
  64. package/src/typings/graphql.ts +31 -0
  65. package/dist/cache.d.ts +0 -18
  66. package/dist/cache.d.ts.map +0 -1
  67. package/dist/cache.js +0 -46
  68. package/dist/cache.js.map +0 -1
  69. package/dist/schema-helper/addResolversToSchema.d.ts +0 -4
  70. package/dist/schema-helper/addResolversToSchema.d.ts.map +0 -1
  71. package/dist/schema-helper/addResolversToSchema.js +0 -62
  72. package/dist/schema-helper/addResolversToSchema.js.map +0 -1
  73. package/dist/schema-helper/index.d.ts +0 -3
  74. package/dist/schema-helper/index.d.ts.map +0 -1
  75. package/dist/schema-helper/index.js +0 -19
  76. package/dist/schema-helper/index.js.map +0 -1
  77. package/dist/schema-helper/resolverMap.d.ts +0 -16
  78. package/dist/schema-helper/resolverMap.d.ts.map +0 -1
  79. package/dist/schema-helper/resolverMap.js.map +0 -1
  80. package/dist/utilities/createHash.d.ts +0 -2
  81. package/dist/utilities/createHash.d.ts.map +0 -1
  82. package/dist/utilities/createHash.js +0 -15
  83. package/dist/utilities/createHash.js.map +0 -1
  84. package/src/__mocks__/apollo-server-env.ts +0 -56
  85. package/src/__mocks__/make-fetch-happen-fetcher.ts +0 -57
  86. package/src/cache.ts +0 -66
  87. package/src/make-fetch-happen.d.ts +0 -59
  88. package/src/schema-helper/addResolversToSchema.ts +0 -83
  89. package/src/schema-helper/index.ts +0 -2
  90. package/src/schema-helper/resolverMap.ts +0 -23
  91. package/src/utilities/createHash.ts +0 -10
@@ -1,28 +1,18 @@
1
- import {
2
- GraphQLRequestContext,
3
- GraphQLResponse,
4
- ValueOrPromise,
5
- GraphQLRequest,
6
- CacheHint,
7
- CacheScope,
8
- CachePolicy,
9
- } from 'apollo-server-types';
10
- import {
11
- ApolloError,
12
- AuthenticationError,
13
- ForbiddenError,
14
- } from 'apollo-server-errors';
15
- import { fetch, Request, Headers, Response } from 'apollo-server-env';
16
1
  import { isObject } from '../utilities/predicates';
17
2
  import { GraphQLDataSource, GraphQLDataSourceProcessOptions, GraphQLDataSourceRequestKind } from './types';
18
- import createSHA from 'apollo-server-core/dist/utils/createSHA';
3
+ import { createHash } from '@apollo/utils.createhash';
19
4
  import { parseCacheControlHeader } from './parseCacheControlHeader';
20
5
  import fetcher from 'make-fetch-happen';
6
+ import { Headers as NodeFetchHeaders, Request as NodeFetchRequest } from 'node-fetch';
7
+ import { Fetcher, FetcherRequestInit, FetcherResponse } from '@apollo/utils.fetcher';
8
+ import { GraphQLError, GraphQLErrorExtensions } from 'graphql';
9
+ import { GatewayCacheHint, GatewayCachePolicy, GatewayGraphQLRequest, GatewayGraphQLRequestContext, GatewayGraphQLResponse } from '@apollo/server-gateway-interface';
10
+
21
11
  export class RemoteGraphQLDataSource<
22
12
  TContext extends Record<string, any> = Record<string, any>,
23
13
  > implements GraphQLDataSource<TContext>
24
14
  {
25
- fetcher: typeof fetch;
15
+ fetcher: Fetcher;
26
16
 
27
17
  constructor(
28
18
  config?: Partial<RemoteGraphQLDataSource<TContext>> &
@@ -30,6 +20,11 @@ export class RemoteGraphQLDataSource<
30
20
  ThisType<RemoteGraphQLDataSource<TContext>>,
31
21
  ) {
32
22
  this.fetcher = fetcher.defaults({
23
+ // Allow an arbitrary number of sockets per subgraph. This is the default
24
+ // behavior of Node's http.Agent as well as the npm package agentkeepalive
25
+ // which wraps it, but is not the default behavior of make-fetch-happen
26
+ // which wraps agentkeepalive (that package sets this to 15 by default).
27
+ maxSockets: Infinity,
33
28
  // although this is the default, we want to take extra care and be very
34
29
  // explicity to ensure that mutations cannot be retried. please leave this
35
30
  // intact.
@@ -70,7 +65,7 @@ export class RemoteGraphQLDataSource<
70
65
 
71
66
  async process(
72
67
  options: GraphQLDataSourceProcessOptions<TContext>,
73
- ): Promise<GraphQLResponse> {
68
+ ): Promise<GatewayGraphQLResponse> {
74
69
  const { request, context: originalContext } = options;
75
70
  // Deal with a bit of a hairy situation in typings: when doing health checks
76
71
  // and schema checks we always pass in `{}` as the context even though it's
@@ -82,7 +77,12 @@ export class RemoteGraphQLDataSource<
82
77
  const context = originalContext as TContext;
83
78
 
84
79
  // Respect incoming http headers (eg, apollo-federation-include-trace).
85
- const headers = (request.http && request.http.headers) || new Headers();
80
+ const headers = new NodeFetchHeaders();
81
+ if (request.http?.headers) {
82
+ for (const [name, value] of request.http.headers) {
83
+ headers.append(name, value);
84
+ }
85
+ }
86
86
  headers.set('Content-Type', 'application/json');
87
87
 
88
88
  request.http = {
@@ -112,7 +112,7 @@ export class RemoteGraphQLDataSource<
112
112
  : null;
113
113
 
114
114
  if (this.apq) {
115
- const apqHash = createSHA('sha256').update(request.query).digest('hex');
115
+ const apqHash = createHash('sha256').update(request.query).digest('hex');
116
116
 
117
117
  // Take the original extensions and extend them with
118
118
  // the necessary "extensions" for APQ handshaking.
@@ -149,7 +149,7 @@ export class RemoteGraphQLDataSource<
149
149
  // If APQ was enabled, we'll run the same request again, but add in the
150
150
  // previously omitted `query`. If APQ was NOT enabled, this is the first
151
151
  // request (non-APQ, all the way).
152
- const requestWithQuery: GraphQLRequest = {
152
+ const requestWithQuery: GatewayGraphQLRequest = {
153
153
  query,
154
154
  ...requestWithoutQuery,
155
155
  };
@@ -163,9 +163,9 @@ export class RemoteGraphQLDataSource<
163
163
  }
164
164
 
165
165
  private async sendRequest(
166
- request: GraphQLRequest,
166
+ request: GatewayGraphQLRequest,
167
167
  context: TContext,
168
- ): Promise<GraphQLResponse> {
168
+ ): Promise<GatewayGraphQLResponse> {
169
169
  // This would represent an internal programming error since this shouldn't
170
170
  // be possible in the way that this method is invoked right now.
171
171
  if (!request.http) {
@@ -177,20 +177,24 @@ export class RemoteGraphQLDataSource<
177
177
  // we're accessing (e.g. url) and what we access it with (e.g. headers).
178
178
  const { http, ...requestWithoutHttp } = request;
179
179
  const stringifiedRequestWithoutHttp = JSON.stringify(requestWithoutHttp);
180
- const fetchRequest = new Request(http.url, {
181
- ...http,
180
+ const requestInit: FetcherRequestInit = {
181
+ method: http.method,
182
+ headers: Object.fromEntries(http.headers),
182
183
  body: stringifiedRequestWithoutHttp,
183
- });
184
+ };
185
+ // Note that we don't actually send this Request object to the fetcher; it
186
+ // is merely sent to methods on this object that might be overridden by users.
187
+ // We are careful to only send data to the overridable fetcher function that uses
188
+ // plain JS objects --- some fetch implementations don't know how to handle
189
+ // Request or Headers objects created by other fetch implementations.
190
+ const fetchRequest = new NodeFetchRequest(http.url, requestInit);
184
191
 
185
- let fetchResponse: Response | undefined;
192
+ let fetchResponse: FetcherResponse | undefined;
186
193
 
187
194
  try {
188
195
  // Use our local `fetcher` to allow for fetch injection
189
196
  // Use the fetcher's `Request` implementation for compatibility
190
- fetchResponse = await this.fetcher(http.url, {
191
- ...http,
192
- body: stringifiedRequestWithoutHttp,
193
- });
197
+ fetchResponse = await this.fetcher(http.url, requestInit);
194
198
 
195
199
  if (!fetchResponse.ok) {
196
200
  throw await this.errorFromResponse(fetchResponse);
@@ -214,7 +218,7 @@ export class RemoteGraphQLDataSource<
214
218
 
215
219
  public willSendRequest?(
216
220
  options: GraphQLDataSourceProcessOptions<TContext>,
217
- ): ValueOrPromise<void>;
221
+ ): void | Promise<void>;
218
222
 
219
223
  private async respond({
220
224
  response,
@@ -222,11 +226,11 @@ export class RemoteGraphQLDataSource<
222
226
  context,
223
227
  overallCachePolicy,
224
228
  }: {
225
- response: GraphQLResponse;
226
- request: GraphQLRequest;
229
+ response: GatewayGraphQLResponse;
230
+ request: GatewayGraphQLRequest;
227
231
  context: TContext;
228
- overallCachePolicy: CachePolicy | null;
229
- }): Promise<GraphQLResponse> {
232
+ overallCachePolicy: GatewayCachePolicy | null;
233
+ }): Promise<GatewayGraphQLResponse> {
230
234
  const processedResponse =
231
235
  typeof this.didReceiveResponse === 'function'
232
236
  ? await this.didReceiveResponse({ response, request, context })
@@ -241,16 +245,16 @@ export class RemoteGraphQLDataSource<
241
245
  // thus the overall response) is uncacheable. (If you don't like this, you
242
246
  // can tweak the `cache-control` header in your `didReceiveResponse`
243
247
  // method.)
244
- const hint: CacheHint = { maxAge: 0 };
248
+ const hint: GatewayCacheHint = { maxAge: 0 };
245
249
  const maxAge = parsed['max-age'];
246
250
  if (typeof maxAge === 'string' && maxAge.match(/^[0-9]+$/)) {
247
251
  hint.maxAge = +maxAge;
248
252
  }
249
253
  if (parsed['private'] === true) {
250
- hint.scope = CacheScope.Private;
254
+ hint.scope = 'PRIVATE';
251
255
  }
252
256
  if (parsed['public'] === true) {
253
- hint.scope = CacheScope.Public;
257
+ hint.scope = 'PUBLIC';
254
258
  }
255
259
  overallCachePolicy.restrict(hint);
256
260
  }
@@ -260,22 +264,22 @@ export class RemoteGraphQLDataSource<
260
264
 
261
265
  public didReceiveResponse?(
262
266
  requestContext: Required<
263
- Pick<GraphQLRequestContext<TContext>, 'request' | 'response' | 'context'>
267
+ Pick<GatewayGraphQLRequestContext<TContext>, 'request' | 'response' | 'context'>
264
268
  >,
265
- ): ValueOrPromise<GraphQLResponse>;
269
+ ): GatewayGraphQLResponse | Promise<GatewayGraphQLResponse>;
266
270
 
267
271
  public didEncounterError(
268
272
  error: Error,
269
- _fetchRequest: Request,
270
- _fetchResponse?: Response,
273
+ _fetchRequest: NodeFetchRequest,
274
+ _fetchResponse?: FetcherResponse,
271
275
  _context?: TContext,
272
276
  ) {
273
277
  throw error;
274
278
  }
275
279
 
276
280
  public parseBody(
277
- fetchResponse: Response,
278
- _fetchRequest?: Request,
281
+ fetchResponse: FetcherResponse,
282
+ _fetchRequest?: NodeFetchRequest,
279
283
  _context?: TContext,
280
284
  ): Promise<object | string> {
281
285
  const contentType = fetchResponse.headers.get('Content-Type');
@@ -286,29 +290,37 @@ export class RemoteGraphQLDataSource<
286
290
  }
287
291
  }
288
292
 
289
- public async errorFromResponse(response: Response) {
290
- const message = `${response.status}: ${response.statusText}`;
291
-
292
- let error: ApolloError;
293
- if (response.status === 401) {
294
- error = new AuthenticationError(message);
295
- } else if (response.status === 403) {
296
- error = new ForbiddenError(message);
297
- } else {
298
- error = new ApolloError(message);
299
- }
300
-
293
+ public async errorFromResponse(response: FetcherResponse) {
301
294
  const body = await this.parseBody(response);
302
295
 
303
- Object.assign(error.extensions, {
296
+ const extensions: GraphQLErrorExtensions = {
304
297
  response: {
305
298
  url: response.url,
306
299
  status: response.status,
307
300
  statusText: response.statusText,
308
301
  body,
309
302
  },
310
- });
303
+ };
304
+
305
+ if (response.status === 401) {
306
+ extensions.code = 'UNAUTHENTICATED';
307
+ } else if (response.status === 403) {
308
+ extensions.code = 'FORBIDDEN';
309
+ }
311
310
 
312
- return error;
311
+ // Note: gateway 0.x still supports graphql-js v15.8, which does
312
+ // not have the options-based GraphQLError constructor. Note that
313
+ // the constructor used here is dropped in graphql-js v17, so this
314
+ // will have to be adjusted if we try to make gateway 0.x support
315
+ // graphql-js v17.
316
+ return new GraphQLError(
317
+ `${response.status}: ${response.statusText}`,
318
+ null,
319
+ null,
320
+ null,
321
+ null,
322
+ null,
323
+ extensions,
324
+ );
313
325
  }
314
326
  }
@@ -1,9 +1,9 @@
1
1
  import { LocalGraphQLDataSource } from '../LocalGraphQLDataSource';
2
2
  import { buildSubgraphSchema } from '@apollo/subgraph';
3
3
  import gql from 'graphql-tag';
4
- import { GraphQLResolverMap } from '../../schema-helper';
5
- import { GraphQLRequestContext } from 'apollo-server-types';
4
+ import { GraphQLResolverMap } from '@apollo/subgraph/src/schema-helper';
6
5
  import { GraphQLDataSourceRequestKind } from '../types';
6
+ import { GatewayGraphQLRequestContext } from '@apollo/server-gateway-interface';
7
7
 
8
8
  describe('constructing requests', () => {
9
9
  it('accepts context', async () => {
@@ -42,7 +42,7 @@ describe('constructing requests', () => {
42
42
  },
43
43
  incomingRequestContext: {
44
44
  context: { userId: 2 },
45
- } as GraphQLRequestContext<{userId: number}>,
45
+ } as GatewayGraphQLRequestContext<{userId: number}>,
46
46
  context: { userId: 2 },
47
47
  });
48
48