@apollo/gateway 2.0.6-rc.0 → 2.1.0-alpha.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 (83) hide show
  1. package/dist/config.d.ts +1 -1
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +2 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  6. package/dist/datasources/RemoteGraphQLDataSource.js +2 -3
  7. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  8. package/dist/datasources/types.d.ts +1 -0
  9. package/dist/datasources/types.d.ts.map +1 -1
  10. package/dist/executeQueryPlan.d.ts +2 -2
  11. package/dist/executeQueryPlan.d.ts.map +1 -1
  12. package/dist/executeQueryPlan.js +24 -16
  13. package/dist/executeQueryPlan.js.map +1 -1
  14. package/dist/index.d.ts +8 -8
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +27 -62
  17. package/dist/index.js.map +1 -1
  18. package/dist/logger.d.ts +3 -0
  19. package/dist/logger.d.ts.map +1 -0
  20. package/dist/logger.js +15 -0
  21. package/dist/logger.js.map +1 -0
  22. package/dist/operationContext.d.ts.map +1 -1
  23. package/dist/operationContext.js +3 -7
  24. package/dist/operationContext.js.map +1 -1
  25. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +1 -1
  26. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -1
  27. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +1 -1
  28. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
  29. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -1
  30. package/dist/supergraphManagers/UplinkSupergraphManager/index.d.ts +61 -0
  31. package/dist/supergraphManagers/UplinkSupergraphManager/index.d.ts.map +1 -0
  32. package/dist/supergraphManagers/UplinkSupergraphManager/index.js +209 -0
  33. package/dist/supergraphManagers/UplinkSupergraphManager/index.js.map +1 -0
  34. package/dist/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/loadSupergraphSdlFromStorage.d.ts +8 -4
  35. package/dist/supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  36. package/dist/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/loadSupergraphSdlFromStorage.js +20 -8
  37. package/dist/supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage.js.map +1 -0
  38. package/dist/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/outOfBandReporter.d.ts +2 -1
  39. package/dist/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.d.ts.map +1 -0
  40. package/dist/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/outOfBandReporter.js +0 -0
  41. package/dist/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.js.map +1 -0
  42. package/dist/supergraphManagers/UplinkSupergraphManager/types.d.ts +9 -0
  43. package/dist/supergraphManagers/UplinkSupergraphManager/types.d.ts.map +1 -0
  44. package/dist/supergraphManagers/UplinkSupergraphManager/types.js +5 -0
  45. package/dist/supergraphManagers/UplinkSupergraphManager/types.js.map +1 -0
  46. package/dist/supergraphManagers/index.d.ts +2 -2
  47. package/dist/supergraphManagers/index.d.ts.map +1 -1
  48. package/dist/supergraphManagers/index.js +17 -4
  49. package/dist/supergraphManagers/index.js.map +1 -1
  50. package/package.json +10 -10
  51. package/src/__tests__/executeQueryPlan.test.ts +199 -1
  52. package/src/__tests__/execution-utils.ts +5 -3
  53. package/src/__tests__/integration/abstract-types.test.ts +31 -65
  54. package/src/__tests__/integration/configuration.test.ts +2 -45
  55. package/src/__tests__/integration/managed.test.ts +292 -0
  56. package/src/__tests__/integration/networkRequests.test.ts +14 -54
  57. package/src/__tests__/integration/nockMocks.ts +7 -6
  58. package/src/config.ts +3 -1
  59. package/src/datasources/RemoteGraphQLDataSource.ts +1 -2
  60. package/src/datasources/types.ts +4 -0
  61. package/src/executeQueryPlan.ts +41 -30
  62. package/src/index.ts +33 -88
  63. package/src/logger.ts +11 -0
  64. package/src/operationContext.ts +5 -7
  65. package/src/supergraphManagers/IntrospectAndCompose/index.ts +1 -1
  66. package/src/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts +1 -1
  67. package/src/supergraphManagers/UplinkSupergraphManager/__tests__/UplinkSupergraphManager.test.ts +65 -0
  68. package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/__tests__/loadSupergraphSdlFromStorage.test.ts +51 -16
  69. package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/__tests__/tsconfig.json +0 -0
  70. package/src/supergraphManagers/UplinkSupergraphManager/index.ts +312 -0
  71. package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/loadSupergraphSdlFromStorage.ts +32 -12
  72. package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/outOfBandReporter.ts +2 -1
  73. package/src/supergraphManagers/UplinkSupergraphManager/types.ts +10 -0
  74. package/src/supergraphManagers/index.ts +2 -2
  75. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +0 -35
  76. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +0 -1
  77. package/dist/supergraphManagers/UplinkFetcher/index.js +0 -114
  78. package/dist/supergraphManagers/UplinkFetcher/index.js.map +0 -1
  79. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +0 -1
  80. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +0 -1
  81. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +0 -1
  82. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +0 -1
  83. package/src/supergraphManagers/UplinkFetcher/index.ts +0 -149
@@ -14,6 +14,8 @@ import {
14
14
  GraphQLSchema,
15
15
  isObjectType,
16
16
  isInterfaceType,
17
+ GraphQLErrorOptions,
18
+ DocumentNode,
17
19
  } from 'graphql';
18
20
  import { Trace, google } from 'apollo-reporting-protobuf';
19
21
  import { GraphQLDataSource, GraphQLDataSourceRequestKind } from './datasources/types';
@@ -31,7 +33,7 @@ import { deepMerge } from './utilities/deepMerge';
31
33
  import { isNotNullOrUndefined } from './utilities/array';
32
34
  import { SpanStatusCode } from "@opentelemetry/api";
33
35
  import { OpenTelemetrySpanNames, tracer } from "./utilities/opentelemetry";
34
- import { defaultRootName } from '@apollo/federation-internals';
36
+ import { defaultRootName, errorCodeDef, ERRORS } from '@apollo/federation-internals';
35
37
 
36
38
  export type ServiceMap = {
37
39
  [serviceName: string]: GraphQLDataSource;
@@ -44,6 +46,7 @@ interface ExecutionContext<TContext> {
44
46
  operationContext: OperationContext;
45
47
  serviceMap: ServiceMap;
46
48
  requestContext: GraphQLRequestContext<TContext>;
49
+ supergraphSchema: GraphQLSchema;
47
50
  errors: GraphQLError[];
48
51
  }
49
52
 
@@ -52,6 +55,7 @@ export async function executeQueryPlan<TContext>(
52
55
  serviceMap: ServiceMap,
53
56
  requestContext: GraphQLRequestContext<TContext>,
54
57
  operationContext: OperationContext,
58
+ supergraphSchema: GraphQLSchema,
55
59
  ): Promise<GraphQLExecutionResult> {
56
60
 
57
61
  const logger = requestContext.logger || console;
@@ -65,6 +69,7 @@ export async function executeQueryPlan<TContext>(
65
69
  operationContext,
66
70
  serviceMap,
67
71
  requestContext,
72
+ supergraphSchema,
68
73
  errors,
69
74
  };
70
75
 
@@ -118,11 +123,7 @@ export async function executeQueryPlan<TContext>(
118
123
  errors: [
119
124
  new GraphQLError(
120
125
  error.message,
121
- undefined,
122
- undefined,
123
- undefined,
124
- undefined,
125
- error as Error,
126
+ { originalError: error },
126
127
  )
127
128
  ]
128
129
  };
@@ -298,7 +299,8 @@ async function executeFetch<TContext>(
298
299
  context,
299
300
  fetch.operation,
300
301
  variables,
301
- fetch.operationName
302
+ fetch.operationName,
303
+ fetch.operationDocumentNode
302
304
  );
303
305
 
304
306
  for (const entity of entities) {
@@ -312,7 +314,10 @@ async function executeFetch<TContext>(
312
314
 
313
315
  entities.forEach((entity, index) => {
314
316
  const representation = executeSelectionSet(
315
- context.operationContext,
317
+ // Note that `requires` may include references to inacessible elements, so we should "execute" it using the supergrah
318
+ // schema, _not_ the API schema (the one in `context.operationContext.schema`). And this is not a security risk since
319
+ // what we're extracting here is what is sent to subgraphs, and subgraphs knows `@inacessible` elements.
320
+ context.supergraphSchema,
316
321
  entity,
317
322
  requires,
318
323
  );
@@ -334,7 +339,8 @@ async function executeFetch<TContext>(
334
339
  context,
335
340
  fetch.operation,
336
341
  {...variables, representations},
337
- fetch.operationName
342
+ fetch.operationName,
343
+ fetch.operationDocumentNode
338
344
  );
339
345
 
340
346
  if (!dataReceivedFromService) {
@@ -376,7 +382,8 @@ async function executeFetch<TContext>(
376
382
  context: ExecutionContext<TContext>,
377
383
  source: string,
378
384
  variables: Record<string, any>,
379
- operationName: string | undefined
385
+ operationName: string | undefined,
386
+ operationDocumentNode?: DocumentNode
380
387
  ): Promise<ResultMap | void | null> {
381
388
  // We declare this as 'any' because it is missing url and method, which
382
389
  // GraphQLRequest.http is supposed to have if it exists.
@@ -414,6 +421,7 @@ async function executeFetch<TContext>(
414
421
  },
415
422
  incomingRequestContext: context.requestContext,
416
423
  context: context.requestContext.context,
424
+ document: operationDocumentNode
417
425
  });
418
426
 
419
427
  if (response.errors) {
@@ -479,7 +487,7 @@ async function executeFetch<TContext>(
479
487
  * @param selectionSet
480
488
  */
481
489
  function executeSelectionSet(
482
- operationContext: OperationContext,
490
+ schema: GraphQLSchema,
483
491
  source: Record<string, any> | null,
484
492
  selections: QueryPlanSelectionNode[],
485
493
  ): Record<string, any> | null {
@@ -514,12 +522,12 @@ function executeSelectionSet(
514
522
  if (Array.isArray(source[responseName])) {
515
523
  result[responseName] = source[responseName].map((value: any) =>
516
524
  selections
517
- ? executeSelectionSet(operationContext, value, selections)
525
+ ? executeSelectionSet(schema, value, selections)
518
526
  : value,
519
527
  );
520
528
  } else if (selections) {
521
529
  result[responseName] = executeSelectionSet(
522
- operationContext,
530
+ schema,
523
531
  source[responseName],
524
532
  selections,
525
533
  );
@@ -533,10 +541,10 @@ function executeSelectionSet(
533
541
  const typename = source && source['__typename'];
534
542
  if (!typename) continue;
535
543
 
536
- if (doesTypeConditionMatch(operationContext.schema, selection.typeCondition, typename)) {
544
+ if (doesTypeConditionMatch(schema, selection.typeCondition, typename)) {
537
545
  deepMerge(
538
546
  result,
539
- executeSelectionSet(operationContext, source, selection.selections),
547
+ executeSelectionSet(schema, source, selection.selections),
540
548
  );
541
549
  }
542
550
  break;
@@ -593,22 +601,25 @@ function downstreamServiceError(
593
601
  if (!message) {
594
602
  message = `Error while fetching subquery from service "${serviceName}"`;
595
603
  }
596
- extensions = {
597
- code: 'DOWNSTREAM_SERVICE_ERROR',
598
- // XXX The presence of a serviceName in extensions is used to
599
- // determine if this error should be captured for metrics reporting.
600
- serviceName,
601
- ...extensions,
604
+
605
+ const errorOptions: GraphQLErrorOptions = {
606
+ originalError: originalError as Error,
607
+ extensions: {
608
+ ...extensions,
609
+ // XXX The presence of a serviceName in extensions is used to
610
+ // determine if this error should be captured for metrics reporting.
611
+ serviceName,
612
+ }
602
613
  };
603
- return new GraphQLError(
604
- message,
605
- undefined,
606
- undefined,
607
- undefined,
608
- undefined,
609
- originalError as Error,
610
- extensions,
611
- );
614
+
615
+ let codeDef = errorCodeDef(originalError);
616
+ // It's possible the orignal has a code, but not one we know about (one generated by the underlying `GraphQLDataSource`,
617
+ // which we don't control). In that case, we want to use that code (and have thus no `ErrorCodeDefinition` usable).
618
+ if (!codeDef && extensions?.code) {
619
+ return new GraphQLError(message, errorOptions);
620
+ }
621
+ // Otherwise, we either use the code we found and know, or default to a general downstream error code.
622
+ return (codeDef ?? ERRORS.DOWNSTREAM_SERVICE_ERROR).err(message, errorOptions);
612
623
  }
613
624
 
614
625
  export const defaultFieldResolverWithAliasSupport: GraphQLFieldResolver<
package/src/index.ts CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  GraphQLSchema,
14
14
  VariableDefinitionNode,
15
15
  } from 'graphql';
16
- import loglevel from 'loglevel';
17
16
  import { buildOperationContext, OperationContext } from './operationContext';
18
17
  import {
19
18
  executeQueryPlan,
@@ -26,7 +25,6 @@ import {
26
25
  } from './datasources/types';
27
26
  import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource';
28
27
  import { getVariableValues } from 'graphql/execution/values';
29
- import fetcher from 'make-fetch-happen';
30
28
  import {
31
29
  QueryPlanner,
32
30
  QueryPlan,
@@ -46,7 +44,6 @@ import {
46
44
  isManagedConfig,
47
45
  SupergraphSdlUpdate,
48
46
  isManuallyManagedSupergraphSdlGatewayConfig,
49
- ManagedGatewayConfig,
50
47
  isStaticSupergraphSdlConfig,
51
48
  SupergraphManager,
52
49
  } from './config';
@@ -55,7 +52,7 @@ import { OpenTelemetrySpanNames, tracer } from './utilities/opentelemetry';
55
52
  import { addExtensions } from './schema-helper/addExtensions';
56
53
  import {
57
54
  IntrospectAndCompose,
58
- UplinkFetcher,
55
+ UplinkSupergraphManager,
59
56
  LegacyFetcher,
60
57
  LocalCompose,
61
58
  } from './supergraphManagers';
@@ -65,7 +62,7 @@ import {
65
62
  Schema,
66
63
  ServiceDefinition,
67
64
  } from '@apollo/federation-internals';
68
- import { Fetcher } from '@apollo/utils.fetcher';
65
+ import { getDefaultLogger } from './logger';
69
66
 
70
67
  type DataSourceMap = {
71
68
  [serviceName: string]: { url?: string; dataSource: GraphQLDataSource };
@@ -145,9 +142,10 @@ export class ApolloGateway implements GraphQLService {
145
142
  private warnedStates: WarnedStates = Object.create(null);
146
143
  private queryPlanner?: QueryPlanner;
147
144
  private supergraphSdl?: string;
148
- private fetcher: Fetcher;
145
+ private supergraphSchema?: GraphQLSchema;
149
146
  private compositionId?: string;
150
147
  private state: GatewayState;
148
+ private _supergraphManager?: SupergraphManager;
151
149
 
152
150
  // Observe query plan, service info, and operation info prior to execution.
153
151
  // The information made available here will give insight into the resulting
@@ -169,11 +167,10 @@ export class ApolloGateway implements GraphQLService {
169
167
  ...config,
170
168
  };
171
169
 
172
- this.logger = this.initLogger();
170
+ this.logger = this.config.logger ?? getDefaultLogger(this.config.debug);
173
171
  this.queryPlanStore = this.initQueryPlanStore(
174
172
  config?.experimental_approximateQueryPlanStoreMiB,
175
173
  );
176
- this.fetcher = config?.fetcher || fetcher;
177
174
 
178
175
  // set up experimental observability callbacks and config settings
179
176
  this.experimental_didResolveQueryPlan =
@@ -194,23 +191,8 @@ export class ApolloGateway implements GraphQLService {
194
191
  this.state = { phase: 'initialized' };
195
192
  }
196
193
 
197
- private initLogger() {
198
- // Setup logging facilities
199
- if (this.config.logger) {
200
- return this.config.logger;
201
- }
202
-
203
- // If the user didn't provide their own logger, we'll initialize one.
204
- const loglevelLogger = loglevel.getLogger(`apollo-gateway`);
205
-
206
- // And also support the `debug` option, if it's truthy.
207
- if (this.config.debug === true) {
208
- loglevelLogger.setLevel(loglevelLogger.levels.DEBUG);
209
- } else {
210
- loglevelLogger.setLevel(loglevelLogger.levels.WARN);
211
- }
212
-
213
- return loglevelLogger;
194
+ public get supergraphManager(): SupergraphManager | undefined {
195
+ return this._supergraphManager;
214
196
  }
215
197
 
216
198
  private initQueryPlanStore(approximateQueryPlanStoreMiB?: number) {
@@ -226,22 +208,6 @@ export class ApolloGateway implements GraphQLService {
226
208
  }
227
209
 
228
210
  private issueConfigurationWarningsIfApplicable() {
229
- // Warn against a pollInterval of < 10s in managed mode and reset it to 10s
230
- if (
231
- isManagedConfig(this.config) &&
232
- this.pollIntervalInMs &&
233
- this.pollIntervalInMs < 10000
234
- ) {
235
- this.pollIntervalInMs = 10000;
236
- this.logger.warn(
237
- 'Polling Apollo services at a frequency of less than once per 10 ' +
238
- 'seconds (10000) is disallowed. Instead, the minimum allowed ' +
239
- 'pollInterval of 10000 will be used. Please reconfigure your ' +
240
- '`fallbackPollIntervalInMs` accordingly. If this is problematic for ' +
241
- 'your team, please contact support.',
242
- );
243
- }
244
-
245
211
  // Warn against using the pollInterval and a serviceList simultaneously
246
212
  // TODO(trevor:removeServiceList)
247
213
  if (this.pollIntervalInMs && isServiceListConfig(this.config)) {
@@ -381,19 +347,21 @@ export class ApolloGateway implements GraphQLService {
381
347
  '`serviceList`, `supergraphSdl`, and `experimental_updateServiceDefinitions`.',
382
348
  );
383
349
  }
384
- const uplinkEndpoints = this.getUplinkEndpoints(this.config);
385
350
 
351
+ const schemaDeliveryEndpoints: string[] | undefined = this.config
352
+ .schemaConfigDeliveryEndpoint
353
+ ? [this.config.schemaConfigDeliveryEndpoint]
354
+ : undefined;
386
355
  await this.initializeSupergraphManager(
387
- new UplinkFetcher({
356
+ new UplinkSupergraphManager({
388
357
  graphRef: this.apolloConfig!.graphRef!,
389
358
  apiKey: this.apolloConfig!.key!,
390
- uplinkEndpoints,
391
- maxRetries:
392
- this.config.uplinkMaxRetries ?? uplinkEndpoints.length * 3 - 1, // -1 for the initial request
393
- subgraphHealthCheck: this.config.serviceHealthCheck,
394
- fetcher: this.fetcher,
359
+ shouldRunSubgraphHealthcheck: this.config.serviceHealthCheck,
360
+ uplinkEndpoints:
361
+ this.config.uplinkEndpoints ?? schemaDeliveryEndpoints,
362
+ maxRetries: this.config.uplinkMaxRetries,
395
363
  logger: this.logger,
396
- fallbackPollIntervalInMs: this.pollIntervalInMs ?? 10000,
364
+ fallbackPollIntervalInMs: this.pollIntervalInMs,
397
365
  }),
398
366
  );
399
367
  }
@@ -415,29 +383,6 @@ export class ApolloGateway implements GraphQLService {
415
383
  };
416
384
  }
417
385
 
418
- private getUplinkEndpoints(config: ManagedGatewayConfig) {
419
- /**
420
- * Configuration priority order:
421
- * 1. `uplinkEndpoints` configuration option
422
- * 2. (deprecated) `schemaConfigDeliveryEndpoint` configuration option
423
- * 3. APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT environment variable
424
- * 4. default (GCP and AWS)
425
- */
426
- const rawEndpointsString =
427
- process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
428
- const envEndpoints = rawEndpointsString?.split(',') ?? null;
429
- return (
430
- config.uplinkEndpoints ??
431
- (config.schemaConfigDeliveryEndpoint
432
- ? [config.schemaConfigDeliveryEndpoint]
433
- : null) ??
434
- envEndpoints ?? [
435
- 'https://uplink.api.apollographql.com/',
436
- 'https://aws.uplink.api.apollographql.com/',
437
- ]
438
- );
439
- }
440
-
441
386
  private getIdForSupergraphSdl(supergraphSdl: string) {
442
387
  return createHash('sha256').update(supergraphSdl).digest('hex');
443
388
  }
@@ -451,11 +396,6 @@ export class ApolloGateway implements GraphQLService {
451
396
  healthCheck: this.externalSubgraphHealthCheckCallback.bind(this),
452
397
  getDataSource: this.externalGetDataSourceCallback.bind(this),
453
398
  });
454
- if (!result?.supergraphSdl) {
455
- throw new Error(
456
- 'Provided `supergraphSdl` function did not return an object containing a `supergraphSdl` property',
457
- );
458
- }
459
399
  if (result?.cleanup) {
460
400
  if (typeof result.cleanup === 'function') {
461
401
  this.toDispose.push(result.cleanup);
@@ -473,6 +413,7 @@ export class ApolloGateway implements GraphQLService {
473
413
  throw e;
474
414
  }
475
415
 
416
+ this._supergraphManager = supergraphManager;
476
417
  this.state = { phase: 'loaded' };
477
418
  }
478
419
 
@@ -566,7 +507,7 @@ export class ApolloGateway implements GraphQLService {
566
507
  // In the case that it throws, the gateway will:
567
508
  // * on initial load, throw the error
568
509
  // * on update, log the error and don't update
569
- const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
510
+ const { supergraphSchema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
570
511
  result.supergraphSdl,
571
512
  );
572
513
 
@@ -575,25 +516,26 @@ export class ApolloGateway implements GraphQLService {
575
516
  const previousCompositionId = this.compositionId;
576
517
 
577
518
  if (previousSchema) {
578
- this.logger.info('Updated Supergraph SDL was found.');
519
+ this.logger.info(`Updated Supergraph SDL was found [Composition ID ${this.compositionId} => ${result.id}]`);
579
520
  }
580
521
 
581
522
  this.compositionId = result.id;
582
523
  this.supergraphSdl = supergraphSdl;
524
+ this.supergraphSchema = supergraphSchema.toGraphQLJSSchema();
583
525
 
584
526
  if (!supergraphSdl) {
585
527
  this.logger.error(
586
528
  "A valid schema couldn't be composed. Falling back to previous schema.",
587
529
  );
588
530
  } else {
589
- this.updateWithSchemaAndNotify(schema, supergraphSdl);
531
+ this.updateWithSchemaAndNotify(supergraphSchema, supergraphSdl);
590
532
 
591
533
  if (this.experimental_didUpdateSupergraph) {
592
534
  this.experimental_didUpdateSupergraph(
593
535
  {
594
536
  compositionId: result.id,
595
537
  supergraphSdl,
596
- schema: schema.toGraphQLJSSchema(),
538
+ schema: this.schema!,
597
539
  },
598
540
  previousCompositionId && previousSupergraphSdl && previousSchema
599
541
  ? {
@@ -698,7 +640,7 @@ export class ApolloGateway implements GraphQLService {
698
640
  this.createServices(serviceList);
699
641
 
700
642
  return {
701
- schema,
643
+ supergraphSchema: schema,
702
644
  supergraphSdl,
703
645
  };
704
646
  }
@@ -903,6 +845,7 @@ export class ApolloGateway implements GraphQLService {
903
845
  serviceMap,
904
846
  requestContext,
905
847
  operationContext,
848
+ this.supergraphSchema!,
906
849
  );
907
850
 
908
851
  const shouldShowQueryPlan =
@@ -1008,9 +951,6 @@ export class ApolloGateway implements GraphQLService {
1008
951
  switch (this.state.phase) {
1009
952
  case 'initialized':
1010
953
  case 'failed to load':
1011
- throw Error(
1012
- 'ApolloGateway.stop does not need to be called before ApolloGateway.load is called successfully',
1013
- );
1014
954
  case 'stopped':
1015
955
  // Calls to stop() are idempotent.
1016
956
  return;
@@ -1105,6 +1045,7 @@ export {
1105
1045
  CompositionInfo,
1106
1046
  IntrospectAndCompose,
1107
1047
  LocalCompose,
1048
+ UplinkSupergraphManager,
1108
1049
  };
1109
1050
 
1110
1051
  export * from './datasources';
@@ -1114,8 +1055,12 @@ export {
1114
1055
  SubgraphHealthCheckFunction,
1115
1056
  GetDataSourceFunction,
1116
1057
  SupergraphSdlHook,
1117
- SupergraphManager
1058
+ SupergraphManager,
1118
1059
  } from './config';
1119
1060
 
1120
- export { UplinkFetcherError } from "./supergraphManagers"
1121
-
1061
+ export {
1062
+ UplinkFetcherError,
1063
+ FailureToFetchSupergraphSdlAfterInit,
1064
+ FailureToFetchSupergraphSdlDuringInit,
1065
+ FailureToFetchSupergraphSdlFunctionParams,
1066
+ } from './supergraphManagers';
package/src/logger.ts ADDED
@@ -0,0 +1,11 @@
1
+ import loglevel from 'loglevel';
2
+ import type { Logger } from '@apollo/utils.logger';
3
+
4
+ export function getDefaultLogger(debug: boolean = true): Logger {
5
+ const logger = loglevel.getLogger('apollo-gateway');
6
+
7
+ const level = debug === true ? loglevel.levels.DEBUG : loglevel.levels.WARN;
8
+ logger.setLevel(level);
9
+
10
+ return logger;
11
+ }
@@ -1,7 +1,7 @@
1
+ import { ERRORS } from '@apollo/federation-internals';
1
2
  import {
2
3
  DocumentNode,
3
4
  FragmentDefinitionNode,
4
- GraphQLError,
5
5
  GraphQLSchema,
6
6
  Kind,
7
7
  OperationDefinitionNode,
@@ -37,7 +37,7 @@ export function buildOperationContext({
37
37
  case Kind.OPERATION_DEFINITION:
38
38
  operationCount++;
39
39
  if (!operationName && operationCount > 1) {
40
- throw new GraphQLError(
40
+ throw ERRORS.INVALID_GRAPHQL.err(
41
41
  'Must provide operation name if query contains ' +
42
42
  'multiple operations.',
43
43
  );
@@ -55,11 +55,9 @@ export function buildOperationContext({
55
55
  }
56
56
  });
57
57
  if (!operation) {
58
- if (operationName) {
59
- throw new GraphQLError(`Unknown operation named "${operationName}".`);
60
- } else {
61
- throw new GraphQLError('Must provide an operation.');
62
- }
58
+ throw ERRORS.INVALID_GRAPHQL.err(
59
+ operationName ? `Unknown operation named "${operationName}".` : 'Must provide an operation.'
60
+ );
63
61
  }
64
62
 
65
63
  return {
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from '@apollo/utils.logger';
2
- import { HeadersInit } from 'node-fetch';
2
+ import type { HeadersInit } from 'node-fetch';
3
3
  import resolvable from '@josephg/resolvable';
4
4
  import {
5
5
  ServiceEndpointDefinition,
@@ -1,6 +1,6 @@
1
1
  import { GraphQLRequest } from 'apollo-server-types';
2
2
  import { parse } from 'graphql';
3
- import { Headers, HeadersInit } from 'node-fetch';
3
+ import { Headers, type HeadersInit } from 'node-fetch';
4
4
  import { ServiceDefinition } from '@apollo/federation-internals';
5
5
  import { GraphQLDataSource, GraphQLDataSourceRequestKind } from '../../datasources/types';
6
6
  import { SERVICE_DEFINITION_QUERY } from '../..';
@@ -0,0 +1,65 @@
1
+ import mockedEnv from 'mocked-env';
2
+
3
+ import { UplinkSupergraphManager } from '@apollo/gateway';
4
+
5
+ let cleanUp: (() => void) | undefined;
6
+
7
+ const logger = {
8
+ warn: jest.fn(),
9
+ debug: jest.fn(),
10
+ error: jest.fn(),
11
+ info: jest.fn(),
12
+ };
13
+ const apiKey = 'OU812';
14
+ const graphRef = 'graph@ref';
15
+
16
+ afterEach(async () => {
17
+ if (cleanUp) {
18
+ cleanUp();
19
+ cleanUp = undefined;
20
+ }
21
+ });
22
+
23
+ describe('UplinkSupergraphManager', () => {
24
+ it('can be minimally constructed', () => {
25
+ new UplinkSupergraphManager({ apiKey, graphRef });
26
+ });
27
+
28
+ describe('setting uplink URLs', () => {
29
+ it('uses default uplink URLs', async () => {
30
+ const manager = new UplinkSupergraphManager({ apiKey, graphRef, logger });
31
+
32
+ expect(manager.uplinkEndpoints).toEqual(UplinkSupergraphManager.DEFAULT_UPLINK_ENDPOINTS);
33
+ });
34
+
35
+ it('can set uplink URLs via config', async () => {
36
+ cleanUp = mockedEnv({
37
+ APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: 'https://env-delivery.com',
38
+ });
39
+ const uplinkEndpoints = [
40
+ 'https://config-delivery1.com',
41
+ 'https://config-delivery2.com',
42
+ ];
43
+
44
+ const manager = new UplinkSupergraphManager({
45
+ apiKey,
46
+ graphRef,
47
+ uplinkEndpoints,
48
+ logger,
49
+ });
50
+
51
+ expect(manager.uplinkEndpoints).toEqual(uplinkEndpoints);
52
+ });
53
+
54
+ it('can set uplink URLs via environment variable', async () => {
55
+ const uplinkUrl = 'https://env-delivery.com';
56
+ cleanUp = mockedEnv({
57
+ APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: uplinkUrl,
58
+ });
59
+
60
+ const manager = new UplinkSupergraphManager({ apiKey, graphRef, logger });
61
+
62
+ expect(manager.uplinkEndpoints).toEqual([uplinkUrl]);
63
+ });
64
+ });
65
+ });