@apollo/federation-internals 2.6.3 → 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.
- package/dist/error.d.ts +19 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +38 -0
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +9 -6
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +7 -2
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +25 -3
- package/dist/federation.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/knownCoreFeatures.d.ts +3 -0
- package/dist/knownCoreFeatures.d.ts.map +1 -1
- package/dist/knownCoreFeatures.js +12 -1
- package/dist/knownCoreFeatures.js.map +1 -1
- package/dist/specs/coreSpec.d.ts +2 -0
- package/dist/specs/coreSpec.d.ts.map +1 -1
- package/dist/specs/coreSpec.js +11 -0
- package/dist/specs/coreSpec.js.map +1 -1
- package/dist/specs/federationSpec.d.ts +4 -1
- package/dist/specs/federationSpec.d.ts.map +1 -1
- package/dist/specs/federationSpec.js +26 -6
- package/dist/specs/federationSpec.js.map +1 -1
- package/dist/specs/joinSpec.d.ts +7 -0
- package/dist/specs/joinSpec.d.ts.map +1 -1
- package/dist/specs/joinSpec.js +13 -1
- package/dist/specs/joinSpec.js.map +1 -1
- package/dist/specs/sourceSpec.d.ts +68 -0
- package/dist/specs/sourceSpec.d.ts.map +1 -0
- package/dist/specs/sourceSpec.js +345 -0
- package/dist/specs/sourceSpec.js.map +1 -0
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +1 -0
- package/dist/supergraphs.js.map +1 -1
- package/package.json +1 -1
- package/src/error.ts +132 -0
- package/src/extractSubgraphsFromSupergraph.ts +9 -6
- package/src/federation.ts +42 -8
- package/src/index.ts +1 -0
- package/src/knownCoreFeatures.ts +16 -1
- package/src/specs/coreSpec.ts +13 -1
- package/src/specs/federationSpec.ts +26 -6
- package/src/specs/joinSpec.ts +36 -1
- package/src/specs/sourceSpec.ts +598 -0
- 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
|
|
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 (!
|
|
424
|
+
if (!joinFieldArgs.graph) {
|
|
425
425
|
continue;
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
const { type: subgraphType, subgraph } = subgraphsInfo.get(
|
|
429
|
-
addSubgraphField({ field, type: subgraphType, subgraph, isShareable, joinFieldArgs
|
|
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(), {
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
package/src/knownCoreFeatures.ts
CHANGED
|
@@ -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
|
|
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
|
*
|
package/src/specs/coreSpec.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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);
|
package/src/specs/joinSpec.ts
CHANGED
|
@@ -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);
|