@apollo/gateway 0.48.3 → 0.50.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 (63) hide show
  1. package/dist/__generated__/graphqlTypes.d.ts +1 -0
  2. package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
  3. package/dist/config.d.ts +8 -3
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +1 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/executeQueryPlan.d.ts.map +1 -1
  8. package/dist/executeQueryPlan.js +4 -3
  9. package/dist/executeQueryPlan.js.map +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +14 -7
  12. package/dist/index.js.map +1 -1
  13. package/dist/schema-helper/index.d.ts +0 -1
  14. package/dist/schema-helper/index.d.ts.map +1 -1
  15. package/dist/schema-helper/index.js +0 -1
  16. package/dist/schema-helper/index.js.map +1 -1
  17. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +1 -1
  18. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -1
  19. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +1 -1
  20. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -1
  21. package/dist/supergraphManagers/LocalCompose/index.d.ts +1 -1
  22. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -1
  23. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +4 -2
  24. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -1
  25. package/dist/supergraphManagers/UplinkFetcher/index.js +20 -4
  26. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -1
  27. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +3 -2
  28. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -1
  29. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js +9 -3
  30. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -1
  31. package/package.json +6 -6
  32. package/src/__generated__/graphqlTypes.ts +1 -1
  33. package/src/__tests__/CucumberREADME.md +1 -0
  34. package/src/__tests__/build-query-plan-fragmentization.feature +10 -0
  35. package/src/__tests__/build-query-plan.feature +84 -16
  36. package/src/__tests__/buildQueryPlan.test.ts +272 -1
  37. package/src/__tests__/execution-utils.ts +4 -4
  38. package/src/__tests__/gateway/executor.test.ts +1 -1
  39. package/src/__tests__/gateway/lifecycle-hooks.test.ts +4 -4
  40. package/src/__tests__/gateway/reporting.test.ts +5 -0
  41. package/src/__tests__/gateway/supergraphSdl.test.ts +4 -4
  42. package/src/__tests__/integration/abstract-types.test.ts +3 -3
  43. package/src/__tests__/integration/configuration.test.ts +1 -12
  44. package/src/__tests__/integration/logger.test.ts +1 -1
  45. package/src/__tests__/integration/networkRequests.test.ts +1 -1
  46. package/src/__tests__/integration/requires.test.ts +1 -1
  47. package/src/__tests__/integration/value-types.test.ts +1 -1
  48. package/src/config.ts +13 -10
  49. package/src/executeQueryPlan.ts +4 -0
  50. package/src/index.ts +13 -7
  51. package/src/schema-helper/index.ts +0 -1
  52. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +1 -1
  53. package/src/supergraphManagers/IntrospectAndCompose/index.ts +1 -1
  54. package/src/supergraphManagers/LegacyFetcher/index.ts +1 -1
  55. package/src/supergraphManagers/LocalCompose/index.ts +1 -1
  56. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +41 -0
  57. package/src/supergraphManagers/UplinkFetcher/index.ts +38 -19
  58. package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +9 -1
  59. package/dist/schema-helper/error.d.ts +0 -6
  60. package/dist/schema-helper/error.d.ts.map +0 -1
  61. package/dist/schema-helper/error.js +0 -14
  62. package/dist/schema-helper/error.js.map +0 -1
  63. package/src/schema-helper/error.ts +0 -11
package/src/index.ts CHANGED
@@ -2,9 +2,9 @@ import { deprecate } from 'util';
2
2
  import { GraphQLService, Unsubscriber } from 'apollo-server-core';
3
3
  import {
4
4
  GraphQLExecutionResult,
5
- Logger,
6
5
  GraphQLRequestContextExecutionDidStart,
7
6
  } from 'apollo-server-types';
7
+ import type { Logger } from '@apollo/utils.logger';
8
8
  import { InMemoryLRUCache } from 'apollo-server-caching';
9
9
  import {
10
10
  isObjectType,
@@ -202,8 +202,12 @@ export class ApolloGateway implements GraphQLService {
202
202
  this.experimental_didUpdateSupergraph =
203
203
  config?.experimental_didUpdateSupergraph;
204
204
 
205
- this.pollIntervalInMs =
206
- config?.pollIntervalInMs ?? config?.experimental_pollInterval;
205
+ if (isManagedConfig(this.config)) {
206
+ this.pollIntervalInMs =
207
+ this.config.fallbackPollIntervalInMs ?? this.config.pollIntervalInMs;
208
+ } else if (isServiceListConfig(this.config)) {
209
+ this.pollIntervalInMs = this.config?.pollIntervalInMs;
210
+ }
207
211
 
208
212
  this.issueConfigurationWarningsIfApplicable();
209
213
 
@@ -254,7 +258,7 @@ export class ApolloGateway implements GraphQLService {
254
258
  'Polling Apollo services at a frequency of less than once per 10 ' +
255
259
  'seconds (10000) is disallowed. Instead, the minimum allowed ' +
256
260
  'pollInterval of 10000 will be used. Please reconfigure your ' +
257
- '`pollIntervalInMs` accordingly. If this is problematic for ' +
261
+ '`fallbackPollIntervalInMs` accordingly. If this is problematic for ' +
258
262
  'your team, please contact support.',
259
263
  );
260
264
  }
@@ -288,9 +292,11 @@ export class ApolloGateway implements GraphQLService {
288
292
  );
289
293
  }
290
294
 
291
- if ('experimental_pollInterval' in this.config) {
295
+ if (isManagedConfig(this.config) && 'pollIntervalInMs' in this.config) {
292
296
  this.logger.warn(
293
- 'The `experimental_pollInterval` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the equivalent `pollIntervalInMs` configuration option.',
297
+ 'The `pollIntervalInMs` option is deprecated and will be removed in a future version of `@apollo/gateway`. ' +
298
+ 'Please migrate to the equivalent `fallbackPollIntervalInMs` configuration option. ' +
299
+ 'The poll interval is now defined by Uplink, this option will only be used if it is greater than the value defined by Uplink or as a fallback.',
294
300
  );
295
301
  }
296
302
  }
@@ -404,7 +410,7 @@ export class ApolloGateway implements GraphQLService {
404
410
  subgraphHealthCheck: this.config.serviceHealthCheck,
405
411
  fetcher: this.fetcher,
406
412
  logger: this.logger,
407
- pollIntervalInMs: this.pollIntervalInMs ?? 10000,
413
+ fallbackPollIntervalInMs: this.pollIntervalInMs ?? 10000,
408
414
  }),
409
415
  );
410
416
  }
@@ -1,3 +1,2 @@
1
1
  export * from './resolverMap';
2
2
  export * from './addResolversToSchema';
3
- export * from './error';
@@ -9,7 +9,7 @@ import { IntrospectAndCompose } from '..';
9
9
  import { mockAllServicesSdlQuerySuccess } from '../../../__tests__/integration/nockMocks';
10
10
  import { getTestingSupergraphSdl, wait } from '../../../__tests__/execution-utils';
11
11
  import resolvable from '@josephg/resolvable';
12
- import { Logger } from 'apollo-server-types';
12
+ import type { Logger } from '@apollo/utils.logger';
13
13
 
14
14
  describe('IntrospectAndCompose', () => {
15
15
  beforeEach(nockBeforeEach);
@@ -3,7 +3,7 @@ import {
3
3
  compositionHasErrors,
4
4
  ServiceDefinition,
5
5
  } from '@apollo/federation';
6
- import { Logger } from 'apollo-server-types';
6
+ import type { Logger } from '@apollo/utils.logger';
7
7
  import { HeadersInit } from 'node-fetch';
8
8
  import resolvable from '@josephg/resolvable';
9
9
  import {
@@ -4,7 +4,7 @@
4
4
  * configuration options of the gateway and will be removed in a future release
5
5
  * along with those options.
6
6
  */
7
- import { Logger } from 'apollo-server-types';
7
+ import type { Logger } from '@apollo/utils.logger';
8
8
  import resolvable from '@josephg/resolvable';
9
9
  import {
10
10
  SupergraphManager,
@@ -1,5 +1,5 @@
1
1
  // TODO(trevor:removeServiceList) the whole file goes away
2
- import { Logger } from 'apollo-server-types';
2
+ import type { Logger } from '@apollo/utils.logger';
3
3
  import {
4
4
  composeAndValidate,
5
5
  compositionHasErrors,
@@ -65,6 +65,7 @@ describe('loadSupergraphSdlFromStorage', () => {
65
65
  compositionId: "originalId-1234",
66
66
  maxRetries: 1,
67
67
  roundRobinSeed: 0,
68
+ earliestFetchTime: null,
68
69
  });
69
70
 
70
71
  expect(result).toMatchObject({
@@ -88,6 +89,7 @@ describe('loadSupergraphSdlFromStorage', () => {
88
89
  compositionId: "originalId-1234",
89
90
  maxRetries: 1,
90
91
  roundRobinSeed: 0,
92
+ earliestFetchTime: null,
91
93
  }),
92
94
  ).rejects.toThrowError(
93
95
  new UplinkFetcherError(
@@ -388,10 +390,49 @@ describe("loadSupergraphSdlFromUplinks", () => {
388
390
  compositionId: "id-1234",
389
391
  maxRetries: 5,
390
392
  roundRobinSeed: 0,
393
+ earliestFetchTime: null,
391
394
  });
392
395
 
393
396
  expect(result).toBeNull();
394
397
  expect(fetcher).toHaveBeenCalledTimes(1);
395
398
  });
399
+
400
+ it("Waits the correct time before retrying", async () => {
401
+ const timeoutSpy = jest.spyOn(global, 'setTimeout');
402
+
403
+ mockSupergraphSdlRequest('originalId-1234', mockCloudConfigUrl1).reply(500);
404
+ mockSupergraphSdlRequestIfAfter('originalId-1234', mockCloudConfigUrl2).reply(
405
+ 200,
406
+ JSON.stringify({
407
+ data: {
408
+ routerConfig: {
409
+ __typename: 'RouterConfigResult',
410
+ id: 'originalId-1234',
411
+ supergraphSdl: getTestingSupergraphSdl()
412
+ },
413
+ },
414
+ }),
415
+ );
416
+ const fetcher = getDefaultFetcher();
417
+
418
+ await loadSupergraphSdlFromUplinks({
419
+ graphRef,
420
+ apiKey,
421
+ endpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2],
422
+ errorReportingEndpoint: undefined,
423
+ fetcher: fetcher,
424
+ compositionId: "originalId-1234",
425
+ maxRetries: 1,
426
+ roundRobinSeed: 0,
427
+ earliestFetchTime: new Date(Date.now() + 1000),
428
+ });
429
+
430
+ // test if setTimeout was called with a value in range to deal with time jitter
431
+ const setTimeoutCall = timeoutSpy.mock.calls[1][1];
432
+ expect(setTimeoutCall).toBeLessThanOrEqual(1000);
433
+ expect(setTimeoutCall).toBeGreaterThanOrEqual(900);
434
+
435
+ timeoutSpy.mockRestore();
436
+ });
396
437
  });
397
438
 
@@ -1,12 +1,12 @@
1
1
  import { fetch } from 'apollo-server-env';
2
- import { Logger } from 'apollo-server-types';
2
+ import type { Logger } from '@apollo/utils.logger';
3
3
  import resolvable from '@josephg/resolvable';
4
4
  import { SupergraphManager, SupergraphSdlHookOptions } from '../../config';
5
5
  import { SubgraphHealthCheckFunction, SupergraphSdlUpdateFunction } from '../..';
6
6
  import { loadSupergraphSdlFromUplinks } from './loadSupergraphSdlFromStorage';
7
7
 
8
8
  export interface UplinkFetcherOptions {
9
- pollIntervalInMs: number;
9
+ fallbackPollIntervalInMs: number;
10
10
  subgraphHealthCheck?: boolean;
11
11
  graphRef: string;
12
12
  apiKey: string;
@@ -31,6 +31,8 @@ export class UplinkFetcher implements SupergraphManager {
31
31
  process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT ?? undefined;
32
32
  private compositionId?: string;
33
33
  private fetchCount: number = 0;
34
+ private minDelayMs: number | null = null;
35
+ private earliestFetchTime: Date | null = null;
34
36
 
35
37
  constructor(options: UplinkFetcherOptions) {
36
38
  this.config = options;
@@ -46,7 +48,12 @@ export class UplinkFetcher implements SupergraphManager {
46
48
 
47
49
  let initialSupergraphSdl: string | null = null;
48
50
  try {
49
- initialSupergraphSdl = await this.updateSupergraphSdl();
51
+ const result = await this.updateSupergraphSdl();
52
+ initialSupergraphSdl = result?.supergraphSdl || null;
53
+ if (result?.minDelaySeconds) {
54
+ this.minDelayMs = 1000 * result?.minDelaySeconds;
55
+ this.earliestFetchTime = new Date(Date.now() + this.minDelayMs);
56
+ }
50
57
  } catch (e) {
51
58
  this.logUpdateFailure(e);
52
59
  throw e;
@@ -83,6 +90,7 @@ export class UplinkFetcher implements SupergraphManager {
83
90
  compositionId: this.compositionId ?? null,
84
91
  maxRetries: this.config.maxRetries,
85
92
  roundRobinSeed: this.fetchCount++,
93
+ earliestFetchTime: this.earliestFetchTime,
86
94
  });
87
95
 
88
96
  if (!result) {
@@ -91,7 +99,8 @@ export class UplinkFetcher implements SupergraphManager {
91
99
  this.compositionId = result.id;
92
100
  // the healthCheck fn is only assigned if it's enabled in the config
93
101
  await this.healthCheck?.(result.supergraphSdl);
94
- return result.supergraphSdl;
102
+ const { supergraphSdl, minDelaySeconds } = result;
103
+ return { supergraphSdl, minDelaySeconds };
95
104
  }
96
105
  }
97
106
 
@@ -101,24 +110,34 @@ export class UplinkFetcher implements SupergraphManager {
101
110
  }
102
111
 
103
112
  private poll() {
104
- this.timerRef = setTimeout(async () => {
105
- if (this.state.phase === 'polling') {
106
- const pollingPromise = resolvable();
107
-
108
- this.state.pollingPromise = pollingPromise;
109
- try {
110
- const maybeNewSupergraphSdl = await this.updateSupergraphSdl();
111
- if (maybeNewSupergraphSdl) {
112
- this.update?.(maybeNewSupergraphSdl);
113
+ this.timerRef = setTimeout(
114
+ async () => {
115
+ if (this.state.phase === 'polling') {
116
+ const pollingPromise = resolvable();
117
+
118
+ this.state.pollingPromise = pollingPromise;
119
+ try {
120
+ const result = await this.updateSupergraphSdl();
121
+ const maybeNewSupergraphSdl = result?.supergraphSdl || null;
122
+ if (result?.minDelaySeconds) {
123
+ this.minDelayMs = 1000 * result?.minDelaySeconds;
124
+ this.earliestFetchTime = new Date(Date.now() + this.minDelayMs);
125
+ }
126
+ if (maybeNewSupergraphSdl) {
127
+ this.update?.(maybeNewSupergraphSdl);
128
+ }
129
+ } catch (e) {
130
+ this.logUpdateFailure(e);
113
131
  }
114
- } catch (e) {
115
- this.logUpdateFailure(e);
132
+ pollingPromise.resolve();
116
133
  }
117
- pollingPromise.resolve();
118
- }
119
134
 
120
- this.poll();
121
- }, this.config.pollIntervalInMs);
135
+ this.poll();
136
+ },
137
+ this.minDelayMs
138
+ ? Math.max(this.minDelayMs, this.config.fallbackPollIntervalInMs)
139
+ : this.config.fallbackPollIntervalInMs,
140
+ );
122
141
  }
123
142
 
124
143
  private logUpdateFailure(e: any) {
@@ -13,6 +13,7 @@ export const SUPERGRAPH_SDL_QUERY = /* GraphQL */`#graphql
13
13
  ... on RouterConfigResult {
14
14
  id
15
15
  supergraphSdl: supergraphSDL
16
+ minDelaySeconds
16
17
  }
17
18
  ... on FetchError {
18
19
  code
@@ -56,6 +57,7 @@ export async function loadSupergraphSdlFromUplinks({
56
57
  compositionId,
57
58
  maxRetries,
58
59
  roundRobinSeed,
60
+ earliestFetchTime,
59
61
  }: {
60
62
  graphRef: string;
61
63
  apiKey: string;
@@ -65,6 +67,7 @@ export async function loadSupergraphSdlFromUplinks({
65
67
  compositionId: string | null;
66
68
  maxRetries: number,
67
69
  roundRobinSeed: number,
70
+ earliestFetchTime: Date | null
68
71
  }) : Promise<SupergraphSdlUpdate | null> {
69
72
  // This Promise resolves with either an updated supergraph or null if no change.
70
73
  // This Promise can reject in the case that none of the retries are successful,
@@ -81,6 +84,10 @@ export async function loadSupergraphSdlFromUplinks({
81
84
  }),
82
85
  {
83
86
  retries: maxRetries,
87
+ onRetry: async () => {
88
+ const delayMS = earliestFetchTime ? earliestFetchTime.getTime() - Date.now(): 0;
89
+ if (delayMS > 0) await new Promise(resolve => setTimeout(resolve, delayMS));
90
+ }
84
91
  },
85
92
  );
86
93
 
@@ -176,9 +183,10 @@ export async function loadSupergraphSdlFromStorage({
176
183
  const {
177
184
  id,
178
185
  supergraphSdl,
186
+ minDelaySeconds,
179
187
  // messages,
180
188
  } = routerConfig;
181
- return { id, supergraphSdl: supergraphSdl! };
189
+ return { id, supergraphSdl: supergraphSdl!, minDelaySeconds };
182
190
  } else if (routerConfig.__typename === 'FetchError') {
183
191
  // FetchError case
184
192
  const { code, message } = routerConfig;
@@ -1,6 +0,0 @@
1
- import { GraphQLError } from "graphql";
2
- export declare class GraphQLSchemaValidationError extends Error {
3
- errors: ReadonlyArray<GraphQLError>;
4
- constructor(errors: ReadonlyArray<GraphQLError>);
5
- }
6
- //# sourceMappingURL=error.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/schema-helper/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,qBAAa,4BAA6B,SAAQ,KAAK;IAClC,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC;gBAAnC,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC;CAOvD"}
@@ -1,14 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GraphQLSchemaValidationError = void 0;
4
- class GraphQLSchemaValidationError extends Error {
5
- constructor(errors) {
6
- super();
7
- this.errors = errors;
8
- this.name = this.constructor.name;
9
- Error.captureStackTrace(this, this.constructor);
10
- this.message = errors.map(error => error.message).join("\n\n");
11
- }
12
- }
13
- exports.GraphQLSchemaValidationError = GraphQLSchemaValidationError;
14
- //# sourceMappingURL=error.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/schema-helper/error.ts"],"names":[],"mappings":";;;AAEA,MAAa,4BAA6B,SAAQ,KAAK;IACrD,YAAmB,MAAmC;QACpD,KAAK,EAAE,CAAC;QADS,WAAM,GAAN,MAAM,CAA6B;QAGpD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAClC,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;CACF;AARD,oEAQC"}
@@ -1,11 +0,0 @@
1
- import { GraphQLError } from "graphql";
2
-
3
- export class GraphQLSchemaValidationError extends Error {
4
- constructor(public errors: ReadonlyArray<GraphQLError>) {
5
- super();
6
-
7
- this.name = this.constructor.name;
8
- Error.captureStackTrace(this, this.constructor);
9
- this.message = errors.map(error => error.message).join("\n\n");
10
- }
11
- }