@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.
- package/LICENSE +95 -0
- package/README.md +1 -1
- package/dist/__generated__/graphqlTypes.d.ts +130 -0
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -0
- package/dist/__generated__/graphqlTypes.js +25 -0
- package/dist/__generated__/graphqlTypes.js.map +1 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +47 -0
- package/dist/config.js.map +1 -0
- package/dist/datasources/LocalGraphQLDataSource.d.ts +3 -3
- package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.js +5 -5
- package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts +6 -4
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +60 -17
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/datasources/index.d.ts +1 -1
- package/dist/datasources/index.d.ts.map +1 -1
- package/dist/datasources/index.js +1 -0
- package/dist/datasources/index.js.map +1 -1
- package/dist/datasources/parseCacheControlHeader.d.ts +2 -0
- package/dist/datasources/parseCacheControlHeader.d.ts.map +1 -0
- package/dist/datasources/parseCacheControlHeader.js +16 -0
- package/dist/datasources/parseCacheControlHeader.js.map +1 -0
- package/dist/datasources/types.d.ts +16 -1
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/datasources/types.js +7 -0
- package/dist/datasources/types.js.map +1 -1
- package/dist/executeQueryPlan.d.ts +2 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +199 -112
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +62 -80
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +543 -234
- package/dist/index.js.map +1 -1
- package/dist/loadServicesFromRemoteEndpoint.d.ts +9 -9
- package/dist/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
- package/dist/loadServicesFromRemoteEndpoint.js +13 -8
- package/dist/loadServicesFromRemoteEndpoint.js.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +13 -0
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/loadSupergraphSdlFromStorage.js +101 -0
- package/dist/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/operationContext.d.ts +17 -0
- package/dist/operationContext.d.ts.map +1 -0
- package/dist/operationContext.js +42 -0
- package/dist/operationContext.js.map +1 -0
- package/dist/outOfBandReporter.d.ts +15 -0
- package/dist/outOfBandReporter.d.ts.map +1 -0
- package/dist/outOfBandReporter.js +88 -0
- package/dist/outOfBandReporter.js.map +1 -0
- package/dist/utilities/array.d.ts +1 -2
- package/dist/utilities/array.d.ts.map +1 -1
- package/dist/utilities/array.js +7 -14
- package/dist/utilities/array.js.map +1 -1
- package/dist/utilities/assert.d.ts +2 -0
- package/dist/utilities/assert.d.ts.map +1 -0
- package/dist/utilities/assert.js +10 -0
- package/dist/utilities/assert.js.map +1 -0
- package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts +3 -0
- package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts.map +1 -0
- package/dist/utilities/cleanErrorOfInaccessibleNames.js +27 -0
- package/dist/utilities/cleanErrorOfInaccessibleNames.js.map +1 -0
- package/dist/utilities/deepMerge.js +2 -2
- package/dist/utilities/deepMerge.js.map +1 -1
- package/dist/utilities/graphql.d.ts +1 -4
- package/dist/utilities/graphql.d.ts.map +1 -1
- package/dist/utilities/graphql.js +3 -36
- package/dist/utilities/graphql.js.map +1 -1
- package/dist/utilities/opentelemetry.d.ts +10 -0
- package/dist/utilities/opentelemetry.d.ts.map +1 -0
- package/dist/utilities/opentelemetry.js +19 -0
- package/dist/utilities/opentelemetry.js.map +1 -0
- package/package.json +30 -21
- package/src/__generated__/graphqlTypes.ts +140 -0
- package/src/__mocks__/apollo-server-env.ts +56 -0
- package/src/__mocks__/make-fetch-happen-fetcher.ts +55 -0
- package/src/__mocks__/tsconfig.json +7 -0
- package/src/__tests__/build-query-plan.feature +40 -311
- package/src/__tests__/buildQueryPlan.test.ts +246 -426
- package/src/__tests__/executeQueryPlan.test.ts +1691 -194
- package/src/__tests__/execution-utils.ts +33 -26
- package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +195 -0
- package/src/__tests__/gateway/buildService.test.ts +16 -19
- package/src/__tests__/gateway/composedSdl.test.ts +44 -0
- package/src/__tests__/gateway/endToEnd.test.ts +166 -0
- package/src/__tests__/gateway/executor.test.ts +49 -43
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -29
- package/src/__tests__/gateway/opentelemetry.test.ts +123 -0
- package/src/__tests__/gateway/queryPlanCache.test.ts +19 -20
- package/src/__tests__/gateway/reporting.test.ts +76 -55
- package/src/__tests__/integration/abstract-types.test.ts +1086 -22
- package/src/__tests__/integration/aliases.test.ts +5 -6
- package/src/__tests__/integration/boolean.test.ts +40 -38
- package/src/__tests__/integration/complex-key.test.ts +41 -56
- package/src/__tests__/integration/configuration.test.ts +321 -0
- package/src/__tests__/integration/custom-directives.test.ts +61 -46
- package/src/__tests__/integration/fragments.test.ts +8 -2
- package/src/__tests__/integration/list-key.test.ts +2 -2
- package/src/__tests__/integration/logger.test.ts +2 -2
- package/src/__tests__/integration/multiple-key.test.ts +11 -12
- package/src/__tests__/integration/mutations.test.ts +8 -5
- package/src/__tests__/integration/networkRequests.test.ts +447 -289
- package/src/__tests__/integration/nockMocks.ts +95 -66
- package/src/__tests__/integration/provides.test.ts +9 -6
- package/src/__tests__/integration/requires.test.ts +17 -15
- package/src/__tests__/integration/scope.test.ts +557 -0
- package/src/__tests__/integration/unions.test.ts +1 -1
- package/src/__tests__/integration/value-types.test.ts +35 -32
- package/src/__tests__/integration/variables.test.ts +8 -2
- package/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +6 -2
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +694 -0
- package/src/__tests__/queryPlanCucumber.test.ts +11 -61
- package/src/__tests__/testSetup.ts +1 -4
- package/src/__tests__/tsconfig.json +2 -1
- package/src/config.ts +225 -0
- package/src/core/__tests__/core.test.ts +412 -0
- package/src/datasources/LocalGraphQLDataSource.ts +9 -10
- package/src/datasources/RemoteGraphQLDataSource.ts +117 -43
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +11 -4
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +148 -79
- package/src/datasources/__tests__/tsconfig.json +4 -2
- package/src/datasources/index.ts +1 -1
- package/src/datasources/parseCacheControlHeader.ts +43 -0
- package/src/datasources/types.ts +47 -2
- package/src/executeQueryPlan.ts +264 -153
- package/src/index.ts +925 -480
- package/src/loadServicesFromRemoteEndpoint.ts +24 -17
- package/src/loadSupergraphSdlFromStorage.ts +140 -0
- package/src/make-fetch-happen.d.ts +2 -2
- package/src/operationContext.ts +70 -0
- package/src/outOfBandReporter.ts +128 -0
- package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +104 -0
- package/src/utilities/__tests__/tsconfig.json +8 -0
- package/src/utilities/array.ts +6 -28
- package/src/utilities/assert.ts +14 -0
- package/src/utilities/cleanErrorOfInaccessibleNames.ts +29 -0
- package/src/utilities/graphql.ts +0 -64
- package/src/utilities/opentelemetry.ts +13 -0
- package/CHANGELOG.md +0 -226
- package/LICENSE.md +0 -20
- package/dist/FieldSet.d.ts +0 -18
- package/dist/FieldSet.d.ts.map +0 -1
- package/dist/FieldSet.js +0 -96
- package/dist/FieldSet.js.map +0 -1
- package/dist/QueryPlan.d.ts +0 -41
- package/dist/QueryPlan.d.ts.map +0 -1
- package/dist/QueryPlan.js +0 -15
- package/dist/QueryPlan.js.map +0 -1
- package/dist/buildQueryPlan.d.ts +0 -44
- package/dist/buildQueryPlan.d.ts.map +0 -1
- package/dist/buildQueryPlan.js +0 -670
- package/dist/buildQueryPlan.js.map +0 -1
- package/dist/loadServicesFromStorage.d.ts +0 -21
- package/dist/loadServicesFromStorage.d.ts.map +0 -1
- package/dist/loadServicesFromStorage.js +0 -64
- package/dist/loadServicesFromStorage.js.map +0 -1
- package/dist/snapshotSerializers/astSerializer.d.ts +0 -3
- package/dist/snapshotSerializers/astSerializer.d.ts.map +0 -1
- package/dist/snapshotSerializers/astSerializer.js +0 -14
- package/dist/snapshotSerializers/astSerializer.js.map +0 -1
- package/dist/snapshotSerializers/index.d.ts +0 -13
- package/dist/snapshotSerializers/index.d.ts.map +0 -1
- package/dist/snapshotSerializers/index.js +0 -15
- package/dist/snapshotSerializers/index.js.map +0 -1
- package/dist/snapshotSerializers/queryPlanSerializer.d.ts +0 -3
- package/dist/snapshotSerializers/queryPlanSerializer.d.ts.map +0 -1
- package/dist/snapshotSerializers/queryPlanSerializer.js +0 -78
- package/dist/snapshotSerializers/queryPlanSerializer.js.map +0 -1
- package/dist/snapshotSerializers/selectionSetSerializer.d.ts +0 -3
- package/dist/snapshotSerializers/selectionSetSerializer.d.ts.map +0 -1
- package/dist/snapshotSerializers/selectionSetSerializer.js +0 -12
- package/dist/snapshotSerializers/selectionSetSerializer.js.map +0 -1
- package/dist/snapshotSerializers/typeSerializer.d.ts +0 -3
- package/dist/snapshotSerializers/typeSerializer.d.ts.map +0 -1
- package/dist/snapshotSerializers/typeSerializer.js +0 -12
- package/dist/snapshotSerializers/typeSerializer.js.map +0 -1
- package/dist/utilities/MultiMap.d.ts +0 -4
- package/dist/utilities/MultiMap.d.ts.map +0 -1
- package/dist/utilities/MultiMap.js +0 -17
- package/dist/utilities/MultiMap.js.map +0 -1
- package/src/FieldSet.ts +0 -169
- package/src/QueryPlan.ts +0 -57
- package/src/__tests__/matchers/toCallService.ts +0 -105
- package/src/__tests__/matchers/toHaveBeenCalledBefore.ts +0 -40
- package/src/__tests__/matchers/toHaveFetched.ts +0 -81
- package/src/__tests__/matchers/toMatchAST.ts +0 -64
- package/src/buildQueryPlan.ts +0 -1190
- package/src/loadServicesFromStorage.ts +0 -170
- package/src/snapshotSerializers/astSerializer.ts +0 -21
- package/src/snapshotSerializers/index.ts +0 -21
- package/src/snapshotSerializers/queryPlanSerializer.ts +0 -144
- package/src/snapshotSerializers/selectionSetSerializer.ts +0 -13
- package/src/snapshotSerializers/typeSerializer.ts +0 -11
- package/src/utilities/MultiMap.ts +0 -11
package/src/executeQueryPlan.ts
CHANGED
|
@@ -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-
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
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
|
-
|
|
23
|
-
|
|
24
|
+
QueryPlanSelectionNode,
|
|
25
|
+
QueryPlanFieldNode,
|
|
26
|
+
getResponseName,
|
|
27
|
+
} from '@apollo/query-planner';
|
|
24
28
|
import { deepMerge } from './utilities/deepMerge';
|
|
25
|
-
import {
|
|
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
|
-
|
|
55
|
+
const logger = requestContext.logger || console;
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
return tracer.startActiveSpan(OpenTelemetrySpanNames.EXECUTE, async span => {
|
|
58
|
+
try {
|
|
59
|
+
const errors: GraphQLError[] = [];
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
fetch.
|
|
222
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
294
|
+
if (!fetch.requires) {
|
|
295
|
+
const dataReceivedFromService = await sendOperation(
|
|
296
|
+
context,
|
|
297
|
+
fetch.operation,
|
|
298
|
+
variables,
|
|
299
|
+
);
|
|
230
300
|
|
|
231
|
-
|
|
232
|
-
|
|
301
|
+
for (const entity of entities) {
|
|
302
|
+
deepMerge(entity, dataReceivedFromService);
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
const requires = fetch.requires;
|
|
233
306
|
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
326
|
+
if ('representations' in variables) {
|
|
327
|
+
throw new Error(`Variables cannot contain key "representations"`);
|
|
328
|
+
}
|
|
255
329
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
336
|
+
if (!dataReceivedFromService) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
266
339
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
485
|
+
for (const selection of selections) {
|
|
400
486
|
switch (selection.kind) {
|
|
401
487
|
case Kind.FIELD:
|
|
402
|
-
const responseName = getResponseName(selection);
|
|
403
|
-
const
|
|
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
|
-
|
|
496
|
+
selections
|
|
497
|
+
? executeSelectionSet(operationContext, value, selections)
|
|
498
|
+
: value,
|
|
411
499
|
);
|
|
412
|
-
} else if (
|
|
500
|
+
} else if (selections) {
|
|
413
501
|
result[responseName] = executeSelectionSet(
|
|
502
|
+
operationContext,
|
|
414
503
|
source[responseName],
|
|
415
|
-
|
|
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 (
|
|
516
|
+
if (doesTypeConditionMatch(operationContext.schema, selection.typeCondition, typename)) {
|
|
428
517
|
deepMerge(
|
|
429
518
|
result,
|
|
430
|
-
executeSelectionSet(source, selection.
|
|
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
|
-
|
|
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
|
}
|