@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/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):
|
|
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 || !
|
|
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):
|
|
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
|
|
304
|
-
if (
|
|
305
|
-
|
|
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(
|
|
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
|
|
655
|
-
//
|
|
656
|
-
//
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
}
|