@apollo/federation-internals 2.0.0-preview.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -3
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +51 -41
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +16 -8
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +205 -53
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +28 -11
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +185 -67
- 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 +17 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +54 -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 +22 -5
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +143 -86
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +6 -2
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +47 -22
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +10 -2
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +634 -16
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +5 -1
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +21 -0
- package/dist/joinSpec.js.map +1 -1
- package/dist/knownCoreFeatures.d.ts +4 -0
- package/dist/knownCoreFeatures.d.ts.map +1 -0
- package/dist/knownCoreFeatures.js +16 -0
- package/dist/knownCoreFeatures.js.map +1 -0
- 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 +7 -2
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +35 -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 +212 -0
- package/src/__tests__/definitions.test.ts +75 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +2229 -137
- package/src/__tests__/schemaUpgrader.test.ts +3 -2
- package/src/__tests__/subgraphValidation.test.ts +419 -4
- package/src/__tests__/values.test.ts +315 -3
- package/src/buildSchema.ts +98 -51
- package/src/coreSpec.ts +277 -65
- package/src/definitions.ts +317 -92
- package/src/directiveAndTypeSpecification.ts +98 -21
- package/src/error.ts +119 -1
- package/src/extractSubgraphsFromSupergraph.ts +7 -1
- package/src/federation.ts +184 -102
- package/src/federationSpec.ts +56 -24
- package/src/inaccessibleSpec.ts +985 -39
- package/src/index.ts +2 -0
- package/src/introspection.ts +8 -3
- package/src/joinSpec.ts +33 -3
- package/src/knownCoreFeatures.ts +13 -0
- 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 +49 -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,10 +1,14 @@
|
|
|
1
1
|
import { ASTNode, DirectiveLocation, GraphQLError, StringValueNode } from "graphql";
|
|
2
2
|
import { URL } from "url";
|
|
3
|
-
import { CoreFeature, Directive, DirectiveDefinition, EnumType, 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';
|
|
7
7
|
import { ERRORS } from "./error";
|
|
8
|
+
import { valueToString } from "./values";
|
|
9
|
+
import { coreFeatureDefinitionIfKnown, registerKnownFeature } from "./knownCoreFeatures";
|
|
10
|
+
import { didYouMean, suggestionList } from "./suggestions";
|
|
11
|
+
import { ArgumentSpecification, createDirectiveSpecification, createEnumTypeSpecification, createScalarTypeSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
|
|
8
12
|
|
|
9
13
|
export const coreIdentity = 'https://specs.apollo.dev/core';
|
|
10
14
|
export const linkIdentity = 'https://specs.apollo.dev/link';
|
|
@@ -61,7 +65,9 @@ export abstract class FeatureDefinition {
|
|
|
61
65
|
return nameInSchema != undefined && (directive.name === nameInSchema || directive.name.startsWith(`${nameInSchema}__`));
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
abstract addElementsToSchema(schema: Schema):
|
|
68
|
+
abstract addElementsToSchema(schema: Schema): GraphQLError[];
|
|
69
|
+
|
|
70
|
+
abstract allElementNames(): string[];
|
|
65
71
|
|
|
66
72
|
protected nameInSchema(schema: Schema): string | undefined {
|
|
67
73
|
const feature = this.featureInSchema(schema);
|
|
@@ -73,9 +79,9 @@ export abstract class FeatureDefinition {
|
|
|
73
79
|
return feature ? feature.directiveNameInSchema(directiveName) : undefined;
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
protected typeNameInSchema(schema: Schema,
|
|
82
|
+
protected typeNameInSchema(schema: Schema, typeName: string): string | undefined {
|
|
77
83
|
const feature = this.featureInSchema(schema);
|
|
78
|
-
return feature ? feature.typeNameInSchema(
|
|
84
|
+
return feature ? feature.typeNameInSchema(typeName) : undefined;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
protected rootDirective<TApplicationArgs extends {[key: string]: any}>(schema: Schema): DirectiveDefinition<TApplicationArgs> | undefined {
|
|
@@ -101,6 +107,14 @@ export abstract class FeatureDefinition {
|
|
|
101
107
|
return schema.addDirectiveDefinition(this.directiveNameInSchema(schema, name)!);
|
|
102
108
|
}
|
|
103
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
|
+
|
|
104
118
|
protected addScalarType(schema: Schema, name: string): ScalarType {
|
|
105
119
|
return schema.addType(new ScalarType(this.typeNameInSchema(schema, name)!));
|
|
106
120
|
}
|
|
@@ -144,33 +158,115 @@ export type CoreImport = {
|
|
|
144
158
|
as?: string,
|
|
145
159
|
};
|
|
146
160
|
|
|
147
|
-
export function extractCoreFeatureImports(directive: Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>): CoreImport[] {
|
|
161
|
+
export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>): CoreImport[] {
|
|
162
|
+
// Note: up to this point, we've kind of cheated with typing and force-casted the arguments to `CoreOrLinkDirectiveArgs`, and while this
|
|
163
|
+
// graphQL type validations ensure this is "mostly" true, the `import' arg is an exception becuse it uses the `link__Import` scalar,
|
|
164
|
+
// and so there is no fine-grained graphQL-side validation of the values. So we'll need to double-check that the values are indeed
|
|
165
|
+
// either a string or a valid `CoreImport` value.
|
|
148
166
|
const args = directive.arguments();
|
|
149
|
-
if (!('import' in args)) {
|
|
167
|
+
if (!('import' in args) || !args.import) {
|
|
150
168
|
return [];
|
|
151
169
|
}
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
170
|
+
const importArgValue = args.import;
|
|
171
|
+
const definition = coreFeatureDefinitionIfKnown(url);
|
|
172
|
+
const knownElements = definition?.allElementNames();
|
|
173
|
+
const errors: GraphQLError[] = [];
|
|
174
|
+
const imports: CoreImport[] = [];
|
|
175
|
+
|
|
176
|
+
importArgLoop:
|
|
177
|
+
for (const elt of importArgValue) {
|
|
178
|
+
if (typeof elt === 'string') {
|
|
179
|
+
imports.push({ name: elt });
|
|
180
|
+
validateImportedName(elt, knownElements, errors, directive);
|
|
156
181
|
continue;
|
|
157
182
|
}
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
message: `Invalid @link
|
|
183
|
+
if (typeof elt !== 'object') {
|
|
184
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
185
|
+
message: `Invalid sub-value ${valueToString(elt)} for @link(import:) argument: values should be either strings or input object values of the form { name: "<importedElement>", as: "<alias>" }.`,
|
|
161
186
|
nodes: directive.sourceAST
|
|
162
|
-
});
|
|
187
|
+
}));
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
let name: string | undefined;
|
|
191
|
+
for (const [key, value] of Object.entries(elt)) {
|
|
192
|
+
switch (key) {
|
|
193
|
+
case 'name':
|
|
194
|
+
if (typeof value !== 'string') {
|
|
195
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
196
|
+
message: `Invalid value for the "name" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
|
|
197
|
+
nodes: directive.sourceAST
|
|
198
|
+
}));
|
|
199
|
+
continue importArgLoop;
|
|
200
|
+
}
|
|
201
|
+
name = value;
|
|
202
|
+
break;
|
|
203
|
+
case 'as':
|
|
204
|
+
if (typeof value !== 'string') {
|
|
205
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
206
|
+
message: `Invalid value for the "as" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
|
|
207
|
+
nodes: directive.sourceAST
|
|
208
|
+
}));
|
|
209
|
+
continue importArgLoop;
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
default:
|
|
213
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
214
|
+
message: `Unknown field "${key}" for sub-value ${valueToString(elt)} of @link(import:) argument.`,
|
|
215
|
+
nodes: directive.sourceAST
|
|
216
|
+
}));
|
|
217
|
+
continue importArgLoop;
|
|
218
|
+
}
|
|
163
219
|
}
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
220
|
+
if (name) {
|
|
221
|
+
const i = elt as CoreImport;
|
|
222
|
+
imports.push(i);
|
|
223
|
+
if (i.as) {
|
|
224
|
+
if (i.name.charAt(0) === '@' && i.as.charAt(0) !== '@') {
|
|
225
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
226
|
+
message: `Invalid @link import renaming: directive "${i.name}" imported name should start with a '@' character, but got "${i.as}".`,
|
|
227
|
+
nodes: directive.sourceAST
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
else if (i.name.charAt(0) !== '@' && i.as.charAt(0) === '@') {
|
|
231
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
232
|
+
message: `Invalid @link import renaming: type "${i.name}" imported name should not start with a '@' character, but got "${i.as}" (or, if @${i.name} is a directive, then it should be referred to with a '@').`,
|
|
233
|
+
nodes: directive.sourceAST
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
validateImportedName(name, knownElements, errors, directive);
|
|
238
|
+
} else {
|
|
239
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
240
|
+
message: `Invalid sub-value ${valueToString(elt)} for @link(import:) argument: missing mandatory "name" field.`,
|
|
167
241
|
nodes: directive.sourceAST
|
|
168
|
-
});
|
|
242
|
+
}));
|
|
169
243
|
}
|
|
170
244
|
}
|
|
245
|
+
|
|
246
|
+
if (errors.length > 0) {
|
|
247
|
+
throw ErrGraphQLValidationFailed(errors);
|
|
248
|
+
}
|
|
171
249
|
return imports;
|
|
172
250
|
}
|
|
173
251
|
|
|
252
|
+
function validateImportedName(name: string, knownElements: string[] | undefined, errors: GraphQLError[], directive: Directive<SchemaDefinition>) {
|
|
253
|
+
if (knownElements && !knownElements.includes(name)) {
|
|
254
|
+
let details = '';
|
|
255
|
+
if (!name.startsWith('@') && knownElements.includes('@' + name)) {
|
|
256
|
+
details = ` Did you mean directive "@${name}"?`;
|
|
257
|
+
} else {
|
|
258
|
+
const suggestions = suggestionList(name, knownElements);
|
|
259
|
+
if (suggestions) {
|
|
260
|
+
details = didYouMean(suggestions);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
264
|
+
message: `Cannot import unknown element "${name}".${details}`,
|
|
265
|
+
nodes: directive.sourceAST
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
174
270
|
export function isCoreSpecDirectiveApplication(directive: Directive<SchemaDefinition, any>): directive is Directive<SchemaDefinition, CoreOrLinkDirectiveArgs> {
|
|
175
271
|
const definition = directive.definition;
|
|
176
272
|
if (!definition) {
|
|
@@ -184,7 +280,7 @@ export function isCoreSpecDirectiveApplication(directive: Directive<SchemaDefini
|
|
|
184
280
|
return false;
|
|
185
281
|
}
|
|
186
282
|
const urlArg = definition.argument('url') ?? definition.argument('feature');
|
|
187
|
-
if (!urlArg || !
|
|
283
|
+
if (!urlArg || !isValidUrlArgumentType(urlArg.type!, directive.schema())) {
|
|
188
284
|
return false;
|
|
189
285
|
}
|
|
190
286
|
|
|
@@ -201,53 +297,112 @@ export function isCoreSpecDirectiveApplication(directive: Directive<SchemaDefini
|
|
|
201
297
|
}
|
|
202
298
|
}
|
|
203
299
|
|
|
300
|
+
function isValidUrlArgumentType(type: InputType, schema: Schema): boolean {
|
|
301
|
+
// Note that the 'url' arg is defined as nullable (mostly for future proofing reasons) but we allow use to provide a definition
|
|
302
|
+
// where it's non-nullable (and in practice, @core (which we never generate anymore, but recognize) definition technically uses
|
|
303
|
+
// with a non-nullable argument, and some fed2 previews did if for @link, so this ensure we handle reading schema generated
|
|
304
|
+
// by those versions just fine).
|
|
305
|
+
return sameType(type, schema.stringType())
|
|
306
|
+
|| sameType(type, new NonNullType(schema.stringType()));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const linkPurposeTypeSpec = createEnumTypeSpecification({
|
|
310
|
+
name: 'Purpose',
|
|
311
|
+
values: corePurposes.map((name) => ({ name, description: purposesDescription(name)}))
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const linkImportTypeSpec = createScalarTypeSpecification({ name: 'Import' });
|
|
315
|
+
|
|
204
316
|
export class CoreSpecDefinition extends FeatureDefinition {
|
|
317
|
+
private readonly directiveDefinitionSpec: DirectiveSpecification;
|
|
318
|
+
|
|
205
319
|
constructor(version: FeatureVersion, identity: string = linkIdentity, name: string = linkDirectiveDefaultName) {
|
|
206
320
|
super(new FeatureUrl(identity, name, version));
|
|
321
|
+
this.directiveDefinitionSpec = createDirectiveSpecification({
|
|
322
|
+
name,
|
|
323
|
+
locations: [DirectiveLocation.SCHEMA],
|
|
324
|
+
repeatable: true,
|
|
325
|
+
argumentFct: (schema, nameInSchema) => this.createDefinitionArgumentSpecifications(schema, nameInSchema),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private createDefinitionArgumentSpecifications(schema: Schema, nameInSchema?: string): { args: ArgumentSpecification[], errors: GraphQLError[] } {
|
|
330
|
+
const args: ArgumentSpecification[] = [
|
|
331
|
+
{ name: this.urlArgName(), type: schema.stringType() },
|
|
332
|
+
{ name: 'as', type: schema.stringType() },
|
|
333
|
+
];
|
|
334
|
+
if (this.supportPurposes()) {
|
|
335
|
+
const purposeName = `${nameInSchema ?? this.url.name}__${linkPurposeTypeSpec.name}`;
|
|
336
|
+
const errors = linkPurposeTypeSpec.checkOrAdd(schema, purposeName);
|
|
337
|
+
if (errors.length > 0) {
|
|
338
|
+
return { args, errors }
|
|
339
|
+
}
|
|
340
|
+
args.push({ name: 'for', type: schema.type(purposeName) as InputType });
|
|
341
|
+
}
|
|
342
|
+
if (this.supportImport()) {
|
|
343
|
+
const importName = `${nameInSchema ?? this.url.name}__${linkImportTypeSpec.name}`;
|
|
344
|
+
const errors = linkImportTypeSpec.checkOrAdd(schema, importName);
|
|
345
|
+
if (errors.length > 0) {
|
|
346
|
+
return { args, errors }
|
|
347
|
+
}
|
|
348
|
+
args.push({ name: 'import', type: new ListType(schema.type(importName)!) });
|
|
349
|
+
}
|
|
350
|
+
return { args, errors: [] };
|
|
207
351
|
}
|
|
208
352
|
|
|
209
|
-
addElementsToSchema(_: Schema):
|
|
353
|
+
addElementsToSchema(_: Schema): GraphQLError[] {
|
|
210
354
|
// Core is special and the @core directive is added in `addToSchema` below
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// TODO: we may want to allow some `import` as argument to this method. When we do, we need to watch for imports of
|
|
359
|
+
// `Purpose` and `Import` and add the types under their imported name.
|
|
360
|
+
addToSchema(schema: Schema, as?: string): GraphQLError[] {
|
|
361
|
+
const errors = this.addDefinitionsToSchema(schema, as);
|
|
362
|
+
if (errors.length > 0) {
|
|
363
|
+
return errors;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Note: we don't use `applyFeatureToSchema` because it would complain the schema is not a core schema, which it isn't
|
|
367
|
+
// until the next line.
|
|
368
|
+
const args = { [this.urlArgName()]: this.toString() } as unknown as CoreOrLinkDirectiveArgs;
|
|
369
|
+
if (as) {
|
|
370
|
+
args.as = as;
|
|
371
|
+
}
|
|
372
|
+
schema.schemaDefinition.applyDirective(as ?? this.url.name, args, true);
|
|
373
|
+
return [];
|
|
211
374
|
}
|
|
212
375
|
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
if (
|
|
376
|
+
addDefinitionsToSchema(schema: Schema, as?: string): GraphQLError[] {
|
|
377
|
+
const existingCore = schema.coreFeatures;
|
|
378
|
+
if (existingCore) {
|
|
379
|
+
if (existingCore.coreItself.url.identity === this.identity) {
|
|
217
380
|
// Already exists with the same version, let it be.
|
|
218
|
-
return;
|
|
381
|
+
return [];
|
|
219
382
|
} else {
|
|
220
|
-
|
|
383
|
+
return [ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
|
|
384
|
+
message: `Cannot add feature ${this} to the schema, it already uses ${existingCore.coreItself.url}`
|
|
385
|
+
})];
|
|
221
386
|
}
|
|
222
387
|
}
|
|
223
388
|
|
|
224
389
|
const nameInSchema = as ?? this.url.name;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
390
|
+
return this.directiveDefinitionSpec.checkOrAdd(schema, nameInSchema);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* The list of all the element names that can be "imported" from this feature. Importantly, directive names
|
|
395
|
+
* must start with a `@`.
|
|
396
|
+
*/
|
|
397
|
+
allElementNames(): string[] {
|
|
398
|
+
const names = [ `@${this.url.name}` ];
|
|
229
399
|
if (this.supportPurposes()) {
|
|
230
|
-
|
|
231
|
-
for (const purpose of corePurposes) {
|
|
232
|
-
purposeEnum.addValue(purpose).description = purposesDescription(purpose);
|
|
233
|
-
}
|
|
234
|
-
core.addArgument('for', purposeEnum);
|
|
400
|
+
names.push('Purpose');
|
|
235
401
|
}
|
|
236
402
|
if (this.supportImport()) {
|
|
237
|
-
|
|
238
|
-
console.trace();
|
|
239
|
-
}
|
|
240
|
-
const importType = schema.addType(new ScalarType(`${nameInSchema}__Import`));
|
|
241
|
-
core.addArgument('import', new ListType(importType));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Note: we don't use `applyFeatureToSchema` because it would complain the schema is not a core schema, which it isn't
|
|
245
|
-
// until the next line.
|
|
246
|
-
const args = { [this.urlArgName()]: this.toString() } as unknown as CoreOrLinkDirectiveArgs;
|
|
247
|
-
if (as) {
|
|
248
|
-
args.as = as;
|
|
403
|
+
names.push('Import');
|
|
249
404
|
}
|
|
250
|
-
|
|
405
|
+
return names;
|
|
251
406
|
}
|
|
252
407
|
|
|
253
408
|
private supportPurposes() {
|
|
@@ -280,7 +435,7 @@ export class CoreSpecDefinition extends FeatureDefinition {
|
|
|
280
435
|
return feature.url.version;
|
|
281
436
|
}
|
|
282
437
|
|
|
283
|
-
applyFeatureToSchema(schema: Schema, feature: FeatureDefinition, as?: string, purpose?: CorePurpose) {
|
|
438
|
+
applyFeatureToSchema(schema: Schema, feature: FeatureDefinition, as?: string, purpose?: CorePurpose): GraphQLError[] {
|
|
284
439
|
const coreDirective = this.coreDirective(schema);
|
|
285
440
|
const args = {
|
|
286
441
|
[this.urlArgName()]: feature.toString(),
|
|
@@ -290,7 +445,7 @@ export class CoreSpecDefinition extends FeatureDefinition {
|
|
|
290
445
|
args.for = purpose;
|
|
291
446
|
}
|
|
292
447
|
schema.schemaDefinition.applyDirective(coreDirective, args);
|
|
293
|
-
feature.addElementsToSchema(schema);
|
|
448
|
+
return feature.addElementsToSchema(schema);
|
|
294
449
|
}
|
|
295
450
|
|
|
296
451
|
extractFeatureUrl(args: CoreOrLinkDirectiveArgs): FeatureUrl {
|
|
@@ -548,20 +703,77 @@ export const CORE_VERSIONS = new FeatureDefinitions<CoreSpecDefinition>(coreIden
|
|
|
548
703
|
export const LINK_VERSIONS = new FeatureDefinitions<CoreSpecDefinition>(linkIdentity)
|
|
549
704
|
.add(new CoreSpecDefinition(new FeatureVersion(1, 0)));
|
|
550
705
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
706
|
+
registerKnownFeature(CORE_VERSIONS);
|
|
707
|
+
registerKnownFeature(LINK_VERSIONS);
|
|
708
|
+
|
|
709
|
+
export function removeAllCoreFeatures(schema: Schema) {
|
|
710
|
+
// Gather a list of core features up front, since we can't fetch them during
|
|
711
|
+
// removal. (Also note that core being a feature itself, this will remove core
|
|
712
|
+
// itself and mark the schema as 'not core').
|
|
713
|
+
const coreFeatures = [...(schema.coreFeatures?.allFeatures() ?? [])];
|
|
714
|
+
|
|
715
|
+
// Remove all feature elements, keeping track of any type references found
|
|
716
|
+
// along the way.
|
|
717
|
+
const typeReferences: {
|
|
718
|
+
feature: CoreFeature;
|
|
719
|
+
type: NamedType;
|
|
720
|
+
references: SchemaElement<any, any>[];
|
|
721
|
+
}[] = [];
|
|
722
|
+
for (const feature of coreFeatures) {
|
|
723
|
+
// Remove feature directive definitions and their applications.
|
|
724
|
+
const featureDirectiveDefs = schema.directives()
|
|
725
|
+
.filter(d => feature.isFeatureDefinition(d));
|
|
726
|
+
featureDirectiveDefs.forEach(def =>
|
|
727
|
+
def.remove().forEach(application => application.remove())
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
// Remove feature types.
|
|
731
|
+
const featureTypes = schema.types()
|
|
732
|
+
.filter(t => feature.isFeatureDefinition(t));
|
|
733
|
+
featureTypes.forEach(type => {
|
|
734
|
+
const references = type.remove();
|
|
735
|
+
if (references.length > 0) {
|
|
736
|
+
typeReferences.push({
|
|
737
|
+
feature,
|
|
738
|
+
type,
|
|
739
|
+
references,
|
|
740
|
+
});
|
|
565
741
|
}
|
|
566
|
-
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Now that we're finished with removals, for any referencers encountered,
|
|
746
|
+
// check whether they're still attached to the schema (and fail if they are).
|
|
747
|
+
//
|
|
748
|
+
// We wait for after all removals are done, since it means we don't have to
|
|
749
|
+
// worry about the ordering of removals (e.g. if one feature element refers
|
|
750
|
+
// to a different feature's element) or any circular references.
|
|
751
|
+
//
|
|
752
|
+
// Note that we fail for ALL type referencers, regardless of whether removing
|
|
753
|
+
// the type necessitates removal of the type referencer. E.g. even if some
|
|
754
|
+
// non-core object type were to implement some core feature interface type, we
|
|
755
|
+
// would still require removal of the non-core object type. Users don't have
|
|
756
|
+
// to enact this removal by removing the object type from their supergraph
|
|
757
|
+
// schema though; they could also just mark it @inaccessible (since this
|
|
758
|
+
// function is called after removeInaccessibleElements()).
|
|
759
|
+
//
|
|
760
|
+
// In the future, we could potentially relax this validation once we determine
|
|
761
|
+
// the appropriate semantics. (This validation has already been relaxed for
|
|
762
|
+
// directive applications, since feature directive definition removal does not
|
|
763
|
+
// necessitate removal of elements with directive applications.)
|
|
764
|
+
const errors: GraphQLError[] = [];
|
|
765
|
+
for (const { feature, type, references } of typeReferences) {
|
|
766
|
+
const referencesInSchema = references.filter(r => r.isAttached());
|
|
767
|
+
if (referencesInSchema.length > 0) {
|
|
768
|
+
errors.push(new GraphQLError(
|
|
769
|
+
`Cannot remove elements of feature ${feature} as feature type ${type}` +
|
|
770
|
+
` is referenced by elements: ${referencesInSchema.join(', ')}`,
|
|
771
|
+
references.map(r => r.sourceAST)
|
|
772
|
+
.filter(n => n !== undefined) as ASTNode[]
|
|
773
|
+
));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (errors.length > 0) {
|
|
777
|
+
throw ErrGraphQLAPISchemaValidationFailed(errors);
|
|
778
|
+
}
|
|
567
779
|
}
|