@apollo/federation-internals 2.9.1 → 2.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/error.d.ts +5 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +10 -0
- package/dist/error.js.map +1 -1
- package/dist/federation.d.ts +2 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +77 -4
- package/dist/federation.js.map +1 -1
- package/dist/operations.d.ts +8 -4
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +42 -25
- package/dist/operations.js.map +1 -1
- package/package.json +1 -1
- package/src/error.ts +36 -0
- package/src/federation.ts +146 -3
- package/src/operations.ts +55 -26
package/package.json
CHANGED
package/src/error.ts
CHANGED
|
@@ -710,6 +710,36 @@ const CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS = makeCodeDefinition(
|
|
|
710
710
|
{ addedIn: '2.7.0' },
|
|
711
711
|
);
|
|
712
712
|
|
|
713
|
+
const COST_APPLIED_TO_INTERFACE_FIELD = makeCodeDefinition(
|
|
714
|
+
'COST_APPLIED_TO_INTERFACE_FIELD',
|
|
715
|
+
'The `@cost` directive must be applied to concrete types',
|
|
716
|
+
{ addedIn: '2.9.2' },
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
const LIST_SIZE_APPLIED_TO_NON_LIST = makeCodeDefinition(
|
|
720
|
+
'LIST_SIZE_APPLIED_TO_NON_LIST',
|
|
721
|
+
'The `@listSize` directive must be applied to list types',
|
|
722
|
+
{ addedIn: '2.9.2' },
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
const LIST_SIZE_INVALID_ASSUMED_SIZE = makeCodeDefinition(
|
|
726
|
+
'LIST_SIZE_INVALID_ASSUMED_SIZE',
|
|
727
|
+
'The `@listSize` directive assumed size cannot be negative',
|
|
728
|
+
{ addedIn: '2.9.2' },
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
const LIST_SIZE_INVALID_SLICING_ARGUMENT = makeCodeDefinition(
|
|
732
|
+
'LIST_SIZE_INVALID_SLICING_ARGUMENT',
|
|
733
|
+
'The `@listSize` directive must have existing integer slicing arguments',
|
|
734
|
+
{ addedIn: '2.9.2' },
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
const LIST_SIZE_INVALID_SIZED_FIELD = makeCodeDefinition(
|
|
738
|
+
'LIST_SIZE_INVALID_SIZED_FIELD',
|
|
739
|
+
'The `@listSize` directive must reference existing list fields as sized fields',
|
|
740
|
+
{ addedIn: '2.9.2' },
|
|
741
|
+
);
|
|
742
|
+
|
|
713
743
|
export const ERROR_CATEGORIES = {
|
|
714
744
|
DIRECTIVE_FIELDS_MISSING_EXTERNAL,
|
|
715
745
|
DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
|
|
@@ -824,6 +854,12 @@ export const ERRORS = {
|
|
|
824
854
|
SOURCE_FIELD_SELECTION_INVALID,
|
|
825
855
|
SOURCE_FIELD_NOT_ON_ROOT_OR_ENTITY_FIELD,
|
|
826
856
|
CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS,
|
|
857
|
+
// Errors related to demand control
|
|
858
|
+
COST_APPLIED_TO_INTERFACE_FIELD,
|
|
859
|
+
LIST_SIZE_APPLIED_TO_NON_LIST,
|
|
860
|
+
LIST_SIZE_INVALID_ASSUMED_SIZE,
|
|
861
|
+
LIST_SIZE_INVALID_SIZED_FIELD,
|
|
862
|
+
LIST_SIZE_INVALID_SLICING_ARGUMENT,
|
|
827
863
|
};
|
|
828
864
|
|
|
829
865
|
const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
|
package/src/federation.ts
CHANGED
|
@@ -36,6 +36,8 @@ import {
|
|
|
36
36
|
isListType,
|
|
37
37
|
isWrapperType,
|
|
38
38
|
possibleRuntimeTypes,
|
|
39
|
+
isIntType,
|
|
40
|
+
Type,
|
|
39
41
|
} from "./definitions";
|
|
40
42
|
import { assert, MultiMap, printHumanReadableList, OrderedMap, mapValues, assertUnreachable } from "./utils";
|
|
41
43
|
import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
@@ -100,7 +102,7 @@ import {
|
|
|
100
102
|
SourceFieldDirectiveArgs,
|
|
101
103
|
SourceTypeDirectiveArgs,
|
|
102
104
|
} from "./specs/sourceSpec";
|
|
103
|
-
import { CostDirectiveArguments, ListSizeDirectiveArguments } from "./specs/costSpec";
|
|
105
|
+
import { COST_VERSIONS, CostDirectiveArguments, ListSizeDirectiveArguments, costIdentity } from "./specs/costSpec";
|
|
104
106
|
|
|
105
107
|
const linkSpec = LINK_VERSIONS.latest();
|
|
106
108
|
const tagSpec = TAG_VERSIONS.latest();
|
|
@@ -1055,6 +1057,123 @@ function validateShareableNotRepeatedOnSameDeclaration(
|
|
|
1055
1057
|
}
|
|
1056
1058
|
}
|
|
1057
1059
|
}
|
|
1060
|
+
|
|
1061
|
+
function validateCostNotAppliedToInterface(application: Directive<SchemaElement<any, any>, CostDirectiveArguments>, errorCollector: GraphQLError[]) {
|
|
1062
|
+
const parent = application.parent;
|
|
1063
|
+
// @cost cannot be used on interfaces https://ibm.github.io/graphql-specs/cost-spec.html#sec-No-Cost-on-Interface-Fields
|
|
1064
|
+
if (parent instanceof FieldDefinition && parent.parent instanceof InterfaceType) {
|
|
1065
|
+
errorCollector.push(ERRORS.COST_APPLIED_TO_INTERFACE_FIELD.err(
|
|
1066
|
+
`@cost cannot be applied to interface "${parent.coordinate}"`,
|
|
1067
|
+
{ nodes: sourceASTs(application, parent) }
|
|
1068
|
+
));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function validateListSizeAppliedToList(
|
|
1073
|
+
application: Directive<SchemaElement<any, any>, ListSizeDirectiveArguments>,
|
|
1074
|
+
parent: FieldDefinition<CompositeType>,
|
|
1075
|
+
errorCollector: GraphQLError[],
|
|
1076
|
+
) {
|
|
1077
|
+
const { sizedFields = [] } = application.arguments();
|
|
1078
|
+
// @listSize must be applied to a list https://ibm.github.io/graphql-specs/cost-spec.html#sec-Valid-List-Size-Target
|
|
1079
|
+
if (!sizedFields.length && parent.type && !isListType(parent.type)) {
|
|
1080
|
+
errorCollector.push(ERRORS.LIST_SIZE_APPLIED_TO_NON_LIST.err(
|
|
1081
|
+
`"${parent.coordinate}" is not a list`,
|
|
1082
|
+
{ nodes: sourceASTs(application, parent) },
|
|
1083
|
+
));
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function validateAssumedSizeNotNegative(
|
|
1088
|
+
application: Directive<SchemaElement<any, any>, ListSizeDirectiveArguments>,
|
|
1089
|
+
parent: FieldDefinition<CompositeType>,
|
|
1090
|
+
errorCollector: GraphQLError[]
|
|
1091
|
+
) {
|
|
1092
|
+
const { assumedSize } = application.arguments();
|
|
1093
|
+
// Validate assumed size, but we differ from https://ibm.github.io/graphql-specs/cost-spec.html#sec-Valid-Assumed-Size.
|
|
1094
|
+
// Assumed size is used as a backup for slicing arguments in the event they are both specified.
|
|
1095
|
+
// The spec aims to rule out cases when the assumed size will never be used because there is always
|
|
1096
|
+
// a slicing argument. Two applications which are compliant with that validation rule can be merged
|
|
1097
|
+
// into an application which is not compliant, thus we need to handle this case gracefully at runtime regardless.
|
|
1098
|
+
// We omit this check to keep the validations to those that will otherwise cause runtime failures.
|
|
1099
|
+
//
|
|
1100
|
+
// With all that said, assumed size should not be negative.
|
|
1101
|
+
if (assumedSize !== undefined && assumedSize !== null && assumedSize < 0) {
|
|
1102
|
+
errorCollector.push(ERRORS.LIST_SIZE_INVALID_ASSUMED_SIZE.err(
|
|
1103
|
+
`Assumed size of "${parent.coordinate}" cannot be negative`,
|
|
1104
|
+
{ nodes: sourceASTs(application, parent) },
|
|
1105
|
+
));
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function isNonNullIntType(ty: Type): boolean {
|
|
1110
|
+
return isNonNullType(ty) && isIntType(ty.ofType)
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function validateSlicingArgumentsAreValidIntegers(
|
|
1114
|
+
application: Directive<SchemaElement<any, any>, ListSizeDirectiveArguments>,
|
|
1115
|
+
parent: FieldDefinition<CompositeType>,
|
|
1116
|
+
errorCollector: GraphQLError[]
|
|
1117
|
+
) {
|
|
1118
|
+
const { slicingArguments = [] } = application.arguments();
|
|
1119
|
+
// Validate slicingArguments https://ibm.github.io/graphql-specs/cost-spec.html#sec-Valid-Slicing-Arguments-Target
|
|
1120
|
+
for (const slicingArgumentName of slicingArguments) {
|
|
1121
|
+
const slicingArgument = parent.argument(slicingArgumentName);
|
|
1122
|
+
if (!slicingArgument?.type) {
|
|
1123
|
+
// Slicing arguments must be one of the field's arguments
|
|
1124
|
+
errorCollector.push(ERRORS.LIST_SIZE_INVALID_SLICING_ARGUMENT.err(
|
|
1125
|
+
`Slicing argument "${slicingArgumentName}" is not an argument of "${parent.coordinate}"`,
|
|
1126
|
+
{ nodes: sourceASTs(application, parent) }
|
|
1127
|
+
));
|
|
1128
|
+
} else if (!isIntType(slicingArgument.type) && !isNonNullIntType(slicingArgument.type)) {
|
|
1129
|
+
// Slicing arguments must be Int or Int!
|
|
1130
|
+
errorCollector.push(ERRORS.LIST_SIZE_INVALID_SLICING_ARGUMENT.err(
|
|
1131
|
+
`Slicing argument "${slicingArgument.coordinate}" must be Int or Int!`,
|
|
1132
|
+
{ nodes: sourceASTs(application, parent) }
|
|
1133
|
+
));
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function isNonNullListType(ty: Type): boolean {
|
|
1139
|
+
return isNonNullType(ty) && isListType(ty.ofType)
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function validateSizedFieldsAreValidLists(
|
|
1143
|
+
application: Directive<SchemaElement<any, any>, ListSizeDirectiveArguments>,
|
|
1144
|
+
parent: FieldDefinition<CompositeType>,
|
|
1145
|
+
errorCollector: GraphQLError[]
|
|
1146
|
+
) {
|
|
1147
|
+
const { sizedFields = [] } = application.arguments();
|
|
1148
|
+
// Validate sizedFields https://ibm.github.io/graphql-specs/cost-spec.html#sec-Valid-Sized-Fields-Target
|
|
1149
|
+
if (sizedFields.length) {
|
|
1150
|
+
if (!parent.type || !isCompositeType(parent.type)) {
|
|
1151
|
+
// The output type must have fields
|
|
1152
|
+
errorCollector.push(ERRORS.LIST_SIZE_INVALID_SIZED_FIELD.err(
|
|
1153
|
+
`Sized fields cannot be used because "${parent.type}" is not a composite type`,
|
|
1154
|
+
{ nodes: sourceASTs(application, parent)}
|
|
1155
|
+
));
|
|
1156
|
+
} else {
|
|
1157
|
+
for (const sizedFieldName of sizedFields) {
|
|
1158
|
+
const sizedField = parent.type.field(sizedFieldName);
|
|
1159
|
+
if (!sizedField) {
|
|
1160
|
+
// Sized fields must be present on the output type
|
|
1161
|
+
errorCollector.push(ERRORS.LIST_SIZE_INVALID_SIZED_FIELD.err(
|
|
1162
|
+
`Sized field "${sizedFieldName}" is not a field on type "${parent.type.coordinate}"`,
|
|
1163
|
+
{ nodes: sourceASTs(application, parent) }
|
|
1164
|
+
));
|
|
1165
|
+
} else if (!sizedField.type || !(isListType(sizedField.type) || isNonNullListType(sizedField.type))) {
|
|
1166
|
+
// Sized fields must be lists
|
|
1167
|
+
errorCollector.push(ERRORS.LIST_SIZE_APPLIED_TO_NON_LIST.err(
|
|
1168
|
+
`Sized field "${sizedField.coordinate}" is not a list`,
|
|
1169
|
+
{ nodes: sourceASTs(application, parent) },
|
|
1170
|
+
));
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1058
1177
|
export class FederationMetadata {
|
|
1059
1178
|
private _externalTester?: ExternalTester;
|
|
1060
1179
|
private _sharingPredicate?: (field: FieldDefinition<CompositeType>) => boolean;
|
|
@@ -1689,7 +1808,7 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
1689
1808
|
const keyApplications = objectType.appliedDirectivesOf(keyDirective);
|
|
1690
1809
|
if (!keyApplications.some(app => app.arguments().resolvable || app.arguments().resolvable === undefined)) {
|
|
1691
1810
|
errorCollector.push(ERRORS.CONTEXT_NO_RESOLVABLE_KEY.err(
|
|
1692
|
-
`Object "${objectType.coordinate}" has no resolvable key but has
|
|
1811
|
+
`Object "${objectType.coordinate}" has no resolvable key but has a field with a contextual argument.`,
|
|
1693
1812
|
{ nodes: sourceASTs(objectType) }
|
|
1694
1813
|
));
|
|
1695
1814
|
}
|
|
@@ -1736,6 +1855,26 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
1736
1855
|
}
|
|
1737
1856
|
}
|
|
1738
1857
|
|
|
1858
|
+
const costFeature = schema.coreFeatures?.getByIdentity(costIdentity);
|
|
1859
|
+
const costSpec = costFeature && COST_VERSIONS.find(costFeature.url.version);
|
|
1860
|
+
const costDirective = costSpec?.costDirective(schema);
|
|
1861
|
+
const listSizeDirective = costSpec?.listSizeDirective(schema);
|
|
1862
|
+
|
|
1863
|
+
// Validate @cost
|
|
1864
|
+
for (const application of costDirective?.applications() ?? []) {
|
|
1865
|
+
validateCostNotAppliedToInterface(application, errorCollector);
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// Validate @listSize
|
|
1869
|
+
for (const application of listSizeDirective?.applications() ?? []) {
|
|
1870
|
+
const parent = application.parent;
|
|
1871
|
+
assert(parent instanceof FieldDefinition, "@listSize can only be applied to FIELD_DEFINITION");
|
|
1872
|
+
validateListSizeAppliedToList(application, parent, errorCollector);
|
|
1873
|
+
validateAssumedSizeNotNegative(application, parent, errorCollector);
|
|
1874
|
+
validateSlicingArgumentsAreValidIntegers(application, parent, errorCollector);
|
|
1875
|
+
validateSizedFieldsAreValidLists(application, parent, errorCollector);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1739
1878
|
return errorCollector;
|
|
1740
1879
|
}
|
|
1741
1880
|
|
|
@@ -2141,12 +2280,14 @@ export function parseFieldSetArgument({
|
|
|
2141
2280
|
fieldAccessor,
|
|
2142
2281
|
validate,
|
|
2143
2282
|
decorateValidationErrors = true,
|
|
2283
|
+
normalize = false,
|
|
2144
2284
|
}: {
|
|
2145
2285
|
parentType: CompositeType,
|
|
2146
2286
|
directive: Directive<SchemaElement<any, any>, {fields: any}>,
|
|
2147
2287
|
fieldAccessor?: (type: CompositeType, fieldName: string) => FieldDefinition<any> | undefined,
|
|
2148
2288
|
validate?: boolean,
|
|
2149
2289
|
decorateValidationErrors?: boolean,
|
|
2290
|
+
normalize?: boolean,
|
|
2150
2291
|
}): SelectionSet {
|
|
2151
2292
|
try {
|
|
2152
2293
|
const selectionSet = parseSelectionSet({
|
|
@@ -2163,7 +2304,9 @@ export function parseFieldSetArgument({
|
|
|
2163
2304
|
}
|
|
2164
2305
|
});
|
|
2165
2306
|
}
|
|
2166
|
-
return
|
|
2307
|
+
return normalize
|
|
2308
|
+
? selectionSet.normalize({ parentType, recursive: true })
|
|
2309
|
+
: selectionSet;
|
|
2167
2310
|
} catch (e) {
|
|
2168
2311
|
if (!(e instanceof GraphQLError) || !decorateValidationErrors) {
|
|
2169
2312
|
throw e;
|
package/src/operations.ts
CHANGED
|
@@ -69,7 +69,7 @@ function haveSameDirectives<TElement extends OperationElement>(op1: TElement, op
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
abstract class AbstractOperationElement<T extends AbstractOperationElement<T>> extends DirectiveTargetElement<T> {
|
|
72
|
-
private
|
|
72
|
+
private attachments?: Map<string, string>;
|
|
73
73
|
|
|
74
74
|
constructor(
|
|
75
75
|
schema: Schema,
|
|
@@ -97,21 +97,21 @@ abstract class AbstractOperationElement<T extends AbstractOperationElement<T>> e
|
|
|
97
97
|
|
|
98
98
|
protected abstract collectVariablesInElement(collector: VariableCollector): void;
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
if (!this.
|
|
102
|
-
this.
|
|
100
|
+
addAttachment(key: string, value: string) {
|
|
101
|
+
if (!this.attachments) {
|
|
102
|
+
this.attachments = new Map();
|
|
103
103
|
}
|
|
104
|
-
this.
|
|
104
|
+
this.attachments.set(key, value);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
return this.
|
|
107
|
+
getAttachment(key: string): string | undefined {
|
|
108
|
+
return this.attachments?.get(key);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
protected
|
|
112
|
-
if (this.
|
|
113
|
-
for (const [k, v] of this.
|
|
114
|
-
elt.
|
|
111
|
+
protected copyAttachmentsTo(elt: AbstractOperationElement<any>) {
|
|
112
|
+
if (this.attachments) {
|
|
113
|
+
for (const [k, v] of this.attachments.entries()) {
|
|
114
|
+
elt.addAttachment(k, v);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -170,6 +170,17 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
|
|
|
170
170
|
baseType(): NamedType {
|
|
171
171
|
return baseType(this.definition.type!);
|
|
172
172
|
}
|
|
173
|
+
|
|
174
|
+
copy(): Field<TArgs> {
|
|
175
|
+
const newField = new Field<TArgs>(
|
|
176
|
+
this.definition,
|
|
177
|
+
this.args,
|
|
178
|
+
this.appliedDirectives,
|
|
179
|
+
this.alias,
|
|
180
|
+
);
|
|
181
|
+
this.copyAttachmentsTo(newField);
|
|
182
|
+
return newField;
|
|
183
|
+
}
|
|
173
184
|
|
|
174
185
|
withUpdatedArguments(newArgs: TArgs): Field<TArgs> {
|
|
175
186
|
const newField = new Field<TArgs>(
|
|
@@ -178,7 +189,7 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
|
|
|
178
189
|
this.appliedDirectives,
|
|
179
190
|
this.alias,
|
|
180
191
|
);
|
|
181
|
-
this.
|
|
192
|
+
this.copyAttachmentsTo(newField);
|
|
182
193
|
return newField;
|
|
183
194
|
}
|
|
184
195
|
|
|
@@ -189,7 +200,7 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
|
|
|
189
200
|
this.appliedDirectives,
|
|
190
201
|
this.alias,
|
|
191
202
|
);
|
|
192
|
-
this.
|
|
203
|
+
this.copyAttachmentsTo(newField);
|
|
193
204
|
return newField;
|
|
194
205
|
}
|
|
195
206
|
|
|
@@ -200,7 +211,7 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
|
|
|
200
211
|
this.appliedDirectives,
|
|
201
212
|
newAlias,
|
|
202
213
|
);
|
|
203
|
-
this.
|
|
214
|
+
this.copyAttachmentsTo(newField);
|
|
204
215
|
return newField;
|
|
205
216
|
}
|
|
206
217
|
|
|
@@ -211,7 +222,7 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
|
|
|
211
222
|
newDirectives,
|
|
212
223
|
this.alias,
|
|
213
224
|
);
|
|
214
|
-
this.
|
|
225
|
+
this.copyAttachmentsTo(newField);
|
|
215
226
|
return newField;
|
|
216
227
|
}
|
|
217
228
|
|
|
@@ -505,13 +516,13 @@ export class FragmentElement extends AbstractOperationElement<FragmentElement> {
|
|
|
505
516
|
// schema (typically, the supergraph) than `this.sourceType` (typically, a subgraph), then the new condition uses the
|
|
506
517
|
// definition of the proper schema (the supergraph in such cases, instead of the subgraph).
|
|
507
518
|
const newFragment = new FragmentElement(newSourceType, newCondition?.name, this.appliedDirectives);
|
|
508
|
-
this.
|
|
519
|
+
this.copyAttachmentsTo(newFragment);
|
|
509
520
|
return newFragment;
|
|
510
521
|
}
|
|
511
522
|
|
|
512
523
|
withUpdatedDirectives(newDirectives: Directive<OperationElement>[]): FragmentElement {
|
|
513
524
|
const newFragment = new FragmentElement(this.sourceType, this.typeCondition, newDirectives);
|
|
514
|
-
this.
|
|
525
|
+
this.copyAttachmentsTo(newFragment);
|
|
515
526
|
return newFragment;
|
|
516
527
|
}
|
|
517
528
|
|
|
@@ -590,7 +601,7 @@ export class FragmentElement extends AbstractOperationElement<FragmentElement> {
|
|
|
590
601
|
}
|
|
591
602
|
|
|
592
603
|
const updated = new FragmentElement(this.sourceType, this.typeCondition, updatedDirectives);
|
|
593
|
-
this.
|
|
604
|
+
this.copyAttachmentsTo(updated);
|
|
594
605
|
return updated;
|
|
595
606
|
}
|
|
596
607
|
|
|
@@ -655,7 +666,7 @@ export class FragmentElement extends AbstractOperationElement<FragmentElement> {
|
|
|
655
666
|
.concat(new Directive<FragmentElement>(deferDirective.name, newDeferArgs));
|
|
656
667
|
|
|
657
668
|
const updated = new FragmentElement(this.sourceType, this.typeCondition, updatedDirectives);
|
|
658
|
-
this.
|
|
669
|
+
this.copyAttachmentsTo(updated);
|
|
659
670
|
return updated;
|
|
660
671
|
}
|
|
661
672
|
|
|
@@ -1182,6 +1193,11 @@ export class NamedFragmentDefinition extends DirectiveTargetElement<NamedFragmen
|
|
|
1182
1193
|
}
|
|
1183
1194
|
}
|
|
1184
1195
|
|
|
1196
|
+
collectVariables(collector: VariableCollector) {
|
|
1197
|
+
this.selectionSet.collectVariables(collector);
|
|
1198
|
+
this.collectVariablesInAppliedDirectives(collector);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1185
1201
|
toFragmentDefinitionNode() : FragmentDefinitionNode {
|
|
1186
1202
|
return {
|
|
1187
1203
|
kind: Kind.FRAGMENT_DEFINITION,
|
|
@@ -1647,10 +1663,10 @@ export class SelectionSet {
|
|
|
1647
1663
|
}
|
|
1648
1664
|
|
|
1649
1665
|
return new FragmentSpreadSelection(this.parentType, namedFragments, fragmentDefinition, []);
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
if (selection.selectionSet) {
|
|
1669
|
+
selection = selection.withUpdatedSelectionSet(selection.selectionSet.minimizeSelectionSet(namedFragments, seenSelections)[0]);
|
|
1654
1670
|
}
|
|
1655
1671
|
return selection;
|
|
1656
1672
|
});
|
|
@@ -2103,10 +2119,10 @@ export class SelectionSet {
|
|
|
2103
2119
|
// By default, we will print the selection the order in which things were added to it.
|
|
2104
2120
|
// If __typename is selected however, we put it first. It's a detail but as __typename is a bit special it looks better,
|
|
2105
2121
|
// and it happens to mimic prior behavior on the query plan side so it saves us from changing tests for no good reasons.
|
|
2106
|
-
const
|
|
2107
|
-
const typenameSelection = this._selections.find((s) =>
|
|
2122
|
+
const isPlainTypenameSelection = (s: Selection) => s.kind === 'FieldSelection' && s.isPlainTypenameField();
|
|
2123
|
+
const typenameSelection = this._selections.find((s) => isPlainTypenameSelection(s));
|
|
2108
2124
|
if (typenameSelection) {
|
|
2109
|
-
return [typenameSelection].concat(this.selections().filter(s => !
|
|
2125
|
+
return [typenameSelection].concat(this.selections().filter(s => !isPlainTypenameSelection(s)));
|
|
2110
2126
|
} else {
|
|
2111
2127
|
return this._selections;
|
|
2112
2128
|
}
|
|
@@ -3042,6 +3058,19 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
3042
3058
|
return this.element.definition.name === typenameFieldName;
|
|
3043
3059
|
}
|
|
3044
3060
|
|
|
3061
|
+
// Is this a plain simple __typename without any directive or alias?
|
|
3062
|
+
isPlainTypenameField(): boolean {
|
|
3063
|
+
return this.element.definition.name === typenameFieldName
|
|
3064
|
+
&& this.element.appliedDirectives.length == 0
|
|
3065
|
+
&& !this.element.alias;
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
withAttachment(key: string, value: string): FieldSelection {
|
|
3069
|
+
const updatedField = this.element.copy();
|
|
3070
|
+
updatedField.addAttachment(key, value);
|
|
3071
|
+
return this.withUpdatedElement(updatedField);
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3045
3074
|
withUpdatedComponents(field: Field<any>, selectionSet: SelectionSet | undefined): FieldSelection {
|
|
3046
3075
|
if (this.element === field && this.selectionSet === selectionSet) {
|
|
3047
3076
|
return this;
|