@apollo/federation-internals 2.7.2 → 2.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo/federation-internals",
3
- "version": "2.7.2",
3
+ "version": "2.7.3",
4
4
  "description": "Apollo Federation internal utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -392,26 +392,11 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
392
392
  }
393
393
 
394
394
  appliedDirectivesToDirectiveNodes() : ConstDirectiveNode[] | undefined {
395
- if (this.appliedDirectives.length == 0) {
396
- return undefined;
397
- }
398
-
399
- return this.appliedDirectives.map(directive => {
400
- return {
401
- kind: Kind.DIRECTIVE,
402
- name: {
403
- kind: Kind.NAME,
404
- value: directive.name,
405
- },
406
- arguments: directive.argumentsToAST()
407
- };
408
- });
395
+ return directivesToDirectiveNodes(this.appliedDirectives);
409
396
  }
410
397
 
411
398
  appliedDirectivesToString(): string {
412
- return this.appliedDirectives.length == 0
413
- ? ''
414
- : ' ' + this.appliedDirectives.join(' ');
399
+ return directivesToString(this.appliedDirectives);
415
400
  }
416
401
 
417
402
  collectVariablesInAppliedDirectives(collector: VariableCollector) {
@@ -3257,6 +3242,37 @@ export class Directive<
3257
3242
  }
3258
3243
  }
3259
3244
 
3245
+ /**
3246
+ * Formats a Directive array as a string (with a leading space, if present).
3247
+ */
3248
+ export function directivesToString(directives?: readonly Directive<any>[])
3249
+ : string
3250
+ {
3251
+ return (!directives || directives.length == 0)
3252
+ ? ''
3253
+ : ' ' + directives.join(' ');
3254
+ }
3255
+
3256
+ /**
3257
+ * Converts a Directive array into DirectiveNode array.
3258
+ */
3259
+ export function directivesToDirectiveNodes(directives?: readonly Directive<any>[])
3260
+ : ConstDirectiveNode[] | undefined
3261
+ {
3262
+ return (!directives || directives.length === 0)
3263
+ ? undefined
3264
+ : directives.map(directive => {
3265
+ return {
3266
+ kind: Kind.DIRECTIVE,
3267
+ name: {
3268
+ kind: Kind.NAME,
3269
+ value: directive.name,
3270
+ },
3271
+ arguments: directive.argumentsToAST()
3272
+ };
3273
+ });
3274
+ }
3275
+
3260
3276
  /**
3261
3277
  * Checks if 2 directive applications should be considered equal.
3262
3278
  *
package/src/operations.ts CHANGED
@@ -48,6 +48,8 @@ import {
48
48
  isObjectType,
49
49
  NamedType,
50
50
  isUnionType,
51
+ directivesToString,
52
+ directivesToDirectiveNodes,
51
53
  } from "./definitions";
52
54
  import { isInterfaceObjectType } from "./federation";
53
55
  import { ERRORS } from "./error";
@@ -877,7 +879,6 @@ function computeFragmentsToKeep(
877
879
  return toExpand.size === 0 ? fragments : fragments.filter((f) => !toExpand.has(f.name));
878
880
  }
879
881
 
880
- // TODO Operations can also have directives
881
882
  export class Operation {
882
883
  constructor(
883
884
  readonly schema: Schema,
@@ -885,7 +886,8 @@ export class Operation {
885
886
  readonly selectionSet: SelectionSet,
886
887
  readonly variableDefinitions: VariableDefinitions,
887
888
  readonly fragments?: NamedFragments,
888
- readonly name?: string) {
889
+ readonly name?: string,
890
+ readonly directives?: readonly Directive<any>[]) {
889
891
  }
890
892
 
891
893
  // Returns a copy of this operation with the provided updated selection set.
@@ -901,7 +903,8 @@ export class Operation {
901
903
  newSelectionSet,
902
904
  this.variableDefinitions,
903
905
  this.fragments,
904
- this.name
906
+ this.name,
907
+ this.directives
905
908
  );
906
909
  }
907
910
 
@@ -917,7 +920,8 @@ export class Operation {
917
920
  newSelectionSet,
918
921
  this.variableDefinitions,
919
922
  newFragments,
920
- this.name
923
+ this.name,
924
+ this.directives
921
925
  );
922
926
  }
923
927
 
@@ -982,6 +986,7 @@ export class Operation {
982
986
  this.variableDefinitions,
983
987
  fragments,
984
988
  this.name,
989
+ this.directives
985
990
  );
986
991
  }
987
992
 
@@ -1053,7 +1058,7 @@ export class Operation {
1053
1058
  }
1054
1059
 
1055
1060
  toString(expandFragments: boolean = false, prettyPrint: boolean = true): string {
1056
- return this.selectionSet.toOperationString(this.rootKind, this.variableDefinitions, this.fragments, this.name, expandFragments, prettyPrint);
1061
+ return this.selectionSet.toOperationString(this.rootKind, this.variableDefinitions, this.fragments, this.name, this.directives, expandFragments, prettyPrint);
1057
1062
  }
1058
1063
  }
1059
1064
 
@@ -1219,6 +1224,14 @@ export class NamedFragmentDefinition extends DirectiveTargetElement<NamedFragmen
1219
1224
  const expandedSelectionSet = this.expandedSelectionSet();
1220
1225
  const selectionSet = expandedSelectionSet.normalize({ parentType: type });
1221
1226
 
1227
+ if (!isObjectType(this.typeCondition)) {
1228
+ // When the type condition of the fragment is not an object type, the `FieldsInSetCanMerge` rule is more
1229
+ // restrictive and any fields can create conflicts. Thus, we have to use the full validator in this case.
1230
+ // (see https://github.com/graphql/graphql-spec/issues/1085 for details.)
1231
+ const validator = FieldsConflictValidator.build(expandedSelectionSet);
1232
+ return { selectionSet, validator };
1233
+ }
1234
+
1222
1235
  // Note that `trimmed` is the difference of 2 selections that may not have been normalized on the same parent type,
1223
1236
  // so in practice, it is possible that `trimmed` contains some of the selections that `selectionSet` contains, but
1224
1237
  // that they have been simplified in `selectionSet` in such a way that the `minus` call does not see it. However,
@@ -2077,6 +2090,7 @@ export class SelectionSet {
2077
2090
  variableDefinitions: VariableDefinitions,
2078
2091
  fragments: NamedFragments | undefined,
2079
2092
  operationName?: string,
2093
+ directives?: readonly Directive<any>[],
2080
2094
  expandFragments: boolean = false,
2081
2095
  prettyPrint: boolean = true
2082
2096
  ): string {
@@ -2090,7 +2104,8 @@ export class SelectionSet {
2090
2104
  const nameAndVariables = operationName
2091
2105
  ? " " + (operationName + (variableDefinitions.isEmpty() ? "" : variableDefinitions.toString()))
2092
2106
  : (variableDefinitions.isEmpty() ? "" : " " + variableDefinitions.toString());
2093
- return fragmentsDefinitions + rootKind + nameAndVariables + " " + this.toString(expandFragments, true, indent);
2107
+ const directives_str = directivesToString(directives);
2108
+ return fragmentsDefinitions + rootKind + nameAndVariables + directives_str + " " + this.toString(expandFragments, true, indent);
2094
2109
  }
2095
2110
 
2096
2111
  /**
@@ -2873,7 +2888,7 @@ class FieldsConflictValidator {
2873
2888
  continue;
2874
2889
  }
2875
2890
 
2876
- // We're basically checking [FieldInSetCanMerge](https://spec.graphql.org/draft/#FieldsInSetCanMerge()),
2891
+ // We're basically checking [FieldsInSetCanMerge](https://spec.graphql.org/draft/#FieldsInSetCanMerge()),
2877
2892
  // but from 2 set of fields (`thisFields` and `thatFields`) of the same response that we know individually
2878
2893
  // merge already.
2879
2894
  for (const [thisField, thisValidator] of thisFields.entries()) {
@@ -3562,7 +3577,7 @@ class FragmentSpreadSelection extends FragmentSelection {
3562
3577
 
3563
3578
  key(): string {
3564
3579
  if (!this.computedKey) {
3565
- this.computedKey = '...' + this.namedFragment.name + (this.spreadDirectives.length === 0 ? '' : ' ' + this.spreadDirectives.join(' '));
3580
+ this.computedKey = '...' + this.namedFragment.name + directivesToString(this.spreadDirectives);
3566
3581
  }
3567
3582
  return this.computedKey;
3568
3583
  }
@@ -3588,18 +3603,7 @@ class FragmentSpreadSelection extends FragmentSelection {
3588
3603
  }
3589
3604
 
3590
3605
  toSelectionNode(): FragmentSpreadNode {
3591
- const directiveNodes = this.spreadDirectives.length === 0
3592
- ? undefined
3593
- : this.spreadDirectives.map(directive => {
3594
- return {
3595
- kind: Kind.DIRECTIVE,
3596
- name: {
3597
- kind: Kind.NAME,
3598
- value: directive.name,
3599
- },
3600
- arguments: directive.argumentsToAST()
3601
- } as DirectiveNode;
3602
- });
3606
+ const directiveNodes = directivesToDirectiveNodes(this.spreadDirectives);
3603
3607
  return {
3604
3608
  kind: Kind.FRAGMENT_SPREAD,
3605
3609
  name: { kind: Kind.NAME, value: this.namedFragment.name },
@@ -3744,9 +3748,7 @@ class FragmentSpreadSelection extends FragmentSelection {
3744
3748
  if (expandFragments) {
3745
3749
  return (indent ?? '') + this.element + ' ' + this.selectionSet.toString(true, true, indent);
3746
3750
  } else {
3747
- const directives = this.spreadDirectives;
3748
- const directiveString = directives.length == 0 ? '' : ' ' + directives.join(' ');
3749
- return (indent ?? '') + '...' + this.namedFragment.name + directiveString;
3751
+ return (indent ?? '') + '...' + this.namedFragment.name + directivesToString(this.spreadDirectives);
3750
3752
  }
3751
3753
  }
3752
3754
  }
@@ -3832,6 +3834,7 @@ export function operationFromDocument(
3832
3834
  }
3833
3835
  ) : Operation {
3834
3836
  let operation: OperationDefinitionNode | undefined;
3837
+ let operation_directives: Directive<any>[] | undefined; // the directives on `operation`
3835
3838
  const operationName = options?.operationName;
3836
3839
  const fragments = new NamedFragments();
3837
3840
  // We do a first pass to collect the operation, and create all named fragment, but without their selection set yet.
@@ -3842,6 +3845,7 @@ export function operationFromDocument(
3842
3845
  validate(!operation || operationName, () => 'Must provide operation name if query contains multiple operations.');
3843
3846
  if (!operationName || (definition.name && definition.name.value === operationName)) {
3844
3847
  operation = definition;
3848
+ operation_directives = directivesOfNodes(schema, definition.directives);
3845
3849
  }
3846
3850
  break;
3847
3851
  case Kind.FRAGMENT_DEFINITION:
@@ -3875,18 +3879,20 @@ export function operationFromDocument(
3875
3879
  }
3876
3880
  });
3877
3881
  fragments.validate(variableDefinitions);
3878
- return operationFromAST({schema, operation, variableDefinitions, fragments, validateInput: options?.validate});
3882
+ return operationFromAST({schema, operation, operation_directives, variableDefinitions, fragments, validateInput: options?.validate});
3879
3883
  }
3880
3884
 
3881
3885
  function operationFromAST({
3882
3886
  schema,
3883
3887
  operation,
3888
+ operation_directives,
3884
3889
  variableDefinitions,
3885
3890
  fragments,
3886
3891
  validateInput,
3887
3892
  }:{
3888
3893
  schema: Schema,
3889
3894
  operation: OperationDefinitionNode,
3895
+ operation_directives?: Directive<any>[],
3890
3896
  variableDefinitions: VariableDefinitions,
3891
3897
  fragments: NamedFragments,
3892
3898
  validateInput?: boolean,
@@ -3906,7 +3912,8 @@ function operationFromAST({
3906
3912
  }),
3907
3913
  variableDefinitions,
3908
3914
  fragmentsIfAny,
3909
- operation.name?.value
3915
+ operation.name?.value,
3916
+ operation_directives
3910
3917
  );
3911
3918
  }
3912
3919
 
@@ -3961,6 +3968,7 @@ export function operationToDocument(operation: Operation): DocumentNode {
3961
3968
  name: operation.name ? { kind: Kind.NAME, value: operation.name } : undefined,
3962
3969
  selectionSet: operation.selectionSet.toSelectionSetNode(),
3963
3970
  variableDefinitions: operation.variableDefinitions.toVariableDefinitionNodes(),
3971
+ directives: directivesToDirectiveNodes(operation.directives),
3964
3972
  };
3965
3973
  const fragmentASTs: DefinitionNode[] = operation.fragments
3966
3974
  ? operation.fragments?.toFragmentDefinitionNodes()