@apollo/federation-internals 2.0.0-preview.8 → 2.0.1
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/CHANGELOG.md +35 -0
- package/dist/buildSchema.js +1 -1
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +13 -6
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +105 -38
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +25 -11
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +115 -63
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +11 -1
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +77 -20
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +13 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +28 -2
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +7 -1
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +19 -6
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +107 -80
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +3 -3
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +34 -26
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +11 -5
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +631 -29
- package/dist/inaccessibleSpec.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/introspection.d.ts.map +1 -1
- package/dist/introspection.js +8 -3
- package/dist/introspection.js.map +1 -1
- package/dist/joinSpec.d.ts +6 -2
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +6 -0
- package/dist/joinSpec.js.map +1 -1
- package/dist/operations.d.ts +1 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +16 -1
- package/dist/operations.js.map +1 -1
- package/dist/{sharing.d.ts → precompute.d.ts} +1 -1
- package/dist/precompute.d.ts.map +1 -0
- package/dist/{sharing.js → precompute.js} +3 -3
- package/dist/precompute.js.map +1 -0
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +17 -7
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/suggestions.d.ts +1 -1
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +2 -0
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts +6 -2
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +30 -14
- package/dist/tagSpec.js.map +1 -1
- package/dist/validate.js +13 -7
- package/dist/validate.js.map +1 -1
- package/dist/values.d.ts +2 -2
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +13 -11
- package/dist/values.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/coreSpec.test.ts +112 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +2229 -137
- package/src/__tests__/subgraphValidation.test.ts +357 -1
- package/src/__tests__/values.test.ts +315 -3
- package/src/buildSchema.ts +1 -1
- package/src/coreSpec.ts +161 -48
- package/src/definitions.ts +223 -90
- package/src/directiveAndTypeSpecification.ts +98 -21
- package/src/error.ts +80 -2
- package/src/extractSubgraphsFromSupergraph.ts +7 -1
- package/src/federation.ts +124 -97
- package/src/federationSpec.ts +37 -30
- package/src/inaccessibleSpec.ts +983 -49
- package/src/index.ts +1 -0
- package/src/introspection.ts +8 -3
- package/src/joinSpec.ts +19 -4
- package/src/operations.ts +15 -0
- package/src/{sharing.ts → precompute.ts} +3 -6
- package/src/schemaUpgrader.ts +29 -13
- package/src/suggestions.ts +1 -1
- package/src/supergraphs.ts +2 -0
- package/src/tagSpec.ts +42 -16
- package/src/validate.ts +20 -9
- package/src/values.ts +39 -12
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/sharing.d.ts.map +0 -1
- package/dist/sharing.js.map +0 -1
package/src/federation.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { KnownTypeNamesInFederationRule } from "./validation/KnownTypeNamesInFed
|
|
|
45
45
|
import { buildSchema, buildSchemaFromAST } from "./buildSchema";
|
|
46
46
|
import { parseSelectionSet, selectionOfElement, SelectionSet } from './operations';
|
|
47
47
|
import { TAG_VERSIONS } from "./tagSpec";
|
|
48
|
+
import { INACCESSIBLE_VERSIONS } from "./inaccessibleSpec";
|
|
48
49
|
import {
|
|
49
50
|
errorCodeDef,
|
|
50
51
|
ErrorCodeDefinition,
|
|
@@ -52,7 +53,7 @@ import {
|
|
|
52
53
|
ERRORS,
|
|
53
54
|
withModifiedErrorMessage,
|
|
54
55
|
} from "./error";
|
|
55
|
-
import { computeShareables } from "./
|
|
56
|
+
import { computeShareables } from "./precompute";
|
|
56
57
|
import {
|
|
57
58
|
CoreSpecDefinition,
|
|
58
59
|
FeatureVersion,
|
|
@@ -71,8 +72,7 @@ import {
|
|
|
71
72
|
externalDirectiveSpec,
|
|
72
73
|
extendsDirectiveSpec,
|
|
73
74
|
shareableDirectiveSpec,
|
|
74
|
-
|
|
75
|
-
inaccessibleDirectiveSpec,
|
|
75
|
+
overrideDirectiveSpec,
|
|
76
76
|
FEDERATION2_SPEC_DIRECTIVES,
|
|
77
77
|
ALL_FEDERATION_DIRECTIVES_DEFAULT_NAMES,
|
|
78
78
|
FEDERATION2_ONLY_SPEC_DIRECTIVES,
|
|
@@ -83,6 +83,7 @@ import { didYouMean, suggestionList } from "./suggestions";
|
|
|
83
83
|
|
|
84
84
|
const linkSpec = LINK_VERSIONS.latest();
|
|
85
85
|
const tagSpec = TAG_VERSIONS.latest();
|
|
86
|
+
const inaccessibleSpec = INACCESSIBLE_VERSIONS.latest();
|
|
86
87
|
const federationSpec = FEDERATION_VERSIONS.latest();
|
|
87
88
|
|
|
88
89
|
// 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,6 +92,8 @@ const federationSpec = FEDERATION_VERSIONS.latest();
|
|
|
91
92
|
// disallowing it feels like more a good thing than a real restriction).
|
|
92
93
|
export const FEDERATION_RESERVED_SUBGRAPH_NAME = '_';
|
|
93
94
|
|
|
95
|
+
export const FEDERATION_UNNAMED_SUBGRAPH_NAME = '<unnamed>';
|
|
96
|
+
|
|
94
97
|
const FEDERATION_OMITTED_VALIDATION_RULES = [
|
|
95
98
|
// We allow subgraphs to declare an extension even if the subgraph itself doesn't have a corresponding definition.
|
|
96
99
|
// The implication being that the definition is in another subgraph.
|
|
@@ -263,53 +266,49 @@ function validateAllFieldSet<TParent extends SchemaElement<any, any>>(
|
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
|
|
266
|
-
export function
|
|
267
|
-
const
|
|
269
|
+
export function collectUsedFields(metadata: FederationMetadata): Set<FieldDefinition<CompositeType>> {
|
|
270
|
+
const usedFields = new Set<FieldDefinition<CompositeType>>();
|
|
268
271
|
|
|
269
272
|
// Collects all external fields used by a key, requires or provides
|
|
270
|
-
|
|
271
|
-
metadata,
|
|
273
|
+
collectUsedFieldsForDirective<CompositeType>(
|
|
272
274
|
metadata.keyDirective(),
|
|
273
275
|
type => type,
|
|
274
|
-
|
|
276
|
+
usedFields,
|
|
275
277
|
);
|
|
276
|
-
|
|
277
|
-
metadata,
|
|
278
|
+
collectUsedFieldsForDirective<FieldDefinition<CompositeType>>(
|
|
278
279
|
metadata.requiresDirective(),
|
|
279
280
|
field => field.parent!,
|
|
280
|
-
|
|
281
|
+
usedFields,
|
|
281
282
|
);
|
|
282
|
-
|
|
283
|
-
metadata,
|
|
283
|
+
collectUsedFieldsForDirective<FieldDefinition<CompositeType>>(
|
|
284
284
|
metadata.providesDirective(),
|
|
285
285
|
field => {
|
|
286
286
|
const type = baseType(field.type!);
|
|
287
287
|
return isCompositeType(type) ? type : undefined;
|
|
288
288
|
},
|
|
289
|
-
|
|
289
|
+
usedFields,
|
|
290
290
|
);
|
|
291
291
|
|
|
292
|
-
// Collects all
|
|
293
|
-
for (const itfType of metadata.schema.
|
|
292
|
+
// Collects all fields used to satisfy an interface constraint
|
|
293
|
+
for (const itfType of metadata.schema.interfaceTypes()) {
|
|
294
294
|
const runtimeTypes = itfType.possibleRuntimeTypes();
|
|
295
295
|
for (const field of itfType.fields()) {
|
|
296
296
|
for (const runtimeType of runtimeTypes) {
|
|
297
297
|
const implemField = runtimeType.field(field.name);
|
|
298
|
-
if (implemField
|
|
299
|
-
|
|
298
|
+
if (implemField) {
|
|
299
|
+
usedFields.add(implemField);
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
return
|
|
305
|
+
return usedFields;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
function
|
|
309
|
-
metadata: FederationMetadata,
|
|
308
|
+
function collectUsedFieldsForDirective<TParent extends SchemaElement<any, any>>(
|
|
310
309
|
definition: DirectiveDefinition<{fields: any}>,
|
|
311
310
|
targetTypeExtractor: (element: TParent) => CompositeType | undefined,
|
|
312
|
-
|
|
311
|
+
usedFieldDefs: Set<FieldDefinition<CompositeType>>
|
|
313
312
|
) {
|
|
314
313
|
for (const application of definition.applications()) {
|
|
315
314
|
const type = targetTypeExtractor(application.parent! as TParent);
|
|
@@ -327,8 +326,7 @@ function collectUsedExternaFieldsForDirective<TParent extends SchemaElement<any,
|
|
|
327
326
|
directive: application as Directive<any, {fields: any}>,
|
|
328
327
|
includeInterfaceFieldsImplementations: true,
|
|
329
328
|
validate: false,
|
|
330
|
-
}).
|
|
331
|
-
.forEach((field) => usedExternalCoordinates.add(field.coordinate));
|
|
329
|
+
}).forEach((field) => usedFieldDefs.add(field));
|
|
332
330
|
}
|
|
333
331
|
}
|
|
334
332
|
|
|
@@ -337,29 +335,26 @@ function collectUsedExternaFieldsForDirective<TParent extends SchemaElement<any,
|
|
|
337
335
|
* interface implementation. Otherwise, the field declaration is somewhat useless.
|
|
338
336
|
*/
|
|
339
337
|
function validateAllExternalFieldsUsed(metadata: FederationMetadata, errorCollector: GraphQLError[]): void {
|
|
340
|
-
const allUsedExternals = collectUsedExternalFieldsCoordinates(metadata);
|
|
341
338
|
for (const type of metadata.schema.types()) {
|
|
342
339
|
if (!isObjectType(type) && !isInterfaceType(type)) {
|
|
343
340
|
continue;
|
|
344
341
|
}
|
|
345
342
|
for (const field of type.fields()) {
|
|
346
|
-
if (!metadata.isFieldExternal(field) ||
|
|
343
|
+
if (!metadata.isFieldExternal(field) || metadata.isFieldUsed(field)) {
|
|
347
344
|
continue;
|
|
348
345
|
}
|
|
349
346
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}));
|
|
356
|
-
}
|
|
347
|
+
errorCollector.push(ERRORS.EXTERNAL_UNUSED.err({
|
|
348
|
+
message: `Field "${field.coordinate}" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface;`
|
|
349
|
+
+ ' the field declaration has no use and should be removed (or the field should not be @external).',
|
|
350
|
+
nodes: field.sourceAST,
|
|
351
|
+
}));
|
|
357
352
|
}
|
|
358
353
|
}
|
|
359
354
|
}
|
|
360
355
|
|
|
361
356
|
function validateNoExternalOnInterfaceFields(metadata: FederationMetadata, errorCollector: GraphQLError[]) {
|
|
362
|
-
for (const itf of metadata.schema.
|
|
357
|
+
for (const itf of metadata.schema.interfaceTypes()) {
|
|
363
358
|
for (const field of itf.fields()) {
|
|
364
359
|
if (metadata.isFieldExternal(field)) {
|
|
365
360
|
errorCollector.push(ERRORS.EXTERNAL_ON_INTERFACE.err({
|
|
@@ -371,10 +366,6 @@ function validateNoExternalOnInterfaceFields(metadata: FederationMetadata, error
|
|
|
371
366
|
}
|
|
372
367
|
}
|
|
373
368
|
|
|
374
|
-
function isFieldSatisfyingInterface(field: FieldDefinition<ObjectType | InterfaceType>): boolean {
|
|
375
|
-
return field.parent.interfaces().some(itf => itf.field(field.name));
|
|
376
|
-
}
|
|
377
|
-
|
|
378
369
|
/**
|
|
379
370
|
* Register errors when, for an interface field, some of the implementations of that field are @external
|
|
380
371
|
* _and_ not all of those field implementation have the same type (which otherwise allowed because field
|
|
@@ -385,7 +376,7 @@ function isFieldSatisfyingInterface(field: FieldDefinition<ObjectType | Interfac
|
|
|
385
376
|
*/
|
|
386
377
|
function validateInterfaceRuntimeImplementationFieldsTypes(
|
|
387
378
|
itf: InterfaceType,
|
|
388
|
-
metadata: FederationMetadata,
|
|
379
|
+
metadata: FederationMetadata,
|
|
389
380
|
errorCollector: GraphQLError[],
|
|
390
381
|
): void {
|
|
391
382
|
const requiresDirective = federationMetadata(itf.schema())?.requiresDirective();
|
|
@@ -426,6 +417,7 @@ function formatFieldsToReturnType([type, implems]: [string, FieldDefinition<Obje
|
|
|
426
417
|
export class FederationMetadata {
|
|
427
418
|
private _externalTester?: ExternalTester;
|
|
428
419
|
private _sharingPredicate?: (field: FieldDefinition<CompositeType>) => boolean;
|
|
420
|
+
private _fieldUsedPredicate?: (field: FieldDefinition<CompositeType>) => boolean;
|
|
429
421
|
private _isFed2Schema?: boolean;
|
|
430
422
|
|
|
431
423
|
constructor(readonly schema: Schema) {
|
|
@@ -435,6 +427,7 @@ export class FederationMetadata {
|
|
|
435
427
|
this._externalTester = undefined;
|
|
436
428
|
this._sharingPredicate = undefined;
|
|
437
429
|
this._isFed2Schema = undefined;
|
|
430
|
+
this._fieldUsedPredicate = undefined;
|
|
438
431
|
}
|
|
439
432
|
|
|
440
433
|
isFed2Schema(): boolean {
|
|
@@ -463,6 +456,18 @@ export class FederationMetadata {
|
|
|
463
456
|
return this._sharingPredicate;
|
|
464
457
|
}
|
|
465
458
|
|
|
459
|
+
private fieldUsedPredicate(): (field: FieldDefinition<CompositeType>) => boolean {
|
|
460
|
+
if (!this._fieldUsedPredicate) {
|
|
461
|
+
const usedFields = collectUsedFields(this);
|
|
462
|
+
this._fieldUsedPredicate = (field: FieldDefinition<CompositeType>) => !!usedFields.has(field);
|
|
463
|
+
}
|
|
464
|
+
return this._fieldUsedPredicate;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
isFieldUsed(field: FieldDefinition<CompositeType>): boolean {
|
|
468
|
+
return this.fieldUsedPredicate()(field);
|
|
469
|
+
}
|
|
470
|
+
|
|
466
471
|
isFieldExternal(field: FieldDefinition<any> | InputFieldDefinition) {
|
|
467
472
|
return this.externalTester().isExternal(field);
|
|
468
473
|
}
|
|
@@ -534,11 +539,15 @@ export class FederationMetadata {
|
|
|
534
539
|
return this.getFederationDirective(keyDirectiveSpec.name);
|
|
535
540
|
}
|
|
536
541
|
|
|
542
|
+
overrideDirective(): DirectiveDefinition<{from: string}> {
|
|
543
|
+
return this.getFederationDirective(overrideDirectiveSpec.name);
|
|
544
|
+
}
|
|
545
|
+
|
|
537
546
|
extendsDirective(): DirectiveDefinition<Record<string, never>> {
|
|
538
547
|
return this.getFederationDirective(extendsDirectiveSpec.name);
|
|
539
548
|
}
|
|
540
549
|
|
|
541
|
-
externalDirective(): DirectiveDefinition<
|
|
550
|
+
externalDirective(): DirectiveDefinition<{reason: string}> {
|
|
542
551
|
return this.getFederationDirective(externalDirectiveSpec.name);
|
|
543
552
|
}
|
|
544
553
|
|
|
@@ -555,15 +564,17 @@ export class FederationMetadata {
|
|
|
555
564
|
}
|
|
556
565
|
|
|
557
566
|
tagDirective(): DirectiveDefinition<{name: string}> {
|
|
558
|
-
return this.getFederationDirective(tagDirectiveSpec.name);
|
|
567
|
+
return this.getFederationDirective(tagSpec.tagDirectiveSpec.name);
|
|
559
568
|
}
|
|
560
569
|
|
|
561
570
|
inaccessibleDirective(): DirectiveDefinition<{}> {
|
|
562
|
-
return this.getFederationDirective(
|
|
571
|
+
return this.getFederationDirective(
|
|
572
|
+
inaccessibleSpec.inaccessibleDirectiveSpec.name
|
|
573
|
+
);
|
|
563
574
|
}
|
|
564
575
|
|
|
565
576
|
allFederationDirectives(): DirectiveDefinition[] {
|
|
566
|
-
const baseDirectives = [
|
|
577
|
+
const baseDirectives: DirectiveDefinition[] = [
|
|
567
578
|
this.keyDirective(),
|
|
568
579
|
this.externalDirective(),
|
|
569
580
|
this.requiresDirective(),
|
|
@@ -572,7 +583,7 @@ export class FederationMetadata {
|
|
|
572
583
|
this.extendsDirective(),
|
|
573
584
|
];
|
|
574
585
|
return this.isFed2Schema()
|
|
575
|
-
? baseDirectives.concat(this.shareableDirective(), this.inaccessibleDirective())
|
|
586
|
+
? baseDirectives.concat(this.shareableDirective(), this.inaccessibleDirective(), this.overrideDirective())
|
|
576
587
|
: baseDirectives;
|
|
577
588
|
}
|
|
578
589
|
|
|
@@ -608,6 +619,10 @@ export class FederationMetadata {
|
|
|
608
619
|
}
|
|
609
620
|
|
|
610
621
|
export class FederationBlueprint extends SchemaBlueprint {
|
|
622
|
+
constructor(private readonly withRootTypeRenaming: boolean) {
|
|
623
|
+
super();
|
|
624
|
+
}
|
|
625
|
+
|
|
611
626
|
onAddedCoreFeature(schema: Schema, feature: CoreFeature) {
|
|
612
627
|
super.onAddedCoreFeature(schema, feature);
|
|
613
628
|
if (feature.url.identity === federationIdentity) {
|
|
@@ -618,19 +633,21 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
618
633
|
}
|
|
619
634
|
}
|
|
620
635
|
|
|
621
|
-
onMissingDirectiveDefinition(schema: Schema, name: string): DirectiveDefinition | undefined {
|
|
636
|
+
onMissingDirectiveDefinition(schema: Schema, name: string, args?: {[key: string]: any}): DirectiveDefinition | GraphQLError[] | undefined {
|
|
622
637
|
if (name === linkDirectiveDefaultName) {
|
|
623
|
-
|
|
624
|
-
|
|
638
|
+
const url = args && (args['url'] as string | undefined);
|
|
639
|
+
const as = url && url.startsWith(linkSpec.identity) ? (args['as'] as string | undefined) : undefined;
|
|
640
|
+
const errors = linkSpec.addDefinitionsToSchema(schema, as);
|
|
641
|
+
return errors.length > 0 ? errors : schema.directive(name);
|
|
625
642
|
}
|
|
626
|
-
return super.onMissingDirectiveDefinition(schema, name);
|
|
643
|
+
return super.onMissingDirectiveDefinition(schema, name, args);
|
|
627
644
|
}
|
|
628
645
|
|
|
629
646
|
ignoreParsedField(type: NamedType, fieldName: string): boolean {
|
|
630
647
|
// Historically, federation 1 has accepted invalid schema, including some where the Query type included
|
|
631
648
|
// the definition of `_entities` (so `_entities(representations: [_Any!]!): [_Entity]!`) but _without_
|
|
632
649
|
// defining the `_Any` or `_Entity` type. So while we want to be stricter for fed2 (so this kind of
|
|
633
|
-
// really weird case can be fixed), we want fed2 to accept as much fed1 schema as possible.
|
|
650
|
+
// really weird case can be fixed), we want fed2 to accept as much fed1 schema as possible.
|
|
634
651
|
//
|
|
635
652
|
// So, to avoid this problem, we ignore the _entities and _service fields if we parse them from
|
|
636
653
|
// a fed1 input schema. Those will be added back anyway (along with the proper types) post-parsing.
|
|
@@ -648,8 +665,8 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
648
665
|
}
|
|
649
666
|
}
|
|
650
667
|
|
|
651
|
-
onDirectiveDefinitionAndSchemaParsed(schema: Schema) {
|
|
652
|
-
completeSubgraphSchema(schema);
|
|
668
|
+
onDirectiveDefinitionAndSchemaParsed(schema: Schema): GraphQLError[] {
|
|
669
|
+
return completeSubgraphSchema(schema);
|
|
653
670
|
}
|
|
654
671
|
|
|
655
672
|
onInvalidation(schema: Schema) {
|
|
@@ -664,22 +681,24 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
664
681
|
|
|
665
682
|
// We rename all root type to their default names (we do here rather than in `prepareValidation` because
|
|
666
683
|
// that can actually fail).
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
684
|
+
if (this.withRootTypeRenaming) {
|
|
685
|
+
for (const k of allSchemaRootKinds) {
|
|
686
|
+
const type = schema.schemaDefinition.root(k)?.type;
|
|
687
|
+
const defaultName = defaultRootName(k);
|
|
688
|
+
if (type && type.name !== defaultName) {
|
|
689
|
+
// We first ensure there is no other type using the default root name. If there is, this is a
|
|
690
|
+
// composition error.
|
|
691
|
+
const existing = schema.type(defaultName);
|
|
692
|
+
if (existing) {
|
|
693
|
+
errors.push(ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err({
|
|
694
|
+
message: `The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): `
|
|
677
695
|
+ 'this is not supported by federation. '
|
|
678
696
|
+ 'If a root type does not use its default name, there should be no other type with that default name.',
|
|
679
|
-
|
|
680
|
-
|
|
697
|
+
nodes: sourceASTs(type, existing),
|
|
698
|
+
}));
|
|
699
|
+
}
|
|
700
|
+
type.rename(defaultName);
|
|
681
701
|
}
|
|
682
|
-
type.rename(defaultName);
|
|
683
702
|
}
|
|
684
703
|
}
|
|
685
704
|
|
|
@@ -764,7 +783,7 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
764
783
|
}
|
|
765
784
|
}
|
|
766
785
|
|
|
767
|
-
for (const itf of schema.
|
|
786
|
+
for (const itf of schema.interfaceTypes()) {
|
|
768
787
|
validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errors);
|
|
769
788
|
}
|
|
770
789
|
|
|
@@ -788,7 +807,6 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
788
807
|
const federationFeature = metadata.federationFeature();
|
|
789
808
|
assert(federationFeature, 'Fed2 subgraph _must_ link to the federation feature')
|
|
790
809
|
const directiveNameInSchema = federationFeature.directiveNameInSchema(unknownDirectiveName);
|
|
791
|
-
console.log(`For ${unknownDirectiveName}, name in schema = ${directiveNameInSchema}`);
|
|
792
810
|
if (directiveNameInSchema.startsWith(federationFeature.nameInSchema + '__')) {
|
|
793
811
|
// There is no import for that directive
|
|
794
812
|
return withModifiedErrorMessage(
|
|
@@ -804,7 +822,7 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
804
822
|
}
|
|
805
823
|
} else {
|
|
806
824
|
return withModifiedErrorMessage(
|
|
807
|
-
error,
|
|
825
|
+
error,
|
|
808
826
|
`${error.message} If you meant the "@${unknownDirectiveName}" federation 2 directive, note that this schema is a federation 1 schema. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`
|
|
809
827
|
);
|
|
810
828
|
}
|
|
@@ -813,7 +831,7 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
813
831
|
const suggestions = suggestionList(unknownDirectiveName, FEDERATION2_ONLY_SPEC_DIRECTIVES.map((spec) => spec.name));
|
|
814
832
|
if (suggestions.length > 0) {
|
|
815
833
|
return withModifiedErrorMessage(
|
|
816
|
-
error,
|
|
834
|
+
error,
|
|
817
835
|
`${error.message}${didYouMean(suggestions.map((s) => '@' + s))} If so, note that ${suggestions.length === 1 ? 'it is a federation 2 directive' : 'they are federation 2 directives'} but this schema is a federation 1 one. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`
|
|
818
836
|
);
|
|
819
837
|
}
|
|
@@ -822,8 +840,6 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
822
840
|
}
|
|
823
841
|
}
|
|
824
842
|
|
|
825
|
-
const federationBlueprint = new FederationBlueprint();
|
|
826
|
-
|
|
827
843
|
function findUnusedNamedForLinkDirective(schema: Schema): string | undefined {
|
|
828
844
|
if (!schema.directive(linkSpec.url.name)) {
|
|
829
845
|
return undefined;
|
|
@@ -851,7 +867,10 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
|
|
|
851
867
|
assert(spec.url.version.satisfies(linkSpec.version), `Fed2 schema must use @link with version >= 1.0, but schema uses ${spec.url}`);
|
|
852
868
|
} else {
|
|
853
869
|
const alias = findUnusedNamedForLinkDirective(schema);
|
|
854
|
-
linkSpec.addToSchema(schema, alias);
|
|
870
|
+
const errors = linkSpec.addToSchema(schema, alias);
|
|
871
|
+
if (errors.length > 0) {
|
|
872
|
+
throw ErrGraphQLValidationFailed(errors);
|
|
873
|
+
}
|
|
855
874
|
spec = linkSpec;
|
|
856
875
|
core = schema.coreFeatures;
|
|
857
876
|
assert(core, 'Schema should now be a core schema');
|
|
@@ -865,12 +884,15 @@ export function setSchemaAsFed2Subgraph(schema: Schema) {
|
|
|
865
884
|
import: FEDERATION2_SPEC_DIRECTIVES.map((spec) => `@${spec.name}`),
|
|
866
885
|
}
|
|
867
886
|
);
|
|
868
|
-
completeSubgraphSchema(schema);
|
|
887
|
+
const errors = completeSubgraphSchema(schema);
|
|
888
|
+
if (errors.length > 0) {
|
|
889
|
+
throw ErrGraphQLValidationFailed(errors);
|
|
890
|
+
}
|
|
869
891
|
}
|
|
870
892
|
|
|
871
893
|
// This is the full @link declaration as added by `asFed2SubgraphDocument`. It's here primarily for uses by tests that print and match
|
|
872
894
|
// subgraph schema to avoid having to update 20+ tests every time we use a new directive or the order of import changes ...
|
|
873
|
-
export const FEDERATION2_LINK_WTH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible"])';
|
|
895
|
+
export const FEDERATION2_LINK_WTH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override"])';
|
|
874
896
|
|
|
875
897
|
export function asFed2SubgraphDocument(document: DocumentNode): DocumentNode {
|
|
876
898
|
const fed2LinkExtension: SchemaExtensionNode = {
|
|
@@ -933,10 +955,11 @@ export function isEntityType(type: NamedType): boolean {
|
|
|
933
955
|
export function buildSubgraph(
|
|
934
956
|
name: string,
|
|
935
957
|
url: string,
|
|
936
|
-
source: DocumentNode | string
|
|
958
|
+
source: DocumentNode | string,
|
|
959
|
+
withRootTypeRenaming: boolean = true,
|
|
937
960
|
): Subgraph {
|
|
938
961
|
const buildOptions = {
|
|
939
|
-
blueprint:
|
|
962
|
+
blueprint: new FederationBlueprint(withRootTypeRenaming),
|
|
940
963
|
validate: false,
|
|
941
964
|
};
|
|
942
965
|
let subgraph: Subgraph;
|
|
@@ -946,7 +969,7 @@ export function buildSubgraph(
|
|
|
946
969
|
: buildSchemaFromAST(source, buildOptions)
|
|
947
970
|
subgraph = new Subgraph(name, url, schema);
|
|
948
971
|
} catch (e) {
|
|
949
|
-
if (e instanceof GraphQLError) {
|
|
972
|
+
if (e instanceof GraphQLError && name !== FEDERATION_UNNAMED_SUBGRAPH_NAME) {
|
|
950
973
|
throw addSubgraphToError(e, name, ERRORS.INVALID_GRAPHQL);
|
|
951
974
|
} else {
|
|
952
975
|
throw e;
|
|
@@ -956,27 +979,30 @@ export function buildSubgraph(
|
|
|
956
979
|
}
|
|
957
980
|
|
|
958
981
|
export function newEmptyFederation2Schema(): Schema {
|
|
959
|
-
const schema = new Schema(
|
|
982
|
+
const schema = new Schema(new FederationBlueprint(true));
|
|
960
983
|
setSchemaAsFed2Subgraph(schema);
|
|
961
984
|
return schema;
|
|
962
985
|
}
|
|
963
986
|
|
|
964
|
-
function completeSubgraphSchema(schema: Schema) {
|
|
987
|
+
function completeSubgraphSchema(schema: Schema): GraphQLError[] {
|
|
965
988
|
const coreFeatures = schema.coreFeatures;
|
|
966
989
|
if (coreFeatures) {
|
|
967
990
|
const fedFeature = coreFeatures.getByIdentity(federationIdentity);
|
|
968
991
|
if (fedFeature) {
|
|
969
|
-
completeFed2SubgraphSchema(schema);
|
|
992
|
+
return completeFed2SubgraphSchema(schema);
|
|
970
993
|
} else {
|
|
971
|
-
completeFed1SubgraphSchema(schema);
|
|
994
|
+
return completeFed1SubgraphSchema(schema);
|
|
972
995
|
}
|
|
973
996
|
} else {
|
|
974
997
|
const fedLink = schema.schemaDefinition.appliedDirectivesOf(linkDirectiveDefaultName).find(isFedSpecLinkDirective);
|
|
975
998
|
if (fedLink) {
|
|
976
|
-
linkSpec.addToSchema(schema);
|
|
977
|
-
|
|
999
|
+
const errors = linkSpec.addToSchema(schema);
|
|
1000
|
+
if (errors.length > 0) {
|
|
1001
|
+
return errors;
|
|
1002
|
+
}
|
|
1003
|
+
return completeFed2SubgraphSchema(schema);
|
|
978
1004
|
} else {
|
|
979
|
-
completeFed1SubgraphSchema(schema);
|
|
1005
|
+
return completeFed1SubgraphSchema(schema);
|
|
980
1006
|
}
|
|
981
1007
|
}
|
|
982
1008
|
}
|
|
@@ -986,15 +1012,16 @@ function isFedSpecLinkDirective(directive: Directive<SchemaDefinition>): directi
|
|
|
986
1012
|
return directive.name === linkDirectiveDefaultName && args['url'] && (args['url'] as string).startsWith(federationIdentity);
|
|
987
1013
|
}
|
|
988
1014
|
|
|
989
|
-
function completeFed1SubgraphSchema(schema: Schema) {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1015
|
+
function completeFed1SubgraphSchema(schema: Schema): GraphQLError[] {
|
|
1016
|
+
return [
|
|
1017
|
+
fieldSetTypeSpec.checkOrAdd(schema, '_' + fieldSetTypeSpec.name),
|
|
1018
|
+
keyDirectiveSpec.checkOrAdd(schema),
|
|
1019
|
+
requiresDirectiveSpec.checkOrAdd(schema),
|
|
1020
|
+
providesDirectiveSpec.checkOrAdd(schema),
|
|
1021
|
+
extendsDirectiveSpec.checkOrAdd(schema),
|
|
1022
|
+
externalDirectiveSpec.checkOrAdd(schema),
|
|
1023
|
+
tagSpec.tagDirectiveSpec.checkOrAdd(schema),
|
|
1024
|
+
].flat();
|
|
998
1025
|
}
|
|
999
1026
|
|
|
1000
1027
|
function completeFed2SubgraphSchema(schema: Schema) {
|
|
@@ -1006,13 +1033,13 @@ function completeFed2SubgraphSchema(schema: Schema) {
|
|
|
1006
1033
|
|
|
1007
1034
|
const spec = FEDERATION_VERSIONS.find(fedFeature.url.version);
|
|
1008
1035
|
if (!spec) {
|
|
1009
|
-
|
|
1036
|
+
return [ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err({
|
|
1010
1037
|
message: `Invalid version ${fedFeature.url.version} for the federation feature in @link direction on schema`,
|
|
1011
1038
|
nodes: fedFeature.directive.sourceAST
|
|
1012
|
-
});
|
|
1039
|
+
})];
|
|
1013
1040
|
}
|
|
1014
1041
|
|
|
1015
|
-
spec.addElementsToSchema(schema);
|
|
1042
|
+
return spec.addElementsToSchema(schema);
|
|
1016
1043
|
}
|
|
1017
1044
|
|
|
1018
1045
|
export function parseFieldSetArgument({
|
|
@@ -1233,7 +1260,7 @@ export const serviceTypeSpec = createObjectTypeSpecification({
|
|
|
1233
1260
|
export const entityTypeSpec = createUnionTypeSpecification({
|
|
1234
1261
|
name: '_Entity',
|
|
1235
1262
|
membersFct: (schema) => {
|
|
1236
|
-
return schema.
|
|
1263
|
+
return schema.objectTypes().filter(isEntityType).map((t) => t.name);
|
|
1237
1264
|
},
|
|
1238
1265
|
});
|
|
1239
1266
|
|
|
@@ -1294,7 +1321,7 @@ export class Subgraph {
|
|
|
1294
1321
|
}
|
|
1295
1322
|
|
|
1296
1323
|
if (!queryType.field(serviceFieldName)) {
|
|
1297
|
-
queryType.addField(serviceFieldName, metadata.serviceType());
|
|
1324
|
+
queryType.addField(serviceFieldName, new NonNullType(metadata.serviceType()));
|
|
1298
1325
|
}
|
|
1299
1326
|
}
|
|
1300
1327
|
|
package/src/federationSpec.ts
CHANGED
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
createDirectiveSpecification,
|
|
10
10
|
createScalarTypeSpecification,
|
|
11
11
|
} from "./directiveAndTypeSpecification";
|
|
12
|
-
import { DirectiveLocation } from "graphql";
|
|
12
|
+
import { DirectiveLocation, GraphQLError } from "graphql";
|
|
13
13
|
import { assert } from "./utils";
|
|
14
|
-
import {
|
|
14
|
+
import { TAG_VERSIONS } from "./tagSpec";
|
|
15
15
|
import { federationMetadata } from "./federation";
|
|
16
16
|
import { registerKnownFeature } from "./knownCoreFeatures";
|
|
17
|
-
import {
|
|
17
|
+
import { INACCESSIBLE_VERSIONS } from "./inaccessibleSpec";
|
|
18
18
|
|
|
19
19
|
export const federationIdentity = 'https://specs.apollo.dev/federation';
|
|
20
20
|
|
|
@@ -24,10 +24,13 @@ export const keyDirectiveSpec = createDirectiveSpecification({
|
|
|
24
24
|
name:'key',
|
|
25
25
|
locations: [DirectiveLocation.OBJECT, DirectiveLocation.INTERFACE],
|
|
26
26
|
repeatable: true,
|
|
27
|
-
argumentFct: (schema) =>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
argumentFct: (schema) => ({
|
|
28
|
+
args: [
|
|
29
|
+
fieldsArgument(schema),
|
|
30
|
+
{ name: 'resolvable', type: schema.booleanType(), defaultValue: true },
|
|
31
|
+
],
|
|
32
|
+
errors: [],
|
|
33
|
+
}),
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
export const extendsDirectiveSpec = createDirectiveSpecification({
|
|
@@ -38,22 +41,28 @@ export const extendsDirectiveSpec = createDirectiveSpecification({
|
|
|
38
41
|
export const externalDirectiveSpec = createDirectiveSpecification({
|
|
39
42
|
name:'external',
|
|
40
43
|
locations: [DirectiveLocation.OBJECT, DirectiveLocation.FIELD_DEFINITION],
|
|
44
|
+
argumentFct: (schema) => ({
|
|
45
|
+
args: [{ name: 'reason', type: schema.stringType() }],
|
|
46
|
+
errors: [],
|
|
47
|
+
}),
|
|
41
48
|
});
|
|
42
49
|
|
|
43
50
|
export const requiresDirectiveSpec = createDirectiveSpecification({
|
|
44
51
|
name:'requires',
|
|
45
52
|
locations: [DirectiveLocation.FIELD_DEFINITION],
|
|
46
|
-
argumentFct: (schema) => {
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
argumentFct: (schema) => ({
|
|
54
|
+
args: [fieldsArgument(schema)],
|
|
55
|
+
errors: [],
|
|
56
|
+
}),
|
|
49
57
|
});
|
|
50
58
|
|
|
51
59
|
export const providesDirectiveSpec = createDirectiveSpecification({
|
|
52
60
|
name:'provides',
|
|
53
61
|
locations: [DirectiveLocation.FIELD_DEFINITION],
|
|
54
|
-
argumentFct: (schema) => {
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
argumentFct: (schema) => ({
|
|
63
|
+
args: [fieldsArgument(schema)],
|
|
64
|
+
errors: [],
|
|
65
|
+
}),
|
|
57
66
|
});
|
|
58
67
|
|
|
59
68
|
export const shareableDirectiveSpec = createDirectiveSpecification({
|
|
@@ -61,18 +70,13 @@ export const shareableDirectiveSpec = createDirectiveSpecification({
|
|
|
61
70
|
locations: [DirectiveLocation.OBJECT, DirectiveLocation.FIELD_DEFINITION],
|
|
62
71
|
});
|
|
63
72
|
|
|
64
|
-
export const
|
|
65
|
-
name:'
|
|
66
|
-
locations:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
export const inaccessibleDirectiveSpec = createDirectiveSpecification({
|
|
74
|
-
name:'inaccessible',
|
|
75
|
-
locations: inaccessibleLocations,
|
|
73
|
+
export const overrideDirectiveSpec = createDirectiveSpecification({
|
|
74
|
+
name: 'override',
|
|
75
|
+
locations: [DirectiveLocation.FIELD_DEFINITION],
|
|
76
|
+
argumentFct: (schema) => ({
|
|
77
|
+
args: [{ name: 'from', type: new NonNullType(schema.stringType()) }],
|
|
78
|
+
errors: [],
|
|
79
|
+
}),
|
|
76
80
|
});
|
|
77
81
|
|
|
78
82
|
function fieldsArgument(schema: Schema): ArgumentSpecification {
|
|
@@ -87,7 +91,8 @@ function fieldSetType(schema: Schema): InputType {
|
|
|
87
91
|
|
|
88
92
|
export const FEDERATION2_ONLY_SPEC_DIRECTIVES = [
|
|
89
93
|
shareableDirectiveSpec,
|
|
90
|
-
inaccessibleDirectiveSpec,
|
|
94
|
+
INACCESSIBLE_VERSIONS.latest().inaccessibleDirectiveSpec,
|
|
95
|
+
overrideDirectiveSpec,
|
|
91
96
|
];
|
|
92
97
|
|
|
93
98
|
// Note that this is only used for federation 2+ (federation 1 adds the same directive, but not through a core spec).
|
|
@@ -96,7 +101,7 @@ export const FEDERATION2_SPEC_DIRECTIVES = [
|
|
|
96
101
|
requiresDirectiveSpec,
|
|
97
102
|
providesDirectiveSpec,
|
|
98
103
|
externalDirectiveSpec,
|
|
99
|
-
tagDirectiveSpec,
|
|
104
|
+
TAG_VERSIONS.latest().tagDirectiveSpec,
|
|
100
105
|
extendsDirectiveSpec, // TODO: should we stop supporting that?
|
|
101
106
|
...FEDERATION2_ONLY_SPEC_DIRECTIVES,
|
|
102
107
|
];
|
|
@@ -115,15 +120,17 @@ export class FederationSpecDefinition extends FeatureDefinition {
|
|
|
115
120
|
super(new FeatureUrl(federationIdentity, 'federation', version));
|
|
116
121
|
}
|
|
117
122
|
|
|
118
|
-
addElementsToSchema(schema: Schema) {
|
|
123
|
+
addElementsToSchema(schema: Schema): GraphQLError[] {
|
|
119
124
|
const feature = this.featureInSchema(schema);
|
|
120
125
|
assert(feature, 'The federation specification should have been added to the schema before this is called');
|
|
121
126
|
|
|
122
|
-
|
|
127
|
+
let errors: GraphQLError[] = [];
|
|
128
|
+
errors = errors.concat(this.addTypeSpec(schema, fieldSetTypeSpec));
|
|
123
129
|
|
|
124
130
|
for (const directive of FEDERATION2_SPEC_DIRECTIVES) {
|
|
125
|
-
|
|
131
|
+
errors = errors.concat(this.addDirectiveSpec(schema, directive));
|
|
126
132
|
}
|
|
133
|
+
return errors;
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
allElementNames(): string[] {
|