@apollo/federation-internals 2.4.10 → 2.5.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/argumentCompositionStrategies.d.ts +12 -7
- package/dist/argumentCompositionStrategies.d.ts.map +1 -1
- package/dist/argumentCompositionStrategies.js +26 -7
- package/dist/argumentCompositionStrategies.js.map +1 -1
- package/dist/authenticatedSpec.d.ts +13 -0
- package/dist/authenticatedSpec.d.ts.map +1 -0
- package/dist/authenticatedSpec.js +36 -0
- package/dist/authenticatedSpec.js.map +1 -0
- package/dist/coreSpec.d.ts +6 -5
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +42 -32
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +2 -3
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +12 -7
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +8 -8
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +21 -16
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +450 -295
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +10 -5
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +58 -16
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +3 -1
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +12 -3
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +1 -1
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +4 -4
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/joinSpec.d.ts +19 -17
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +3 -3
- package/dist/joinSpec.js.map +1 -1
- package/dist/requiresScopesSpec.d.ts +16 -0
- package/dist/requiresScopesSpec.d.ts.map +1 -0
- package/dist/requiresScopesSpec.js +55 -0
- package/dist/requiresScopesSpec.js.map +1 -0
- package/dist/supergraphs.d.ts +19 -4
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +40 -14
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts +1 -1
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +4 -4
- package/dist/tagSpec.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +11 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/argumentCompositionStrategies.ts +32 -9
- package/src/authenticatedSpec.ts +62 -0
- package/src/coreSpec.ts +57 -35
- package/src/definitions.ts +22 -9
- package/src/directiveAndTypeSpecification.ts +25 -24
- package/src/error.ts +2 -2
- package/src/extractSubgraphsFromSupergraph.ts +647 -393
- package/src/federation.ts +95 -16
- package/src/federationSpec.ts +13 -5
- package/src/inaccessibleSpec.ts +4 -4
- package/src/index.ts +2 -1
- package/src/joinSpec.ts +23 -13
- package/src/precompute.ts +1 -1
- package/src/requiresScopesSpec.ts +76 -0
- package/src/supergraphs.ts +64 -16
- package/src/tagSpec.ts +4 -4
- package/src/utils.ts +10 -0
package/src/federation.ts
CHANGED
|
@@ -68,6 +68,9 @@ import {
|
|
|
68
68
|
linkDirectiveDefaultName,
|
|
69
69
|
linkIdentity,
|
|
70
70
|
FeatureUrl,
|
|
71
|
+
CoreImport,
|
|
72
|
+
extractCoreFeatureImports,
|
|
73
|
+
CoreOrLinkDirectiveArgs,
|
|
71
74
|
} from "./coreSpec";
|
|
72
75
|
import {
|
|
73
76
|
FEDERATION_VERSIONS,
|
|
@@ -86,6 +89,9 @@ import { joinIdentity } from "./joinSpec";
|
|
|
86
89
|
const linkSpec = LINK_VERSIONS.latest();
|
|
87
90
|
const tagSpec = TAG_VERSIONS.latest();
|
|
88
91
|
const federationSpec = FEDERATION_VERSIONS.latest();
|
|
92
|
+
// Some users rely on auto-expanding fed v1 graphs with fed v2 directives. While technically we should only expand @tag
|
|
93
|
+
// directive from v2 definitions, we will continue expanding other directives (up to v2.4) to ensure backwards compatibility.
|
|
94
|
+
const autoExpandedFederationSpec = FEDERATION_VERSIONS.find(new FeatureVersion(2, 4))!;
|
|
89
95
|
|
|
90
96
|
// We don't let user use this as a subgraph name. That allows us to use it in `query graphs` to name the source of roots
|
|
91
97
|
// in the "federated query graph" without worrying about conflict (see `FEDERATED_GRAPH_ROOT_SOURCE` in `querygraph.ts`).
|
|
@@ -112,6 +118,24 @@ const FEDERATION_SPECIFIC_VALIDATION_RULES = [
|
|
|
112
118
|
const FEDERATION_VALIDATION_RULES = specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES);
|
|
113
119
|
|
|
114
120
|
const ALL_DEFAULT_FEDERATION_DIRECTIVE_NAMES: string[] = Object.values(FederationDirectiveName);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Federation 1 has that specificity that it wasn't using @link to name-space federation elements,
|
|
124
|
+
* and so to "distinguish" the few federation type names, it prefixed those with a `_`. That is,
|
|
125
|
+
* the `FieldSet` type was named `_FieldSet` in federation1. To handle this without too much effort,
|
|
126
|
+
* we use a fake `CoreFeature` with imports for all the fed1 types to use those specific "aliases"
|
|
127
|
+
* and we pass it when adding those types. This allows to reuse the same `TypeSpecification` objects
|
|
128
|
+
* for both fed1 and fed2. Note that in the object below, all that is used is the imports, the rest
|
|
129
|
+
* is just filling the blanks.
|
|
130
|
+
*/
|
|
131
|
+
const FAKE_FED1_CORE_FEATURE_TO_RENAME_TYPES: CoreFeature = new CoreFeature(
|
|
132
|
+
new FeatureUrl('<fed1>', 'fed1', new FeatureVersion(0, 1)),
|
|
133
|
+
'fed1',
|
|
134
|
+
new Directive('fed1'),
|
|
135
|
+
FEDERATION1_TYPES.map((spec) => ({ name: spec.name, as: '_' + spec.name})),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
|
|
115
139
|
function validateFieldSetSelections({
|
|
116
140
|
directiveName,
|
|
117
141
|
selectionSet,
|
|
@@ -738,6 +762,14 @@ export class FederationMetadata {
|
|
|
738
762
|
return this.getPost20FederationDirective(FederationDirectiveName.INTERFACE_OBJECT);
|
|
739
763
|
}
|
|
740
764
|
|
|
765
|
+
authenticatedDirective(): Post20FederationDirectiveDefinition<{}> {
|
|
766
|
+
return this.getPost20FederationDirective(FederationDirectiveName.AUTHENTICATED);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
requiresScopesDirective(): Post20FederationDirectiveDefinition<{scopes: string[]}> {
|
|
770
|
+
return this.getPost20FederationDirective(FederationDirectiveName.REQUIRES_SCOPES);
|
|
771
|
+
}
|
|
772
|
+
|
|
741
773
|
allFederationDirectives(): DirectiveDefinition[] {
|
|
742
774
|
const baseDirectives: DirectiveDefinition[] = [
|
|
743
775
|
this.keyDirective(),
|
|
@@ -763,6 +795,16 @@ export class FederationMetadata {
|
|
|
763
795
|
baseDirectives.push(interfaceObjectDirective);
|
|
764
796
|
}
|
|
765
797
|
|
|
798
|
+
const authenticatedDirective = this.authenticatedDirective();
|
|
799
|
+
if (isFederationDirectiveDefinedInSchema(authenticatedDirective)) {
|
|
800
|
+
baseDirectives.push(authenticatedDirective);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const requiresScopesDirective = this.requiresScopesDirective();
|
|
804
|
+
if (isFederationDirectiveDefinedInSchema(requiresScopesDirective)) {
|
|
805
|
+
baseDirectives.push(requiresScopesDirective);
|
|
806
|
+
}
|
|
807
|
+
|
|
766
808
|
return baseDirectives;
|
|
767
809
|
}
|
|
768
810
|
|
|
@@ -784,16 +826,33 @@ export class FederationMetadata {
|
|
|
784
826
|
}
|
|
785
827
|
|
|
786
828
|
allFederationTypes(): NamedType[] {
|
|
787
|
-
|
|
829
|
+
// We manually include the `_Any`, `_Service` and `Entity` types because there are not strictly
|
|
830
|
+
// speaking part of the federation @link spec.
|
|
831
|
+
const fedTypes: NamedType[] = [
|
|
788
832
|
this.anyType(),
|
|
789
833
|
this.serviceType(),
|
|
790
|
-
this.fieldSetType(),
|
|
791
834
|
];
|
|
835
|
+
|
|
836
|
+
const fedFeature = this.federationFeature();
|
|
837
|
+
if (fedFeature) {
|
|
838
|
+
const featureDef = FEDERATION_VERSIONS.find(fedFeature.url.version);
|
|
839
|
+
assert(featureDef, () => `Federation spec should be known, but got ${fedFeature.url}`);
|
|
840
|
+
for (const typeSpec of featureDef.typeSpecs()) {
|
|
841
|
+
const type = this.schema.type(fedFeature.typeNameInSchema(typeSpec.name));
|
|
842
|
+
if (type) {
|
|
843
|
+
fedTypes.push(type);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
} else {
|
|
847
|
+
// Fed1: the only type we had was _FieldSet.
|
|
848
|
+
fedTypes.push(this.fieldSetType());
|
|
849
|
+
}
|
|
850
|
+
|
|
792
851
|
const entityType = this.entityType();
|
|
793
852
|
if (entityType) {
|
|
794
|
-
|
|
853
|
+
fedTypes.push(entityType);
|
|
795
854
|
}
|
|
796
|
-
return
|
|
855
|
+
return fedTypes;
|
|
797
856
|
}
|
|
798
857
|
}
|
|
799
858
|
|
|
@@ -831,14 +890,20 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
831
890
|
}
|
|
832
891
|
}
|
|
833
892
|
|
|
834
|
-
onMissingDirectiveDefinition(schema: Schema,
|
|
835
|
-
if (name === linkDirectiveDefaultName) {
|
|
893
|
+
onMissingDirectiveDefinition(schema: Schema, directive: Directive): DirectiveDefinition | GraphQLError[] | undefined {
|
|
894
|
+
if (directive.name === linkDirectiveDefaultName) {
|
|
895
|
+
const args = directive.arguments();
|
|
836
896
|
const url = args && (args['url'] as string | undefined);
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
897
|
+
let as: string | undefined = undefined;
|
|
898
|
+
let imports: CoreImport[] = [];
|
|
899
|
+
if (url && url.startsWith(linkSpec.identity)) {
|
|
900
|
+
as = args['as'] as string | undefined;
|
|
901
|
+
imports = extractCoreFeatureImports(linkSpec.url, directive as Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>);
|
|
902
|
+
}
|
|
903
|
+
const errors = linkSpec.addDefinitionsToSchema(schema, as, imports);
|
|
904
|
+
return errors.length > 0 ? errors : schema.directive(directive.name);
|
|
840
905
|
}
|
|
841
|
-
return super.onMissingDirectiveDefinition(schema,
|
|
906
|
+
return super.onMissingDirectiveDefinition(schema, directive);
|
|
842
907
|
}
|
|
843
908
|
|
|
844
909
|
ignoreParsedField(type: NamedType, fieldName: string): boolean {
|
|
@@ -1073,7 +1138,7 @@ function findUnusedNamedForLinkDirective(schema: Schema): string | undefined {
|
|
|
1073
1138
|
// The schema already defines a directive named `@link` so we need to use an alias.
|
|
1074
1139
|
// To keep it simple, we add a number in the end (so we try `@link1`, and if that's taken `@link2`, ...)
|
|
1075
1140
|
const baseName = linkSpec.url.name;
|
|
1076
|
-
|
|
1141
|
+
const n = 1;
|
|
1077
1142
|
for (;;) {
|
|
1078
1143
|
const candidate = baseName + n;
|
|
1079
1144
|
if (!schema.directive(candidate)) {
|
|
@@ -1106,7 +1171,7 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
|
|
|
1106
1171
|
core.coreItself.nameInSchema,
|
|
1107
1172
|
{
|
|
1108
1173
|
url: federationSpec.url.toString(),
|
|
1109
|
-
import:
|
|
1174
|
+
import: autoExpandedFederationSpec.directiveSpecs().map((spec) => `@${spec.name}`),
|
|
1110
1175
|
}
|
|
1111
1176
|
);
|
|
1112
1177
|
const errors = completeSubgraphSchema(schema);
|
|
@@ -1117,7 +1182,9 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
|
|
|
1117
1182
|
|
|
1118
1183
|
// This is the full @link declaration as added by `asFed2SubgraphDocument`. It's here primarily for uses by tests that print and match
|
|
1119
1184
|
// subgraph schema to avoid having to update 20+ tests every time we use a new directive or the order of import changes ...
|
|
1120
|
-
export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.
|
|
1185
|
+
export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes"])';
|
|
1186
|
+
// This is the full @link declaration that is added when upgrading fed v1 subgraphs to v2 version. It should only be used by tests.
|
|
1187
|
+
export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
|
|
1121
1188
|
|
|
1122
1189
|
/**
|
|
1123
1190
|
* Given a document that is assumed to _not_ be a fed2 schema (it does not have a `@link` to the federation spec),
|
|
@@ -1126,8 +1193,11 @@ export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apo
|
|
|
1126
1193
|
* @param document - the document to "augment".
|
|
1127
1194
|
* @param options.addAsSchemaExtension - defines whethere the added `@link` is added as a schema extension (`extend schema`) or
|
|
1128
1195
|
* added to the schema definition. Defaults to `true` (added as an extension), as this mimics what we tends to write manually.
|
|
1196
|
+
* @param options.includeAllImports - defines whether we should auto import ALL latest federation v2 directive definitions or include
|
|
1197
|
+
* only limited set of directives (i.e. federation v2.4 definitions)
|
|
1129
1198
|
*/
|
|
1130
|
-
export function asFed2SubgraphDocument(document: DocumentNode, options?: { addAsSchemaExtension
|
|
1199
|
+
export function asFed2SubgraphDocument(document: DocumentNode, options?: { addAsSchemaExtension?: boolean, includeAllImports?: boolean }): DocumentNode {
|
|
1200
|
+
const importedDirectives = options?.includeAllImports ? federationSpec.directiveSpecs() : autoExpandedFederationSpec.directiveSpecs();
|
|
1131
1201
|
const directiveToAdd: ConstDirectiveNode = ({
|
|
1132
1202
|
kind: Kind.DIRECTIVE,
|
|
1133
1203
|
name: { kind: Kind.NAME, value: linkDirectiveDefaultName },
|
|
@@ -1140,7 +1210,7 @@ export function asFed2SubgraphDocument(document: DocumentNode, options?: { addAs
|
|
|
1140
1210
|
{
|
|
1141
1211
|
kind: Kind.ARGUMENT,
|
|
1142
1212
|
name: { kind: Kind.NAME, value: 'import' },
|
|
1143
|
-
value: { kind: Kind.LIST, values:
|
|
1213
|
+
value: { kind: Kind.LIST, values: importedDirectives.map((spec) => ({ kind: Kind.STRING, value: `@${spec.name}` })) }
|
|
1144
1214
|
}
|
|
1145
1215
|
]
|
|
1146
1216
|
});
|
|
@@ -1343,7 +1413,7 @@ function completeFed1SubgraphSchema(schema: Schema): GraphQLError[] {
|
|
|
1343
1413
|
}
|
|
1344
1414
|
}
|
|
1345
1415
|
|
|
1346
|
-
const errors = FEDERATION1_TYPES.map((spec) => spec.checkOrAdd(schema,
|
|
1416
|
+
const errors = FEDERATION1_TYPES.map((spec) => spec.checkOrAdd(schema, FAKE_FED1_CORE_FEATURE_TO_RENAME_TYPES))
|
|
1347
1417
|
.concat(FEDERATION1_DIRECTIVES.map((spec) => spec.checkOrAdd(schema)))
|
|
1348
1418
|
.flat();
|
|
1349
1419
|
|
|
@@ -1681,6 +1751,15 @@ export class Subgraph {
|
|
|
1681
1751
|
}
|
|
1682
1752
|
}
|
|
1683
1753
|
|
|
1754
|
+
/**
|
|
1755
|
+
* Same as `Schema.assumeValid`. Use carefully.
|
|
1756
|
+
*/
|
|
1757
|
+
assumeValid(): Subgraph {
|
|
1758
|
+
this.addFederationOperations();
|
|
1759
|
+
this.schema.assumeValid();
|
|
1760
|
+
return this;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1684
1763
|
validate(): Subgraph {
|
|
1685
1764
|
try {
|
|
1686
1765
|
this.addFederationOperations();
|
package/src/federationSpec.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { TAG_VERSIONS } from "./tagSpec";
|
|
|
15
15
|
import { federationMetadata } from "./federation";
|
|
16
16
|
import { registerKnownFeature } from "./knownCoreFeatures";
|
|
17
17
|
import { INACCESSIBLE_VERSIONS } from "./inaccessibleSpec";
|
|
18
|
+
import { AUTHENTICATED_VERSIONS } from "./authenticatedSpec";
|
|
19
|
+
import { REQUIRES_SCOPES_VERSIONS } from "./requiresScopesSpec";
|
|
18
20
|
|
|
19
21
|
export const federationIdentity = 'https://specs.apollo.dev/federation';
|
|
20
22
|
|
|
@@ -34,6 +36,8 @@ export enum FederationDirectiveName {
|
|
|
34
36
|
INACCESSIBLE = 'inaccessible',
|
|
35
37
|
COMPOSE_DIRECTIVE = 'composeDirective',
|
|
36
38
|
INTERFACE_OBJECT = 'interfaceObject',
|
|
39
|
+
AUTHENTICATED = 'authenticated',
|
|
40
|
+
REQUIRES_SCOPES = 'requiresScopes',
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
const fieldSetTypeSpec = createScalarTypeSpecification({ name: FederationTypeName.FIELD_SET });
|
|
@@ -116,7 +120,7 @@ export class FederationSpecDefinition extends FeatureDefinition {
|
|
|
116
120
|
repeatable: version >= (new FeatureVersion(2, 2)),
|
|
117
121
|
}));
|
|
118
122
|
|
|
119
|
-
this.
|
|
123
|
+
this.registerSubFeature(INACCESSIBLE_VERSIONS.getMinimumRequiredVersion(version));
|
|
120
124
|
|
|
121
125
|
this.registerDirective(createDirectiveSpecification({
|
|
122
126
|
name: FederationDirectiveName.OVERRIDE,
|
|
@@ -138,9 +142,12 @@ export class FederationSpecDefinition extends FeatureDefinition {
|
|
|
138
142
|
name: FederationDirectiveName.INTERFACE_OBJECT,
|
|
139
143
|
locations: [DirectiveLocation.OBJECT],
|
|
140
144
|
}));
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
|
|
145
|
+
this.registerSubFeature(TAG_VERSIONS.find(new FeatureVersion(0, 3))!);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (version >= (new FeatureVersion(2, 5))) {
|
|
149
|
+
this.registerSubFeature(AUTHENTICATED_VERSIONS.find(new FeatureVersion(0, 1))!);
|
|
150
|
+
this.registerSubFeature(REQUIRES_SCOPES_VERSIONS.find(new FeatureVersion(0, 1))!);
|
|
144
151
|
}
|
|
145
152
|
}
|
|
146
153
|
}
|
|
@@ -150,6 +157,7 @@ export const FEDERATION_VERSIONS = new FeatureDefinitions<FederationSpecDefiniti
|
|
|
150
157
|
.add(new FederationSpecDefinition(new FeatureVersion(2, 1)))
|
|
151
158
|
.add(new FederationSpecDefinition(new FeatureVersion(2, 2)))
|
|
152
159
|
.add(new FederationSpecDefinition(new FeatureVersion(2, 3)))
|
|
153
|
-
.add(new FederationSpecDefinition(new FeatureVersion(2, 4)))
|
|
160
|
+
.add(new FederationSpecDefinition(new FeatureVersion(2, 4)))
|
|
161
|
+
.add(new FederationSpecDefinition(new FeatureVersion(2, 5)));
|
|
154
162
|
|
|
155
163
|
registerKnownFeature(FEDERATION_VERSIONS);
|
package/src/inaccessibleSpec.ts
CHANGED
|
@@ -39,8 +39,8 @@ export class InaccessibleSpecDefinition extends FeatureDefinition {
|
|
|
39
39
|
public readonly inaccessibleDirectiveSpec: DirectiveSpecification;
|
|
40
40
|
private readonly printedInaccessibleDefinition: string;
|
|
41
41
|
|
|
42
|
-
constructor(version: FeatureVersion) {
|
|
43
|
-
super(new FeatureUrl(inaccessibleIdentity, 'inaccessible', version));
|
|
42
|
+
constructor(version: FeatureVersion, minimumFederationVersion?: FeatureVersion) {
|
|
43
|
+
super(new FeatureUrl(inaccessibleIdentity, 'inaccessible', version), minimumFederationVersion);
|
|
44
44
|
this.inaccessibleLocations = [
|
|
45
45
|
DirectiveLocation.FIELD_DEFINITION,
|
|
46
46
|
DirectiveLocation.OBJECT,
|
|
@@ -63,7 +63,7 @@ export class InaccessibleSpecDefinition extends FeatureDefinition {
|
|
|
63
63
|
name: 'inaccessible',
|
|
64
64
|
locations: this.inaccessibleLocations,
|
|
65
65
|
composes: true,
|
|
66
|
-
supergraphSpecification: () => INACCESSIBLE_VERSIONS.
|
|
66
|
+
supergraphSpecification: (fedVersion) => INACCESSIBLE_VERSIONS.getMinimumRequiredVersion(fedVersion),
|
|
67
67
|
});
|
|
68
68
|
this.registerDirective(this.inaccessibleDirectiveSpec);
|
|
69
69
|
}
|
|
@@ -95,7 +95,7 @@ export class InaccessibleSpecDefinition extends FeatureDefinition {
|
|
|
95
95
|
|
|
96
96
|
export const INACCESSIBLE_VERSIONS = new FeatureDefinitions<InaccessibleSpecDefinition>(inaccessibleIdentity)
|
|
97
97
|
.add(new InaccessibleSpecDefinition(new FeatureVersion(0, 1)))
|
|
98
|
-
.add(new InaccessibleSpecDefinition(new FeatureVersion(0, 2)));
|
|
98
|
+
.add(new InaccessibleSpecDefinition(new FeatureVersion(0, 2), new FeatureVersion(2, 0)));
|
|
99
99
|
|
|
100
100
|
registerKnownFeature(INACCESSIBLE_VERSIONS);
|
|
101
101
|
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,6 @@ export * from './tagSpec';
|
|
|
13
13
|
export * from './inaccessibleSpec';
|
|
14
14
|
export * from './federationSpec';
|
|
15
15
|
export * from './supergraphs';
|
|
16
|
-
export * from './extractSubgraphsFromSupergraph';
|
|
17
16
|
export * from './error';
|
|
18
17
|
export * from './schemaUpgrader';
|
|
19
18
|
export * from './suggestions';
|
|
@@ -21,3 +20,5 @@ export * from './graphQLJSSchemaToAST';
|
|
|
21
20
|
export * from './directiveAndTypeSpecification';
|
|
22
21
|
export { coreFeatureDefinitionIfKnown } from './knownCoreFeatures';
|
|
23
22
|
export * from './argumentCompositionStrategies';
|
|
23
|
+
export * from './authenticatedSpec';
|
|
24
|
+
export * from './requiresScopesSpec';
|
package/src/joinSpec.ts
CHANGED
|
@@ -30,9 +30,27 @@ function sanitizeGraphQLName(name: string) {
|
|
|
30
30
|
return toUpper;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export type JoinTypeDirectiveArguments = {
|
|
34
|
+
graph: string,
|
|
35
|
+
key?: string,
|
|
36
|
+
extension?: boolean,
|
|
37
|
+
resolvable?: boolean,
|
|
38
|
+
isInterfaceObject?: boolean
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type JoinFieldDirectiveArguments = {
|
|
42
|
+
graph?: string,
|
|
43
|
+
requires?: string,
|
|
44
|
+
provides?: string,
|
|
45
|
+
override?: string,
|
|
46
|
+
type?: string,
|
|
47
|
+
external?: boolean,
|
|
48
|
+
usedOverridden?: boolean,
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
export class JoinSpecDefinition extends FeatureDefinition {
|
|
34
|
-
constructor(version: FeatureVersion) {
|
|
35
|
-
super(new FeatureUrl(joinIdentity, 'join', version));
|
|
52
|
+
constructor(version: FeatureVersion, minimumFederationVersion?: FeatureVersion) {
|
|
53
|
+
super(new FeatureUrl(joinIdentity, 'join', version), minimumFederationVersion);
|
|
36
54
|
}
|
|
37
55
|
|
|
38
56
|
private isV01() {
|
|
@@ -174,7 +192,7 @@ export class JoinSpecDefinition extends FeatureDefinition {
|
|
|
174
192
|
return this.directive(schema, 'graph')!;
|
|
175
193
|
}
|
|
176
194
|
|
|
177
|
-
typeDirective(schema: Schema): DirectiveDefinition<
|
|
195
|
+
typeDirective(schema: Schema): DirectiveDefinition<JoinTypeDirectiveArguments> {
|
|
178
196
|
return this.directive(schema, 'type')!;
|
|
179
197
|
}
|
|
180
198
|
|
|
@@ -182,15 +200,7 @@ export class JoinSpecDefinition extends FeatureDefinition {
|
|
|
182
200
|
return this.directive(schema, 'implements');
|
|
183
201
|
}
|
|
184
202
|
|
|
185
|
-
fieldDirective(schema: Schema): DirectiveDefinition<{
|
|
186
|
-
graph?: string,
|
|
187
|
-
requires?: string,
|
|
188
|
-
provides?: string,
|
|
189
|
-
override?: string,
|
|
190
|
-
type?: string,
|
|
191
|
-
external?: boolean,
|
|
192
|
-
usedOverridden?: boolean,
|
|
193
|
-
}> {
|
|
203
|
+
fieldDirective(schema: Schema): DirectiveDefinition<JoinFieldDirectiveArguments> {
|
|
194
204
|
return this.directive(schema, 'field')!;
|
|
195
205
|
}
|
|
196
206
|
|
|
@@ -220,6 +230,6 @@ export class JoinSpecDefinition extends FeatureDefinition {
|
|
|
220
230
|
export const JOIN_VERSIONS = new FeatureDefinitions<JoinSpecDefinition>(joinIdentity)
|
|
221
231
|
.add(new JoinSpecDefinition(new FeatureVersion(0, 1)))
|
|
222
232
|
.add(new JoinSpecDefinition(new FeatureVersion(0, 2)))
|
|
223
|
-
.add(new JoinSpecDefinition(new FeatureVersion(0, 3)));
|
|
233
|
+
.add(new JoinSpecDefinition(new FeatureVersion(0, 3), new FeatureVersion(2, 0)));
|
|
224
234
|
|
|
225
235
|
registerKnownFeature(JOIN_VERSIONS);
|
package/src/precompute.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function computeShareables(schema: Schema): (field: FieldDefinition<Compo
|
|
|
20
20
|
// by default are key fields).
|
|
21
21
|
const shareableDirective = metadata.isFed2Schema() ? metadata.shareableDirective() : undefined;
|
|
22
22
|
|
|
23
|
-
const shareableFields: Set<
|
|
23
|
+
const shareableFields: Set<string> = new Set();
|
|
24
24
|
const addKeyFields = (type: CompositeType) => {
|
|
25
25
|
for (const key of type.appliedDirectivesOf(keyDirective)) {
|
|
26
26
|
collectTargetFields({
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { DirectiveLocation } from "graphql";
|
|
2
|
+
import {
|
|
3
|
+
CorePurpose,
|
|
4
|
+
FeatureDefinition,
|
|
5
|
+
FeatureDefinitions,
|
|
6
|
+
FeatureUrl,
|
|
7
|
+
FeatureVersion,
|
|
8
|
+
} from "./coreSpec";
|
|
9
|
+
import { DirectiveDefinition, ListType, NonNullType, Schema } from "./definitions";
|
|
10
|
+
import { createDirectiveSpecification, createScalarTypeSpecification } from "./directiveAndTypeSpecification";
|
|
11
|
+
import { registerKnownFeature } from "./knownCoreFeatures";
|
|
12
|
+
import { ARGUMENT_COMPOSITION_STRATEGIES } from "./argumentCompositionStrategies";
|
|
13
|
+
import { assert } from "./utils";
|
|
14
|
+
|
|
15
|
+
export enum RequiresScopesTypeName {
|
|
16
|
+
SCOPE = 'Scope',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
20
|
+
public static readonly directiveName = "requiresScopes";
|
|
21
|
+
public static readonly identity =
|
|
22
|
+
`https://specs.apollo.dev/${RequiresScopesSpecDefinition.directiveName}`;
|
|
23
|
+
|
|
24
|
+
constructor(version: FeatureVersion) {
|
|
25
|
+
super(
|
|
26
|
+
new FeatureUrl(
|
|
27
|
+
RequiresScopesSpecDefinition.identity,
|
|
28
|
+
RequiresScopesSpecDefinition.directiveName,
|
|
29
|
+
version,
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
this.registerType(createScalarTypeSpecification({ name: RequiresScopesTypeName.SCOPE }));
|
|
34
|
+
|
|
35
|
+
this.registerDirective(createDirectiveSpecification({
|
|
36
|
+
name: RequiresScopesSpecDefinition.directiveName,
|
|
37
|
+
args: [{
|
|
38
|
+
name: 'scopes',
|
|
39
|
+
type: (schema, feature) => {
|
|
40
|
+
assert(feature, "Shouldn't be added without being attached to a @link spec");
|
|
41
|
+
const scopeName = feature.typeNameInSchema(RequiresScopesTypeName.SCOPE);
|
|
42
|
+
const scopeType = schema.type(scopeName);
|
|
43
|
+
assert(scopeType, () => `Expected "${scopeName}" to be defined`);
|
|
44
|
+
return new NonNullType(new ListType(new NonNullType(scopeType)));
|
|
45
|
+
},
|
|
46
|
+
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.UNION,
|
|
47
|
+
}],
|
|
48
|
+
locations: [
|
|
49
|
+
DirectiveLocation.FIELD_DEFINITION,
|
|
50
|
+
DirectiveLocation.OBJECT,
|
|
51
|
+
DirectiveLocation.INTERFACE,
|
|
52
|
+
DirectiveLocation.SCALAR,
|
|
53
|
+
DirectiveLocation.ENUM,
|
|
54
|
+
],
|
|
55
|
+
composes: true,
|
|
56
|
+
supergraphSpecification: () => REQUIRES_SCOPES_VERSIONS.latest(),
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
requiresScopesDirective(
|
|
61
|
+
schema: Schema
|
|
62
|
+
): DirectiveDefinition<{ name: string }> {
|
|
63
|
+
return this.directive(schema, RequiresScopesSpecDefinition.directiveName)!;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get defaultCorePurpose(): CorePurpose {
|
|
67
|
+
return 'SECURITY';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const REQUIRES_SCOPES_VERSIONS =
|
|
72
|
+
new FeatureDefinitions<RequiresScopesSpecDefinition>(
|
|
73
|
+
RequiresScopesSpecDefinition.identity
|
|
74
|
+
).add(new RequiresScopesSpecDefinition(new FeatureVersion(0, 1)));
|
|
75
|
+
|
|
76
|
+
registerKnownFeature(REQUIRES_SCOPES_VERSIONS);
|
package/src/supergraphs.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { ErrCoreCheckFailed, FeatureUrl, FeatureVersion } from "./coreSpec";
|
|
|
3
3
|
import { CoreFeatures, Schema, sourceASTs } from "./definitions";
|
|
4
4
|
import { joinIdentity, JoinSpecDefinition, JOIN_VERSIONS } from "./joinSpec";
|
|
5
5
|
import { buildSchema, buildSchemaFromAST } from "./buildSchema";
|
|
6
|
-
import { extractSubgraphsNamesAndUrlsFromSupergraph } from "./extractSubgraphsFromSupergraph";
|
|
6
|
+
import { extractSubgraphsNamesAndUrlsFromSupergraph, extractSubgraphsFromSupergraph } from "./extractSubgraphsFromSupergraph";
|
|
7
7
|
import { ERRORS } from "./error";
|
|
8
|
+
import { Subgraphs } from ".";
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
+
export const DEFAULT_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
|
|
10
11
|
'https://specs.apollo.dev/core/v0.1',
|
|
11
12
|
'https://specs.apollo.dev/core/v0.2',
|
|
12
13
|
'https://specs.apollo.dev/join/v0.1',
|
|
@@ -21,24 +22,12 @@ const SUPPORTED_FEATURES = new Set([
|
|
|
21
22
|
|
|
22
23
|
const coreVersionZeroDotOneUrl = FeatureUrl.parse('https://specs.apollo.dev/core/v0.1');
|
|
23
24
|
|
|
24
|
-
export function buildSupergraphSchema(supergraphSdl: string | DocumentNode): [Schema, {name: string, url: string}[]] {
|
|
25
|
-
// We delay validation because `checkFeatureSupport` gives slightly more useful errors if, say, 'for' is used with core v0.1.
|
|
26
|
-
const schema = typeof supergraphSdl === 'string'
|
|
27
|
-
? buildSchema(supergraphSdl, { validate: false })
|
|
28
|
-
: buildSchemaFromAST(supergraphSdl, { validate: false });
|
|
29
|
-
|
|
30
|
-
const [coreFeatures] = validateSupergraph(schema);
|
|
31
|
-
checkFeatureSupport(coreFeatures);
|
|
32
|
-
schema.validate();
|
|
33
|
-
return [schema, extractSubgraphsNamesAndUrlsFromSupergraph(schema)];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
25
|
/**
|
|
37
26
|
* Checks that only our hard-coded list of features are part of the provided schema, and that if
|
|
38
27
|
* the schema uses core v0.1, then it doesn't use the 'for' (purpose) argument.
|
|
39
28
|
* Throws if that is not true.
|
|
40
29
|
*/
|
|
41
|
-
function checkFeatureSupport(coreFeatures: CoreFeatures) {
|
|
30
|
+
function checkFeatureSupport(coreFeatures: CoreFeatures, supportedFeatures: Set<string>) {
|
|
42
31
|
const errors: GraphQLError[] = [];
|
|
43
32
|
const coreItself = coreFeatures.coreItself;
|
|
44
33
|
if (coreItself.url.equals(coreVersionZeroDotOneUrl)) {
|
|
@@ -56,7 +45,7 @@ function checkFeatureSupport(coreFeatures: CoreFeatures) {
|
|
|
56
45
|
|
|
57
46
|
for (const feature of coreFeatures.allFeatures()) {
|
|
58
47
|
if (feature.url.equals(coreVersionZeroDotOneUrl) || feature.purpose === 'EXECUTION' || feature.purpose === 'SECURITY') {
|
|
59
|
-
if (!
|
|
48
|
+
if (!supportedFeatures.has(feature.url.base.toString())) {
|
|
60
49
|
errors.push(ERRORS.UNSUPPORTED_LINKED_FEATURE.err(
|
|
61
50
|
`feature ${feature.url} is for: ${feature.purpose} but is unsupported`,
|
|
62
51
|
{ nodes: feature.directive.sourceAST },
|
|
@@ -89,3 +78,62 @@ export function validateSupergraph(supergraph: Schema): [CoreFeatures, JoinSpecD
|
|
|
89
78
|
export function isFed1Supergraph(supergraph: Schema): boolean {
|
|
90
79
|
return validateSupergraph(supergraph)[1].version.equals(new FeatureVersion(0, 1));
|
|
91
80
|
}
|
|
81
|
+
|
|
82
|
+
export class Supergraph {
|
|
83
|
+
private readonly containedSubgraphs: readonly {name: string, url: string}[];
|
|
84
|
+
// Lazily computed as that requires a bit of work.
|
|
85
|
+
private _subgraphs?: Subgraphs;
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
readonly schema: Schema,
|
|
89
|
+
supportedFeatures: Set<string> | null = DEFAULT_SUPPORTED_SUPERGRAPH_FEATURES,
|
|
90
|
+
private readonly shouldValidate: boolean = true,
|
|
91
|
+
) {
|
|
92
|
+
const [coreFeatures] = validateSupergraph(schema);
|
|
93
|
+
|
|
94
|
+
if (supportedFeatures !== null) {
|
|
95
|
+
checkFeatureSupport(coreFeatures, supportedFeatures);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (shouldValidate) {
|
|
99
|
+
schema.validate();
|
|
100
|
+
} else {
|
|
101
|
+
schema.assumeValid();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.containedSubgraphs = extractSubgraphsNamesAndUrlsFromSupergraph(schema);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static build(supergraphSdl: string | DocumentNode, options?: { supportedFeatures?: Set<string>, validateSupergraph?: boolean }) {
|
|
108
|
+
// We delay validation because `checkFeatureSupport` in the constructor gives slightly more useful errors if, say, 'for' is used with core v0.1.
|
|
109
|
+
const schema = typeof supergraphSdl === 'string'
|
|
110
|
+
? buildSchema(supergraphSdl, { validate: false })
|
|
111
|
+
: buildSchemaFromAST(supergraphSdl, { validate: false });
|
|
112
|
+
|
|
113
|
+
return new Supergraph(schema, options?.supportedFeatures, options?.validateSupergraph);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The list of names/urls of the subgraphs contained in this subgraph.
|
|
118
|
+
*
|
|
119
|
+
* Note that this is a subset of what `this.subgraphs()` returns, but contrarily to that method, this method does not do a full extraction of the
|
|
120
|
+
* subgraphs schema.
|
|
121
|
+
*/
|
|
122
|
+
subgraphsMetadata(): readonly {name: string, url: string}[] {
|
|
123
|
+
return this.containedSubgraphs;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
subgraphs(): Subgraphs {
|
|
127
|
+
if (!this._subgraphs) {
|
|
128
|
+
// Note that `extractSubgraphsFromSupergraph` redo a little bit of work we're already one, like validating
|
|
129
|
+
// the supergraph. We could refactor things to avoid it, but it's completely negligible in practice so we
|
|
130
|
+
// can leave that to "some day, maybe".
|
|
131
|
+
this._subgraphs = extractSubgraphsFromSupergraph(this.schema, this.shouldValidate);
|
|
132
|
+
}
|
|
133
|
+
return this._subgraphs;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
apiSchema(): Schema {
|
|
137
|
+
return this.schema.toAPISchema();
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/tagSpec.ts
CHANGED
|
@@ -13,8 +13,8 @@ export class TagSpecDefinition extends FeatureDefinition {
|
|
|
13
13
|
public readonly tagDirectiveSpec: DirectiveSpecification;
|
|
14
14
|
private readonly printedTagDefinition: string;
|
|
15
15
|
|
|
16
|
-
constructor(version: FeatureVersion) {
|
|
17
|
-
super(new FeatureUrl(tagIdentity, 'tag', version));
|
|
16
|
+
constructor(version: FeatureVersion, minimumFederationVersion?: FeatureVersion) {
|
|
17
|
+
super(new FeatureUrl(tagIdentity, 'tag', version), minimumFederationVersion);
|
|
18
18
|
this.tagLocations = [
|
|
19
19
|
DirectiveLocation.FIELD_DEFINITION,
|
|
20
20
|
DirectiveLocation.OBJECT,
|
|
@@ -43,7 +43,7 @@ export class TagSpecDefinition extends FeatureDefinition {
|
|
|
43
43
|
repeatable: true,
|
|
44
44
|
args: [{ name: 'name', type: (schema) => new NonNullType(schema.stringType()) }],
|
|
45
45
|
composes: true,
|
|
46
|
-
supergraphSpecification: () => TAG_VERSIONS.
|
|
46
|
+
supergraphSpecification: (fedVersion) => TAG_VERSIONS.getMinimumRequiredVersion(fedVersion),
|
|
47
47
|
});
|
|
48
48
|
this.registerDirective(this.tagDirectiveSpec);
|
|
49
49
|
}
|
|
@@ -77,6 +77,6 @@ export class TagSpecDefinition extends FeatureDefinition {
|
|
|
77
77
|
export const TAG_VERSIONS = new FeatureDefinitions<TagSpecDefinition>(tagIdentity)
|
|
78
78
|
.add(new TagSpecDefinition(new FeatureVersion(0, 1)))
|
|
79
79
|
.add(new TagSpecDefinition(new FeatureVersion(0, 2)))
|
|
80
|
-
.add(new TagSpecDefinition(new FeatureVersion(0, 3)));
|
|
80
|
+
.add(new TagSpecDefinition(new FeatureVersion(0, 3), new FeatureVersion(2, 0)));
|
|
81
81
|
|
|
82
82
|
registerKnownFeature(TAG_VERSIONS);
|
package/src/utils.ts
CHANGED
|
@@ -431,3 +431,13 @@ export function isNonEmptyArray<T>(array: T[]): array is NonEmptyArray<T> {
|
|
|
431
431
|
return array.length > 0;
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
// We can switch to `Array.prototype.findLast` when we drop support for Node 16
|
|
435
|
+
export function findLast<T>(array: T[], predicate: (t: T) => boolean): T | undefined {
|
|
436
|
+
for (let i = array.length - 1; i >= 0; i--) {
|
|
437
|
+
const t = array[i];
|
|
438
|
+
if (predicate(t)) {
|
|
439
|
+
return t;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|