@apollo/gateway 0.50.2 → 0.52.1

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 (64) 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 +28 -28
  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 +12 -33
  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/package.json +10 -12
  36. package/src/__generated__/graphqlTypes.ts +1 -1
  37. package/src/__tests__/executeQueryPlan.test.ts +14 -5
  38. package/src/__tests__/execution-utils.ts +3 -3
  39. package/src/__tests__/gateway/buildService.test.ts +81 -83
  40. package/src/__tests__/gateway/executor.test.ts +20 -17
  41. package/src/__tests__/gateway/opentelemetry.test.ts +3 -7
  42. package/src/__tests__/gateway/supergraphSdl.test.ts +10 -12
  43. package/src/config.ts +4 -6
  44. package/src/datasources/LocalGraphQLDataSource.ts +2 -4
  45. package/src/datasources/RemoteGraphQLDataSource.ts +72 -59
  46. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +2 -2
  47. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +120 -159
  48. package/src/datasources/types.ts +5 -5
  49. package/src/executeQueryPlan.ts +18 -18
  50. package/src/index.ts +24 -70
  51. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -6
  52. package/src/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts +2 -2
  53. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +70 -74
  54. package/src/supergraphManagers/UplinkFetcher/index.ts +2 -2
  55. package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +23 -17
  56. package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +9 -7
  57. package/dist/cache.d.ts +0 -18
  58. package/dist/cache.d.ts.map +0 -1
  59. package/dist/cache.js +0 -46
  60. package/dist/cache.js.map +0 -1
  61. package/src/__mocks__/apollo-server-env.ts +0 -56
  62. package/src/__mocks__/make-fetch-happen-fetcher.ts +0 -57
  63. package/src/cache.ts +0 -66
  64. package/src/make-fetch-happen.d.ts +0 -59
@@ -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
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 = {
@@ -107,7 +107,8 @@ export class RemoteGraphQLDataSource<
107
107
  const overallCachePolicy =
108
108
  this.honorSubgraphCacheControlHeader &&
109
109
  options.kind === GraphQLDataSourceRequestKind.INCOMING_OPERATION &&
110
- options.incomingRequestContext.overallCachePolicy?.restrict
110
+ options.incomingRequestContext.overallCachePolicy &&
111
+ 'restrict' in options.incomingRequestContext.overallCachePolicy
111
112
  ? options.incomingRequestContext.overallCachePolicy
112
113
  : null;
113
114
 
@@ -149,7 +150,7 @@ export class RemoteGraphQLDataSource<
149
150
  // If APQ was enabled, we'll run the same request again, but add in the
150
151
  // previously omitted `query`. If APQ was NOT enabled, this is the first
151
152
  // request (non-APQ, all the way).
152
- const requestWithQuery: GraphQLRequest = {
153
+ const requestWithQuery: GatewayGraphQLRequest = {
153
154
  query,
154
155
  ...requestWithoutQuery,
155
156
  };
@@ -163,9 +164,9 @@ export class RemoteGraphQLDataSource<
163
164
  }
164
165
 
165
166
  private async sendRequest(
166
- request: GraphQLRequest,
167
+ request: GatewayGraphQLRequest,
167
168
  context: TContext,
168
- ): Promise<GraphQLResponse> {
169
+ ): Promise<GatewayGraphQLResponse> {
169
170
  // This would represent an internal programming error since this shouldn't
170
171
  // be possible in the way that this method is invoked right now.
171
172
  if (!request.http) {
@@ -177,20 +178,24 @@ export class RemoteGraphQLDataSource<
177
178
  // we're accessing (e.g. url) and what we access it with (e.g. headers).
178
179
  const { http, ...requestWithoutHttp } = request;
179
180
  const stringifiedRequestWithoutHttp = JSON.stringify(requestWithoutHttp);
180
- const fetchRequest = new Request(http.url, {
181
- ...http,
181
+ const requestInit: FetcherRequestInit = {
182
+ method: http.method,
183
+ headers: Object.fromEntries(http.headers),
182
184
  body: stringifiedRequestWithoutHttp,
183
- });
185
+ };
186
+ // Note that we don't actually send this Request object to the fetcher; it
187
+ // is merely sent to methods on this object that might be overridden by users.
188
+ // We are careful to only send data to the overridable fetcher function that uses
189
+ // plain JS objects --- some fetch implementations don't know how to handle
190
+ // Request or Headers objects created by other fetch implementations.
191
+ const fetchRequest = new NodeFetchRequest(http.url, requestInit);
184
192
 
185
- let fetchResponse: Response | undefined;
193
+ let fetchResponse: FetcherResponse | undefined;
186
194
 
187
195
  try {
188
196
  // Use our local `fetcher` to allow for fetch injection
189
197
  // Use the fetcher's `Request` implementation for compatibility
190
- fetchResponse = await this.fetcher(http.url, {
191
- ...http,
192
- body: stringifiedRequestWithoutHttp,
193
- });
198
+ fetchResponse = await this.fetcher(http.url, requestInit);
194
199
 
195
200
  if (!fetchResponse.ok) {
196
201
  throw await this.errorFromResponse(fetchResponse);
@@ -214,7 +219,7 @@ export class RemoteGraphQLDataSource<
214
219
 
215
220
  public willSendRequest?(
216
221
  options: GraphQLDataSourceProcessOptions<TContext>,
217
- ): ValueOrPromise<void>;
222
+ ): void | Promise<void>;
218
223
 
219
224
  private async respond({
220
225
  response,
@@ -222,11 +227,11 @@ export class RemoteGraphQLDataSource<
222
227
  context,
223
228
  overallCachePolicy,
224
229
  }: {
225
- response: GraphQLResponse;
226
- request: GraphQLRequest;
230
+ response: GatewayGraphQLResponse;
231
+ request: GatewayGraphQLRequest;
227
232
  context: TContext;
228
- overallCachePolicy: CachePolicy | null;
229
- }): Promise<GraphQLResponse> {
233
+ overallCachePolicy: GatewayCachePolicy | null;
234
+ }): Promise<GatewayGraphQLResponse> {
230
235
  const processedResponse =
231
236
  typeof this.didReceiveResponse === 'function'
232
237
  ? await this.didReceiveResponse({ response, request, context })
@@ -241,16 +246,16 @@ export class RemoteGraphQLDataSource<
241
246
  // thus the overall response) is uncacheable. (If you don't like this, you
242
247
  // can tweak the `cache-control` header in your `didReceiveResponse`
243
248
  // method.)
244
- const hint: CacheHint = { maxAge: 0 };
249
+ const hint: GatewayCacheHint = { maxAge: 0 };
245
250
  const maxAge = parsed['max-age'];
246
251
  if (typeof maxAge === 'string' && maxAge.match(/^[0-9]+$/)) {
247
252
  hint.maxAge = +maxAge;
248
253
  }
249
254
  if (parsed['private'] === true) {
250
- hint.scope = CacheScope.Private;
255
+ hint.scope = 'PRIVATE';
251
256
  }
252
257
  if (parsed['public'] === true) {
253
- hint.scope = CacheScope.Public;
258
+ hint.scope = 'PUBLIC';
254
259
  }
255
260
  overallCachePolicy.restrict(hint);
256
261
  }
@@ -260,22 +265,22 @@ export class RemoteGraphQLDataSource<
260
265
 
261
266
  public didReceiveResponse?(
262
267
  requestContext: Required<
263
- Pick<GraphQLRequestContext<TContext>, 'request' | 'response' | 'context'>
268
+ Pick<GatewayGraphQLRequestContext<TContext>, 'request' | 'response' | 'context'>
264
269
  >,
265
- ): ValueOrPromise<GraphQLResponse>;
270
+ ): GatewayGraphQLResponse | Promise<GatewayGraphQLResponse>;
266
271
 
267
272
  public didEncounterError(
268
273
  error: Error,
269
- _fetchRequest: Request,
270
- _fetchResponse?: Response,
274
+ _fetchRequest: NodeFetchRequest,
275
+ _fetchResponse?: FetcherResponse,
271
276
  _context?: TContext,
272
277
  ) {
273
278
  throw error;
274
279
  }
275
280
 
276
281
  public parseBody(
277
- fetchResponse: Response,
278
- _fetchRequest?: Request,
282
+ fetchResponse: FetcherResponse,
283
+ _fetchRequest?: NodeFetchRequest,
279
284
  _context?: TContext,
280
285
  ): Promise<object | string> {
281
286
  const contentType = fetchResponse.headers.get('Content-Type');
@@ -286,29 +291,37 @@ export class RemoteGraphQLDataSource<
286
291
  }
287
292
  }
288
293
 
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
-
294
+ public async errorFromResponse(response: FetcherResponse) {
301
295
  const body = await this.parseBody(response);
302
296
 
303
- Object.assign(error.extensions, {
297
+ const extensions: GraphQLErrorExtensions = {
304
298
  response: {
305
299
  url: response.url,
306
300
  status: response.status,
307
301
  statusText: response.statusText,
308
302
  body,
309
303
  },
310
- });
304
+ };
305
+
306
+ if (response.status === 401) {
307
+ extensions.code = 'UNAUTHENTICATED';
308
+ } else if (response.status === 403) {
309
+ extensions.code = 'FORBIDDEN';
310
+ }
311
311
 
312
- return error;
312
+ // Note: gateway 0.x still supports graphql-js v15.8, which does
313
+ // not have the options-based GraphQLError constructor. Note that
314
+ // the constructor used here is dropped in graphql-js v17, so this
315
+ // will have to be adjusted if we try to make gateway 0.x support
316
+ // graphql-js v17.
317
+ return new GraphQLError(
318
+ `${response.status}: ${response.statusText}`,
319
+ null,
320
+ null,
321
+ null,
322
+ null,
323
+ null,
324
+ extensions,
325
+ );
313
326
  }
314
327
  }
@@ -2,8 +2,8 @@ import { LocalGraphQLDataSource } from '../LocalGraphQLDataSource';
2
2
  import { buildSubgraphSchema } from '@apollo/subgraph';
3
3
  import gql from 'graphql-tag';
4
4
  import { GraphQLResolverMap } from '@apollo/subgraph/src/schema-helper';
5
- import { GraphQLRequestContext } from 'apollo-server-types';
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