@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/coreSpec.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ASTNode, DirectiveLocation, GraphQLError, StringValueNode } from "graphql";
2
2
  import { URL } from "url";
3
- import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLValidationFailed, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition } from "./definitions";
3
+ import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement } from "./definitions";
4
4
  import { sameType } from "./types";
5
5
  import { err } from '@apollo/core-schema';
6
6
  import { assert } from './utils';
@@ -8,6 +8,7 @@ import { ERRORS } from "./error";
8
8
  import { valueToString } from "./values";
9
9
  import { coreFeatureDefinitionIfKnown, registerKnownFeature } from "./knownCoreFeatures";
10
10
  import { didYouMean, suggestionList } from "./suggestions";
11
+ import { ArgumentSpecification, createDirectiveSpecification, createEnumTypeSpecification, createScalarTypeSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
11
12
 
12
13
  export const coreIdentity = 'https://specs.apollo.dev/core';
13
14
  export const linkIdentity = 'https://specs.apollo.dev/link';
@@ -64,7 +65,7 @@ export abstract class FeatureDefinition {
64
65
  return nameInSchema != undefined && (directive.name === nameInSchema || directive.name.startsWith(`${nameInSchema}__`));
65
66
  }
66
67
 
67
- abstract addElementsToSchema(schema: Schema): void;
68
+ abstract addElementsToSchema(schema: Schema): GraphQLError[];
68
69
 
69
70
  abstract allElementNames(): string[];
70
71
 
@@ -106,6 +107,14 @@ export abstract class FeatureDefinition {
106
107
  return schema.addDirectiveDefinition(this.directiveNameInSchema(schema, name)!);
107
108
  }
108
109
 
110
+ protected addDirectiveSpec(schema: Schema, spec: DirectiveSpecification): GraphQLError[] {
111
+ return spec.checkOrAdd(schema, this.directiveNameInSchema(schema, spec.name));
112
+ }
113
+
114
+ protected addTypeSpec(schema: Schema, spec: TypeSpecification): GraphQLError[] {
115
+ return spec.checkOrAdd(schema, this.typeNameInSchema(schema, spec.name));
116
+ }
117
+
109
118
  protected addScalarType(schema: Schema, name: string): ScalarType {
110
119
  return schema.addType(new ScalarType(this.typeNameInSchema(schema, name)!));
111
120
  }
@@ -122,6 +131,10 @@ export abstract class FeatureDefinition {
122
131
  return features.getByIdentity(this.identity);
123
132
  }
124
133
 
134
+ get defaultCorePurpose(): CorePurpose | undefined {
135
+ return undefined;
136
+ }
137
+
125
138
  toString(): string {
126
139
  return `${this.identity}/${this.version}`
127
140
  }
@@ -271,7 +284,7 @@ export function isCoreSpecDirectiveApplication(directive: Directive<SchemaDefini
271
284
  return false;
272
285
  }
273
286
  const urlArg = definition.argument('url') ?? definition.argument('feature');
274
- if (!urlArg || !sameType(urlArg.type!, new NonNullType(directive.schema().stringType()))) {
287
+ if (!urlArg || !isValidUrlArgumentType(urlArg.type!, directive.schema())) {
275
288
  return false;
276
289
  }
277
290
 
@@ -288,43 +301,70 @@ export function isCoreSpecDirectiveApplication(directive: Directive<SchemaDefini
288
301
  }
289
302
  }
290
303
 
304
+ function isValidUrlArgumentType(type: InputType, schema: Schema): boolean {
305
+ // Note that the 'url' arg is defined as nullable (mostly for future proofing reasons) but we allow use to provide a definition
306
+ // where it's non-nullable (and in practice, @core (which we never generate anymore, but recognize) definition technically uses
307
+ // with a non-nullable argument, and some fed2 previews did if for @link, so this ensure we handle reading schema generated
308
+ // by those versions just fine).
309
+ return sameType(type, schema.stringType())
310
+ || sameType(type, new NonNullType(schema.stringType()));
311
+ }
312
+
313
+ const linkPurposeTypeSpec = createEnumTypeSpecification({
314
+ name: 'Purpose',
315
+ values: corePurposes.map((name) => ({ name, description: purposesDescription(name)}))
316
+ });
317
+
318
+ const linkImportTypeSpec = createScalarTypeSpecification({ name: 'Import' });
319
+
291
320
  export class CoreSpecDefinition extends FeatureDefinition {
321
+ private readonly directiveDefinitionSpec: DirectiveSpecification;
322
+
292
323
  constructor(version: FeatureVersion, identity: string = linkIdentity, name: string = linkDirectiveDefaultName) {
293
324
  super(new FeatureUrl(identity, name, version));
325
+ this.directiveDefinitionSpec = createDirectiveSpecification({
326
+ name,
327
+ locations: [DirectiveLocation.SCHEMA],
328
+ repeatable: true,
329
+ argumentFct: (schema, nameInSchema) => this.createDefinitionArgumentSpecifications(schema, nameInSchema),
330
+ });
331
+ }
332
+
333
+ private createDefinitionArgumentSpecifications(schema: Schema, nameInSchema?: string): { args: ArgumentSpecification[], errors: GraphQLError[] } {
334
+ const args: ArgumentSpecification[] = [
335
+ { name: this.urlArgName(), type: schema.stringType() },
336
+ { name: 'as', type: schema.stringType() },
337
+ ];
338
+ if (this.supportPurposes()) {
339
+ const purposeName = `${nameInSchema ?? this.url.name}__${linkPurposeTypeSpec.name}`;
340
+ const errors = linkPurposeTypeSpec.checkOrAdd(schema, purposeName);
341
+ if (errors.length > 0) {
342
+ return { args, errors }
343
+ }
344
+ args.push({ name: 'for', type: schema.type(purposeName) as InputType });
345
+ }
346
+ if (this.supportImport()) {
347
+ const importName = `${nameInSchema ?? this.url.name}__${linkImportTypeSpec.name}`;
348
+ const errors = linkImportTypeSpec.checkOrAdd(schema, importName);
349
+ if (errors.length > 0) {
350
+ return { args, errors }
351
+ }
352
+ args.push({ name: 'import', type: new ListType(schema.type(importName)!) });
353
+ }
354
+ return { args, errors: [] };
294
355
  }
295
356
 
296
- addElementsToSchema(_: Schema): void {
357
+ addElementsToSchema(_: Schema): GraphQLError[] {
297
358
  // Core is special and the @core directive is added in `addToSchema` below
359
+ return [];
298
360
  }
299
361
 
300
362
  // TODO: we may want to allow some `import` as argument to this method. When we do, we need to watch for imports of
301
363
  // `Purpose` and `Import` and add the types under their imported name.
302
- addToSchema(schema: Schema, as?: string) {
303
- const existing = schema.coreFeatures;
304
- if (existing) {
305
- if (existing.coreItself.url.identity === this.identity) {
306
- // Already exists with the same version, let it be.
307
- return;
308
- } else {
309
- throw buildError(`Cannot add feature ${this} to the schema, it already uses ${existing.coreItself.url}`);
310
- }
311
- }
312
-
313
- const nameInSchema = as ?? this.url.name;
314
- const core = schema.addDirectiveDefinition(nameInSchema).addLocations(DirectiveLocation.SCHEMA);
315
- core.repeatable = true;
316
- core.addArgument(this.urlArgName(), new NonNullType(schema.stringType()));
317
- core.addArgument('as', schema.stringType());
318
- if (this.supportPurposes()) {
319
- const purposeEnum = schema.addType(new EnumType(`${nameInSchema}__Purpose`));
320
- for (const purpose of corePurposes) {
321
- purposeEnum.addValue(purpose).description = purposesDescription(purpose);
322
- }
323
- core.addArgument('for', purposeEnum);
324
- }
325
- if (this.supportImport()) {
326
- const importType = schema.addType(new ScalarType(`${nameInSchema}__Import`));
327
- core.addArgument('import', new ListType(importType));
364
+ addToSchema(schema: Schema, as?: string): GraphQLError[] {
365
+ const errors = this.addDefinitionsToSchema(schema, as);
366
+ if (errors.length > 0) {
367
+ return errors;
328
368
  }
329
369
 
330
370
  // Note: we don't use `applyFeatureToSchema` because it would complain the schema is not a core schema, which it isn't
@@ -333,7 +373,25 @@ export class CoreSpecDefinition extends FeatureDefinition {
333
373
  if (as) {
334
374
  args.as = as;
335
375
  }
336
- schema.schemaDefinition.applyDirective(nameInSchema, args);
376
+ schema.schemaDefinition.applyDirective(as ?? this.url.name, args, true);
377
+ return [];
378
+ }
379
+
380
+ addDefinitionsToSchema(schema: Schema, as?: string): GraphQLError[] {
381
+ const existingCore = schema.coreFeatures;
382
+ if (existingCore) {
383
+ if (existingCore.coreItself.url.identity === this.identity) {
384
+ // Already exists with the same version, let it be.
385
+ return [];
386
+ } else {
387
+ return [ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
388
+ message: `Cannot add feature ${this} to the schema, it already uses ${existingCore.coreItself.url}`
389
+ })];
390
+ }
391
+ }
392
+
393
+ const nameInSchema = as ?? this.url.name;
394
+ return this.directiveDefinitionSpec.checkOrAdd(schema, nameInSchema);
337
395
  }
338
396
 
339
397
  /**
@@ -381,7 +439,7 @@ export class CoreSpecDefinition extends FeatureDefinition {
381
439
  return feature.url.version;
382
440
  }
383
441
 
384
- applyFeatureToSchema(schema: Schema, feature: FeatureDefinition, as?: string, purpose?: CorePurpose) {
442
+ applyFeatureToSchema(schema: Schema, feature: FeatureDefinition, as?: string, purpose?: CorePurpose): GraphQLError[] {
385
443
  const coreDirective = this.coreDirective(schema);
386
444
  const args = {
387
445
  [this.urlArgName()]: feature.toString(),
@@ -391,8 +449,9 @@ export class CoreSpecDefinition extends FeatureDefinition {
391
449
  args.for = purpose;
392
450
  }
393
451
  schema.schemaDefinition.applyDirective(coreDirective, args);
394
- feature.addElementsToSchema(schema);
452
+ return feature.addElementsToSchema(schema);
395
453
  }
454
+
396
455
  extractFeatureUrl(args: CoreOrLinkDirectiveArgs): FeatureUrl {
397
456
  return FeatureUrl.parse(args[this.urlArgName()]!);
398
457
  }
@@ -651,20 +710,74 @@ export const LINK_VERSIONS = new FeatureDefinitions<CoreSpecDefinition>(linkIden
651
710
  registerKnownFeature(CORE_VERSIONS);
652
711
  registerKnownFeature(LINK_VERSIONS);
653
712
 
654
- export function removeFeatureElements(schema: Schema, feature: CoreFeature) {
655
- // Removing directives first, so that when we remove types, the checks that there is no references don't fail due a directive of a the feature
656
- // actually using the type.
657
- const featureDirectives = schema.directives().filter(d => feature.isFeatureDefinition(d));
658
- featureDirectives.forEach(d => d.remove().forEach(application => application.remove()));
659
-
660
- const featureTypes = schema.types().filter(t => feature.isFeatureDefinition(t));
661
- featureTypes.forEach(type => {
662
- const references = type.remove();
663
- if (references.length > 0) {
664
- throw new GraphQLError(
665
- `Cannot remove elements of feature ${feature} as feature type ${type} is referenced by elements: ${references.join(', ')}`,
666
- references.map(r => r.sourceAST).filter(n => n !== undefined) as ASTNode[]
667
- );
713
+ export function removeAllCoreFeatures(schema: Schema) {
714
+ // Gather a list of core features up front, since we can't fetch them during
715
+ // removal. (Also note that core being a feature itself, this will remove core
716
+ // itself and mark the schema as 'not core').
717
+ const coreFeatures = [...(schema.coreFeatures?.allFeatures() ?? [])];
718
+
719
+ // Remove all feature elements, keeping track of any type references found
720
+ // along the way.
721
+ const typeReferences: {
722
+ feature: CoreFeature;
723
+ type: NamedType;
724
+ references: SchemaElement<any, any>[];
725
+ }[] = [];
726
+ for (const feature of coreFeatures) {
727
+ // Remove feature directive definitions and their applications.
728
+ const featureDirectiveDefs = schema.directives()
729
+ .filter(d => feature.isFeatureDefinition(d));
730
+ featureDirectiveDefs.forEach(def =>
731
+ def.remove().forEach(application => application.remove())
732
+ );
733
+
734
+ // Remove feature types.
735
+ const featureTypes = schema.types()
736
+ .filter(t => feature.isFeatureDefinition(t));
737
+ featureTypes.forEach(type => {
738
+ const references = type.remove();
739
+ if (references.length > 0) {
740
+ typeReferences.push({
741
+ feature,
742
+ type,
743
+ references,
744
+ });
668
745
  }
669
- });
746
+ });
747
+ }
748
+
749
+ // Now that we're finished with removals, for any referencers encountered,
750
+ // check whether they're still attached to the schema (and fail if they are).
751
+ //
752
+ // We wait for after all removals are done, since it means we don't have to
753
+ // worry about the ordering of removals (e.g. if one feature element refers
754
+ // to a different feature's element) or any circular references.
755
+ //
756
+ // Note that we fail for ALL type referencers, regardless of whether removing
757
+ // the type necessitates removal of the type referencer. E.g. even if some
758
+ // non-core object type were to implement some core feature interface type, we
759
+ // would still require removal of the non-core object type. Users don't have
760
+ // to enact this removal by removing the object type from their supergraph
761
+ // schema though; they could also just mark it @inaccessible (since this
762
+ // function is called after removeInaccessibleElements()).
763
+ //
764
+ // In the future, we could potentially relax this validation once we determine
765
+ // the appropriate semantics. (This validation has already been relaxed for
766
+ // directive applications, since feature directive definition removal does not
767
+ // necessitate removal of elements with directive applications.)
768
+ const errors: GraphQLError[] = [];
769
+ for (const { feature, type, references } of typeReferences) {
770
+ const referencesInSchema = references.filter(r => r.isAttached());
771
+ if (referencesInSchema.length > 0) {
772
+ errors.push(new GraphQLError(
773
+ `Cannot remove elements of feature ${feature} as feature type ${type}` +
774
+ ` is referenced by elements: ${referencesInSchema.join(', ')}`,
775
+ references.map(r => r.sourceAST)
776
+ .filter(n => n !== undefined) as ASTNode[]
777
+ ));
778
+ }
779
+ }
780
+ if (errors.length > 0) {
781
+ throw ErrGraphQLAPISchemaValidationFailed(errors);
782
+ }
670
783
  }