@apollo/federation-internals 2.6.2 → 2.7.0

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 (48) hide show
  1. package/dist/error.d.ts +19 -0
  2. package/dist/error.d.ts.map +1 -1
  3. package/dist/error.js +38 -0
  4. package/dist/error.js.map +1 -1
  5. package/dist/extractSubgraphsFromSupergraph.js +9 -6
  6. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  7. package/dist/federation.d.ts +7 -2
  8. package/dist/federation.d.ts.map +1 -1
  9. package/dist/federation.js +25 -3
  10. package/dist/federation.js.map +1 -1
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/knownCoreFeatures.d.ts +3 -0
  16. package/dist/knownCoreFeatures.d.ts.map +1 -1
  17. package/dist/knownCoreFeatures.js +12 -1
  18. package/dist/knownCoreFeatures.js.map +1 -1
  19. package/dist/specs/coreSpec.d.ts +2 -0
  20. package/dist/specs/coreSpec.d.ts.map +1 -1
  21. package/dist/specs/coreSpec.js +11 -0
  22. package/dist/specs/coreSpec.js.map +1 -1
  23. package/dist/specs/federationSpec.d.ts +4 -1
  24. package/dist/specs/federationSpec.d.ts.map +1 -1
  25. package/dist/specs/federationSpec.js +26 -6
  26. package/dist/specs/federationSpec.js.map +1 -1
  27. package/dist/specs/joinSpec.d.ts +7 -0
  28. package/dist/specs/joinSpec.d.ts.map +1 -1
  29. package/dist/specs/joinSpec.js +13 -1
  30. package/dist/specs/joinSpec.js.map +1 -1
  31. package/dist/specs/sourceSpec.d.ts +68 -0
  32. package/dist/specs/sourceSpec.d.ts.map +1 -0
  33. package/dist/specs/sourceSpec.js +345 -0
  34. package/dist/specs/sourceSpec.js.map +1 -0
  35. package/dist/supergraphs.d.ts.map +1 -1
  36. package/dist/supergraphs.js +1 -0
  37. package/dist/supergraphs.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/error.ts +132 -0
  40. package/src/extractSubgraphsFromSupergraph.ts +9 -6
  41. package/src/federation.ts +42 -8
  42. package/src/index.ts +1 -0
  43. package/src/knownCoreFeatures.ts +16 -1
  44. package/src/specs/coreSpec.ts +13 -1
  45. package/src/specs/federationSpec.ts +26 -6
  46. package/src/specs/joinSpec.ts +36 -1
  47. package/src/specs/sourceSpec.ts +598 -0
  48. package/src/supergraphs.ts +1 -0
package/src/error.ts CHANGED
@@ -513,6 +513,12 @@ const OVERRIDE_ON_INTERFACE = makeCodeDefinition(
513
513
  { addedIn: '2.3.0' },
514
514
  );
515
515
 
516
+ const OVERRIDE_LABEL_INVALID = makeCodeDefinition(
517
+ 'OVERRIDE_LABEL_INVALID',
518
+ 'The @override directive `label` argument must match the pattern /^[a-zA-Z][a-zA-Z0-9_\-:./]*$/ or /^percent\((\d{1,2}(\.\d{1,8})?|100)\)$/',
519
+ { addedIn: '2.7.0' },
520
+ );
521
+
516
522
  const UNSUPPORTED_FEATURE = makeCodeDefinition(
517
523
  'UNSUPPORTED_FEATURE',
518
524
  'Indicates an error due to feature currently unsupported by federation.',
@@ -555,6 +561,112 @@ const INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE = makeCodeDefinition(
555
561
  { addedIn: '2.3.0' },
556
562
  )
557
563
 
564
+ const SOURCE_API_NAME_INVALID = makeCodeDefinition(
565
+ 'SOURCE_API_NAME_INVALID',
566
+ 'Each `@sourceAPI` directive must take a unique and valid name as an argument',
567
+ { addedIn: '2.7.0' },
568
+ );
569
+
570
+ const SOURCE_API_PROTOCOL_INVALID = makeCodeDefinition(
571
+ 'SOURCE_API_PROTOCOL_INVALID',
572
+ 'Each `@sourceAPI` directive must specify exactly one of the known protocols',
573
+ { addedIn: '2.7.0' },
574
+ );
575
+
576
+ const SOURCE_API_HTTP_BASE_URL_INVALID = makeCodeDefinition(
577
+ 'SOURCE_API_HTTP_BASE_URL_INVALID',
578
+ 'The `@sourceAPI` directive must specify a valid http.baseURL',
579
+ { addedIn: '2.7.0' },
580
+ );
581
+
582
+ const SOURCE_HTTP_HEADERS_INVALID = makeCodeDefinition(
583
+ 'SOURCE_HTTP_HEADERS_INVALID',
584
+ 'The `http.headers` argument of `@source*` directives must specify valid HTTP headers',
585
+ { addedIn: '2.7.0' },
586
+ );
587
+
588
+ const SOURCE_TYPE_API_ERROR = makeCodeDefinition(
589
+ 'SOURCE_TYPE_API_ERROR',
590
+ 'The `api` argument of the `@sourceType` directive must match a valid `@sourceAPI` name',
591
+ { addedIn: '2.7.0' },
592
+ );
593
+
594
+ const SOURCE_TYPE_PROTOCOL_INVALID = makeCodeDefinition(
595
+ 'SOURCE_TYPE_PROTOCOL_INVALID',
596
+ 'The `@sourceType` directive must specify the same protocol as its corresponding `@sourceAPI`',
597
+ { addedIn: '2.7.0' },
598
+ );
599
+
600
+ const SOURCE_TYPE_HTTP_METHOD_INVALID = makeCodeDefinition(
601
+ 'SOURCE_TYPE_HTTP_METHOD_INVALID',
602
+ 'The `@sourceType` directive must specify exactly one of `http.GET` or `http.POST`',
603
+ { addedIn: '2.7.0' },
604
+ );
605
+
606
+ const SOURCE_TYPE_HTTP_PATH_INVALID = makeCodeDefinition(
607
+ 'SOURCE_TYPE_HTTP_PATH_INVALID',
608
+ 'The `@sourceType` directive must specify a valid URL template for `http.GET` or `http.POST`',
609
+ { addedIn: '2.7.0' },
610
+ );
611
+
612
+ const SOURCE_TYPE_HTTP_BODY_INVALID = makeCodeDefinition(
613
+ 'SOURCE_TYPE_HTTP_BODY_INVALID',
614
+ 'If the `@sourceType` specifies `http.body`, it must be a valid `JSONSelection`',
615
+ { addedIn: '2.7.0' },
616
+ );
617
+
618
+ const SOURCE_TYPE_ON_NON_OBJECT_OR_NON_ENTITY = makeCodeDefinition(
619
+ 'SOURCE_TYPE_ON_NON_OBJECT_OR_NON_ENTITY',
620
+ 'The `@sourceType` directive must be applied to an object or interface type that also has `@key`',
621
+ { addedIn: '2.7.0' },
622
+ );
623
+
624
+ const SOURCE_TYPE_SELECTION_INVALID = makeCodeDefinition(
625
+ 'SOURCE_TYPE_SELECTION_INVALID',
626
+ 'The `selection` argument of the `@sourceType` directive must be a valid `JSONSelection` that outputs fields of the GraphQL type',
627
+ );
628
+
629
+ const SOURCE_FIELD_API_ERROR = makeCodeDefinition(
630
+ 'SOURCE_FIELD_API_ERROR',
631
+ 'The `api` argument of the `@sourceField` directive must match a valid `@sourceAPI` name',
632
+ { addedIn: '2.7.0' },
633
+ );
634
+
635
+ const SOURCE_FIELD_PROTOCOL_INVALID = makeCodeDefinition(
636
+ 'SOURCE_FIELD_PROTOCOL_INVALID',
637
+ 'If `@sourceField` specifies a protocol, it must match the corresponding `@sourceAPI` protocol',
638
+ { addedIn: '2.7.0' },
639
+ );
640
+
641
+ const SOURCE_FIELD_HTTP_METHOD_INVALID = makeCodeDefinition(
642
+ 'SOURCE_FIELD_HTTP_METHOD_INVALID',
643
+ 'The `@sourceField` directive must specify at most one of `http.{GET,POST,PUT,PATCH,DELETE}`',
644
+ { addedIn: '2.7.0' },
645
+ );
646
+
647
+ const SOURCE_FIELD_HTTP_PATH_INVALID = makeCodeDefinition(
648
+ 'SOURCE_FIELD_HTTP_PATH_INVALID',
649
+ 'The `@sourceField` directive must specify a valid URL template for `http.{GET,POST,PUT,PATCH,DELETE}`',
650
+ { addedIn: '2.7.0' },
651
+ );
652
+
653
+ const SOURCE_FIELD_HTTP_BODY_INVALID = makeCodeDefinition(
654
+ 'SOURCE_FIELD_HTTP_BODY_INVALID',
655
+ 'If `@sourceField` specifies http.body, it must be a valid `JSONSelection` matching available arguments and fields',
656
+ { addedIn: '2.7.0' },
657
+ );
658
+
659
+ const SOURCE_FIELD_SELECTION_INVALID = makeCodeDefinition(
660
+ 'SOURCE_FIELD_SELECTION_INVALID',
661
+ 'The `selection` argument of the `@sourceField` directive must be a valid `JSONSelection` that outputs fields of the GraphQL type',
662
+ { addedIn: '2.7.0' },
663
+ );
664
+
665
+ const SOURCE_FIELD_NOT_ON_ROOT_OR_ENTITY_FIELD = makeCodeDefinition(
666
+ 'SOURCE_FIELD_NOT_ON_ROOT_OR_ENTITY_FIELD',
667
+ 'The `@sourceField` directive must be applied to a field of the `Query` or `Mutation` types, or of an entity type',
668
+ { addedIn: '2.7.0' },
669
+ );
558
670
 
559
671
  export const ERROR_CATEGORIES = {
560
672
  DIRECTIVE_FIELDS_MISSING_EXTERNAL,
@@ -633,6 +745,7 @@ export const ERRORS = {
633
745
  OVERRIDE_FROM_SELF_ERROR,
634
746
  OVERRIDE_SOURCE_HAS_OVERRIDE,
635
747
  OVERRIDE_ON_INTERFACE,
748
+ OVERRIDE_LABEL_INVALID,
636
749
  UNSUPPORTED_FEATURE,
637
750
  INVALID_FEDERATION_SUPERGRAPH,
638
751
  DOWNSTREAM_SERVICE_ERROR,
@@ -643,6 +756,25 @@ export const ERRORS = {
643
756
  INTERFACE_OBJECT_USAGE_ERROR,
644
757
  INTERFACE_KEY_NOT_ON_IMPLEMENTATION,
645
758
  INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE,
759
+ // Errors related to @sourceAPI, @sourceType, and/or @sourceField
760
+ SOURCE_API_NAME_INVALID,
761
+ SOURCE_API_PROTOCOL_INVALID,
762
+ SOURCE_API_HTTP_BASE_URL_INVALID,
763
+ SOURCE_HTTP_HEADERS_INVALID,
764
+ SOURCE_TYPE_API_ERROR,
765
+ SOURCE_TYPE_PROTOCOL_INVALID,
766
+ SOURCE_TYPE_HTTP_METHOD_INVALID,
767
+ SOURCE_TYPE_HTTP_PATH_INVALID,
768
+ SOURCE_TYPE_HTTP_BODY_INVALID,
769
+ SOURCE_TYPE_ON_NON_OBJECT_OR_NON_ENTITY,
770
+ SOURCE_TYPE_SELECTION_INVALID,
771
+ SOURCE_FIELD_API_ERROR,
772
+ SOURCE_FIELD_PROTOCOL_INVALID,
773
+ SOURCE_FIELD_HTTP_METHOD_INVALID,
774
+ SOURCE_FIELD_HTTP_PATH_INVALID,
775
+ SOURCE_FIELD_HTTP_BODY_INVALID,
776
+ SOURCE_FIELD_SELECTION_INVALID,
777
+ SOURCE_FIELD_NOT_ON_ROOT_OR_ENTITY_FIELD,
646
778
  };
647
779
 
648
780
  const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
@@ -418,15 +418,15 @@ function extractObjOrItfContent(args: ExtractArguments, info: TypeInfo<ObjectTyp
418
418
  }).length > 1;
419
419
 
420
420
  for (const application of fieldApplications) {
421
- const args = application.arguments();
421
+ const joinFieldArgs = application.arguments();
422
422
  // We use a @join__field with no graph to indicates when a field in the supergraph does not come
423
423
  // directly from any subgraph and there is thus nothing to do to "extract" it.
424
- if (!args.graph) {
424
+ if (!joinFieldArgs.graph) {
425
425
  continue;
426
426
  }
427
427
 
428
- const { type: subgraphType, subgraph } = subgraphsInfo.get(args.graph)!;
429
- addSubgraphField({ field, type: subgraphType, subgraph, isShareable, joinFieldArgs: args});
428
+ const { type: subgraphType, subgraph } = subgraphsInfo.get(joinFieldArgs.graph)!;
429
+ addSubgraphField({ field, type: subgraphType, subgraph, isShareable, joinFieldArgs });
430
430
  }
431
431
  }
432
432
  }
@@ -615,11 +615,14 @@ function addSubgraphField({
615
615
  subgraphField.applyDirective(subgraph.metadata().externalDirective());
616
616
  }
617
617
  const usedOverridden = !!joinFieldArgs?.usedOverridden;
618
- if (usedOverridden) {
618
+ if (usedOverridden && !joinFieldArgs?.overrideLabel) {
619
619
  subgraphField.applyDirective(subgraph.metadata().externalDirective(), {'reason': '[overridden]'});
620
620
  }
621
621
  if (joinFieldArgs?.override) {
622
- subgraphField.applyDirective(subgraph.metadata().overrideDirective(), {'from': joinFieldArgs.override});
622
+ subgraphField.applyDirective(subgraph.metadata().overrideDirective(), {
623
+ from: joinFieldArgs.override,
624
+ ...(joinFieldArgs.overrideLabel ? { label: joinFieldArgs.overrideLabel } : {})
625
+ });
623
626
  }
624
627
  if (isShareable && !external && !usedOverridden) {
625
628
  subgraphField.applyDirective(subgraph.metadata().shareableDirective());
package/src/federation.ts CHANGED
@@ -83,8 +83,13 @@ import {
83
83
  import { defaultPrintOptions, PrintOptions as PrintOptions, printSchema } from "./print";
84
84
  import { createObjectTypeSpecification, createScalarTypeSpecification, createUnionTypeSpecification } from "./directiveAndTypeSpecification";
85
85
  import { didYouMean, suggestionList } from "./suggestions";
86
- import { coreFeatureDefinitionIfKnown } from "./knownCoreFeatures";
86
+ import { coreFeatureDefinitionIfKnown, validateKnownFeatures } from "./knownCoreFeatures";
87
87
  import { joinIdentity } from "./specs/joinSpec";
88
+ import {
89
+ SourceAPIDirectiveArgs,
90
+ SourceFieldDirectiveArgs,
91
+ SourceTypeDirectiveArgs,
92
+ } from "./specs/sourceSpec";
88
93
 
89
94
  const linkSpec = LINK_VERSIONS.latest();
90
95
  const tagSpec = TAG_VERSIONS.latest();
@@ -578,8 +583,7 @@ export class FederationMetadata {
578
583
  private _fieldUsedPredicate?: (field: FieldDefinition<CompositeType>) => boolean;
579
584
  private _isFed2Schema?: boolean;
580
585
 
581
- constructor(readonly schema: Schema) {
582
- }
586
+ constructor(readonly schema: Schema) {}
583
587
 
584
588
  private onInvalidate() {
585
589
  this._externalTester = undefined;
@@ -722,7 +726,7 @@ export class FederationMetadata {
722
726
  return this.getLegacyFederationDirective(FederationDirectiveName.KEY);
723
727
  }
724
728
 
725
- overrideDirective(): DirectiveDefinition<{from: string}> {
729
+ overrideDirective(): DirectiveDefinition<{from: string, label?: string}> {
726
730
  return this.getLegacyFederationDirective(FederationDirectiveName.OVERRIDE);
727
731
  }
728
732
 
@@ -774,6 +778,18 @@ export class FederationMetadata {
774
778
  return this.getPost20FederationDirective(FederationDirectiveName.POLICY);
775
779
  }
776
780
 
781
+ sourceAPIDirective(): Post20FederationDirectiveDefinition<SourceAPIDirectiveArgs> {
782
+ return this.getPost20FederationDirective(FederationDirectiveName.SOURCE_API);
783
+ }
784
+
785
+ sourceTypeDirective(): Post20FederationDirectiveDefinition<SourceTypeDirectiveArgs> {
786
+ return this.getPost20FederationDirective(FederationDirectiveName.SOURCE_TYPE);
787
+ }
788
+
789
+ sourceFieldDirective(): Post20FederationDirectiveDefinition<SourceFieldDirectiveArgs> {
790
+ return this.getPost20FederationDirective(FederationDirectiveName.SOURCE_FIELD);
791
+ }
792
+
777
793
  allFederationDirectives(): DirectiveDefinition[] {
778
794
  const baseDirectives: DirectiveDefinition[] = [
779
795
  this.keyDirective(),
@@ -814,6 +830,19 @@ export class FederationMetadata {
814
830
  baseDirectives.push(policyDirective);
815
831
  }
816
832
 
833
+ const sourceAPIDirective = this.sourceAPIDirective();
834
+ if (isFederationDirectiveDefinedInSchema(sourceAPIDirective)) {
835
+ baseDirectives.push(sourceAPIDirective);
836
+ }
837
+ const sourceTypeDirective = this.sourceTypeDirective();
838
+ if (isFederationDirectiveDefinedInSchema(sourceTypeDirective)) {
839
+ baseDirectives.push(sourceTypeDirective);
840
+ }
841
+ const sourceFieldDirective = this.sourceFieldDirective();
842
+ if (isFederationDirectiveDefinedInSchema(sourceFieldDirective)) {
843
+ baseDirectives.push(sourceFieldDirective);
844
+ }
845
+
817
846
  return baseDirectives;
818
847
  }
819
848
 
@@ -1051,6 +1080,11 @@ export class FederationBlueprint extends SchemaBlueprint {
1051
1080
  validateKeyOnInterfacesAreAlsoOnAllImplementations(metadata, errorCollector);
1052
1081
  validateInterfaceObjectsAreOnEntities(metadata, errorCollector);
1053
1082
 
1083
+ // FeatureDefinition objects passed to registerKnownFeature can register
1084
+ // validation functions for subgraph schemas by overriding the
1085
+ // validateSubgraphSchema method.
1086
+ validateKnownFeatures(schema, errorCollector);
1087
+
1054
1088
  // If tag is redefined by the user, make sure the definition is compatible with what we expect
1055
1089
  const tagDirective = metadata.tagDirective();
1056
1090
  if (tagDirective) {
@@ -1191,9 +1225,9 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
1191
1225
 
1192
1226
  // This is the full @link declaration as added by `asFed2SubgraphDocument`. It's here primarily for uses by tests that print and match
1193
1227
  // subgraph schema to avoid having to update 20+ tests every time we use a new directive or the order of import changes ...
1194
- export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy"])';
1228
+ export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy", "@sourceAPI", "@sourceType", "@sourceField"])';
1195
1229
  // This is the full @link declaration that is added when upgrading fed v1 subgraphs to v2 version. It should only be used by tests.
1196
- export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
1230
+ export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.7", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
1197
1231
 
1198
1232
  /**
1199
1233
  * Given a document that is assumed to _not_ be a fed2 schema (it does not have a `@link` to the federation spec),
@@ -1439,7 +1473,7 @@ function completeFed2SubgraphSchema(schema: Schema): GraphQLError[] {
1439
1473
  const spec = FEDERATION_VERSIONS.find(fedFeature.url.version);
1440
1474
  if (!spec) {
1441
1475
  return [ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err(
1442
- `Invalid version ${fedFeature.url.version} for the federation feature in @link direction on schema`,
1476
+ `Invalid version ${fedFeature.url.version} for the federation feature in @link directive on schema`,
1443
1477
  { nodes: fedFeature.directive.sourceAST },
1444
1478
  )];
1445
1479
  }
@@ -1561,7 +1595,7 @@ export function collectTargetFields({
1561
1595
  validate,
1562
1596
  });
1563
1597
  } catch (e) {
1564
- // If we explicitely requested no validation, then we shouldn't throw a (graphQL) error, but if we do, we swallow it
1598
+ // If we explicitly requested no validation, then we shouldn't throw a (graphQL) error, but if we do, we swallow it
1565
1599
  // (returning a partial result, but we assume it is fine).
1566
1600
  const isGraphQLError = errorCauses(e) !== undefined
1567
1601
  if (!isGraphQLError || validate) {
package/src/index.ts CHANGED
@@ -23,3 +23,4 @@ export * from './argumentCompositionStrategies';
23
23
  export * from './specs/authenticatedSpec';
24
24
  export * from './specs/requiresScopesSpec';
25
25
  export * from './specs/policySpec';
26
+ export * from './specs/sourceSpec';
@@ -1,6 +1,8 @@
1
+ import { GraphQLError } from "graphql";
2
+ import { Schema } from "./definitions";
1
3
  import { FeatureDefinition, FeatureDefinitions, FeatureUrl } from "./specs/coreSpec";
2
4
 
3
- const registeredFeatures: Map<string, FeatureDefinitions> = new Map();
5
+ const registeredFeatures = new Map<string, FeatureDefinitions>();
4
6
 
5
7
  export function registerKnownFeature(definitions: FeatureDefinitions) {
6
8
  if (!registeredFeatures.has(definitions.identity)) {
@@ -12,6 +14,19 @@ export function coreFeatureDefinitionIfKnown(url: FeatureUrl): FeatureDefinition
12
14
  return registeredFeatures.get(url.identity)?.find(url.version);
13
15
  }
14
16
 
17
+ export function validateKnownFeatures(
18
+ schema: Schema,
19
+ errorCollector: GraphQLError[] = [],
20
+ ): GraphQLError[] {
21
+ registeredFeatures.forEach(definitions => {
22
+ const feature = definitions.latest();
23
+ if (feature.validateSubgraphSchema !== FeatureDefinition.prototype.validateSubgraphSchema) {
24
+ errorCollector.push(...feature.validateSubgraphSchema(schema));
25
+ }
26
+ });
27
+ return errorCollector;
28
+ }
29
+
15
30
  /**
16
31
  * Removes a feature from the set of known features.
17
32
  *
@@ -117,6 +117,11 @@ export abstract class FeatureDefinition {
117
117
  .concat(this.typeSpecs().map((spec) => spec.name));
118
118
  }
119
119
 
120
+ // No-op implementation that can be overridden by subclasses.
121
+ validateSubgraphSchema(_schema: Schema): GraphQLError[] {
122
+ return [];
123
+ }
124
+
120
125
  protected nameInSchema(schema: Schema): string | undefined {
121
126
  const feature = this.featureInSchema(schema);
122
127
  return feature?.nameInSchema;
@@ -750,7 +755,14 @@ export class FeatureUrl {
750
755
  public readonly element?: string,
751
756
  ) { }
752
757
 
753
- /// Parse a spec URL or throw
758
+ public static maybeParse(input: string, node?: ASTNode): FeatureUrl | undefined {
759
+ try {
760
+ return FeatureUrl.parse(input, node);
761
+ } catch (err) {
762
+ return undefined;
763
+ }
764
+ }
765
+ /// Parse a spec URL or throw
754
766
  public static parse(input: string, node?: ASTNode): FeatureUrl {
755
767
  const url = new URL(input)
756
768
  if (!url.pathname || url.pathname === '/') {
@@ -18,6 +18,7 @@ import { INACCESSIBLE_VERSIONS } from "./inaccessibleSpec";
18
18
  import { AUTHENTICATED_VERSIONS } from "./authenticatedSpec";
19
19
  import { REQUIRES_SCOPES_VERSIONS } from "./requiresScopesSpec";
20
20
  import { POLICY_VERSIONS } from './policySpec';
21
+ import { SOURCE_VERSIONS } from './sourceSpec';
21
22
 
22
23
  export const federationIdentity = 'https://specs.apollo.dev/federation';
23
24
 
@@ -40,6 +41,9 @@ export enum FederationDirectiveName {
40
41
  AUTHENTICATED = 'authenticated',
41
42
  REQUIRES_SCOPES = 'requiresScopes',
42
43
  POLICY = 'policy',
44
+ SOURCE_API = 'sourceAPI',
45
+ SOURCE_TYPE = 'sourceType',
46
+ SOURCE_FIELD = 'sourceField',
43
47
  }
44
48
 
45
49
  const fieldSetTypeSpec = createScalarTypeSpecification({ name: FederationTypeName.FIELD_SET });
@@ -124,11 +128,22 @@ export class FederationSpecDefinition extends FeatureDefinition {
124
128
 
125
129
  this.registerSubFeature(INACCESSIBLE_VERSIONS.getMinimumRequiredVersion(version));
126
130
 
127
- this.registerDirective(createDirectiveSpecification({
128
- name: FederationDirectiveName.OVERRIDE,
129
- locations: [DirectiveLocation.FIELD_DEFINITION],
130
- args: [{ name: 'from', type: (schema) => new NonNullType(schema.stringType()) }],
131
- }));
131
+ if (version >= (new FeatureVersion(2, 7))) {
132
+ this.registerDirective(createDirectiveSpecification({
133
+ name: FederationDirectiveName.OVERRIDE,
134
+ locations: [DirectiveLocation.FIELD_DEFINITION],
135
+ args: [
136
+ { name: 'from', type: (schema) => new NonNullType(schema.stringType()) },
137
+ { name: 'label', type: (schema) => schema.stringType() },
138
+ ],
139
+ }));
140
+ } else {
141
+ this.registerDirective(createDirectiveSpecification({
142
+ name: FederationDirectiveName.OVERRIDE,
143
+ locations: [DirectiveLocation.FIELD_DEFINITION],
144
+ args: [{ name: 'from', type: (schema) => new NonNullType(schema.stringType()) }],
145
+ }));
146
+ }
132
147
 
133
148
  if (version.gte(new FeatureVersion(2, 1))) {
134
149
  this.registerDirective(createDirectiveSpecification({
@@ -155,6 +170,10 @@ export class FederationSpecDefinition extends FeatureDefinition {
155
170
  if (version.gte(new FeatureVersion(2, 6))) {
156
171
  this.registerSubFeature(POLICY_VERSIONS.find(new FeatureVersion(0, 1))!);
157
172
  }
173
+
174
+ if (version.gte(new FeatureVersion(2, 7))) {
175
+ this.registerSubFeature(SOURCE_VERSIONS.find(new FeatureVersion(0, 1))!);
176
+ }
158
177
  }
159
178
  }
160
179
 
@@ -165,6 +184,7 @@ export const FEDERATION_VERSIONS = new FeatureDefinitions<FederationSpecDefiniti
165
184
  .add(new FederationSpecDefinition(new FeatureVersion(2, 3)))
166
185
  .add(new FederationSpecDefinition(new FeatureVersion(2, 4)))
167
186
  .add(new FederationSpecDefinition(new FeatureVersion(2, 5)))
168
- .add(new FederationSpecDefinition(new FeatureVersion(2, 6)));
187
+ .add(new FederationSpecDefinition(new FeatureVersion(2, 6)))
188
+ .add(new FederationSpecDefinition(new FeatureVersion(2, 7)));
169
189
 
170
190
  registerKnownFeature(FEDERATION_VERSIONS);
@@ -6,6 +6,7 @@ import {
6
6
  ScalarType,
7
7
  Schema,
8
8
  NonNullType,
9
+ ListType,
9
10
  } from "../definitions";
10
11
  import { Subgraph, Subgraphs } from "../federation";
11
12
  import { registerKnownFeature } from '../knownCoreFeatures';
@@ -46,8 +47,15 @@ export type JoinFieldDirectiveArguments = {
46
47
  type?: string,
47
48
  external?: boolean,
48
49
  usedOverridden?: boolean,
50
+ overrideLabel?: string,
49
51
  }
50
52
 
53
+ export type JoinDirectiveArguments = {
54
+ graphs: string[],
55
+ name: string,
56
+ args?: Record<string, any>,
57
+ };
58
+
51
59
  export class JoinSpecDefinition extends FeatureDefinition {
52
60
  constructor(version: FeatureVersion, minimumFederationVersion?: FeatureVersion) {
53
61
  super(new FeatureUrl(joinIdentity, 'join', version), minimumFederationVersion);
@@ -126,6 +134,27 @@ export class JoinSpecDefinition extends FeatureDefinition {
126
134
  joinEnumValue.addArgument('graph', new NonNullType(graphEnum));
127
135
  }
128
136
 
137
+ if (this.version.gte(new FeatureVersion(0, 4))) {
138
+ const joinDirective = this.addDirective(schema, 'directive').addLocations(
139
+ DirectiveLocation.SCHEMA,
140
+ DirectiveLocation.OBJECT,
141
+ DirectiveLocation.INTERFACE,
142
+ DirectiveLocation.FIELD_DEFINITION,
143
+ );
144
+ joinDirective.repeatable = true;
145
+ // Note this 'graphs' argument is plural, since the same directive
146
+ // application can appear on the same schema element in multiple subgraphs.
147
+ // Repetition of a graph in this 'graphs' list is allowed, and corresponds
148
+ // to repeated application of the same directive in the same subgraph, which
149
+ // is allowed.
150
+ joinDirective.addArgument('graphs', new ListType(new NonNullType(graphEnum)));
151
+ joinDirective.addArgument('name', new NonNullType(schema.stringType()));
152
+ joinDirective.addArgument('args', this.addScalarType(schema, 'DirectiveArguments'));
153
+
154
+ //progressive override
155
+ joinField.addArgument('overrideLabel', schema.stringType());
156
+ }
157
+
129
158
  if (this.isV01()) {
130
159
  const joinOwner = this.addDirective(schema, 'owner').addLocations(DirectiveLocation.OBJECT);
131
160
  joinOwner.addArgument('graph', new NonNullType(graphEnum));
@@ -192,6 +221,10 @@ export class JoinSpecDefinition extends FeatureDefinition {
192
221
  return this.directive(schema, 'graph')!;
193
222
  }
194
223
 
224
+ directiveDirective(schema: Schema): DirectiveDefinition<JoinDirectiveArguments> {
225
+ return this.directive(schema, 'directive')!;
226
+ }
227
+
195
228
  typeDirective(schema: Schema): DirectiveDefinition<JoinTypeDirectiveArguments> {
196
229
  return this.directive(schema, 'type')!;
197
230
  }
@@ -227,9 +260,11 @@ export class JoinSpecDefinition extends FeatureDefinition {
227
260
  // for federation 2 in general.
228
261
  // - 0.2: this is the original version released with federation 2.
229
262
  // - 0.3: adds the `isInterfaceObject` argument to `@join__type`, and make the `graph` in `@join__field` skippable.
263
+ // - 0.4: adds the optional `overrideLabel` argument to `@join_field` for progressive override.
230
264
  export const JOIN_VERSIONS = new FeatureDefinitions<JoinSpecDefinition>(joinIdentity)
231
265
  .add(new JoinSpecDefinition(new FeatureVersion(0, 1)))
232
266
  .add(new JoinSpecDefinition(new FeatureVersion(0, 2)))
233
- .add(new JoinSpecDefinition(new FeatureVersion(0, 3), new FeatureVersion(2, 0)));
267
+ .add(new JoinSpecDefinition(new FeatureVersion(0, 3), new FeatureVersion(2, 0)))
268
+ .add(new JoinSpecDefinition(new FeatureVersion(0, 4), new FeatureVersion(2, 7)));
234
269
 
235
270
  registerKnownFeature(JOIN_VERSIONS);