@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.
Files changed (36) hide show
  1. package/dist/argumentCompositionStrategies.d.ts +6 -0
  2. package/dist/argumentCompositionStrategies.d.ts.map +1 -1
  3. package/dist/argumentCompositionStrategies.js +77 -0
  4. package/dist/argumentCompositionStrategies.js.map +1 -1
  5. package/dist/error.d.ts +3 -0
  6. package/dist/error.d.ts.map +1 -1
  7. package/dist/error.js +6 -0
  8. package/dist/error.js.map +1 -1
  9. package/dist/federation.d.ts +2 -1
  10. package/dist/federation.d.ts.map +1 -1
  11. package/dist/federation.js +37 -3
  12. package/dist/federation.js.map +1 -1
  13. package/dist/operations.d.ts +2 -0
  14. package/dist/operations.d.ts.map +1 -1
  15. package/dist/operations.js +14 -7
  16. package/dist/operations.js.map +1 -1
  17. package/dist/specs/authenticatedSpec.d.ts +2 -0
  18. package/dist/specs/authenticatedSpec.d.ts.map +1 -1
  19. package/dist/specs/authenticatedSpec.js +3 -0
  20. package/dist/specs/authenticatedSpec.js.map +1 -1
  21. package/dist/specs/policySpec.d.ts +4 -0
  22. package/dist/specs/policySpec.d.ts.map +1 -1
  23. package/dist/specs/policySpec.js +4 -1
  24. package/dist/specs/policySpec.js.map +1 -1
  25. package/dist/specs/requiresScopesSpec.d.ts +4 -0
  26. package/dist/specs/requiresScopesSpec.d.ts.map +1 -1
  27. package/dist/specs/requiresScopesSpec.js +4 -1
  28. package/dist/specs/requiresScopesSpec.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/argumentCompositionStrategies.ts +114 -2
  31. package/src/error.ts +21 -0
  32. package/src/federation.ts +46 -3
  33. package/src/operations.ts +19 -7
  34. package/src/specs/authenticatedSpec.ts +5 -0
  35. package/src/specs/policySpec.ts +6 -2
  36. 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,gDAAuD;AACvD,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,KAAK;iBAC3D,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,IAAI,kBAAkB;QACpB,OAAO,UAAU,CAAC;IACpB,CAAC;;AA3CH,oEA4CC;AA3CwB,0CAAa,GAAG,gBAAgB,CAAC;AACjC,qCAAQ,GAC7B,4BAA4B,4BAA4B,CAAC,aAAa,EAAE,CAAC;AA2ChE,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"}
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo/federation-internals",
3
- "version": "2.9.2",
3
+ "version": "2.9.4",
4
4
  "description": "Apollo Federation internal utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,4 +1,4 @@
1
- import { InputType, NonNullType, Schema, isListType, isNonNullType } from "./definitions"
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: mergeNullableValues(
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 an a field with a contextual argument.`,
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 selectionSet;
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
- } else if (selection.kind === 'FieldSelection') {
1662
- if (selection.selectionSet) {
1663
- selection = selection.withUpdatedSelectionSet(selection.selectionSet.minimizeSelectionSet(namedFragments, seenSelections)[0]);
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 isNonAliasedTypenameSelection = (s: Selection) => s.kind === 'FieldSelection' && !s.element.alias && s.element.name === typenameFieldName;
2118
- const typenameSelection = this._selections.find((s) => isNonAliasedTypenameSelection(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 => !isNonAliasedTypenameSelection(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
  }
@@ -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.UNION,
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.UNION,
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
  }