@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/buildQueryPlan.ts
DELETED
|
@@ -1,1190 +0,0 @@
|
|
|
1
|
-
import { isNotNullOrUndefined } from 'apollo-env';
|
|
2
|
-
import {
|
|
3
|
-
DocumentNode,
|
|
4
|
-
FieldNode,
|
|
5
|
-
FragmentDefinitionNode,
|
|
6
|
-
getNamedType,
|
|
7
|
-
getOperationRootType,
|
|
8
|
-
GraphQLAbstractType,
|
|
9
|
-
GraphQLCompositeType,
|
|
10
|
-
GraphQLError,
|
|
11
|
-
GraphQLField,
|
|
12
|
-
GraphQLObjectType,
|
|
13
|
-
GraphQLSchema,
|
|
14
|
-
GraphQLType,
|
|
15
|
-
InlineFragmentNode,
|
|
16
|
-
isAbstractType,
|
|
17
|
-
isCompositeType,
|
|
18
|
-
isIntrospectionType,
|
|
19
|
-
isListType,
|
|
20
|
-
isNamedType,
|
|
21
|
-
isObjectType,
|
|
22
|
-
Kind,
|
|
23
|
-
OperationDefinitionNode,
|
|
24
|
-
SelectionSetNode,
|
|
25
|
-
typeFromAST,
|
|
26
|
-
TypeNameMetaFieldDef,
|
|
27
|
-
visit,
|
|
28
|
-
VariableDefinitionNode,
|
|
29
|
-
OperationTypeNode,
|
|
30
|
-
print,
|
|
31
|
-
stripIgnoredCharacters,
|
|
32
|
-
} from 'graphql';
|
|
33
|
-
import {
|
|
34
|
-
Field,
|
|
35
|
-
FieldSet,
|
|
36
|
-
groupByParentType,
|
|
37
|
-
groupByResponseName,
|
|
38
|
-
matchesField,
|
|
39
|
-
selectionSetFromFieldSet,
|
|
40
|
-
Scope,
|
|
41
|
-
} from './FieldSet';
|
|
42
|
-
import {
|
|
43
|
-
FetchNode,
|
|
44
|
-
ParallelNode,
|
|
45
|
-
PlanNode,
|
|
46
|
-
SequenceNode,
|
|
47
|
-
QueryPlan,
|
|
48
|
-
ResponsePath,
|
|
49
|
-
OperationContext,
|
|
50
|
-
FragmentMap,
|
|
51
|
-
} from './QueryPlan';
|
|
52
|
-
import { getFieldDef, getResponseName } from './utilities/graphql';
|
|
53
|
-
import { MultiMap } from './utilities/MultiMap';
|
|
54
|
-
import { getFederationMetadata } from '@apollo/federation/dist/composition/utils';
|
|
55
|
-
|
|
56
|
-
const typenameField = {
|
|
57
|
-
kind: Kind.FIELD,
|
|
58
|
-
name: {
|
|
59
|
-
kind: Kind.NAME,
|
|
60
|
-
value: TypeNameMetaFieldDef.name,
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export interface BuildQueryPlanOptions {
|
|
65
|
-
autoFragmentization: boolean;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function buildQueryPlan(
|
|
69
|
-
operationContext: OperationContext,
|
|
70
|
-
options: BuildQueryPlanOptions = { autoFragmentization: false },
|
|
71
|
-
): QueryPlan {
|
|
72
|
-
const context = buildQueryPlanningContext(operationContext, options);
|
|
73
|
-
|
|
74
|
-
if (context.operation.operation === 'subscription') {
|
|
75
|
-
throw new GraphQLError(
|
|
76
|
-
'Query planning does not support subscriptions for now.',
|
|
77
|
-
[context.operation],
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const rootType = getOperationRootType(context.schema, context.operation);
|
|
82
|
-
|
|
83
|
-
const isMutation = context.operation.operation === 'mutation';
|
|
84
|
-
|
|
85
|
-
const fields = collectFields(
|
|
86
|
-
context,
|
|
87
|
-
context.newScope(rootType),
|
|
88
|
-
context.operation.selectionSet,
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Mutations are a bit more specific in how FetchGroups can be built, as some
|
|
92
|
-
// calls to the same service may need to be executed serially.
|
|
93
|
-
const groups = isMutation
|
|
94
|
-
? splitRootFieldsSerially(context, fields)
|
|
95
|
-
: splitRootFields(context, fields);
|
|
96
|
-
|
|
97
|
-
const nodes = groups.map(group =>
|
|
98
|
-
executionNodeForGroup(context, group, rootType),
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
kind: 'QueryPlan',
|
|
103
|
-
node: nodes.length
|
|
104
|
-
? flatWrap(isMutation ? 'Sequence' : 'Parallel', nodes)
|
|
105
|
-
: undefined,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function executionNodeForGroup(
|
|
110
|
-
context: QueryPlanningContext,
|
|
111
|
-
{
|
|
112
|
-
serviceName,
|
|
113
|
-
fields,
|
|
114
|
-
requiredFields,
|
|
115
|
-
internalFragments,
|
|
116
|
-
mergeAt,
|
|
117
|
-
dependentGroups,
|
|
118
|
-
}: FetchGroup,
|
|
119
|
-
parentType?: GraphQLCompositeType,
|
|
120
|
-
): PlanNode {
|
|
121
|
-
const selectionSet = selectionSetFromFieldSet(fields, parentType);
|
|
122
|
-
const requires =
|
|
123
|
-
requiredFields.length > 0
|
|
124
|
-
? selectionSetFromFieldSet(requiredFields)
|
|
125
|
-
: undefined;
|
|
126
|
-
const variableUsages = context.getVariableUsages(
|
|
127
|
-
selectionSet,
|
|
128
|
-
internalFragments,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const operation = requires
|
|
132
|
-
? operationForEntitiesFetch({
|
|
133
|
-
selectionSet,
|
|
134
|
-
variableUsages,
|
|
135
|
-
internalFragments,
|
|
136
|
-
})
|
|
137
|
-
: operationForRootFetch({
|
|
138
|
-
selectionSet,
|
|
139
|
-
variableUsages,
|
|
140
|
-
internalFragments,
|
|
141
|
-
operation: context.operation.operation,
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const fetchNode: FetchNode = {
|
|
145
|
-
kind: 'Fetch',
|
|
146
|
-
serviceName,
|
|
147
|
-
selectionSet,
|
|
148
|
-
requires,
|
|
149
|
-
variableUsages,
|
|
150
|
-
internalFragments,
|
|
151
|
-
source: stripIgnoredCharacters(print(operation)),
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const node: PlanNode =
|
|
155
|
-
mergeAt && mergeAt.length > 0
|
|
156
|
-
? {
|
|
157
|
-
kind: 'Flatten',
|
|
158
|
-
path: mergeAt,
|
|
159
|
-
node: fetchNode,
|
|
160
|
-
}
|
|
161
|
-
: fetchNode;
|
|
162
|
-
|
|
163
|
-
if (dependentGroups.length > 0) {
|
|
164
|
-
const dependentNodes = dependentGroups.map(dependentGroup =>
|
|
165
|
-
executionNodeForGroup(context, dependentGroup),
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
return flatWrap('Sequence', [node, flatWrap('Parallel', dependentNodes)]);
|
|
169
|
-
} else {
|
|
170
|
-
return node;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
interface VariableUsages {
|
|
175
|
-
[name: string]: VariableDefinitionNode
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function mapFetchNodeToVariableDefinitions(
|
|
179
|
-
variableUsages: VariableUsages,
|
|
180
|
-
): VariableDefinitionNode[] {
|
|
181
|
-
return variableUsages ? Object.values(variableUsages) : [];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function operationForRootFetch({
|
|
185
|
-
selectionSet,
|
|
186
|
-
variableUsages,
|
|
187
|
-
internalFragments,
|
|
188
|
-
operation = 'query',
|
|
189
|
-
}: {
|
|
190
|
-
selectionSet: SelectionSetNode;
|
|
191
|
-
variableUsages: VariableUsages;
|
|
192
|
-
internalFragments: Set<FragmentDefinitionNode>;
|
|
193
|
-
operation?: OperationTypeNode;
|
|
194
|
-
}): DocumentNode {
|
|
195
|
-
return {
|
|
196
|
-
kind: Kind.DOCUMENT,
|
|
197
|
-
definitions: [
|
|
198
|
-
{
|
|
199
|
-
kind: Kind.OPERATION_DEFINITION,
|
|
200
|
-
operation,
|
|
201
|
-
selectionSet,
|
|
202
|
-
variableDefinitions: mapFetchNodeToVariableDefinitions(variableUsages),
|
|
203
|
-
},
|
|
204
|
-
...internalFragments,
|
|
205
|
-
],
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function operationForEntitiesFetch({
|
|
210
|
-
selectionSet,
|
|
211
|
-
variableUsages,
|
|
212
|
-
internalFragments,
|
|
213
|
-
}: {
|
|
214
|
-
selectionSet: SelectionSetNode;
|
|
215
|
-
variableUsages: VariableUsages;
|
|
216
|
-
internalFragments: Set<FragmentDefinitionNode>;
|
|
217
|
-
}): DocumentNode {
|
|
218
|
-
const representationsVariable = {
|
|
219
|
-
kind: Kind.VARIABLE,
|
|
220
|
-
name: { kind: Kind.NAME, value: 'representations' },
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
kind: Kind.DOCUMENT,
|
|
225
|
-
definitions: [
|
|
226
|
-
{
|
|
227
|
-
kind: Kind.OPERATION_DEFINITION,
|
|
228
|
-
operation: 'query',
|
|
229
|
-
variableDefinitions: ([
|
|
230
|
-
{
|
|
231
|
-
kind: Kind.VARIABLE_DEFINITION,
|
|
232
|
-
variable: representationsVariable,
|
|
233
|
-
type: {
|
|
234
|
-
kind: Kind.NON_NULL_TYPE,
|
|
235
|
-
type: {
|
|
236
|
-
kind: Kind.LIST_TYPE,
|
|
237
|
-
type: {
|
|
238
|
-
kind: Kind.NON_NULL_TYPE,
|
|
239
|
-
type: {
|
|
240
|
-
kind: Kind.NAMED_TYPE,
|
|
241
|
-
name: { kind: Kind.NAME, value: '_Any' },
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
] as VariableDefinitionNode[]).concat(
|
|
248
|
-
mapFetchNodeToVariableDefinitions(variableUsages),
|
|
249
|
-
),
|
|
250
|
-
selectionSet: {
|
|
251
|
-
kind: Kind.SELECTION_SET,
|
|
252
|
-
selections: [
|
|
253
|
-
{
|
|
254
|
-
kind: Kind.FIELD,
|
|
255
|
-
name: { kind: Kind.NAME, value: '_entities' },
|
|
256
|
-
arguments: [
|
|
257
|
-
{
|
|
258
|
-
kind: Kind.ARGUMENT,
|
|
259
|
-
name: {
|
|
260
|
-
kind: Kind.NAME,
|
|
261
|
-
value: representationsVariable.name.value,
|
|
262
|
-
},
|
|
263
|
-
value: representationsVariable,
|
|
264
|
-
},
|
|
265
|
-
],
|
|
266
|
-
selectionSet,
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
...internalFragments,
|
|
272
|
-
],
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Wraps the given nodes in a ParallelNode or SequenceNode, unless there's only
|
|
277
|
-
// one node, in which case it is returned directly. Any nodes of the same kind
|
|
278
|
-
// in the given list have their sub-nodes flattened into the list: ie,
|
|
279
|
-
// flatWrap('Sequence', [a, flatWrap('Sequence', b, c), d]) returns a SequenceNode
|
|
280
|
-
// with four children.
|
|
281
|
-
function flatWrap(
|
|
282
|
-
kind: ParallelNode['kind'] | SequenceNode['kind'],
|
|
283
|
-
nodes: PlanNode[],
|
|
284
|
-
): PlanNode {
|
|
285
|
-
if (nodes.length === 0) {
|
|
286
|
-
throw Error('programming error: should always be called with nodes');
|
|
287
|
-
}
|
|
288
|
-
if (nodes.length === 1) {
|
|
289
|
-
return nodes[0];
|
|
290
|
-
}
|
|
291
|
-
return {
|
|
292
|
-
kind,
|
|
293
|
-
nodes: nodes.flatMap(n => (n.kind === kind ? n.nodes : [n])),
|
|
294
|
-
} as PlanNode;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function splitRootFields(
|
|
298
|
-
context: QueryPlanningContext,
|
|
299
|
-
fields: FieldSet,
|
|
300
|
-
): FetchGroup[] {
|
|
301
|
-
const groupsByService: {
|
|
302
|
-
[serviceName: string]: FetchGroup;
|
|
303
|
-
} = Object.create(null);
|
|
304
|
-
|
|
305
|
-
function groupForService(serviceName: string) {
|
|
306
|
-
let group = groupsByService[serviceName];
|
|
307
|
-
|
|
308
|
-
if (!group) {
|
|
309
|
-
group = new FetchGroup(serviceName);
|
|
310
|
-
groupsByService[serviceName] = group;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return group;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
splitFields(context, [], fields, field => {
|
|
317
|
-
const { scope, fieldNode, fieldDef } = field;
|
|
318
|
-
const { parentType } = scope;
|
|
319
|
-
|
|
320
|
-
const owningService = context.getOwningService(parentType, fieldDef);
|
|
321
|
-
|
|
322
|
-
if (!owningService) {
|
|
323
|
-
throw new GraphQLError(
|
|
324
|
-
`Couldn't find owning service for field "${parentType.name}.${fieldDef.name}"`,
|
|
325
|
-
fieldNode,
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return groupForService(owningService);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
return Object.values(groupsByService);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// For mutations, we need to respect the order of the fields, in order to
|
|
336
|
-
// determine which fields can be batched together in the same request. If
|
|
337
|
-
// they're "split" by fields belonging to other services, then we need to manage
|
|
338
|
-
// the proper sequencing at the gateway level. In this example, we need 3
|
|
339
|
-
// FetchGroups (requests) in sequence:
|
|
340
|
-
//
|
|
341
|
-
// mutation abc {
|
|
342
|
-
// createReview() # reviews service (1)
|
|
343
|
-
// updateReview() # reviews service (1)
|
|
344
|
-
// login() # account service (2)
|
|
345
|
-
// deleteReview() # reviews service (3)
|
|
346
|
-
// }
|
|
347
|
-
function splitRootFieldsSerially(
|
|
348
|
-
context: QueryPlanningContext,
|
|
349
|
-
fields: FieldSet,
|
|
350
|
-
): FetchGroup[] {
|
|
351
|
-
const fetchGroups: FetchGroup[] = [];
|
|
352
|
-
|
|
353
|
-
function groupForField(serviceName: string) {
|
|
354
|
-
let group: FetchGroup;
|
|
355
|
-
|
|
356
|
-
// If the most recent FetchGroup in the array belongs to the same service,
|
|
357
|
-
// the field in question can be batched within that group.
|
|
358
|
-
const previousGroup = fetchGroups[fetchGroups.length - 1];
|
|
359
|
-
if (previousGroup && previousGroup.serviceName === serviceName) {
|
|
360
|
-
return previousGroup;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// If there's no previous group, or the previous group is from a different
|
|
364
|
-
// service, then we need to add a new FetchGroup.
|
|
365
|
-
group = new FetchGroup(serviceName);
|
|
366
|
-
fetchGroups.push(group);
|
|
367
|
-
|
|
368
|
-
return group;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
splitFields(context, [], fields, field => {
|
|
372
|
-
const { scope, fieldNode, fieldDef } = field;
|
|
373
|
-
const { parentType } = scope;
|
|
374
|
-
|
|
375
|
-
const owningService = context.getOwningService(parentType, fieldDef);
|
|
376
|
-
|
|
377
|
-
if (!owningService) {
|
|
378
|
-
throw new GraphQLError(
|
|
379
|
-
`Couldn't find owning service for field "${parentType.name}.${fieldDef.name}"`,
|
|
380
|
-
fieldNode,
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return groupForField(owningService);
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
return fetchGroups;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function splitSubfields(
|
|
391
|
-
context: QueryPlanningContext,
|
|
392
|
-
path: ResponsePath,
|
|
393
|
-
fields: FieldSet,
|
|
394
|
-
parentGroup: FetchGroup,
|
|
395
|
-
) {
|
|
396
|
-
splitFields(context, path, fields, field => {
|
|
397
|
-
const { scope, fieldNode, fieldDef } = field;
|
|
398
|
-
const { parentType } = scope;
|
|
399
|
-
|
|
400
|
-
let baseService, owningService;
|
|
401
|
-
|
|
402
|
-
const parentTypeFederationMetadata = getFederationMetadata(parentType);
|
|
403
|
-
if (parentTypeFederationMetadata?.isValueType) {
|
|
404
|
-
baseService = parentGroup.serviceName;
|
|
405
|
-
owningService = parentGroup.serviceName;
|
|
406
|
-
} else {
|
|
407
|
-
baseService = context.getBaseService(parentType);
|
|
408
|
-
owningService = context.getOwningService(parentType, fieldDef);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (!baseService) {
|
|
412
|
-
throw new GraphQLError(
|
|
413
|
-
`Couldn't find base service for type "${parentType.name}"`,
|
|
414
|
-
fieldNode,
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (!owningService) {
|
|
419
|
-
throw new GraphQLError(
|
|
420
|
-
`Couldn't find owning service for field "${parentType.name}.${fieldDef.name}"`,
|
|
421
|
-
fieldNode,
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
// Is the field defined on the base service?
|
|
425
|
-
if (owningService === baseService) {
|
|
426
|
-
// Can we fetch the field from the parent group?
|
|
427
|
-
if (
|
|
428
|
-
owningService === parentGroup.serviceName ||
|
|
429
|
-
parentGroup.providedFields.some(matchesField(field))
|
|
430
|
-
) {
|
|
431
|
-
return parentGroup;
|
|
432
|
-
} else {
|
|
433
|
-
// We need to fetch the key fields from the parent group first, and then
|
|
434
|
-
// use a dependent fetch from the owning service.
|
|
435
|
-
let keyFields = context.getKeyFields({
|
|
436
|
-
parentType,
|
|
437
|
-
serviceName: parentGroup.serviceName,
|
|
438
|
-
});
|
|
439
|
-
if (
|
|
440
|
-
keyFields.length === 0 ||
|
|
441
|
-
(keyFields.length === 1 &&
|
|
442
|
-
keyFields[0].fieldDef.name === '__typename')
|
|
443
|
-
) {
|
|
444
|
-
// Only __typename key found.
|
|
445
|
-
// In some cases, the parent group does not have any @key directives.
|
|
446
|
-
// Fall back to owning group's keys
|
|
447
|
-
keyFields = context.getKeyFields({
|
|
448
|
-
parentType,
|
|
449
|
-
serviceName: owningService,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
return parentGroup.dependentGroupForService(owningService, keyFields);
|
|
453
|
-
}
|
|
454
|
-
} else {
|
|
455
|
-
// It's an extension field, so we need to fetch the required fields first.
|
|
456
|
-
const requiredFields = context.getRequiredFields(
|
|
457
|
-
parentType,
|
|
458
|
-
fieldDef,
|
|
459
|
-
owningService,
|
|
460
|
-
);
|
|
461
|
-
|
|
462
|
-
// Can we fetch the required fields from the parent group?
|
|
463
|
-
if (
|
|
464
|
-
requiredFields.every(requiredField =>
|
|
465
|
-
parentGroup.providedFields.some(matchesField(requiredField)),
|
|
466
|
-
)
|
|
467
|
-
) {
|
|
468
|
-
if (owningService === parentGroup.serviceName) {
|
|
469
|
-
return parentGroup;
|
|
470
|
-
} else {
|
|
471
|
-
return parentGroup.dependentGroupForService(
|
|
472
|
-
owningService,
|
|
473
|
-
requiredFields,
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
} else {
|
|
477
|
-
// We need to go through the base group first.
|
|
478
|
-
|
|
479
|
-
const keyFields = context.getKeyFields({
|
|
480
|
-
parentType,
|
|
481
|
-
serviceName: parentGroup.serviceName,
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
if (!keyFields) {
|
|
485
|
-
throw new GraphQLError(
|
|
486
|
-
`Couldn't find keys for type "${parentType.name}}" in service "${baseService}"`,
|
|
487
|
-
fieldNode,
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (baseService === parentGroup.serviceName) {
|
|
492
|
-
return parentGroup.dependentGroupForService(
|
|
493
|
-
owningService,
|
|
494
|
-
requiredFields,
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const baseGroup = parentGroup.dependentGroupForService(
|
|
499
|
-
baseService,
|
|
500
|
-
keyFields,
|
|
501
|
-
);
|
|
502
|
-
|
|
503
|
-
return baseGroup.dependentGroupForService(
|
|
504
|
-
owningService,
|
|
505
|
-
requiredFields,
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function splitFields(
|
|
513
|
-
context: QueryPlanningContext,
|
|
514
|
-
path: ResponsePath,
|
|
515
|
-
fields: FieldSet,
|
|
516
|
-
groupForField: (field: Field<GraphQLObjectType>) => FetchGroup,
|
|
517
|
-
) {
|
|
518
|
-
for (const fieldsForResponseName of groupByResponseName(fields).values()) {
|
|
519
|
-
for (const [parentType, fieldsForParentType] of groupByParentType(fieldsForResponseName)) {
|
|
520
|
-
// Field nodes that share the same response name and parent type are guaranteed
|
|
521
|
-
// to have the same field name and arguments. We only need the other nodes when
|
|
522
|
-
// merging selection sets, to take node-specific subfields and directives
|
|
523
|
-
// into account.
|
|
524
|
-
|
|
525
|
-
const field = fieldsForParentType[0];
|
|
526
|
-
const { scope, fieldDef } = field;
|
|
527
|
-
|
|
528
|
-
// We skip `__typename` for root types.
|
|
529
|
-
if (fieldDef.name === TypeNameMetaFieldDef.name) {
|
|
530
|
-
const { schema } = context;
|
|
531
|
-
const roots = [
|
|
532
|
-
schema.getQueryType(),
|
|
533
|
-
schema.getMutationType(),
|
|
534
|
-
schema.getSubscriptionType(),
|
|
535
|
-
]
|
|
536
|
-
.filter(isNotNullOrUndefined)
|
|
537
|
-
.map(type => type.name);
|
|
538
|
-
if (roots.indexOf(parentType.name) > -1) continue;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// We skip introspection fields like `__schema` and `__type`.
|
|
542
|
-
if (isIntrospectionType(getNamedType(fieldDef.type))) {
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if (isObjectType(parentType) && scope.possibleTypes.includes(parentType)) {
|
|
547
|
-
// If parent type is an object type, we can directly look for the right
|
|
548
|
-
// group.
|
|
549
|
-
const group = groupForField(field as Field<GraphQLObjectType>);
|
|
550
|
-
group.fields.push(
|
|
551
|
-
completeField(
|
|
552
|
-
context,
|
|
553
|
-
scope as Scope<typeof parentType>,
|
|
554
|
-
group,
|
|
555
|
-
path,
|
|
556
|
-
fieldsForParentType,
|
|
557
|
-
),
|
|
558
|
-
);
|
|
559
|
-
} else {
|
|
560
|
-
// For interfaces however, we need to look at all possible runtime types.
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* The following is an optimization to prevent an explosion of type
|
|
564
|
-
* conditions to services when it isn't needed. If all possible runtime
|
|
565
|
-
* types can be fufilled by only one service then we don't need to
|
|
566
|
-
* expand the fields into unique type conditions.
|
|
567
|
-
*/
|
|
568
|
-
|
|
569
|
-
// Collect all of the field defs on the possible runtime types
|
|
570
|
-
const possibleFieldDefs = scope.possibleTypes.map(
|
|
571
|
-
runtimeType => context.getFieldDef(runtimeType, field.fieldNode),
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
// If none of the field defs have a federation property, this interface's
|
|
575
|
-
// implementors can all be resolved within the same service.
|
|
576
|
-
const hasNoExtendingFieldDefs = !possibleFieldDefs.some(
|
|
577
|
-
getFederationMetadata,
|
|
578
|
-
);
|
|
579
|
-
|
|
580
|
-
// With no extending field definitions, we can engage the optimization
|
|
581
|
-
if (hasNoExtendingFieldDefs) {
|
|
582
|
-
const group = groupForField(field as Field<GraphQLObjectType>);
|
|
583
|
-
group.fields.push(
|
|
584
|
-
completeField(context, scope, group, path, fieldsForResponseName)
|
|
585
|
-
);
|
|
586
|
-
continue;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// We keep track of which possible runtime parent types can be fetched
|
|
590
|
-
// from which group,
|
|
591
|
-
const groupsByRuntimeParentTypes = new MultiMap<
|
|
592
|
-
FetchGroup,
|
|
593
|
-
GraphQLObjectType
|
|
594
|
-
>();
|
|
595
|
-
|
|
596
|
-
for (const runtimeParentType of scope.possibleTypes) {
|
|
597
|
-
const fieldDef = context.getFieldDef(
|
|
598
|
-
runtimeParentType,
|
|
599
|
-
field.fieldNode,
|
|
600
|
-
);
|
|
601
|
-
groupsByRuntimeParentTypes.add(
|
|
602
|
-
groupForField({
|
|
603
|
-
scope: context.newScope(runtimeParentType, scope),
|
|
604
|
-
fieldNode: field.fieldNode,
|
|
605
|
-
fieldDef,
|
|
606
|
-
}),
|
|
607
|
-
runtimeParentType,
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// We add the field separately for each runtime parent type.
|
|
612
|
-
for (const [group, runtimeParentTypes] of groupsByRuntimeParentTypes) {
|
|
613
|
-
for (const runtimeParentType of runtimeParentTypes) {
|
|
614
|
-
// We need to adjust the fields to contain the right fieldDef for
|
|
615
|
-
// their runtime parent type.
|
|
616
|
-
|
|
617
|
-
const fieldDef = context.getFieldDef(
|
|
618
|
-
runtimeParentType,
|
|
619
|
-
field.fieldNode,
|
|
620
|
-
);
|
|
621
|
-
|
|
622
|
-
const fieldsWithRuntimeParentType = fieldsForParentType.map(field => ({
|
|
623
|
-
...field,
|
|
624
|
-
fieldDef,
|
|
625
|
-
}));
|
|
626
|
-
|
|
627
|
-
group.fields.push(
|
|
628
|
-
completeField(
|
|
629
|
-
context,
|
|
630
|
-
context.newScope(runtimeParentType, scope),
|
|
631
|
-
group,
|
|
632
|
-
path,
|
|
633
|
-
fieldsWithRuntimeParentType,
|
|
634
|
-
),
|
|
635
|
-
);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function completeField(
|
|
644
|
-
context: QueryPlanningContext,
|
|
645
|
-
scope: Scope<GraphQLCompositeType>,
|
|
646
|
-
parentGroup: FetchGroup,
|
|
647
|
-
path: ResponsePath,
|
|
648
|
-
fields: FieldSet,
|
|
649
|
-
): Field {
|
|
650
|
-
const { fieldNode, fieldDef } = fields[0];
|
|
651
|
-
const returnType = getNamedType(fieldDef.type);
|
|
652
|
-
|
|
653
|
-
if (!isCompositeType(returnType)) {
|
|
654
|
-
// FIXME: We should look at all field nodes to make sure we take directives
|
|
655
|
-
// into account (or remove directives for the time being).
|
|
656
|
-
return { scope, fieldNode, fieldDef };
|
|
657
|
-
} else {
|
|
658
|
-
// For composite types, we need to recurse.
|
|
659
|
-
|
|
660
|
-
const fieldPath = addPath(path, getResponseName(fieldNode), fieldDef.type);
|
|
661
|
-
|
|
662
|
-
const subGroup = new FetchGroup(parentGroup.serviceName);
|
|
663
|
-
subGroup.mergeAt = fieldPath;
|
|
664
|
-
|
|
665
|
-
subGroup.providedFields = context.getProvidedFields(
|
|
666
|
-
fieldDef,
|
|
667
|
-
parentGroup.serviceName,
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
// For abstract types, we always need to request `__typename`
|
|
671
|
-
if (isAbstractType(returnType)) {
|
|
672
|
-
subGroup.fields.push({
|
|
673
|
-
scope: context.newScope(returnType, scope),
|
|
674
|
-
fieldNode: typenameField,
|
|
675
|
-
fieldDef: TypeNameMetaFieldDef,
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
const subfields = collectSubfields(context, returnType, fields);
|
|
680
|
-
splitSubfields(context, fieldPath, subfields, subGroup);
|
|
681
|
-
|
|
682
|
-
parentGroup.otherDependentGroups.push(...subGroup.dependentGroups);
|
|
683
|
-
|
|
684
|
-
let definition: FragmentDefinitionNode;
|
|
685
|
-
let selectionSet = selectionSetFromFieldSet(subGroup.fields, returnType);
|
|
686
|
-
|
|
687
|
-
if (context.autoFragmentization && subGroup.fields.length > 2) {
|
|
688
|
-
({ definition, selectionSet } = getInternalFragment(
|
|
689
|
-
selectionSet,
|
|
690
|
-
returnType,
|
|
691
|
-
context,
|
|
692
|
-
));
|
|
693
|
-
parentGroup.internalFragments.add(definition);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// "Hoist" internalFragments of the subGroup into the parentGroup so all
|
|
697
|
-
// fragments can be included in the final request for the root FetchGroup
|
|
698
|
-
subGroup.internalFragments.forEach(fragment => {
|
|
699
|
-
parentGroup.internalFragments.add(fragment);
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
return {
|
|
703
|
-
scope,
|
|
704
|
-
fieldNode: {
|
|
705
|
-
...fieldNode,
|
|
706
|
-
selectionSet,
|
|
707
|
-
},
|
|
708
|
-
fieldDef,
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
function getInternalFragment(
|
|
714
|
-
selectionSet: SelectionSetNode,
|
|
715
|
-
returnType: GraphQLCompositeType,
|
|
716
|
-
context: QueryPlanningContext
|
|
717
|
-
) {
|
|
718
|
-
const key = JSON.stringify(selectionSet);
|
|
719
|
-
if (!context.internalFragments.has(key)) {
|
|
720
|
-
const name = `__QueryPlanFragment_${context.internalFragmentCount++}__`;
|
|
721
|
-
|
|
722
|
-
const definition: FragmentDefinitionNode = {
|
|
723
|
-
kind: Kind.FRAGMENT_DEFINITION,
|
|
724
|
-
name: {
|
|
725
|
-
kind: Kind.NAME,
|
|
726
|
-
value: name,
|
|
727
|
-
},
|
|
728
|
-
typeCondition: {
|
|
729
|
-
kind: Kind.NAMED_TYPE,
|
|
730
|
-
name: {
|
|
731
|
-
kind: Kind.NAME,
|
|
732
|
-
value: returnType.name,
|
|
733
|
-
},
|
|
734
|
-
},
|
|
735
|
-
selectionSet,
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
const fragmentSelection: SelectionSetNode = {
|
|
739
|
-
kind: Kind.SELECTION_SET,
|
|
740
|
-
selections: [
|
|
741
|
-
{
|
|
742
|
-
kind: Kind.FRAGMENT_SPREAD,
|
|
743
|
-
name: {
|
|
744
|
-
kind: Kind.NAME,
|
|
745
|
-
value: name,
|
|
746
|
-
},
|
|
747
|
-
},
|
|
748
|
-
],
|
|
749
|
-
};
|
|
750
|
-
|
|
751
|
-
context.internalFragments.set(key, {
|
|
752
|
-
name,
|
|
753
|
-
definition,
|
|
754
|
-
selectionSet: fragmentSelection,
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
return context.internalFragments.get(key)!;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function collectFields(
|
|
762
|
-
context: QueryPlanningContext,
|
|
763
|
-
scope: Scope<GraphQLCompositeType>,
|
|
764
|
-
selectionSet: SelectionSetNode,
|
|
765
|
-
fields: FieldSet = [],
|
|
766
|
-
visitedFragmentNames: { [fragmentName: string]: boolean } = Object.create(
|
|
767
|
-
null,
|
|
768
|
-
),
|
|
769
|
-
): FieldSet {
|
|
770
|
-
for (const selection of selectionSet.selections) {
|
|
771
|
-
switch (selection.kind) {
|
|
772
|
-
case Kind.FIELD:
|
|
773
|
-
const fieldDef = context.getFieldDef(scope.parentType, selection);
|
|
774
|
-
fields.push({ scope, fieldNode: selection, fieldDef });
|
|
775
|
-
break;
|
|
776
|
-
case Kind.INLINE_FRAGMENT: {
|
|
777
|
-
const newScope = context.newScope(getFragmentCondition(selection), scope);
|
|
778
|
-
if (newScope.possibleTypes.length === 0) {
|
|
779
|
-
break;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
collectFields(
|
|
783
|
-
context,
|
|
784
|
-
context.newScope(getFragmentCondition(selection), scope),
|
|
785
|
-
selection.selectionSet,
|
|
786
|
-
fields,
|
|
787
|
-
visitedFragmentNames,
|
|
788
|
-
);
|
|
789
|
-
break;
|
|
790
|
-
}
|
|
791
|
-
case Kind.FRAGMENT_SPREAD:
|
|
792
|
-
const fragmentName = selection.name.value;
|
|
793
|
-
|
|
794
|
-
const fragment = context.fragments[fragmentName];
|
|
795
|
-
if (!fragment) {
|
|
796
|
-
continue;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
const newScope = context.newScope(getFragmentCondition(fragment), scope);
|
|
800
|
-
if (newScope.possibleTypes.length === 0) {
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
if (visitedFragmentNames[fragmentName]) {
|
|
805
|
-
continue;
|
|
806
|
-
}
|
|
807
|
-
visitedFragmentNames[fragmentName] = true;
|
|
808
|
-
|
|
809
|
-
collectFields(
|
|
810
|
-
context,
|
|
811
|
-
newScope,
|
|
812
|
-
fragment.selectionSet,
|
|
813
|
-
fields,
|
|
814
|
-
visitedFragmentNames,
|
|
815
|
-
);
|
|
816
|
-
break;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
return fields;
|
|
821
|
-
|
|
822
|
-
function getFragmentCondition(
|
|
823
|
-
fragment: FragmentDefinitionNode | InlineFragmentNode,
|
|
824
|
-
): GraphQLCompositeType {
|
|
825
|
-
const typeConditionNode = fragment.typeCondition;
|
|
826
|
-
if (!typeConditionNode) return scope.parentType;
|
|
827
|
-
|
|
828
|
-
return typeFromAST(
|
|
829
|
-
context.schema,
|
|
830
|
-
typeConditionNode,
|
|
831
|
-
) as GraphQLCompositeType;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// Collecting subfields collapses parent types, because it merges
|
|
836
|
-
// selection sets without taking the runtime parent type of the field
|
|
837
|
-
// into account. If we want to keep track of multiple levels of possible
|
|
838
|
-
// types, this is where that would need to happen.
|
|
839
|
-
export function collectSubfields(
|
|
840
|
-
context: QueryPlanningContext,
|
|
841
|
-
returnType: GraphQLCompositeType,
|
|
842
|
-
fields: FieldSet,
|
|
843
|
-
): FieldSet {
|
|
844
|
-
let subfields: FieldSet = [];
|
|
845
|
-
const visitedFragmentNames = Object.create(null);
|
|
846
|
-
|
|
847
|
-
for (const field of fields) {
|
|
848
|
-
const selectionSet = field.fieldNode.selectionSet;
|
|
849
|
-
|
|
850
|
-
if (selectionSet) {
|
|
851
|
-
subfields = collectFields(
|
|
852
|
-
context,
|
|
853
|
-
context.newScope(returnType),
|
|
854
|
-
selectionSet,
|
|
855
|
-
subfields,
|
|
856
|
-
visitedFragmentNames,
|
|
857
|
-
);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
return subfields;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
class FetchGroup {
|
|
865
|
-
constructor(
|
|
866
|
-
public readonly serviceName: string,
|
|
867
|
-
public readonly fields: FieldSet = [],
|
|
868
|
-
public readonly internalFragments: Set<FragmentDefinitionNode> = new Set()
|
|
869
|
-
) {}
|
|
870
|
-
|
|
871
|
-
requiredFields: FieldSet = [];
|
|
872
|
-
providedFields: FieldSet = [];
|
|
873
|
-
|
|
874
|
-
mergeAt?: ResponsePath;
|
|
875
|
-
|
|
876
|
-
private dependentGroupsByService: {
|
|
877
|
-
[serviceName: string]: FetchGroup;
|
|
878
|
-
} = Object.create(null);
|
|
879
|
-
public otherDependentGroups: FetchGroup[] = [];
|
|
880
|
-
|
|
881
|
-
dependentGroupForService(serviceName: string, requiredFields: FieldSet) {
|
|
882
|
-
let group = this.dependentGroupsByService[serviceName];
|
|
883
|
-
|
|
884
|
-
if (!group) {
|
|
885
|
-
group = new FetchGroup(serviceName);
|
|
886
|
-
group.mergeAt = this.mergeAt;
|
|
887
|
-
this.dependentGroupsByService[serviceName] = group;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
if (requiredFields) {
|
|
891
|
-
if (group.requiredFields) {
|
|
892
|
-
group.requiredFields.push(...requiredFields);
|
|
893
|
-
} else {
|
|
894
|
-
group.requiredFields = requiredFields;
|
|
895
|
-
}
|
|
896
|
-
this.fields.push(...requiredFields);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
return group;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
get dependentGroups() {
|
|
903
|
-
return [
|
|
904
|
-
...Object.values(this.dependentGroupsByService),
|
|
905
|
-
...this.otherDependentGroups,
|
|
906
|
-
];
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// Adapted from buildExecutionContext in graphql-js
|
|
911
|
-
export function buildOperationContext(
|
|
912
|
-
schema: GraphQLSchema,
|
|
913
|
-
document: DocumentNode,
|
|
914
|
-
operationName?: string,
|
|
915
|
-
): OperationContext {
|
|
916
|
-
let operation: OperationDefinitionNode | undefined;
|
|
917
|
-
const fragments: {
|
|
918
|
-
[fragmentName: string]: FragmentDefinitionNode;
|
|
919
|
-
} = Object.create(null);
|
|
920
|
-
document.definitions.forEach(definition => {
|
|
921
|
-
switch (definition.kind) {
|
|
922
|
-
case Kind.OPERATION_DEFINITION:
|
|
923
|
-
if (!operationName && operation) {
|
|
924
|
-
throw new GraphQLError(
|
|
925
|
-
'Must provide operation name if query contains ' +
|
|
926
|
-
'multiple operations.',
|
|
927
|
-
);
|
|
928
|
-
}
|
|
929
|
-
if (
|
|
930
|
-
!operationName ||
|
|
931
|
-
(definition.name && definition.name.value === operationName)
|
|
932
|
-
) {
|
|
933
|
-
operation = definition;
|
|
934
|
-
}
|
|
935
|
-
break;
|
|
936
|
-
case Kind.FRAGMENT_DEFINITION:
|
|
937
|
-
fragments[definition.name.value] = definition;
|
|
938
|
-
break;
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
if (!operation) {
|
|
942
|
-
if (operationName) {
|
|
943
|
-
throw new GraphQLError(`Unknown operation named "${operationName}".`);
|
|
944
|
-
} else {
|
|
945
|
-
throw new GraphQLError('Must provide an operation.');
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
return { schema, operation, fragments };
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
export function buildQueryPlanningContext(
|
|
953
|
-
{ operation, schema, fragments }: OperationContext,
|
|
954
|
-
options: BuildQueryPlanOptions,
|
|
955
|
-
): QueryPlanningContext {
|
|
956
|
-
return new QueryPlanningContext(
|
|
957
|
-
schema,
|
|
958
|
-
operation,
|
|
959
|
-
fragments,
|
|
960
|
-
options.autoFragmentization,
|
|
961
|
-
);
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
export class QueryPlanningContext {
|
|
965
|
-
public internalFragments: Map<
|
|
966
|
-
string,
|
|
967
|
-
{
|
|
968
|
-
name: string;
|
|
969
|
-
definition: FragmentDefinitionNode;
|
|
970
|
-
selectionSet: SelectionSetNode;
|
|
971
|
-
}
|
|
972
|
-
> = new Map();
|
|
973
|
-
|
|
974
|
-
public internalFragmentCount = 0;
|
|
975
|
-
|
|
976
|
-
protected variableDefinitions: {
|
|
977
|
-
[name: string]: VariableDefinitionNode;
|
|
978
|
-
};
|
|
979
|
-
|
|
980
|
-
constructor(
|
|
981
|
-
public readonly schema: GraphQLSchema,
|
|
982
|
-
public readonly operation: OperationDefinitionNode,
|
|
983
|
-
public readonly fragments: FragmentMap,
|
|
984
|
-
public readonly autoFragmentization: boolean,
|
|
985
|
-
) {
|
|
986
|
-
this.variableDefinitions = Object.create(null);
|
|
987
|
-
visit(operation, {
|
|
988
|
-
VariableDefinition: definition => {
|
|
989
|
-
this.variableDefinitions[definition.variable.name.value] = definition;
|
|
990
|
-
},
|
|
991
|
-
});
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
getFieldDef(parentType: GraphQLCompositeType, fieldNode: FieldNode) {
|
|
995
|
-
const fieldName = fieldNode.name.value;
|
|
996
|
-
|
|
997
|
-
const fieldDef = getFieldDef(this.schema, parentType, fieldName);
|
|
998
|
-
|
|
999
|
-
if (!fieldDef) {
|
|
1000
|
-
throw new GraphQLError(
|
|
1001
|
-
`Cannot query field "${fieldNode.name.value}" on type "${String(
|
|
1002
|
-
parentType,
|
|
1003
|
-
)}"`,
|
|
1004
|
-
fieldNode,
|
|
1005
|
-
);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
return fieldDef;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
getPossibleTypes(
|
|
1012
|
-
type: GraphQLAbstractType | GraphQLObjectType,
|
|
1013
|
-
): ReadonlyArray<GraphQLObjectType> {
|
|
1014
|
-
return isAbstractType(type) ? this.schema.getPossibleTypes(type) : [type];
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
getVariableUsages(
|
|
1018
|
-
selectionSet: SelectionSetNode,
|
|
1019
|
-
fragments: Set<FragmentDefinitionNode>,
|
|
1020
|
-
) {
|
|
1021
|
-
const usages: {
|
|
1022
|
-
[name: string]: VariableDefinitionNode;
|
|
1023
|
-
} = Object.create(null);
|
|
1024
|
-
|
|
1025
|
-
// Construct a document of the selection set and fragment definitions so we
|
|
1026
|
-
// can visit them, adding all variable usages to the `usages` object.
|
|
1027
|
-
const document: DocumentNode = {
|
|
1028
|
-
kind: Kind.DOCUMENT,
|
|
1029
|
-
definitions: [
|
|
1030
|
-
{ kind: Kind.OPERATION_DEFINITION, selectionSet, operation: 'query' },
|
|
1031
|
-
...Array.from(fragments),
|
|
1032
|
-
],
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
visit(document, {
|
|
1036
|
-
Variable: (node) => {
|
|
1037
|
-
usages[node.name.value] = this.variableDefinitions[node.name.value];
|
|
1038
|
-
},
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
return usages;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
newScope<TParent extends GraphQLCompositeType>(
|
|
1045
|
-
parentType: TParent,
|
|
1046
|
-
enclosingScope?: Scope<GraphQLCompositeType>,
|
|
1047
|
-
): Scope<TParent> {
|
|
1048
|
-
return {
|
|
1049
|
-
parentType,
|
|
1050
|
-
possibleTypes: enclosingScope
|
|
1051
|
-
? this.getPossibleTypes(parentType).filter(type =>
|
|
1052
|
-
enclosingScope.possibleTypes.includes(type),
|
|
1053
|
-
)
|
|
1054
|
-
: this.getPossibleTypes(parentType),
|
|
1055
|
-
enclosingScope,
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
getBaseService(parentType: GraphQLObjectType): string | null {
|
|
1060
|
-
return (getFederationMetadata(parentType)?.serviceName) || null;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
getOwningService(
|
|
1064
|
-
parentType: GraphQLObjectType,
|
|
1065
|
-
fieldDef: GraphQLField<any, any>,
|
|
1066
|
-
): string | null {
|
|
1067
|
-
const fieldFederationMetadata = getFederationMetadata(fieldDef);
|
|
1068
|
-
if (
|
|
1069
|
-
fieldFederationMetadata?.serviceName &&
|
|
1070
|
-
!fieldFederationMetadata?.belongsToValueType
|
|
1071
|
-
) {
|
|
1072
|
-
return fieldFederationMetadata.serviceName;
|
|
1073
|
-
} else {
|
|
1074
|
-
return this.getBaseService(parentType);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
getKeyFields({
|
|
1079
|
-
parentType,
|
|
1080
|
-
serviceName,
|
|
1081
|
-
fetchAll = false,
|
|
1082
|
-
}: {
|
|
1083
|
-
parentType: GraphQLCompositeType;
|
|
1084
|
-
serviceName: string;
|
|
1085
|
-
fetchAll?: boolean;
|
|
1086
|
-
}): FieldSet {
|
|
1087
|
-
const keyFields: FieldSet = [];
|
|
1088
|
-
|
|
1089
|
-
keyFields.push({
|
|
1090
|
-
scope: {
|
|
1091
|
-
parentType,
|
|
1092
|
-
possibleTypes: this.getPossibleTypes(parentType),
|
|
1093
|
-
},
|
|
1094
|
-
fieldNode: typenameField,
|
|
1095
|
-
fieldDef: TypeNameMetaFieldDef,
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
for (const possibleType of this.getPossibleTypes(parentType)) {
|
|
1099
|
-
const keys = getFederationMetadata(possibleType)?.keys?.[serviceName];
|
|
1100
|
-
|
|
1101
|
-
if (!(keys && keys.length > 0)) continue;
|
|
1102
|
-
|
|
1103
|
-
if (fetchAll) {
|
|
1104
|
-
keyFields.push(
|
|
1105
|
-
...keys.flatMap(key =>
|
|
1106
|
-
collectFields(this, this.newScope(possibleType), {
|
|
1107
|
-
kind: Kind.SELECTION_SET,
|
|
1108
|
-
selections: key,
|
|
1109
|
-
}),
|
|
1110
|
-
),
|
|
1111
|
-
);
|
|
1112
|
-
} else {
|
|
1113
|
-
keyFields.push(
|
|
1114
|
-
...collectFields(this, this.newScope(possibleType), {
|
|
1115
|
-
kind: Kind.SELECTION_SET,
|
|
1116
|
-
selections: keys[0],
|
|
1117
|
-
}),
|
|
1118
|
-
);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
return keyFields;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
getRequiredFields(
|
|
1126
|
-
parentType: GraphQLCompositeType,
|
|
1127
|
-
fieldDef: GraphQLField<any, any>,
|
|
1128
|
-
serviceName: string,
|
|
1129
|
-
): FieldSet {
|
|
1130
|
-
const requiredFields: FieldSet = [];
|
|
1131
|
-
|
|
1132
|
-
requiredFields.push(...this.getKeyFields({ parentType, serviceName }));
|
|
1133
|
-
|
|
1134
|
-
const fieldFederationMetadata = getFederationMetadata(fieldDef);
|
|
1135
|
-
if (fieldFederationMetadata?.requires) {
|
|
1136
|
-
requiredFields.push(
|
|
1137
|
-
...collectFields(this, this.newScope(parentType), {
|
|
1138
|
-
kind: Kind.SELECTION_SET,
|
|
1139
|
-
selections: fieldFederationMetadata.requires,
|
|
1140
|
-
}),
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
return requiredFields;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
getProvidedFields(
|
|
1148
|
-
fieldDef: GraphQLField<any, any>,
|
|
1149
|
-
serviceName: string,
|
|
1150
|
-
): FieldSet {
|
|
1151
|
-
const returnType = getNamedType(fieldDef.type);
|
|
1152
|
-
if (!isCompositeType(returnType)) return [];
|
|
1153
|
-
|
|
1154
|
-
const providedFields: FieldSet = [];
|
|
1155
|
-
|
|
1156
|
-
providedFields.push(
|
|
1157
|
-
...this.getKeyFields({
|
|
1158
|
-
parentType: returnType,
|
|
1159
|
-
serviceName,
|
|
1160
|
-
fetchAll: true,
|
|
1161
|
-
}),
|
|
1162
|
-
);
|
|
1163
|
-
|
|
1164
|
-
const fieldFederationMetadata = getFederationMetadata(fieldDef);
|
|
1165
|
-
if (fieldFederationMetadata?.provides) {
|
|
1166
|
-
providedFields.push(
|
|
1167
|
-
...collectFields(this, this.newScope(returnType), {
|
|
1168
|
-
kind: Kind.SELECTION_SET,
|
|
1169
|
-
selections: fieldFederationMetadata.provides,
|
|
1170
|
-
}),
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
return providedFields;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
function addPath(path: ResponsePath, responseName: string, type: GraphQLType) {
|
|
1179
|
-
path = [...path, responseName];
|
|
1180
|
-
|
|
1181
|
-
while (!isNamedType(type)) {
|
|
1182
|
-
if (isListType(type)) {
|
|
1183
|
-
path.push('@');
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
type = type.ofType;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
return path;
|
|
1190
|
-
}
|