@apollo/gateway 2.0.0-alpha.6 → 2.0.0-preview.10

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 (60) hide show
  1. package/README.md +7 -5
  2. package/dist/__generated__/graphqlTypes.d.ts +4 -0
  3. package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
  4. package/dist/__generated__/graphqlTypes.js.map +1 -1
  5. package/dist/config.d.ts +8 -3
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +1 -0
  8. package/dist/config.js.map +1 -1
  9. package/dist/executeQueryPlan.d.ts.map +1 -1
  10. package/dist/executeQueryPlan.js +4 -3
  11. package/dist/executeQueryPlan.js.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +19 -8
  14. package/dist/index.js.map +1 -1
  15. package/dist/schema-helper/index.js +5 -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/IntrospectAndCompose/index.js.map +1 -1
  20. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +1 -1
  21. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -1
  22. package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -1
  23. package/dist/supergraphManagers/LocalCompose/index.d.ts +1 -1
  24. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -1
  25. package/dist/supergraphManagers/LocalCompose/index.js.map +1 -1
  26. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +4 -2
  27. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -1
  28. package/dist/supergraphManagers/UplinkFetcher/index.js +20 -4
  29. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -1
  30. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +3 -2
  31. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -1
  32. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js +9 -3
  33. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -1
  34. package/package.json +8 -8
  35. package/src/__generated__/graphqlTypes.ts +11 -2
  36. package/src/__tests__/build-query-plan.feature +32 -16
  37. package/src/__tests__/buildQueryPlan.test.ts +51 -4
  38. package/src/__tests__/executeQueryPlan.test.ts +256 -1
  39. package/src/__tests__/execution-utils.ts +3 -2
  40. package/src/__tests__/gateway/executor.test.ts +1 -1
  41. package/src/__tests__/gateway/lifecycle-hooks.test.ts +10 -4
  42. package/src/__tests__/gateway/reporting.test.ts +5 -0
  43. package/src/__tests__/gateway/supergraphSdl.test.ts +6 -4
  44. package/src/__tests__/integration/abstract-types.test.ts +6 -7
  45. package/src/__tests__/integration/complex-key.test.ts +1 -6
  46. package/src/__tests__/integration/configuration.test.ts +1 -12
  47. package/src/__tests__/integration/logger.test.ts +1 -1
  48. package/src/__tests__/integration/networkRequests.test.ts +1 -1
  49. package/src/__tests__/integration/value-types.test.ts +4 -4
  50. package/src/config.ts +13 -10
  51. package/src/core/__tests__/core.test.ts +8 -8
  52. package/src/executeQueryPlan.ts +4 -0
  53. package/src/index.ts +13 -7
  54. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +1 -1
  55. package/src/supergraphManagers/IntrospectAndCompose/index.ts +1 -1
  56. package/src/supergraphManagers/LegacyFetcher/index.ts +1 -1
  57. package/src/supergraphManagers/LocalCompose/index.ts +1 -1
  58. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +41 -0
  59. package/src/supergraphManagers/UplinkFetcher/index.ts +38 -19
  60. package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +9 -1
@@ -31,7 +31,7 @@ describe('core v0.1', () => {
31
31
 
32
32
  directive @tag(
33
33
  name: String!
34
- ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
34
+ ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
35
35
 
36
36
  enum CacheControlScope {
37
37
  PRIVATE
@@ -87,7 +87,7 @@ describe('core v0.1', () => {
87
87
 
88
88
  directive @tag(
89
89
  name: String!
90
- ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
90
+ ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
91
91
 
92
92
  enum CacheControlScope {
93
93
  PRIVATE
@@ -124,7 +124,7 @@ describe('core v0.2', () => {
124
124
  schema
125
125
  @core(feature: "https://specs.apollo.dev/core/v0.2")
126
126
  @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
127
- @core(feature: "https://specs.apollo.dev/tag/v0.1") {
127
+ @core(feature: "https://specs.apollo.dev/tag/v0.2") {
128
128
  query: Query
129
129
  }
130
130
 
@@ -151,7 +151,7 @@ describe('core v0.2', () => {
151
151
 
152
152
  directive @tag(
153
153
  name: String!
154
- ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
154
+ ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
155
155
 
156
156
  enum CacheControlScope {
157
157
  PRIVATE
@@ -193,7 +193,7 @@ describe('core v0.2', () => {
193
193
  schema
194
194
  @core(feature: "https://specs.apollo.dev/core/v0.2")
195
195
  @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
196
- @core(feature: "https://specs.apollo.dev/tag/v0.1")
196
+ @core(feature: "https://specs.apollo.dev/tag/v0.2")
197
197
  @core(feature: "https://specs.apollo.dev/unsupported-feature/v0.1") {
198
198
  query: Query
199
199
  }
@@ -221,7 +221,7 @@ describe('core v0.2', () => {
221
221
 
222
222
  directive @tag(
223
223
  name: String!
224
- ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
224
+ ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
225
225
 
226
226
  enum CacheControlScope {
227
227
  PRIVATE
@@ -293,7 +293,7 @@ describe('core v0.2', () => {
293
293
 
294
294
  directive @tag(
295
295
  name: String!
296
- ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
296
+ ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
297
297
 
298
298
  enum CacheControlScope {
299
299
  PRIVATE
@@ -369,7 +369,7 @@ describe('core v0.2', () => {
369
369
 
370
370
  directive @tag(
371
371
  name: String!
372
- ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
372
+ ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
373
373
 
374
374
  enum CacheControlScope {
375
375
  PRIVATE
@@ -298,6 +298,7 @@ async function executeFetch<TContext>(
298
298
  context,
299
299
  fetch.operation,
300
300
  variables,
301
+ fetch.operationName
301
302
  );
302
303
 
303
304
  for (const entity of entities) {
@@ -333,6 +334,7 @@ async function executeFetch<TContext>(
333
334
  context,
334
335
  fetch.operation,
335
336
  {...variables, representations},
337
+ fetch.operationName
336
338
  );
337
339
 
338
340
  if (!dataReceivedFromService) {
@@ -374,6 +376,7 @@ async function executeFetch<TContext>(
374
376
  context: ExecutionContext<TContext>,
375
377
  source: string,
376
378
  variables: Record<string, any>,
379
+ operationName: string | undefined
377
380
  ): Promise<ResultMap | void | null> {
378
381
  // We declare this as 'any' because it is missing url and method, which
379
382
  // GraphQLRequest.http is supposed to have if it exists.
@@ -402,6 +405,7 @@ async function executeFetch<TContext>(
402
405
  request: {
403
406
  query: source,
404
407
  variables,
408
+ operationName,
405
409
  http,
406
410
  },
407
411
  incomingRequestContext: context.requestContext,
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,
@@ -200,8 +200,12 @@ export class ApolloGateway implements GraphQLService {
200
200
  this.experimental_didUpdateSupergraph =
201
201
  config?.experimental_didUpdateSupergraph;
202
202
 
203
- this.pollIntervalInMs =
204
- config?.pollIntervalInMs ?? config?.experimental_pollInterval;
203
+ if (isManagedConfig(this.config)) {
204
+ this.pollIntervalInMs =
205
+ this.config.fallbackPollIntervalInMs ?? this.config.pollIntervalInMs;
206
+ } else if (isServiceListConfig(this.config)) {
207
+ this.pollIntervalInMs = this.config?.pollIntervalInMs;
208
+ }
205
209
 
206
210
  this.issueConfigurationWarningsIfApplicable();
207
211
 
@@ -252,7 +256,7 @@ export class ApolloGateway implements GraphQLService {
252
256
  'Polling Apollo services at a frequency of less than once per 10 ' +
253
257
  'seconds (10000) is disallowed. Instead, the minimum allowed ' +
254
258
  'pollInterval of 10000 will be used. Please reconfigure your ' +
255
- '`pollIntervalInMs` accordingly. If this is problematic for ' +
259
+ '`fallbackPollIntervalInMs` accordingly. If this is problematic for ' +
256
260
  'your team, please contact support.',
257
261
  );
258
262
  }
@@ -286,9 +290,11 @@ export class ApolloGateway implements GraphQLService {
286
290
  );
287
291
  }
288
292
 
289
- if ('experimental_pollInterval' in this.config) {
293
+ if (isManagedConfig(this.config) && 'pollIntervalInMs' in this.config) {
290
294
  this.logger.warn(
291
- '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.',
295
+ 'The `pollIntervalInMs` option is deprecated and will be removed in a future version of `@apollo/gateway`. ' +
296
+ 'Please migrate to the equivalent `fallbackPollIntervalInMs` configuration option. ' +
297
+ '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.',
292
298
  );
293
299
  }
294
300
  }
@@ -406,7 +412,7 @@ export class ApolloGateway implements GraphQLService {
406
412
  subgraphHealthCheck: this.config.serviceHealthCheck,
407
413
  fetcher: this.fetcher,
408
414
  logger: this.logger,
409
- pollIntervalInMs: this.pollIntervalInMs ?? 10000,
415
+ fallbackPollIntervalInMs: this.pollIntervalInMs ?? 10000,
410
416
  }),
411
417
  );
412
418
  }
@@ -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);
@@ -1,4 +1,4 @@
1
- import { Logger } from 'apollo-server-types';
1
+ import type { Logger } from '@apollo/utils.logger';
2
2
  import { HeadersInit } from 'node-fetch';
3
3
  import resolvable from '@josephg/resolvable';
4
4
  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 { composeServices } from '@apollo/composition';
4
4
  import {
5
5
  GetDataSourceFunction,
@@ -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;