@apollo/federation-internals 2.9.2 → 2.9.4
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/argumentCompositionStrategies.d.ts +6 -0
- package/dist/argumentCompositionStrategies.d.ts.map +1 -1
- package/dist/argumentCompositionStrategies.js +77 -0
- package/dist/argumentCompositionStrategies.js.map +1 -1
- package/dist/error.d.ts +3 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +6 -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 +37 -3
- package/dist/federation.js.map +1 -1
- package/dist/operations.d.ts +2 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +14 -7
- package/dist/operations.js.map +1 -1
- package/dist/specs/authenticatedSpec.d.ts +2 -0
- package/dist/specs/authenticatedSpec.d.ts.map +1 -1
- package/dist/specs/authenticatedSpec.js +3 -0
- package/dist/specs/authenticatedSpec.js.map +1 -1
- package/dist/specs/policySpec.d.ts +4 -0
- package/dist/specs/policySpec.d.ts.map +1 -1
- package/dist/specs/policySpec.js +4 -1
- package/dist/specs/policySpec.js.map +1 -1
- package/dist/specs/requiresScopesSpec.d.ts +4 -0
- package/dist/specs/requiresScopesSpec.d.ts.map +1 -1
- package/dist/specs/requiresScopesSpec.js +4 -1
- package/dist/specs/requiresScopesSpec.js.map +1 -1
- package/package.json +1 -1
- package/src/argumentCompositionStrategies.ts +114 -2
- package/src/error.ts +21 -0
- package/src/federation.ts +46 -3
- package/src/operations.ts +19 -7
- package/src/specs/authenticatedSpec.ts +5 -0
- package/src/specs/policySpec.ts +6 -2
- package/src/specs/requiresScopesSpec.ts +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requiresScopesSpec.js","sourceRoot":"","sources":["../../src/specs/requiresScopesSpec.ts"],"names":[],"mappings":";;;AAAA,qCAA4C;AAC5C,yCAMoB;AACpB,
|
|
1
|
+
{"version":3,"file":"requiresScopesSpec.js","sourceRoot":"","sources":["../../src/specs/requiresScopesSpec.ts"],"names":[],"mappings":";;;AAAA,qCAA4C;AAC5C,yCAMoB;AACpB,gDAAkF;AAClF,oFAA+G;AAC/G,4DAA4D;AAC5D,oFAAmF;AACnF,oCAAkC;AAElC,IAAY,sBAEX;AAFD,WAAY,sBAAsB;IAChC,yCAAe,CAAA;AACjB,CAAC,EAFW,sBAAsB,sCAAtB,sBAAsB,QAEjC;AAED,MAAa,4BAA6B,SAAQ,4BAAiB;IAKjE,YAAY,OAAuB;QACjC,KAAK,CACH,IAAI,qBAAU,CACZ,4BAA4B,CAAC,QAAQ,EACrC,4BAA4B,CAAC,aAAa,EAC1C,OAAO,CACR,CACF,CAAC;QAEF,IAAI,CAAC,YAAY,CAAC,IAAA,6DAA6B,EAAC,EAAE,IAAI,EAAE,sBAAsB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAEzF,IAAI,CAAC,iBAAiB,CAAC,IAAA,4DAA4B,EAAC;YAClD,IAAI,EAAE,4BAA4B,CAAC,aAAa;YAChD,IAAI,EAAE,CAAC;oBACL,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;wBACxB,IAAA,cAAM,EAAC,OAAO,EAAE,2DAA2D,CAAC,CAAC;wBAC7E,MAAM,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;wBACzE,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;wBACzC,IAAA,cAAM,EAAC,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,SAAS,iBAAiB,CAAC,CAAC;wBACjE,OAAO,IAAI,yBAAW,CAAC,IAAI,sBAAQ,CAAC,IAAI,yBAAW,CAAC,IAAI,sBAAQ,CAAC,IAAI,yBAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAClG,CAAC;oBACD,mBAAmB,EAAE,+DAA+B,CAAC,eAAe;iBACrE,CAAC;YACF,SAAS,EAAE;gBACT,2BAAiB,CAAC,gBAAgB;gBAClC,2BAAiB,CAAC,MAAM;gBACxB,2BAAiB,CAAC,SAAS;gBAC3B,2BAAiB,CAAC,MAAM;gBACxB,2BAAiB,CAAC,IAAI;aACvB;YACD,QAAQ,EAAE,IAAI;YACd,uBAAuB,EAAE,GAAG,EAAE,CAAC,gCAAwB,CAAC,MAAM,EAAE;SACjE,CAAC,CAAC,CAAC;IACN,CAAC;IAED,uBAAuB,CAAC,MAAc;QACpC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,4BAA4B,CAAC,aAAa,CAAC,CAAC;IAC5E,CAAC;IAED,IAAI,kBAAkB;QACpB,OAAO,UAAU,CAAC;IACpB,CAAC;;AA/CH,oEAgDC;AA/CwB,0CAAa,GAAG,gBAAgB,CAAC;AACjC,qCAAQ,GAC7B,4BAA4B,4BAA4B,CAAC,aAAa,EAAE,CAAC;AA+ChE,QAAA,wBAAwB,GACnC,IAAI,6BAAkB,CACpB,4BAA4B,CAAC,QAAQ,CACtC,CAAC,GAAG,CAAC,IAAI,4BAA4B,CAAC,IAAI,yBAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpE,IAAA,wCAAoB,EAAC,gCAAwB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {InputType, NonNullType, Schema, isListType, isNonNullType} from "./definitions"
|
|
2
2
|
import { sameType } from "./types";
|
|
3
3
|
import { valueEquals } from "./values";
|
|
4
4
|
|
|
@@ -19,6 +19,14 @@ function supportFixedTypes(types: (schema: Schema) => InputType[]): TypeSupportV
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function supportAnyNonNullNestedArray(): TypeSupportValidator {
|
|
23
|
+
return (_, type) =>
|
|
24
|
+
isNonNullType(type) && isListType(type.ofType)
|
|
25
|
+
&& isNonNullType(type.ofType.ofType) && isListType(type.ofType.ofType.ofType)
|
|
26
|
+
? { valid: true }
|
|
27
|
+
: { valid: false, supportedMsg: 'non nullable nested list types of any type' }
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
function supportAnyNonNullArray(): TypeSupportValidator {
|
|
23
31
|
return (_, type) => isNonNullType(type) && isListType(type.ofType)
|
|
24
32
|
? { valid: true }
|
|
@@ -54,6 +62,104 @@ function unionValues(values: any[]): any {
|
|
|
54
62
|
}, []);
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Performs conjunction of 2d arrays that represent conditions in Disjunctive Normal Form.
|
|
67
|
+
*
|
|
68
|
+
* * Each inner array is interpreted as the conjunction of the conditions in the array.
|
|
69
|
+
* * The top-level array is interpreted as the disjunction of the inner arrays
|
|
70
|
+
*
|
|
71
|
+
* Algorithm
|
|
72
|
+
* * filter out duplicate entries to limit the amount of necessary computations
|
|
73
|
+
* * calculate cartesian product of the arrays to find all possible combinations
|
|
74
|
+
* * simplify combinations by dropping duplicate conditions (i.e. p ^ p = p, p ^ q = q ^ p)
|
|
75
|
+
* * eliminate entries that are subsumed by others (i.e. (p ^ q) subsumes (p ^ q ^ r))
|
|
76
|
+
*/
|
|
77
|
+
function dnfConjunction<T>(values: T[][][]): T[][] {
|
|
78
|
+
// should never be the case
|
|
79
|
+
if (values.length == 0) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// we first filter out duplicate values from candidates
|
|
84
|
+
// this avoids exponential computation of exactly the same conditions
|
|
85
|
+
const filtered = filterNestedArrayDuplicates(values);
|
|
86
|
+
|
|
87
|
+
// initialize with first entry
|
|
88
|
+
let result: T[][] = filtered[0];
|
|
89
|
+
// perform cartesian product to find all possible entries
|
|
90
|
+
for (let i = 1; i < filtered.length; i++) {
|
|
91
|
+
const current = filtered[i];
|
|
92
|
+
const accumulator: T[][] = [];
|
|
93
|
+
const seen = new Set<string>;
|
|
94
|
+
|
|
95
|
+
for (const accElement of result) {
|
|
96
|
+
for (const currentElement of current) {
|
|
97
|
+
// filter out elements that are already present in accElement
|
|
98
|
+
const filteredElement = currentElement.filter((e) => !accElement.includes(e));
|
|
99
|
+
const candidate = [...accElement, ...filteredElement].sort();
|
|
100
|
+
const key = JSON.stringify(candidate);
|
|
101
|
+
// only add entries which has not been seen yet
|
|
102
|
+
if (!seen.has(key)) {
|
|
103
|
+
seen.add(key);
|
|
104
|
+
accumulator.push(candidate);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Now we need to deduplicate the results. Given that
|
|
109
|
+
// - outer array implies OR requirements
|
|
110
|
+
// - inner array implies AND requirements
|
|
111
|
+
// We can filter out any inner arrays that fully contain other inner arrays, i.e.
|
|
112
|
+
// A OR B OR (A AND B) OR (A AND B AND C) => A OR B
|
|
113
|
+
result = deduplicateSubsumedValues(accumulator);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function filterNestedArrayDuplicates<T>(values: T[][][]): T[][][] {
|
|
119
|
+
const filtered: T[][][] = [];
|
|
120
|
+
const seen = new Set<string>;
|
|
121
|
+
values.forEach((value) => {
|
|
122
|
+
value.sort();
|
|
123
|
+
const key = JSON.stringify(value);
|
|
124
|
+
if (!seen.has(key)) {
|
|
125
|
+
seen.add(key);
|
|
126
|
+
filtered.push(value);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return filtered;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function deduplicateSubsumedValues<T>(values: T[][]): T[][] {
|
|
133
|
+
const result: T[][] = [];
|
|
134
|
+
// we first sort by length as the longer ones might be dropped
|
|
135
|
+
values.sort((first, second) => {
|
|
136
|
+
if (first.length < second.length) {
|
|
137
|
+
return -1;
|
|
138
|
+
} else if (first.length > second.length) {
|
|
139
|
+
return 1;
|
|
140
|
+
} else {
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
for (const candidate of values) {
|
|
146
|
+
const entry = new Set(candidate);
|
|
147
|
+
let redundant = false;
|
|
148
|
+
for (const r of result) {
|
|
149
|
+
if (r.every(e => entry.has(e))) {
|
|
150
|
+
// if `r` is a subset of a `candidate` then it means `candidate` is redundant
|
|
151
|
+
redundant = true;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!redundant) {
|
|
157
|
+
result.push(candidate);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
57
163
|
export const ARGUMENT_COMPOSITION_STRATEGIES = {
|
|
58
164
|
MAX: {
|
|
59
165
|
name: 'MAX',
|
|
@@ -95,7 +201,8 @@ export const ARGUMENT_COMPOSITION_STRATEGIES = {
|
|
|
95
201
|
schema.booleanType(),
|
|
96
202
|
new NonNullType(schema.booleanType())
|
|
97
203
|
]),
|
|
98
|
-
mergeValues:
|
|
204
|
+
mergeValues:
|
|
205
|
+
mergeNullableValues(
|
|
99
206
|
(values: boolean[]) => values.every((v) => v)
|
|
100
207
|
),
|
|
101
208
|
},
|
|
@@ -113,5 +220,10 @@ export const ARGUMENT_COMPOSITION_STRATEGIES = {
|
|
|
113
220
|
name: 'NULLABLE_UNION',
|
|
114
221
|
isTypeSupported: supportAnyArray(),
|
|
115
222
|
mergeValues: mergeNullableValues(unionValues),
|
|
223
|
+
},
|
|
224
|
+
DNF_CONJUNCTION: {
|
|
225
|
+
name: 'DNF_CONJUNCTION',
|
|
226
|
+
isTypeSupported: supportAnyNonNullNestedArray(),
|
|
227
|
+
mergeValues: dnfConjunction
|
|
116
228
|
}
|
|
117
229
|
}
|
package/src/error.ts
CHANGED
|
@@ -740,6 +740,24 @@ const LIST_SIZE_INVALID_SIZED_FIELD = makeCodeDefinition(
|
|
|
740
740
|
{ addedIn: '2.9.2' },
|
|
741
741
|
);
|
|
742
742
|
|
|
743
|
+
const MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED = makeCodeDefinition(
|
|
744
|
+
'MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED',
|
|
745
|
+
'The maximum number of validation subgraph paths has been exceeded.',
|
|
746
|
+
{ addedIn: '2.8.0' },
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
const AUTHENTICATION_APPLIED_ON_INTERFACE = makeCodeDefinition(
|
|
750
|
+
'AUTHENTICATION_APPLIED_ON_INTERFACE',
|
|
751
|
+
'The @authenticated, @requiresScopes and @policy directive cannot be applied on interface, interface object or their fields.',
|
|
752
|
+
{ addedIn: '2.9.4' },
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
const MISSING_TRANSITIVE_AUTH_REQUIREMENTS = makeCodeDefinition(
|
|
756
|
+
'MISSING_TRANSITIVE_AUTH_REQUIREMENTS',
|
|
757
|
+
'Field missing transitive @authenticated, @requiresScopes and/or @policy auth requirements needed to access dependent data.',
|
|
758
|
+
{ addedIn: '2.9.4' },
|
|
759
|
+
)
|
|
760
|
+
|
|
743
761
|
export const ERROR_CATEGORIES = {
|
|
744
762
|
DIRECTIVE_FIELDS_MISSING_EXTERNAL,
|
|
745
763
|
DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
|
|
@@ -860,6 +878,9 @@ export const ERRORS = {
|
|
|
860
878
|
LIST_SIZE_INVALID_ASSUMED_SIZE,
|
|
861
879
|
LIST_SIZE_INVALID_SIZED_FIELD,
|
|
862
880
|
LIST_SIZE_INVALID_SLICING_ARGUMENT,
|
|
881
|
+
MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED,
|
|
882
|
+
AUTHENTICATION_APPLIED_ON_INTERFACE,
|
|
883
|
+
MISSING_TRANSITIVE_AUTH_REQUIREMENTS,
|
|
863
884
|
};
|
|
864
885
|
|
|
865
886
|
const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
|
package/src/federation.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
isWrapperType,
|
|
38
38
|
possibleRuntimeTypes,
|
|
39
39
|
isIntType,
|
|
40
|
-
Type,
|
|
40
|
+
Type, isFieldDefinition,
|
|
41
41
|
} from "./definitions";
|
|
42
42
|
import { assert, MultiMap, printHumanReadableList, OrderedMap, mapValues, assertUnreachable } from "./utils";
|
|
43
43
|
import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
@@ -1808,7 +1808,7 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
1808
1808
|
const keyApplications = objectType.appliedDirectivesOf(keyDirective);
|
|
1809
1809
|
if (!keyApplications.some(app => app.arguments().resolvable || app.arguments().resolvable === undefined)) {
|
|
1810
1810
|
errorCollector.push(ERRORS.CONTEXT_NO_RESOLVABLE_KEY.err(
|
|
1811
|
-
`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.`,
|
|
1812
1812
|
{ nodes: sourceASTs(objectType) }
|
|
1813
1813
|
));
|
|
1814
1814
|
}
|
|
@@ -1875,6 +1875,9 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
1875
1875
|
validateSizedFieldsAreValidLists(application, parent, errorCollector);
|
|
1876
1876
|
}
|
|
1877
1877
|
|
|
1878
|
+
// Validate @authenticated, @requireScopes and @policy
|
|
1879
|
+
validateNoAuthenticationOnInterfaces(metadata, errorCollector);
|
|
1880
|
+
|
|
1878
1881
|
return errorCollector;
|
|
1879
1882
|
}
|
|
1880
1883
|
|
|
@@ -2280,12 +2283,14 @@ export function parseFieldSetArgument({
|
|
|
2280
2283
|
fieldAccessor,
|
|
2281
2284
|
validate,
|
|
2282
2285
|
decorateValidationErrors = true,
|
|
2286
|
+
normalize = false,
|
|
2283
2287
|
}: {
|
|
2284
2288
|
parentType: CompositeType,
|
|
2285
2289
|
directive: Directive<SchemaElement<any, any>, {fields: any}>,
|
|
2286
2290
|
fieldAccessor?: (type: CompositeType, fieldName: string) => FieldDefinition<any> | undefined,
|
|
2287
2291
|
validate?: boolean,
|
|
2288
2292
|
decorateValidationErrors?: boolean,
|
|
2293
|
+
normalize?: boolean,
|
|
2289
2294
|
}): SelectionSet {
|
|
2290
2295
|
try {
|
|
2291
2296
|
const selectionSet = parseSelectionSet({
|
|
@@ -2302,7 +2307,9 @@ export function parseFieldSetArgument({
|
|
|
2302
2307
|
}
|
|
2303
2308
|
});
|
|
2304
2309
|
}
|
|
2305
|
-
return
|
|
2310
|
+
return normalize
|
|
2311
|
+
? selectionSet.normalize({ parentType, recursive: true })
|
|
2312
|
+
: selectionSet;
|
|
2306
2313
|
} catch (e) {
|
|
2307
2314
|
if (!(e instanceof GraphQLError) || !decorateValidationErrors) {
|
|
2308
2315
|
throw e;
|
|
@@ -2922,3 +2929,39 @@ function withoutNonExternalLeafFields(selectionSet: SelectionSet): SelectionSet
|
|
|
2922
2929
|
return undefined;
|
|
2923
2930
|
});
|
|
2924
2931
|
}
|
|
2932
|
+
|
|
2933
|
+
function validateNoAuthenticationOnInterfaces(metadata: FederationMetadata, errorCollector: GraphQLError[]) {
|
|
2934
|
+
const authenticatedDirective = metadata.authenticatedDirective();
|
|
2935
|
+
const requiresScopesDirective = metadata.requiresScopesDirective();
|
|
2936
|
+
const policyDirective = metadata.policyDirective();
|
|
2937
|
+
[authenticatedDirective, requiresScopesDirective, policyDirective].forEach((directive) => {
|
|
2938
|
+
for (const application of directive.applications()) {
|
|
2939
|
+
const element = application.parent;
|
|
2940
|
+
function isAppliedOnInterface(type: Type) {
|
|
2941
|
+
return isInterfaceType(type) || isInterfaceObjectType(baseType(type));
|
|
2942
|
+
}
|
|
2943
|
+
function isAppliedOnInterfaceField(elem: SchemaElement<any, any>) {
|
|
2944
|
+
return isFieldDefinition(elem) && isAppliedOnInterface(elem.parent);
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
if (isAppliedOnInterface(element) || isAppliedOnInterfaceField(element)) {
|
|
2948
|
+
let kind = '';
|
|
2949
|
+
switch (element.kind) {
|
|
2950
|
+
case 'FieldDefinition':
|
|
2951
|
+
kind = 'field';
|
|
2952
|
+
break;
|
|
2953
|
+
case 'InterfaceType':
|
|
2954
|
+
kind = 'interface';
|
|
2955
|
+
break;
|
|
2956
|
+
case 'ObjectType':
|
|
2957
|
+
kind = 'interface object';
|
|
2958
|
+
break;
|
|
2959
|
+
}
|
|
2960
|
+
errorCollector.push(ERRORS.AUTHENTICATION_APPLIED_ON_INTERFACE.err(
|
|
2961
|
+
`Invalid use of @${directive.name} on ${kind} "${element.coordinate}": @${directive.name} cannot be applied on interfaces, interface objects or their fields`,
|
|
2962
|
+
{nodes: sourceASTs(application, element.parent)},
|
|
2963
|
+
));
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
});
|
|
2967
|
+
}
|
package/src/operations.ts
CHANGED
|
@@ -1193,6 +1193,11 @@ export class NamedFragmentDefinition extends DirectiveTargetElement<NamedFragmen
|
|
|
1193
1193
|
}
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
collectVariables(collector: VariableCollector) {
|
|
1197
|
+
this.selectionSet.collectVariables(collector);
|
|
1198
|
+
this.collectVariablesInAppliedDirectives(collector);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1196
1201
|
toFragmentDefinitionNode() : FragmentDefinitionNode {
|
|
1197
1202
|
return {
|
|
1198
1203
|
kind: Kind.FRAGMENT_DEFINITION,
|
|
@@ -1658,10 +1663,10 @@ export class SelectionSet {
|
|
|
1658
1663
|
}
|
|
1659
1664
|
|
|
1660
1665
|
return new FragmentSpreadSelection(this.parentType, namedFragments, fragmentDefinition, []);
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
if (selection.selectionSet) {
|
|
1669
|
+
selection = selection.withUpdatedSelectionSet(selection.selectionSet.minimizeSelectionSet(namedFragments, seenSelections)[0]);
|
|
1665
1670
|
}
|
|
1666
1671
|
return selection;
|
|
1667
1672
|
});
|
|
@@ -2114,10 +2119,10 @@ export class SelectionSet {
|
|
|
2114
2119
|
// By default, we will print the selection the order in which things were added to it.
|
|
2115
2120
|
// If __typename is selected however, we put it first. It's a detail but as __typename is a bit special it looks better,
|
|
2116
2121
|
// and it happens to mimic prior behavior on the query plan side so it saves us from changing tests for no good reasons.
|
|
2117
|
-
const
|
|
2118
|
-
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));
|
|
2119
2124
|
if (typenameSelection) {
|
|
2120
|
-
return [typenameSelection].concat(this.selections().filter(s => !
|
|
2125
|
+
return [typenameSelection].concat(this.selections().filter(s => !isPlainTypenameSelection(s)));
|
|
2121
2126
|
} else {
|
|
2122
2127
|
return this._selections;
|
|
2123
2128
|
}
|
|
@@ -3053,6 +3058,13 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
3053
3058
|
return this.element.definition.name === typenameFieldName;
|
|
3054
3059
|
}
|
|
3055
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
|
+
|
|
3056
3068
|
withAttachment(key: string, value: string): FieldSelection {
|
|
3057
3069
|
const updatedField = this.element.copy();
|
|
3058
3070
|
updatedField.addAttachment(key, value);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "./coreSpec";
|
|
9
9
|
import { createDirectiveSpecification } from "../directiveAndTypeSpecification";
|
|
10
10
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
11
|
+
import {DirectiveDefinition, Schema} from "../definitions";
|
|
11
12
|
|
|
12
13
|
export class AuthenticatedSpecDefinition extends FeatureDefinition {
|
|
13
14
|
public static readonly directiveName = "authenticated";
|
|
@@ -37,6 +38,10 @@ export class AuthenticatedSpecDefinition extends FeatureDefinition {
|
|
|
37
38
|
}));
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
authenticatedDirective(schema: Schema): DirectiveDefinition | undefined {
|
|
42
|
+
return this.directive(schema, AuthenticatedSpecDefinition.directiveName);
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
get defaultCorePurpose(): CorePurpose {
|
|
41
46
|
return 'SECURITY';
|
|
42
47
|
}
|
package/src/specs/policySpec.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FeatureUrl,
|
|
7
7
|
FeatureVersion,
|
|
8
8
|
} from "./coreSpec";
|
|
9
|
-
import { ListType, NonNullType } from "../definitions";
|
|
9
|
+
import {DirectiveDefinition, ListType, NonNullType, Schema} from "../definitions";
|
|
10
10
|
import { createDirectiveSpecification, createScalarTypeSpecification } from "../directiveAndTypeSpecification";
|
|
11
11
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
12
12
|
import { ARGUMENT_COMPOSITION_STRATEGIES } from "../argumentCompositionStrategies";
|
|
@@ -42,7 +42,7 @@ export class PolicySpecDefinition extends FeatureDefinition {
|
|
|
42
42
|
assert(PolicyType, () => `Expected "${policyName}" to be defined`);
|
|
43
43
|
return new NonNullType(new ListType(new NonNullType(new ListType(new NonNullType(PolicyType)))));
|
|
44
44
|
},
|
|
45
|
-
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.
|
|
45
|
+
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.DNF_CONJUNCTION,
|
|
46
46
|
}],
|
|
47
47
|
locations: [
|
|
48
48
|
DirectiveLocation.FIELD_DEFINITION,
|
|
@@ -56,6 +56,10 @@ export class PolicySpecDefinition extends FeatureDefinition {
|
|
|
56
56
|
}));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
policyDirective(schema: Schema): DirectiveDefinition<{policies: string[][]}> | undefined {
|
|
60
|
+
return this.directive(schema, PolicySpecDefinition.directiveName);
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
get defaultCorePurpose(): CorePurpose {
|
|
60
64
|
return 'SECURITY';
|
|
61
65
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FeatureUrl,
|
|
7
7
|
FeatureVersion,
|
|
8
8
|
} from "./coreSpec";
|
|
9
|
-
import { ListType, NonNullType } from "../definitions";
|
|
9
|
+
import {DirectiveDefinition, ListType, NonNullType, Schema} from "../definitions";
|
|
10
10
|
import { createDirectiveSpecification, createScalarTypeSpecification } from "../directiveAndTypeSpecification";
|
|
11
11
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
12
12
|
import { ARGUMENT_COMPOSITION_STRATEGIES } from "../argumentCompositionStrategies";
|
|
@@ -43,7 +43,7 @@ export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
|
43
43
|
assert(scopeType, () => `Expected "${scopeName}" to be defined`);
|
|
44
44
|
return new NonNullType(new ListType(new NonNullType(new ListType(new NonNullType(scopeType)))));
|
|
45
45
|
},
|
|
46
|
-
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.
|
|
46
|
+
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.DNF_CONJUNCTION,
|
|
47
47
|
}],
|
|
48
48
|
locations: [
|
|
49
49
|
DirectiveLocation.FIELD_DEFINITION,
|
|
@@ -57,6 +57,10 @@ export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
|
57
57
|
}));
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
requiresScopesDirective(schema: Schema): DirectiveDefinition<{scopes: string[][]}> | undefined {
|
|
61
|
+
return this.directive(schema, RequiresScopesSpecDefinition.directiveName);
|
|
62
|
+
}
|
|
63
|
+
|
|
60
64
|
get defaultCorePurpose(): CorePurpose {
|
|
61
65
|
return 'SECURITY';
|
|
62
66
|
}
|