@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.
Files changed (101) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/buildSchema.js +1 -1
  3. package/dist/buildSchema.js.map +1 -1
  4. package/dist/coreSpec.d.ts +13 -6
  5. package/dist/coreSpec.d.ts.map +1 -1
  6. package/dist/coreSpec.js +105 -38
  7. package/dist/coreSpec.js.map +1 -1
  8. package/dist/definitions.d.ts +25 -11
  9. package/dist/definitions.d.ts.map +1 -1
  10. package/dist/definitions.js +115 -63
  11. package/dist/definitions.js.map +1 -1
  12. package/dist/directiveAndTypeSpecification.d.ts +11 -1
  13. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  14. package/dist/directiveAndTypeSpecification.js +77 -20
  15. package/dist/directiveAndTypeSpecification.js.map +1 -1
  16. package/dist/error.d.ts +13 -0
  17. package/dist/error.d.ts.map +1 -1
  18. package/dist/error.js +28 -2
  19. package/dist/error.js.map +1 -1
  20. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  21. package/dist/extractSubgraphsFromSupergraph.js +7 -1
  22. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  23. package/dist/federation.d.ts +19 -6
  24. package/dist/federation.d.ts.map +1 -1
  25. package/dist/federation.js +107 -80
  26. package/dist/federation.js.map +1 -1
  27. package/dist/federationSpec.d.ts +3 -3
  28. package/dist/federationSpec.d.ts.map +1 -1
  29. package/dist/federationSpec.js +34 -26
  30. package/dist/federationSpec.js.map +1 -1
  31. package/dist/inaccessibleSpec.d.ts +11 -5
  32. package/dist/inaccessibleSpec.d.ts.map +1 -1
  33. package/dist/inaccessibleSpec.js +631 -29
  34. package/dist/inaccessibleSpec.js.map +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/introspection.d.ts.map +1 -1
  40. package/dist/introspection.js +8 -3
  41. package/dist/introspection.js.map +1 -1
  42. package/dist/joinSpec.d.ts +6 -2
  43. package/dist/joinSpec.d.ts.map +1 -1
  44. package/dist/joinSpec.js +6 -0
  45. package/dist/joinSpec.js.map +1 -1
  46. package/dist/operations.d.ts +1 -0
  47. package/dist/operations.d.ts.map +1 -1
  48. package/dist/operations.js +16 -1
  49. package/dist/operations.js.map +1 -1
  50. package/dist/{sharing.d.ts → precompute.d.ts} +1 -1
  51. package/dist/precompute.d.ts.map +1 -0
  52. package/dist/{sharing.js → precompute.js} +3 -3
  53. package/dist/precompute.js.map +1 -0
  54. package/dist/schemaUpgrader.d.ts.map +1 -1
  55. package/dist/schemaUpgrader.js +17 -7
  56. package/dist/schemaUpgrader.js.map +1 -1
  57. package/dist/suggestions.d.ts +1 -1
  58. package/dist/suggestions.d.ts.map +1 -1
  59. package/dist/suggestions.js.map +1 -1
  60. package/dist/supergraphs.d.ts.map +1 -1
  61. package/dist/supergraphs.js +2 -0
  62. package/dist/supergraphs.js.map +1 -1
  63. package/dist/tagSpec.d.ts +6 -2
  64. package/dist/tagSpec.d.ts.map +1 -1
  65. package/dist/tagSpec.js +30 -14
  66. package/dist/tagSpec.js.map +1 -1
  67. package/dist/validate.js +13 -7
  68. package/dist/validate.js.map +1 -1
  69. package/dist/values.d.ts +2 -2
  70. package/dist/values.d.ts.map +1 -1
  71. package/dist/values.js +13 -11
  72. package/dist/values.js.map +1 -1
  73. package/package.json +4 -4
  74. package/src/__tests__/coreSpec.test.ts +112 -0
  75. package/src/__tests__/removeInaccessibleElements.test.ts +2229 -137
  76. package/src/__tests__/subgraphValidation.test.ts +357 -1
  77. package/src/__tests__/values.test.ts +315 -3
  78. package/src/buildSchema.ts +1 -1
  79. package/src/coreSpec.ts +161 -48
  80. package/src/definitions.ts +223 -90
  81. package/src/directiveAndTypeSpecification.ts +98 -21
  82. package/src/error.ts +80 -2
  83. package/src/extractSubgraphsFromSupergraph.ts +7 -1
  84. package/src/federation.ts +124 -97
  85. package/src/federationSpec.ts +37 -30
  86. package/src/inaccessibleSpec.ts +983 -49
  87. package/src/index.ts +1 -0
  88. package/src/introspection.ts +8 -3
  89. package/src/joinSpec.ts +19 -4
  90. package/src/operations.ts +15 -0
  91. package/src/{sharing.ts → precompute.ts} +3 -6
  92. package/src/schemaUpgrader.ts +29 -13
  93. package/src/suggestions.ts +1 -1
  94. package/src/supergraphs.ts +2 -0
  95. package/src/tagSpec.ts +42 -16
  96. package/src/validate.ts +20 -9
  97. package/src/values.ts +39 -12
  98. package/tsconfig.test.tsbuildinfo +1 -1
  99. package/tsconfig.tsbuildinfo +1 -1
  100. package/dist/sharing.d.ts.map +0 -1
  101. 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 "./sharing";
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
- tagDirectiveSpec,
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 collectUsedExternalFieldsCoordinates(metadata: FederationMetadata): Set<string> {
267
- const usedExternalCoordinates = new Set<string>();
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
- collectUsedExternaFieldsForDirective<CompositeType>(
271
- metadata,
273
+ collectUsedFieldsForDirective<CompositeType>(
272
274
  metadata.keyDirective(),
273
275
  type => type,
274
- usedExternalCoordinates,
276
+ usedFields,
275
277
  );
276
- collectUsedExternaFieldsForDirective<FieldDefinition<CompositeType>>(
277
- metadata,
278
+ collectUsedFieldsForDirective<FieldDefinition<CompositeType>>(
278
279
  metadata.requiresDirective(),
279
280
  field => field.parent!,
280
- usedExternalCoordinates,
281
+ usedFields,
281
282
  );
282
- collectUsedExternaFieldsForDirective<FieldDefinition<CompositeType>>(
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
- usedExternalCoordinates,
289
+ usedFields,
290
290
  );
291
291
 
292
- // Collects all external fields used to satisfy an interface constraint
293
- for (const itfType of metadata.schema.types<InterfaceType>('InterfaceType')) {
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 && metadata.isFieldExternal(implemField)) {
299
- usedExternalCoordinates.add(implemField.coordinate);
298
+ if (implemField) {
299
+ usedFields.add(implemField);
300
300
  }
301
301
  }
302
302
  }
303
303
  }
304
304
 
305
- return usedExternalCoordinates;
305
+ return usedFields;
306
306
  }
307
307
 
308
- function collectUsedExternaFieldsForDirective<TParent extends SchemaElement<any, any>>(
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
- usedExternalCoordinates: Set<string>
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
- }).filter((field) => metadata.isFieldExternal(field))
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) || allUsedExternals.has(field.coordinate)) {
343
+ if (!metadata.isFieldExternal(field) || metadata.isFieldUsed(field)) {
347
344
  continue;
348
345
  }
349
346
 
350
- if (!isFieldSatisfyingInterface(field)) {
351
- errorCollector.push(ERRORS.EXTERNAL_UNUSED.err({
352
- message: `Field "${field.coordinate}" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface;`
353
- + ' the field declaration has no use and should be removed (or the field should not be @external).',
354
- nodes: field.sourceAST,
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.types<InterfaceType>('InterfaceType')) {
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<Record<string, never>> {
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(inaccessibleDirectiveSpec.name);
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
- linkSpec.addToSchema(schema);
624
- return schema.directive(name);
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
- for (const k of allSchemaRootKinds) {
668
- const type = schema.schemaDefinition.root(k)?.type;
669
- const defaultName = defaultRootName(k);
670
- if (type && type.name !== defaultName) {
671
- // We first ensure there is no other type using the default root name. If there is, this is a
672
- // composition error.
673
- const existing = schema.type(defaultName);
674
- if (existing) {
675
- errors.push(ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err({
676
- message: `The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): `
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
- nodes: sourceASTs(type, existing),
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.types<InterfaceType>('InterfaceType')) {
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: federationBlueprint,
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(federationBlueprint);
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
- completeFed2SubgraphSchema(schema);
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
- fieldSetTypeSpec.checkOrAdd(schema, '_' + fieldSetTypeSpec.name);
991
-
992
- keyDirectiveSpec.checkOrAdd(schema);
993
- requiresDirectiveSpec.checkOrAdd(schema);
994
- providesDirectiveSpec.checkOrAdd(schema);
995
- extendsDirectiveSpec.checkOrAdd(schema);
996
- externalDirectiveSpec.checkOrAdd(schema);
997
- tagDirectiveSpec.checkOrAdd(schema);
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
- throw ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err({
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.types<ObjectType>("ObjectType").filter(isEntityType).map((t) => t.name);
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
 
@@ -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 { tagLocations } from "./tagSpec";
14
+ import { TAG_VERSIONS } from "./tagSpec";
15
15
  import { federationMetadata } from "./federation";
16
16
  import { registerKnownFeature } from "./knownCoreFeatures";
17
- import { inaccessibleLocations } from "./inaccessibleSpec";
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
- fieldsArgument(schema),
29
- { name: 'resolvable', type: schema.booleanType(), defaultValue: true },
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
- return [fieldsArgument(schema)];
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
- return [fieldsArgument(schema)];
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 tagDirectiveSpec = createDirectiveSpecification({
65
- name:'tag',
66
- locations: tagLocations,
67
- repeatable: true,
68
- argumentFct: (schema) => {
69
- return [{ name: 'name', type: new NonNullType(schema.stringType()) }];
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
- fieldSetTypeSpec.checkOrAdd(schema, feature.typeNameInSchema(fieldSetTypeSpec.name));
127
+ let errors: GraphQLError[] = [];
128
+ errors = errors.concat(this.addTypeSpec(schema, fieldSetTypeSpec));
123
129
 
124
130
  for (const directive of FEDERATION2_SPEC_DIRECTIVES) {
125
- directive.checkOrAdd(schema, feature.directiveNameInSchema(directive.name));
131
+ errors = errors.concat(this.addDirectiveSpec(schema, directive));
126
132
  }
133
+ return errors;
127
134
  }
128
135
 
129
136
  allElementNames(): string[] {