@apollo/gateway 0.300.0-alpha.2 → 2.0.0-alpha.2

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 (198) hide show
  1. package/LICENSE +95 -0
  2. package/README.md +1 -1
  3. package/dist/__generated__/graphqlTypes.d.ts +130 -0
  4. package/dist/__generated__/graphqlTypes.d.ts.map +1 -0
  5. package/dist/__generated__/graphqlTypes.js +25 -0
  6. package/dist/__generated__/graphqlTypes.js.map +1 -0
  7. package/dist/config.d.ts +104 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +47 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/datasources/LocalGraphQLDataSource.d.ts +3 -3
  12. package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
  13. package/dist/datasources/LocalGraphQLDataSource.js +5 -5
  14. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  15. package/dist/datasources/RemoteGraphQLDataSource.d.ts +6 -4
  16. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  17. package/dist/datasources/RemoteGraphQLDataSource.js +60 -17
  18. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  19. package/dist/datasources/index.d.ts +1 -1
  20. package/dist/datasources/index.d.ts.map +1 -1
  21. package/dist/datasources/index.js +1 -0
  22. package/dist/datasources/index.js.map +1 -1
  23. package/dist/datasources/parseCacheControlHeader.d.ts +2 -0
  24. package/dist/datasources/parseCacheControlHeader.d.ts.map +1 -0
  25. package/dist/datasources/parseCacheControlHeader.js +16 -0
  26. package/dist/datasources/parseCacheControlHeader.js.map +1 -0
  27. package/dist/datasources/types.d.ts +16 -1
  28. package/dist/datasources/types.d.ts.map +1 -1
  29. package/dist/datasources/types.js +7 -0
  30. package/dist/datasources/types.js.map +1 -1
  31. package/dist/executeQueryPlan.d.ts +2 -1
  32. package/dist/executeQueryPlan.d.ts.map +1 -1
  33. package/dist/executeQueryPlan.js +199 -112
  34. package/dist/executeQueryPlan.js.map +1 -1
  35. package/dist/index.d.ts +62 -80
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +543 -234
  38. package/dist/index.js.map +1 -1
  39. package/dist/loadServicesFromRemoteEndpoint.d.ts +9 -9
  40. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
  41. package/dist/loadServicesFromRemoteEndpoint.js +13 -8
  42. package/dist/loadServicesFromRemoteEndpoint.js.map +1 -1
  43. package/dist/loadSupergraphSdlFromStorage.d.ts +13 -0
  44. package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  45. package/dist/loadSupergraphSdlFromStorage.js +101 -0
  46. package/dist/loadSupergraphSdlFromStorage.js.map +1 -0
  47. package/dist/operationContext.d.ts +17 -0
  48. package/dist/operationContext.d.ts.map +1 -0
  49. package/dist/operationContext.js +42 -0
  50. package/dist/operationContext.js.map +1 -0
  51. package/dist/outOfBandReporter.d.ts +15 -0
  52. package/dist/outOfBandReporter.d.ts.map +1 -0
  53. package/dist/outOfBandReporter.js +88 -0
  54. package/dist/outOfBandReporter.js.map +1 -0
  55. package/dist/utilities/array.d.ts +1 -2
  56. package/dist/utilities/array.d.ts.map +1 -1
  57. package/dist/utilities/array.js +7 -14
  58. package/dist/utilities/array.js.map +1 -1
  59. package/dist/utilities/assert.d.ts +2 -0
  60. package/dist/utilities/assert.d.ts.map +1 -0
  61. package/dist/utilities/assert.js +10 -0
  62. package/dist/utilities/assert.js.map +1 -0
  63. package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts +3 -0
  64. package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts.map +1 -0
  65. package/dist/utilities/cleanErrorOfInaccessibleNames.js +27 -0
  66. package/dist/utilities/cleanErrorOfInaccessibleNames.js.map +1 -0
  67. package/dist/utilities/deepMerge.js +2 -2
  68. package/dist/utilities/deepMerge.js.map +1 -1
  69. package/dist/utilities/graphql.d.ts +1 -4
  70. package/dist/utilities/graphql.d.ts.map +1 -1
  71. package/dist/utilities/graphql.js +3 -36
  72. package/dist/utilities/graphql.js.map +1 -1
  73. package/dist/utilities/opentelemetry.d.ts +10 -0
  74. package/dist/utilities/opentelemetry.d.ts.map +1 -0
  75. package/dist/utilities/opentelemetry.js +19 -0
  76. package/dist/utilities/opentelemetry.js.map +1 -0
  77. package/package.json +30 -21
  78. package/src/__generated__/graphqlTypes.ts +140 -0
  79. package/src/__mocks__/apollo-server-env.ts +56 -0
  80. package/src/__mocks__/make-fetch-happen-fetcher.ts +55 -0
  81. package/src/__mocks__/tsconfig.json +7 -0
  82. package/src/__tests__/build-query-plan.feature +40 -311
  83. package/src/__tests__/buildQueryPlan.test.ts +246 -426
  84. package/src/__tests__/executeQueryPlan.test.ts +1691 -194
  85. package/src/__tests__/execution-utils.ts +33 -26
  86. package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +195 -0
  87. package/src/__tests__/gateway/buildService.test.ts +16 -19
  88. package/src/__tests__/gateway/composedSdl.test.ts +44 -0
  89. package/src/__tests__/gateway/endToEnd.test.ts +166 -0
  90. package/src/__tests__/gateway/executor.test.ts +49 -43
  91. package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -29
  92. package/src/__tests__/gateway/opentelemetry.test.ts +123 -0
  93. package/src/__tests__/gateway/queryPlanCache.test.ts +19 -20
  94. package/src/__tests__/gateway/reporting.test.ts +76 -55
  95. package/src/__tests__/integration/abstract-types.test.ts +1086 -22
  96. package/src/__tests__/integration/aliases.test.ts +5 -6
  97. package/src/__tests__/integration/boolean.test.ts +40 -38
  98. package/src/__tests__/integration/complex-key.test.ts +41 -56
  99. package/src/__tests__/integration/configuration.test.ts +321 -0
  100. package/src/__tests__/integration/custom-directives.test.ts +61 -46
  101. package/src/__tests__/integration/fragments.test.ts +8 -2
  102. package/src/__tests__/integration/list-key.test.ts +2 -2
  103. package/src/__tests__/integration/logger.test.ts +2 -2
  104. package/src/__tests__/integration/multiple-key.test.ts +11 -12
  105. package/src/__tests__/integration/mutations.test.ts +8 -5
  106. package/src/__tests__/integration/networkRequests.test.ts +447 -289
  107. package/src/__tests__/integration/nockMocks.ts +95 -66
  108. package/src/__tests__/integration/provides.test.ts +9 -6
  109. package/src/__tests__/integration/requires.test.ts +17 -15
  110. package/src/__tests__/integration/scope.test.ts +557 -0
  111. package/src/__tests__/integration/unions.test.ts +1 -1
  112. package/src/__tests__/integration/value-types.test.ts +35 -32
  113. package/src/__tests__/integration/variables.test.ts +8 -2
  114. package/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +6 -2
  115. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +694 -0
  116. package/src/__tests__/queryPlanCucumber.test.ts +11 -61
  117. package/src/__tests__/testSetup.ts +1 -4
  118. package/src/__tests__/tsconfig.json +2 -1
  119. package/src/config.ts +225 -0
  120. package/src/core/__tests__/core.test.ts +412 -0
  121. package/src/datasources/LocalGraphQLDataSource.ts +9 -10
  122. package/src/datasources/RemoteGraphQLDataSource.ts +117 -43
  123. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +11 -4
  124. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +148 -79
  125. package/src/datasources/__tests__/tsconfig.json +4 -2
  126. package/src/datasources/index.ts +1 -1
  127. package/src/datasources/parseCacheControlHeader.ts +43 -0
  128. package/src/datasources/types.ts +47 -2
  129. package/src/executeQueryPlan.ts +264 -153
  130. package/src/index.ts +925 -480
  131. package/src/loadServicesFromRemoteEndpoint.ts +24 -17
  132. package/src/loadSupergraphSdlFromStorage.ts +140 -0
  133. package/src/make-fetch-happen.d.ts +2 -2
  134. package/src/operationContext.ts +70 -0
  135. package/src/outOfBandReporter.ts +128 -0
  136. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +104 -0
  137. package/src/utilities/__tests__/tsconfig.json +8 -0
  138. package/src/utilities/array.ts +6 -28
  139. package/src/utilities/assert.ts +14 -0
  140. package/src/utilities/cleanErrorOfInaccessibleNames.ts +29 -0
  141. package/src/utilities/graphql.ts +0 -64
  142. package/src/utilities/opentelemetry.ts +13 -0
  143. package/CHANGELOG.md +0 -226
  144. package/LICENSE.md +0 -20
  145. package/dist/FieldSet.d.ts +0 -18
  146. package/dist/FieldSet.d.ts.map +0 -1
  147. package/dist/FieldSet.js +0 -96
  148. package/dist/FieldSet.js.map +0 -1
  149. package/dist/QueryPlan.d.ts +0 -41
  150. package/dist/QueryPlan.d.ts.map +0 -1
  151. package/dist/QueryPlan.js +0 -15
  152. package/dist/QueryPlan.js.map +0 -1
  153. package/dist/buildQueryPlan.d.ts +0 -44
  154. package/dist/buildQueryPlan.d.ts.map +0 -1
  155. package/dist/buildQueryPlan.js +0 -670
  156. package/dist/buildQueryPlan.js.map +0 -1
  157. package/dist/loadServicesFromStorage.d.ts +0 -21
  158. package/dist/loadServicesFromStorage.d.ts.map +0 -1
  159. package/dist/loadServicesFromStorage.js +0 -64
  160. package/dist/loadServicesFromStorage.js.map +0 -1
  161. package/dist/snapshotSerializers/astSerializer.d.ts +0 -3
  162. package/dist/snapshotSerializers/astSerializer.d.ts.map +0 -1
  163. package/dist/snapshotSerializers/astSerializer.js +0 -14
  164. package/dist/snapshotSerializers/astSerializer.js.map +0 -1
  165. package/dist/snapshotSerializers/index.d.ts +0 -13
  166. package/dist/snapshotSerializers/index.d.ts.map +0 -1
  167. package/dist/snapshotSerializers/index.js +0 -15
  168. package/dist/snapshotSerializers/index.js.map +0 -1
  169. package/dist/snapshotSerializers/queryPlanSerializer.d.ts +0 -3
  170. package/dist/snapshotSerializers/queryPlanSerializer.d.ts.map +0 -1
  171. package/dist/snapshotSerializers/queryPlanSerializer.js +0 -78
  172. package/dist/snapshotSerializers/queryPlanSerializer.js.map +0 -1
  173. package/dist/snapshotSerializers/selectionSetSerializer.d.ts +0 -3
  174. package/dist/snapshotSerializers/selectionSetSerializer.d.ts.map +0 -1
  175. package/dist/snapshotSerializers/selectionSetSerializer.js +0 -12
  176. package/dist/snapshotSerializers/selectionSetSerializer.js.map +0 -1
  177. package/dist/snapshotSerializers/typeSerializer.d.ts +0 -3
  178. package/dist/snapshotSerializers/typeSerializer.d.ts.map +0 -1
  179. package/dist/snapshotSerializers/typeSerializer.js +0 -12
  180. package/dist/snapshotSerializers/typeSerializer.js.map +0 -1
  181. package/dist/utilities/MultiMap.d.ts +0 -4
  182. package/dist/utilities/MultiMap.d.ts.map +0 -1
  183. package/dist/utilities/MultiMap.js +0 -17
  184. package/dist/utilities/MultiMap.js.map +0 -1
  185. package/src/FieldSet.ts +0 -169
  186. package/src/QueryPlan.ts +0 -57
  187. package/src/__tests__/matchers/toCallService.ts +0 -105
  188. package/src/__tests__/matchers/toHaveBeenCalledBefore.ts +0 -40
  189. package/src/__tests__/matchers/toHaveFetched.ts +0 -81
  190. package/src/__tests__/matchers/toMatchAST.ts +0 -64
  191. package/src/buildQueryPlan.ts +0 -1190
  192. package/src/loadServicesFromStorage.ts +0 -170
  193. package/src/snapshotSerializers/astSerializer.ts +0 -21
  194. package/src/snapshotSerializers/index.ts +0 -21
  195. package/src/snapshotSerializers/queryPlanSerializer.ts +0 -144
  196. package/src/snapshotSerializers/selectionSetSerializer.ts +0 -13
  197. package/src/snapshotSerializers/typeSerializer.ts +0 -11
  198. package/src/utilities/MultiMap.ts +0 -11
@@ -7,22 +7,29 @@ import {
7
7
  execute,
8
8
  GraphQLError,
9
9
  Kind,
10
- SelectionSetNode,
11
10
  TypeNameMetaFieldDef,
12
11
  GraphQLFieldResolver,
12
+ GraphQLFormattedError,
13
+ isAbstractType,
14
+ GraphQLSchema,
13
15
  } from 'graphql';
14
- import { Trace, google } from 'apollo-engine-reporting-protobuf';
15
- import { defaultRootOperationNameLookup } from '@apollo/federation';
16
- import { GraphQLDataSource } from './datasources/types';
16
+ import { Trace, google } from 'apollo-reporting-protobuf';
17
+ import { GraphQLDataSource, GraphQLDataSourceRequestKind } from './datasources/types';
18
+ import { OperationContext } from './operationContext';
17
19
  import {
18
20
  FetchNode,
19
21
  PlanNode,
20
22
  QueryPlan,
21
23
  ResponsePath,
22
- OperationContext,
23
- } from './QueryPlan';
24
+ QueryPlanSelectionNode,
25
+ QueryPlanFieldNode,
26
+ getResponseName,
27
+ } from '@apollo/query-planner';
24
28
  import { deepMerge } from './utilities/deepMerge';
25
- import { getResponseName } from './utilities/graphql';
29
+ import { isNotNullOrUndefined } from './utilities/array';
30
+ import { SpanStatusCode } from "@opentelemetry/api";
31
+ import { OpenTelemetrySpanNames, tracer } from "./utilities/opentelemetry";
32
+ import { defaultRootName } from '@apollo/federation-internals';
26
33
 
27
34
  export type ServiceMap = {
28
35
  [serviceName: string]: GraphQLDataSource;
@@ -44,64 +51,122 @@ export async function executeQueryPlan<TContext>(
44
51
  requestContext: GraphQLRequestContext<TContext>,
45
52
  operationContext: OperationContext,
46
53
  ): Promise<GraphQLExecutionResult> {
47
- const errors: GraphQLError[] = [];
48
-
49
- const context: ExecutionContext<TContext> = {
50
- queryPlan,
51
- operationContext,
52
- serviceMap,
53
- requestContext,
54
- errors,
55
- };
56
54
 
57
- let data: ResultMap | undefined | null = Object.create(null);
55
+ const logger = requestContext.logger || console;
58
56
 
59
- const captureTraces = !!(
60
- requestContext.metrics && requestContext.metrics.captureTraces
61
- );
57
+ return tracer.startActiveSpan(OpenTelemetrySpanNames.EXECUTE, async span => {
58
+ try {
59
+ const errors: GraphQLError[] = [];
62
60
 
63
- if (queryPlan.node) {
64
- const traceNode = await executeNode(
65
- context,
66
- queryPlan.node,
67
- data!,
68
- [],
69
- captureTraces,
70
- );
71
- if (captureTraces) {
72
- requestContext.metrics!.queryPlanTrace = traceNode;
73
- }
74
- }
61
+ const context: ExecutionContext<TContext> = {
62
+ queryPlan,
63
+ operationContext,
64
+ serviceMap,
65
+ requestContext,
66
+ errors,
67
+ };
75
68
 
76
- // FIXME: Re-executing the query is a pretty heavy handed way of making sure
77
- // only explicitly requested fields are included and field ordering follows
78
- // the original query.
79
- // It is also used to allow execution of introspection queries though.
80
- try {
81
- ({ data } = await execute({
82
- schema: operationContext.schema,
83
- document: {
84
- kind: Kind.DOCUMENT,
85
- definitions: [
86
- operationContext.operation,
87
- ...Object.values(operationContext.fragments),
88
- ],
89
- },
90
- rootValue: data,
91
- variableValues: requestContext.request.variables,
92
- fieldResolver: defaultFieldResolverWithAliasSupport,
93
- }));
94
- } catch (error) {
95
- return { errors: [error] };
96
- }
69
+ let data: ResultMap | undefined | null = Object.create(null);
70
+
71
+ const captureTraces = !!(
72
+ requestContext.metrics && requestContext.metrics.captureTraces
73
+ );
74
+
75
+ if (queryPlan.node) {
76
+ const traceNode = await executeNode(
77
+ context,
78
+ queryPlan.node,
79
+ data!,
80
+ [],
81
+ captureTraces,
82
+ );
83
+ if (captureTraces) {
84
+ requestContext.metrics!.queryPlanTrace = traceNode;
85
+ }
86
+ }
87
+
88
+ const result = await tracer.startActiveSpan(OpenTelemetrySpanNames.POST_PROCESSING, async (span) => {
89
+
90
+ // FIXME: Re-executing the query is a pretty heavy handed way of making sure
91
+ // only explicitly requested fields are included and field ordering follows
92
+ // the original query.
93
+ // It is also used to allow execution of introspection queries though.
94
+ try {
95
+ const schema = operationContext.schema;
96
+ ({ data } = await execute({
97
+ schema,
98
+ document: {
99
+ kind: Kind.DOCUMENT,
100
+ definitions: [
101
+ operationContext.operation,
102
+ ...Object.values(operationContext.fragments),
103
+ ],
104
+ },
105
+ rootValue: data,
106
+ variableValues: requestContext.request.variables,
107
+ // See also `wrapSchemaWithAliasResolver` in `gateway-js/src/index.ts`.
108
+ fieldResolver: defaultFieldResolverWithAliasSupport,
109
+ }));
110
+ } catch (error) {
111
+ span.setStatus({ code:SpanStatusCode.ERROR });
112
+ if (error instanceof GraphQLError) {
113
+ return { errors: [error] };
114
+ } else if (error instanceof Error) {
115
+ return {
116
+ errors: [
117
+ new GraphQLError(
118
+ error.message,
119
+ undefined,
120
+ undefined,
121
+ undefined,
122
+ undefined,
123
+ error as Error,
124
+ )
125
+ ]
126
+ };
127
+ } else {
128
+ // The above cases should cover the known cases, but if we received
129
+ // something else in the `catch` — like an object or something, we
130
+ // may not want to merely return this to the client.
131
+ logger.error(
132
+ "Unexpected error during query plan execution: " + error);
133
+ return {
134
+ errors: [
135
+ new GraphQLError(
136
+ "Unexpected error during query plan execution",
137
+ )
138
+ ]
139
+ };
140
+ }
141
+ }
142
+ finally {
143
+ span.end()
144
+ }
145
+ if(errors.length > 0) {
146
+ span.setStatus({ code:SpanStatusCode.ERROR });
147
+ }
148
+ return errors.length === 0 ? { data } : { errors, data };
149
+ });
97
150
 
98
- return errors.length === 0 ? { data } : { errors, data };
151
+ if(result.errors) {
152
+ span.setStatus({ code:SpanStatusCode.ERROR });
153
+ }
154
+ return result;
155
+ }
156
+ catch (err) {
157
+ span.setStatus({ code:SpanStatusCode.ERROR });
158
+ throw err;
159
+ }
160
+ finally {
161
+ span.end();
162
+ }
163
+ });
99
164
  }
100
165
 
101
166
  // Note: this function always returns a protobuf QueryPlanNode tree, even if
102
167
  // we're going to ignore it, because it makes the code much simpler and more
103
168
  // typesafe. However, it doesn't actually ask for traces from the backend
104
- // service unless we are capturing traces for Engine.
169
+ // service unless we are capturing traces for Studio.
105
170
  async function executeNode<TContext>(
106
171
  context: ExecutionContext<TContext>,
107
172
  node: PlanNode,
@@ -189,92 +254,120 @@ async function executeNode<TContext>(
189
254
  async function executeFetch<TContext>(
190
255
  context: ExecutionContext<TContext>,
191
256
  fetch: FetchNode,
192
- results: ResultMap | ResultMap[],
257
+ results: ResultMap | (ResultMap | null | undefined)[],
193
258
  _path: ResponsePath,
194
259
  traceNode: Trace.QueryPlanNode.FetchNode | null,
195
260
  ): Promise<void> {
261
+
196
262
  const logger = context.requestContext.logger || console;
197
263
  const service = context.serviceMap[fetch.serviceName];
198
- if (!service) {
199
- throw new Error(`Couldn't find service with name "${fetch.serviceName}"`);
200
- }
201
264
 
202
- const entities = Array.isArray(results) ? results : [results];
203
- if (entities.length < 1) return;
265
+ return tracer.startActiveSpan(OpenTelemetrySpanNames.FETCH, {attributes:{service:fetch.serviceName}}, async span => {
266
+ try {
267
+ if (!service) {
268
+ throw new Error(`Couldn't find service with name "${fetch.serviceName}"`);
269
+ }
204
270
 
205
- let variables = Object.create(null);
206
- if (fetch.variableUsages) {
207
- for (const variableName of Object.keys(fetch.variableUsages)) {
208
- const providedVariables = context.requestContext.request.variables;
209
- if (
210
- providedVariables &&
211
- typeof providedVariables[variableName] !== 'undefined'
212
- ) {
213
- variables[variableName] = providedVariables[variableName];
271
+ let entities: ResultMap[];
272
+ if (Array.isArray(results)) {
273
+ // Remove null or undefined entities from the list
274
+ entities = results.filter(isNotNullOrUndefined);
275
+ } else {
276
+ entities = [results];
214
277
  }
215
- }
216
- }
217
278
 
218
- if (!fetch.requires) {
219
- const dataReceivedFromService = await sendOperation(
220
- context,
221
- fetch.source,
222
- variables,
223
- );
279
+ if (entities.length < 1) return;
280
+
281
+ const variables = Object.create(null);
282
+ if (fetch.variableUsages) {
283
+ for (const variableName of fetch.variableUsages) {
284
+ const providedVariables = context.requestContext.request.variables;
285
+ if (
286
+ providedVariables &&
287
+ typeof providedVariables[variableName] !== 'undefined'
288
+ ) {
289
+ variables[variableName] = providedVariables[variableName];
290
+ }
291
+ }
292
+ }
224
293
 
225
- for (const entity of entities) {
226
- deepMerge(entity, dataReceivedFromService);
227
- }
228
- } else {
229
- const requires = fetch.requires;
294
+ if (!fetch.requires) {
295
+ const dataReceivedFromService = await sendOperation(
296
+ context,
297
+ fetch.operation,
298
+ variables,
299
+ );
230
300
 
231
- const representations: ResultMap[] = [];
232
- const representationToEntity: number[] = [];
301
+ for (const entity of entities) {
302
+ deepMerge(entity, dataReceivedFromService);
303
+ }
304
+ } else {
305
+ const requires = fetch.requires;
233
306
 
234
- entities.forEach((entity, index) => {
235
- const representation = executeSelectionSet(entity, requires);
236
- if (representation && representation[TypeNameMetaFieldDef.name]) {
237
- representations.push(representation);
238
- representationToEntity.push(index);
239
- }
240
- });
307
+ const representations: ResultMap[] = [];
308
+ const representationToEntity: number[] = [];
241
309
 
242
- if ('representations' in variables) {
243
- throw new Error(`Variables cannot contain key "representations"`);
244
- }
310
+ entities.forEach((entity, index) => {
311
+ const representation = executeSelectionSet(
312
+ context.operationContext,
313
+ entity,
314
+ requires,
315
+ );
316
+ if (representation && representation[TypeNameMetaFieldDef.name]) {
317
+ representations.push(representation);
318
+ representationToEntity.push(index);
319
+ }
320
+ });
245
321
 
246
- const dataReceivedFromService = await sendOperation(
247
- context,
248
- fetch.source,
249
- { ...variables, representations },
250
- );
322
+ // If there are no representations, that means the type conditions in
323
+ // the requires don't match any entities.
324
+ if (representations.length < 1) return;
251
325
 
252
- if (!dataReceivedFromService) {
253
- return;
254
- }
326
+ if ('representations' in variables) {
327
+ throw new Error(`Variables cannot contain key "representations"`);
328
+ }
255
329
 
256
- if (
257
- !(
258
- dataReceivedFromService._entities &&
259
- Array.isArray(dataReceivedFromService._entities)
260
- )
261
- ) {
262
- throw new Error(`Expected "data._entities" in response to be an array`);
263
- }
330
+ const dataReceivedFromService = await sendOperation(
331
+ context,
332
+ fetch.operation,
333
+ {...variables, representations},
334
+ );
264
335
 
265
- const receivedEntities = dataReceivedFromService._entities;
336
+ if (!dataReceivedFromService) {
337
+ return;
338
+ }
266
339
 
267
- if (receivedEntities.length !== representations.length) {
268
- throw new Error(
269
- `Expected "data._entities" to contain ${representations.length} elements`,
270
- );
271
- }
340
+ if (
341
+ !(
342
+ dataReceivedFromService._entities &&
343
+ Array.isArray(dataReceivedFromService._entities)
344
+ )
345
+ ) {
346
+ throw new Error(`Expected "data._entities" in response to be an array`);
347
+ }
272
348
 
273
- for (let i = 0; i < entities.length; i++) {
274
- deepMerge(entities[representationToEntity[i]], receivedEntities[i]);
275
- }
276
- }
349
+ const receivedEntities = dataReceivedFromService._entities;
277
350
 
351
+ if (receivedEntities.length !== representations.length) {
352
+ throw new Error(
353
+ `Expected "data._entities" to contain ${representations.length} elements`,
354
+ );
355
+ }
356
+
357
+ for (let i = 0; i < entities.length; i++) {
358
+ deepMerge(entities[representationToEntity[i]], receivedEntities[i]);
359
+ }
360
+ }
361
+ }
362
+ catch (err) {
363
+ span.setStatus({ code:SpanStatusCode.ERROR });
364
+ throw err;
365
+ }
366
+ finally
367
+ {
368
+ span.end();
369
+ }
370
+ });
278
371
  async function sendOperation(
279
372
  context: ExecutionContext<TContext>,
280
373
  source: string,
@@ -284,7 +377,7 @@ async function executeFetch<TContext>(
284
377
  // GraphQLRequest.http is supposed to have if it exists.
285
378
  let http: any;
286
379
 
287
- // If we're capturing a trace for Engine, then save the operation text to
380
+ // If we're capturing a trace for Studio, then save the operation text to
288
381
  // the node we're building and tell the federated service to include a trace
289
382
  // in its response.
290
383
  if (traceNode) {
@@ -303,29 +396,24 @@ async function executeFetch<TContext>(
303
396
  }
304
397
 
305
398
  const response = await service.process({
399
+ kind: GraphQLDataSourceRequestKind.INCOMING_OPERATION,
306
400
  request: {
307
401
  query: source,
308
402
  variables,
309
403
  http,
310
404
  },
405
+ incomingRequestContext: context.requestContext,
311
406
  context: context.requestContext.context,
312
407
  });
313
408
 
314
409
  if (response.errors) {
315
- const errors = response.errors.map(error =>
316
- downstreamServiceError(
317
- error.message,
318
- fetch.serviceName,
319
- source,
320
- variables,
321
- error.extensions,
322
- error.path,
323
- ),
410
+ const errors = response.errors.map((error) =>
411
+ downstreamServiceError(error, fetch.serviceName),
324
412
  );
325
413
  context.errors.push(...errors);
326
414
  }
327
415
 
328
- // If we're capturing a trace for Engine, save the received trace into the
416
+ // If we're capturing a trace for Studio, save the received trace into the
329
417
  // query plan.
330
418
  if (traceNode) {
331
419
  traceNode.receivedTime = dateToProtoTimestamp(new Date());
@@ -362,10 +450,7 @@ async function executeFetch<TContext>(
362
450
  // to have the default names (Query, Mutation, Subscription) even
363
451
  // if the implementing services choose different names, so we override
364
452
  // whatever the implementing service reported here.
365
- const rootTypeName =
366
- defaultRootOperationNameLookup[
367
- context.operationContext.operation.operation
368
- ];
453
+ const rootTypeName = defaultRootName(context.operationContext.operation.operation);
369
454
  traceNode.trace.root?.child?.forEach((child) => {
370
455
  child.parentType = rootTypeName;
371
456
  });
@@ -384,8 +469,9 @@ async function executeFetch<TContext>(
384
469
  * @param selectionSet
385
470
  */
386
471
  function executeSelectionSet(
472
+ operationContext: OperationContext,
387
473
  source: Record<string, any> | null,
388
- selectionSet: SelectionSetNode,
474
+ selections: QueryPlanSelectionNode[],
389
475
  ): Record<string, any> | null {
390
476
 
391
477
  // If the underlying service has returned null for the parent (source)
@@ -396,23 +482,26 @@ function executeSelectionSet(
396
482
 
397
483
  const result: Record<string, any> = Object.create(null);
398
484
 
399
- for (const selection of selectionSet.selections) {
485
+ for (const selection of selections) {
400
486
  switch (selection.kind) {
401
487
  case Kind.FIELD:
402
- const responseName = getResponseName(selection);
403
- const selectionSet = selection.selectionSet;
488
+ const responseName = getResponseName(selection as QueryPlanFieldNode);
489
+ const selections = (selection as QueryPlanFieldNode).selections;
404
490
 
405
491
  if (typeof source[responseName] === 'undefined') {
406
492
  throw new Error(`Field "${responseName}" was not found in response.`);
407
493
  }
408
494
  if (Array.isArray(source[responseName])) {
409
495
  result[responseName] = source[responseName].map((value: any) =>
410
- selectionSet ? executeSelectionSet(value, selectionSet) : value,
496
+ selections
497
+ ? executeSelectionSet(operationContext, value, selections)
498
+ : value,
411
499
  );
412
- } else if (selectionSet) {
500
+ } else if (selections) {
413
501
  result[responseName] = executeSelectionSet(
502
+ operationContext,
414
503
  source[responseName],
415
- selectionSet,
504
+ selections,
416
505
  );
417
506
  } else {
418
507
  result[responseName] = source[responseName];
@@ -424,10 +513,10 @@ function executeSelectionSet(
424
513
  const typename = source && source['__typename'];
425
514
  if (!typename) continue;
426
515
 
427
- if (typename === selection.typeCondition.name.value) {
516
+ if (doesTypeConditionMatch(operationContext.schema, selection.typeCondition, typename)) {
428
517
  deepMerge(
429
518
  result,
430
- executeSelectionSet(source, selection.selectionSet),
519
+ executeSelectionSet(operationContext, source, selection.selections),
431
520
  );
432
521
  }
433
522
  break;
@@ -437,6 +526,32 @@ function executeSelectionSet(
437
526
  return result;
438
527
  }
439
528
 
529
+ function doesTypeConditionMatch(
530
+ schema: GraphQLSchema,
531
+ typeCondition: string,
532
+ typename: string,
533
+ ): boolean {
534
+ if (typeCondition === typename) {
535
+ return true;
536
+ }
537
+
538
+ const type = schema.getType(typename);
539
+ if (!type) {
540
+ return false;
541
+ }
542
+
543
+ const conditionalType = schema.getType(typeCondition);
544
+ if (!conditionalType) {
545
+ return false;
546
+ }
547
+
548
+ if (isAbstractType(conditionalType)) {
549
+ return schema.isSubType(conditionalType, type);
550
+ }
551
+
552
+ return false;
553
+ }
554
+
440
555
  function flattenResultsAtPath(value: any, path: ResponsePath): any {
441
556
  if (path.length === 0) return value;
442
557
  if (value === undefined || value === null) return value;
@@ -450,13 +565,11 @@ function flattenResultsAtPath(value: any, path: ResponsePath): any {
450
565
  }
451
566
 
452
567
  function downstreamServiceError(
453
- message: string | undefined,
568
+ originalError: GraphQLFormattedError,
454
569
  serviceName: string,
455
- query: string,
456
- variables?: Record<string, any>,
457
- extensions?: Record<string, any>,
458
- path?: ReadonlyArray<string | number> | undefined,
459
570
  ) {
571
+ let { message, extensions } = originalError;
572
+
460
573
  if (!message) {
461
574
  message = `Error while fetching subquery from service "${serviceName}"`;
462
575
  }
@@ -465,8 +578,6 @@ function downstreamServiceError(
465
578
  // XXX The presence of a serviceName in extensions is used to
466
579
  // determine if this error should be captured for metrics reporting.
467
580
  serviceName,
468
- query,
469
- variables,
470
581
  ...extensions,
471
582
  };
472
583
  return new GraphQLError(
@@ -474,8 +585,8 @@ function downstreamServiceError(
474
585
  undefined,
475
586
  undefined,
476
587
  undefined,
477
- path,
478
588
  undefined,
589
+ originalError as Error,
479
590
  extensions,
480
591
  );
481
592
  }