@apollo/gateway 2.3.0-beta.2 → 2.3.0-beta.3

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.
@@ -1,6 +1,5 @@
1
1
  import { Headers } from 'node-fetch';
2
2
  import {
3
- execute,
4
3
  GraphQLError,
5
4
  Kind,
6
5
  TypeNameMetaFieldDef,
@@ -12,6 +11,9 @@ import {
12
11
  isInterfaceType,
13
12
  GraphQLErrorOptions,
14
13
  DocumentNode,
14
+ executeSync,
15
+ OperationTypeNode,
16
+ FieldNode,
15
17
  } from 'graphql';
16
18
  import { Trace, google } from '@apollo/usage-reporting-protobuf';
17
19
  import { GraphQLDataSource, GraphQLDataSourceRequestKind } from './datasources/types';
@@ -31,8 +33,9 @@ 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 { assert, defaultRootName, errorCodeDef, ERRORS, isDefined } from '@apollo/federation-internals';
36
+ import { assert, defaultRootName, errorCodeDef, ERRORS, isDefined, operationFromDocument, Schema } from '@apollo/federation-internals';
35
37
  import { GatewayGraphQLRequestContext, GatewayExecutionResult } from '@apollo/server-gateway-interface';
38
+ import { computeResponse } from './resultShaping';
36
39
 
37
40
  export type ServiceMap = {
38
41
  [serviceName: string]: GraphQLDataSource;
@@ -40,6 +43,24 @@ export type ServiceMap = {
40
43
 
41
44
  type ResultMap = Record<string, any>;
42
45
 
46
+ /**
47
+ * Represents some "cursor" within the full result, or put another way, a path into the full result and where it points to.
48
+ *
49
+ * Note that results can include lists and the the `path` considered can traverse those lists (the path will have a '@' character) so
50
+ * the data pointed by a cursor is not necessarily a single "branch" of the full results, but is in general a flattened list of all
51
+ * the sub-branches pointed by the path.
52
+ */
53
+ type ResultCursor = {
54
+ // Path into `fullResult` this cursor is pointing at.
55
+ path: ResponsePath,
56
+
57
+ // The data pointed by this cursor.
58
+ data: ResultMap | ResultMap[],
59
+
60
+ // The full result .
61
+ fullResult: ResultMap,
62
+ }
63
+
43
64
  interface ExecutionContext {
44
65
  queryPlan: QueryPlan;
45
66
  operationContext: OperationContext;
@@ -49,12 +70,42 @@ interface ExecutionContext {
49
70
  errors: GraphQLError[];
50
71
  }
51
72
 
73
+ function makeIntrospectionQueryDocument(introspectionSelection: FieldNode): DocumentNode {
74
+ return {
75
+ kind: Kind.DOCUMENT,
76
+ definitions: [
77
+ {
78
+ kind: Kind.OPERATION_DEFINITION,
79
+ operation: OperationTypeNode.QUERY,
80
+ selectionSet: {
81
+ kind: Kind.SELECTION_SET,
82
+ selections: [ introspectionSelection ],
83
+ }
84
+ }
85
+ ],
86
+ };
87
+ }
88
+
89
+ function executeIntrospection(
90
+ schema: GraphQLSchema,
91
+ introspectionSelection: FieldNode,
92
+ ): any {
93
+ const { data } = executeSync({
94
+ schema,
95
+ document: makeIntrospectionQueryDocument(introspectionSelection),
96
+ rootValue: {},
97
+ });
98
+ assert(data, () => `Introspection query for ${JSON.stringify(introspectionSelection)} should not have failed`);
99
+ return data[introspectionSelection.name.value];
100
+ }
101
+
52
102
  export async function executeQueryPlan(
53
103
  queryPlan: QueryPlan,
54
104
  serviceMap: ServiceMap,
55
105
  requestContext: GatewayGraphQLRequestContext,
56
106
  operationContext: OperationContext,
57
107
  supergraphSchema: GraphQLSchema,
108
+ apiSchema: Schema,
58
109
  ): Promise<GatewayExecutionResult> {
59
110
 
60
111
  const logger = requestContext.logger || console;
@@ -72,7 +123,7 @@ export async function executeQueryPlan(
72
123
  errors,
73
124
  };
74
125
 
75
- let data: ResultMap | undefined | null = Object.create(null);
126
+ const unfilteredData: ResultMap = Object.create(null);
76
127
 
77
128
  const captureTraces = !!(
78
129
  requestContext.metrics && requestContext.metrics.captureTraces
@@ -82,8 +133,11 @@ export async function executeQueryPlan(
82
133
  const traceNode = await executeNode(
83
134
  context,
84
135
  queryPlan.node,
85
- data!,
86
- [],
136
+ {
137
+ path: [],
138
+ data: unfilteredData,
139
+ fullResult: unfilteredData,
140
+ },
87
141
  captureTraces,
88
142
  );
89
143
  if (captureTraces) {
@@ -92,27 +146,51 @@ export async function executeQueryPlan(
92
146
  }
93
147
 
94
148
  const result = await tracer.startActiveSpan(OpenTelemetrySpanNames.POST_PROCESSING, async (span) => {
95
-
96
- // FIXME: Re-executing the query is a pretty heavy handed way of making sure
97
- // only explicitly requested fields are included and field ordering follows
98
- // the original query.
99
- // It is also used to allow execution of introspection queries though.
149
+ let data;
100
150
  try {
101
- const schema = operationContext.schema;
102
- ({ data } = await execute({
103
- schema,
104
- document: {
151
+ const operation = operationFromDocument(
152
+ apiSchema,
153
+ {
105
154
  kind: Kind.DOCUMENT,
106
155
  definitions: [
107
156
  operationContext.operation,
108
157
  ...Object.values(operationContext.fragments),
109
158
  ],
110
159
  },
111
- rootValue: data,
112
- variableValues: requestContext.request.variables,
113
- // See also `wrapSchemaWithAliasResolver` in `gateway-js/src/index.ts`.
114
- fieldResolver: defaultFieldResolverWithAliasSupport,
160
+ {
161
+ validate: false,
162
+ }
163
+ );
164
+
165
+ let postProcessingErrors: GraphQLError[];
166
+ ({ data, errors: postProcessingErrors } = computeResponse({
167
+ operation,
168
+ variables: requestContext.request.variables,
169
+ input: unfilteredData,
170
+ introspectionHandling: (f) => executeIntrospection(operationContext.schema, f.expandFragments().toSelectionNode()),
115
171
  }));
172
+
173
+ // If we have errors during the post-processing, we ignore them if any other errors have been thrown during
174
+ // query plan execution. That is because in many cases, errors during query plan execution will leave the
175
+ // internal data in a state that triggers additional post-processing errors, but that leads to 2 errors recorded
176
+ // for the same problem and that is unexpected by clients. See https://github.com/apollographql/federation/issues/981
177
+ // for additional context.
178
+ // If we had no errors during query plan execution, then we do ship any post-processing ones as there is little
179
+ // reason not to and it might genuinely help debugging (note that if subgraphs return no errors and we assume that
180
+ // subgraph do return graphQL valid responses, then our composition rules should guarantee no post-processing errors,
181
+ // so getting a post-processing error points to either 1) a bug in our code or in composition or 2) a subgraph not
182
+ // returning valid graphQL results, both of which are well worth surfacing (see [this comment for instance](https://github.com/apollographql/federation/pull/159#issuecomment-801132906))).
183
+ //
184
+ // That said, note that this is still not perfect in the sense that if someone does get subgraph errors, then
185
+ // while postProcessingErrors may duplicate those, it may also contain additional unrelated errors (again, something
186
+ // like a subgraph returning non-grapqlQL valid data unknowingly), and we don't surface those. In a perfect worlds
187
+ // we've be able to filter the post-proessing errors that duplicate errors from subgraph and still ship anything that
188
+ // remains, but it's unclear how to do that at all (it migth be that checking the error path helps, but not sure
189
+ // that's fullproof).
190
+ if (errors.length === 0 && postProcessingErrors.length > 0) {
191
+ span.setStatus({ code:SpanStatusCode.ERROR });
192
+ return { errors: postProcessingErrors, data };
193
+ }
116
194
  } catch (error) {
117
195
  span.setStatus({ code:SpanStatusCode.ERROR });
118
196
  if (error instanceof GraphQLError) {
@@ -172,11 +250,10 @@ export async function executeQueryPlan(
172
250
  async function executeNode(
173
251
  context: ExecutionContext,
174
252
  node: PlanNode,
175
- results: ResultMap | ResultMap[],
176
- path: ResponsePath,
253
+ currentCursor: ResultCursor | undefined,
177
254
  captureTraces: boolean,
178
255
  ): Promise<Trace.QueryPlanNode> {
179
- if (!results) {
256
+ if (!currentCursor) {
180
257
  // XXX I don't understand `results` threading well enough to understand when this happens
181
258
  // and if this corresponds to a real query plan node that should be reported or not.
182
259
  //
@@ -193,8 +270,7 @@ async function executeNode(
193
270
  const childTraceNode = await executeNode(
194
271
  context,
195
272
  childNode,
196
- results,
197
- path,
273
+ currentCursor,
198
274
  captureTraces,
199
275
  );
200
276
  traceNode.nodes.push(childTraceNode!);
@@ -203,8 +279,13 @@ async function executeNode(
203
279
  }
204
280
  case 'Parallel': {
205
281
  const childTraceNodes = await Promise.all(
206
- node.nodes.map(async childNode =>
207
- executeNode(context, childNode, results, path, captureTraces),
282
+ node.nodes.map(async (childNode) =>
283
+ executeNode(
284
+ context,
285
+ childNode,
286
+ currentCursor,
287
+ captureTraces,
288
+ ),
208
289
  ),
209
290
  );
210
291
  return new Trace.QueryPlanNode({
@@ -225,8 +306,7 @@ async function executeNode(
225
306
  node: await executeNode(
226
307
  context,
227
308
  node.node,
228
- flattenResultsAtPath(results, node.path),
229
- [...path, ...node.path],
309
+ moveIntoCursor(currentCursor, node.path),
230
310
  captureTraces,
231
311
  ),
232
312
  }),
@@ -241,8 +321,7 @@ async function executeNode(
241
321
  await executeFetch(
242
322
  context,
243
323
  node,
244
- results,
245
- path,
324
+ currentCursor,
246
325
  captureTraces ? traceNode : null,
247
326
  );
248
327
  } catch (error) {
@@ -262,8 +341,7 @@ async function executeNode(
262
341
  async function executeFetch(
263
342
  context: ExecutionContext,
264
343
  fetch: FetchNode,
265
- results: ResultMap | (ResultMap | null | undefined)[],
266
- _path: ResponsePath,
344
+ currentCursor: ResultCursor,
267
345
  traceNode: Trace.QueryPlanNode.FetchNode | null,
268
346
  ): Promise<void> {
269
347
 
@@ -277,11 +355,11 @@ async function executeFetch(
277
355
  }
278
356
 
279
357
  let entities: ResultMap[];
280
- if (Array.isArray(results)) {
358
+ if (Array.isArray(currentCursor.data)) {
281
359
  // Remove null or undefined entities from the list
282
- entities = results.filter(isNotNullOrUndefined);
360
+ entities = currentCursor.data.filter(isNotNullOrUndefined);
283
361
  } else {
284
- entities = [results];
362
+ entities = [currentCursor.data];
285
363
  }
286
364
 
287
365
  if (entities.length < 1) return;
@@ -300,13 +378,7 @@ async function executeFetch(
300
378
  }
301
379
 
302
380
  if (!fetch.requires) {
303
- const dataReceivedFromService = await sendOperation(
304
- context,
305
- fetch.operation,
306
- variables,
307
- fetch.operationName,
308
- fetch.operationDocumentNode
309
- );
381
+ const dataReceivedFromService = await sendOperation(variables);
310
382
 
311
383
  for (const entity of entities) {
312
384
  deepMerge(entity, withFetchRewrites(dataReceivedFromService, fetch.outputRewrites));
@@ -341,13 +413,7 @@ async function executeFetch(
341
413
  throw new Error(`Variables cannot contain key "representations"`);
342
414
  }
343
415
 
344
- const dataReceivedFromService = await sendOperation(
345
- context,
346
- fetch.operation,
347
- {...variables, representations},
348
- fetch.operationName,
349
- fetch.operationDocumentNode
350
- );
416
+ const dataReceivedFromService = await sendOperation({...variables, representations});
351
417
 
352
418
  if (!dataReceivedFromService) {
353
419
  return;
@@ -384,13 +450,11 @@ async function executeFetch(
384
450
  span.end();
385
451
  }
386
452
  });
453
+
387
454
  async function sendOperation(
388
- context: ExecutionContext,
389
- source: string,
390
455
  variables: Record<string, any>,
391
- operationName: string | undefined,
392
- operationDocumentNode?: DocumentNode
393
456
  ): Promise<ResultMap | void | null> {
457
+
394
458
  // We declare this as 'any' because it is missing url and method, which
395
459
  // GraphQLRequest.http is supposed to have if it exists.
396
460
  // (This is admittedly kinda weird, since we currently do pass url and
@@ -420,19 +484,21 @@ async function executeFetch(
420
484
  const response = await service.process({
421
485
  kind: GraphQLDataSourceRequestKind.INCOMING_OPERATION,
422
486
  request: {
423
- query: source,
487
+ query: fetch.operation,
424
488
  variables,
425
- operationName,
489
+ operationName: fetch.operationName,
426
490
  http,
427
491
  },
428
492
  incomingRequestContext: context.requestContext,
429
493
  context: context.requestContext.context,
430
- document: operationDocumentNode
494
+ document: fetch.operationDocumentNode,
431
495
  });
432
496
 
433
497
  if (response.errors) {
498
+ const errorPathHelper = makeLazyErrorPathGenerator(fetch, currentCursor);
499
+
434
500
  const errors = response.errors.map((error) =>
435
- downstreamServiceError(error, fetch.serviceName),
501
+ downstreamServiceError(error, fetch.serviceName, errorPathHelper),
436
502
  );
437
503
  context.errors.push(...errors);
438
504
  }
@@ -474,7 +540,9 @@ async function executeFetch(
474
540
  // to have the default names (Query, Mutation, Subscription) even
475
541
  // if the implementing services choose different names, so we override
476
542
  // whatever the implementing service reported here.
477
- const rootTypeName = defaultRootName(context.operationContext.operation.operation);
543
+ const rootTypeName = defaultRootName(
544
+ context.operationContext.operation.operation,
545
+ );
478
546
  traceNode.trace.root?.child?.forEach((child) => {
479
547
  child.parentType = rootTypeName;
480
548
  });
@@ -487,6 +555,108 @@ async function executeFetch(
487
555
  }
488
556
  }
489
557
 
558
+ type ErrorPathGenerator = (
559
+ path: GraphQLErrorOptions['path'],
560
+ ) => GraphQLErrorOptions['path'];
561
+
562
+ /**
563
+ * Given response data collected so far and a path such as:
564
+ *
565
+ * ["foo", "@", "bar", "@"]
566
+ *
567
+ * the returned function generates a list of "hydrated" paths, replacing the
568
+ * `"@"` with array indices from the actual data. When we encounter an error in
569
+ * a subgraph fetch, we can use the index in the error's path (e.g.
570
+ * `["_entities", 2, "boom"]`) to look up the appropriate "hydrated" path
571
+ * prefix. The result is something like:
572
+ *
573
+ * ["foo", 1, "bar", 2, "boom"]
574
+ *
575
+ * The returned function is lazy — if we don't encounter errors and it's never
576
+ * called, then we never process the response data to hydrate the paths.
577
+ *
578
+ * This approach is inspired by Apollo Router: https://github.com/apollographql/router/blob/0fd59d2e11cc09e82c876a5fee263b5658cb9539/apollo-router/src/query_planner/fetch.rs#L295-L403
579
+ */
580
+ function makeLazyErrorPathGenerator(
581
+ fetch: FetchNode,
582
+ cursor: ResultCursor,
583
+ ): ErrorPathGenerator {
584
+ let hydratedPaths: ResponsePath[] | undefined;
585
+
586
+ return (errorPath: GraphQLErrorOptions['path']) => {
587
+ if (fetch.requires && typeof errorPath?.[1] === 'number') {
588
+ // only generate paths if we need to look them up via entity index
589
+ if (!hydratedPaths) {
590
+ hydratedPaths = [];
591
+ generateHydratedPaths(
592
+ [],
593
+ cursor.path,
594
+ cursor.fullResult,
595
+ hydratedPaths,
596
+ );
597
+ }
598
+
599
+ const hydratedPath = hydratedPaths[errorPath[1]] ?? [];
600
+ return [...hydratedPath, ...errorPath.slice(2)];
601
+ } else {
602
+ return errorPath ? [...cursor.path, ...errorPath.slice()] : undefined;
603
+ }
604
+ };
605
+ }
606
+
607
+ /**
608
+ * Given a deeply nested object and a path such as `["foo", "@", "bar", "@"]`,
609
+ * walk the path to build up a list of of "hydrated" paths that match the data,
610
+ * such as:
611
+ *
612
+ * [
613
+ * ["foo", 0, "bar", 0, "boom"],
614
+ * ["foo", 0, "bar", 1, "boom"]
615
+ * ["foo", 1, "bar", 0, "boom"],
616
+ * ["foo", 1, "bar", 1, "boom"]
617
+ * ]
618
+ */
619
+ export function generateHydratedPaths(
620
+ parent: ResponsePath,
621
+ path: ResponsePath,
622
+ data: ResultMap | null,
623
+ result: ResponsePath[],
624
+ ) {
625
+ const head = path[0];
626
+
627
+ if (data == null) {
628
+ return;
629
+ }
630
+
631
+ if (head == null) { // terminate recursion
632
+ result.push(parent.slice());
633
+ } else if (head === '@') {
634
+ assert(Array.isArray(data), 'expected array when encountering `@`');
635
+ for (const [i, value] of data.entries()) {
636
+ parent.push(i);
637
+ generateHydratedPaths(parent, path.slice(1), value, result);
638
+ parent.pop();
639
+ }
640
+ } else if (typeof head === 'string') {
641
+ if (Array.isArray(data)) {
642
+ for (const [i, value] of data.entries()) {
643
+ parent.push(i);
644
+ generateHydratedPaths(parent, path, value, result);
645
+ parent.pop();
646
+ }
647
+ } else {
648
+ if (head in data) {
649
+ const value = data[head];
650
+ parent.push(head);
651
+ generateHydratedPaths(parent, path.slice(1), value, result);
652
+ parent.pop();
653
+ }
654
+ }
655
+ } else {
656
+ assert(false, `unknown path part "${head}"`);
657
+ }
658
+ }
659
+
490
660
  function applyOrMapRecursive(value: any | any[], fct: (v: any) => any | undefined): any | any[] | undefined {
491
661
  if (Array.isArray(value)) {
492
662
  const res = value.map((elt) => applyOrMapRecursive(elt, fct)).filter(isDefined);
@@ -674,7 +844,16 @@ function doesTypeConditionMatch(
674
844
  return false;
675
845
  }
676
846
 
677
- function flattenResultsAtPath(value: any, path: ResponsePath): any {
847
+ function moveIntoCursor(cursor: ResultCursor, pathInCursor: ResponsePath): ResultCursor | undefined {
848
+ const data = flattenResultsAtPath(cursor.data, pathInCursor);
849
+ return data ? {
850
+ path: cursor.path.concat(pathInCursor),
851
+ data,
852
+ fullResult: cursor.fullResult,
853
+ } : undefined;
854
+ }
855
+
856
+ function flattenResultsAtPath(value: ResultCursor['data'] | undefined | null, path: ResponsePath): ResultCursor['data'] | undefined | null {
678
857
  if (path.length === 0) return value;
679
858
  if (value === undefined || value === null) return value;
680
859
 
@@ -682,6 +861,11 @@ function flattenResultsAtPath(value: any, path: ResponsePath): any {
682
861
  if (current === '@') {
683
862
  return value.flatMap((element: any) => flattenResultsAtPath(element, rest));
684
863
  } else {
864
+ assert(typeof current === 'string', () => `Unexpected ${typeof current} found in path`);
865
+ assert(!Array.isArray(value), () => `Unexpected array in result for path element ${current}`);
866
+ // Note that this typecheck because `value[current]` is of type `any` and so the typechecker "trusts us", but in
867
+ // practice this only work because we use this on path that do not point to leaf types, and the `value[current]`
868
+ // is never a base type (non-object nor null/undefined).
685
869
  return flattenResultsAtPath(value[current], rest);
686
870
  }
687
871
  }
@@ -689,8 +873,10 @@ function flattenResultsAtPath(value: any, path: ResponsePath): any {
689
873
  function downstreamServiceError(
690
874
  originalError: GraphQLFormattedError,
691
875
  serviceName: string,
876
+ generateErrorPath: ErrorPathGenerator,
692
877
  ) {
693
- let { message, extensions } = originalError;
878
+ let { message } = originalError;
879
+ const { extensions } = originalError;
694
880
 
695
881
  if (!message) {
696
882
  message = `Error while fetching subquery from service "${serviceName}"`;
@@ -698,12 +884,13 @@ function downstreamServiceError(
698
884
 
699
885
  const errorOptions: GraphQLErrorOptions = {
700
886
  originalError: originalError as Error,
887
+ path: generateErrorPath(originalError.path),
701
888
  extensions: {
702
889
  ...extensions,
703
890
  // XXX The presence of a serviceName in extensions is used to
704
891
  // determine if this error should be captured for metrics reporting.
705
892
  serviceName,
706
- }
893
+ },
707
894
  };
708
895
 
709
896
  const codeDef = errorCodeDef(originalError);
@@ -713,7 +900,10 @@ function downstreamServiceError(
713
900
  return new GraphQLError(message, errorOptions);
714
901
  }
715
902
  // Otherwise, we either use the code we found and know, or default to a general downstream error code.
716
- return (codeDef ?? ERRORS.DOWNSTREAM_SERVICE_ERROR).err(message, errorOptions);
903
+ return (codeDef ?? ERRORS.DOWNSTREAM_SERVICE_ERROR).err(
904
+ message,
905
+ errorOptions,
906
+ );
717
907
  }
718
908
 
719
909
  export const defaultFieldResolverWithAliasSupport: GraphQLFieldResolver<
package/src/index.ts CHANGED
@@ -3,8 +3,6 @@ import { createHash } from '@apollo/utils.createhash';
3
3
  import type { Logger } from '@apollo/utils.logger';
4
4
  import LRUCache from 'lru-cache';
5
5
  import {
6
- isObjectType,
7
- isIntrospectionType,
8
6
  GraphQLSchema,
9
7
  VariableDefinitionNode,
10
8
  } from 'graphql';
@@ -12,7 +10,6 @@ import { buildOperationContext, OperationContext } from './operationContext';
12
10
  import {
13
11
  executeQueryPlan,
14
12
  ServiceMap,
15
- defaultFieldResolverWithAliasSupport,
16
13
  } from './executeQueryPlan';
17
14
  import {
18
15
  GraphQLDataSource,
@@ -118,8 +115,8 @@ export class ApolloGateway implements GatewayInterface {
118
115
  public schema?: GraphQLSchema;
119
116
  // Same as a `schema` but as a `Schema` to avoid reconverting when we need it.
120
117
  // TODO(sylvain): if we add caching in `Schema.toGraphQLJSSchema`, we could maybe only keep `apiSchema`
121
- // and make `schema` a getter (though `schema` does rely on `wrapSchemaWithAliasResolver` so this should
122
- // be accounted for and this may look ugly). Unsure if moving from a member to a getter could break anyone externally however
118
+ // and make `schema` a getter (though `schema` does add some extension and this should
119
+ // be accounted for). Unsure if moving from a member to a getter could break anyone externally however
123
120
  // (also unclear why we expose a mutable member public in the first place; don't everything break if the
124
121
  // use manually assigns `schema`?).
125
122
  private apiSchema?: Schema;
@@ -559,9 +556,7 @@ export class ApolloGateway implements GatewayInterface {
559
556
  ): void {
560
557
  this.queryPlanStore.clear();
561
558
  this.apiSchema = coreSchema.toAPISchema();
562
- this.schema = addExtensions(
563
- wrapSchemaWithAliasResolver(this.apiSchema.toGraphQLJSSchema()),
564
- );
559
+ this.schema = addExtensions(this.apiSchema.toGraphQLJSSchema());
565
560
  this.queryPlanner = new QueryPlanner(coreSchema, this.config.queryPlannerConfig);
566
561
 
567
562
  // Notify onSchemaChange listeners of the updated schema
@@ -828,6 +823,7 @@ export class ApolloGateway implements GatewayInterface {
828
823
  requestContext,
829
824
  operationContext,
830
825
  this.supergraphSchema!,
826
+ this.apiSchema!,
831
827
  );
832
828
 
833
829
  const shouldShowQueryPlan =
@@ -984,26 +980,6 @@ function approximateObjectSize<T>(obj: T): number {
984
980
  return Buffer.byteLength(JSON.stringify(obj), 'utf8');
985
981
  }
986
982
 
987
- // We can't use transformSchema here because the extension data for query
988
- // planning would be lost. Instead we set a resolver for each field
989
- // in order to counteract GraphQLExtensions preventing a defaultFieldResolver
990
- // from doing the same job
991
- function wrapSchemaWithAliasResolver(schema: GraphQLSchema): GraphQLSchema {
992
- const typeMap = schema.getTypeMap();
993
- Object.keys(typeMap).forEach((typeName) => {
994
- const type = typeMap[typeName];
995
-
996
- if (isObjectType(type) && !isIntrospectionType(type)) {
997
- const fields = type.getFields();
998
- Object.keys(fields).forEach((fieldName) => {
999
- const field = fields[fieldName];
1000
- field.resolve = defaultFieldResolverWithAliasSupport;
1001
- });
1002
- }
1003
- });
1004
- return schema;
1005
- }
1006
-
1007
983
  // Throw this in places that should be unreachable (because all other cases have
1008
984
  // been handled, reducing the type of the argument to `never`). TypeScript will
1009
985
  // complain if in fact there is a valid type for the argument.