@apollo/federation-internals 2.0.0-preview.5 → 2.0.0-preview.9
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 +15 -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 +15 -7
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +171 -42
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +10 -5
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +98 -19
- 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 +67 -19
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +7 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +33 -1
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +6 -0
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +18 -4
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +113 -63
- 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 +5 -1
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +31 -3
- 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} +1 -1
- package/dist/precompute.js.map +1 -0
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +1 -2
- 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 +1 -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/package.json +3 -3
- package/src/__tests__/coreSpec.test.ts +100 -0
- package/src/__tests__/definitions.test.ts +75 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +59 -6
- package/src/__tests__/schemaUpgrader.test.ts +3 -2
- package/src/__tests__/subgraphValidation.test.ts +402 -4
- package/src/buildSchema.ts +98 -51
- package/src/coreSpec.ts +208 -50
- package/src/definitions.ts +128 -21
- package/src/directiveAndTypeSpecification.ts +80 -20
- package/src/error.ts +58 -0
- package/src/extractSubgraphsFromSupergraph.ts +6 -0
- package/src/federation.ts +150 -78
- package/src/federationSpec.ts +56 -24
- package/src/inaccessibleSpec.ts +39 -11
- 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} +1 -2
- package/src/schemaUpgrader.ts +4 -7
- package/src/suggestions.ts +1 -1
- package/src/supergraphs.ts +1 -0
- package/src/tagSpec.ts +49 -16
- 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, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition } 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));
|
|
403
|
+
names.push('Import');
|
|
242
404
|
}
|
|
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;
|
|
249
|
-
}
|
|
250
|
-
schema.schemaDefinition.applyDirective(nameInSchema, args);
|
|
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,6 +703,9 @@ 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
|
|
|
706
|
+
registerKnownFeature(CORE_VERSIONS);
|
|
707
|
+
registerKnownFeature(LINK_VERSIONS);
|
|
708
|
+
|
|
551
709
|
export function removeFeatureElements(schema: Schema, feature: CoreFeature) {
|
|
552
710
|
// 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
|
|
553
711
|
// actually using the type.
|
package/src/definitions.ts
CHANGED
|
@@ -39,12 +39,15 @@ import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
|
39
39
|
import { specifiedSDLRules } from "graphql/validation/specifiedRules";
|
|
40
40
|
import { validateSchema } from "./validate";
|
|
41
41
|
import { createDirectiveSpecification, createScalarTypeSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
|
|
42
|
+
import { didYouMean, suggestionList } from "./suggestions";
|
|
43
|
+
import { withModifiedErrorMessage } from "./error";
|
|
42
44
|
|
|
43
45
|
const validationErrorCode = 'GraphQLValidationFailed';
|
|
46
|
+
const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL schema.';
|
|
44
47
|
|
|
45
|
-
export const ErrGraphQLValidationFailed = (causes: GraphQLError[]) =>
|
|
48
|
+
export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: string = DEFAULT_VALIDATION_ERROR_MESSAGE) =>
|
|
46
49
|
err(validationErrorCode, {
|
|
47
|
-
message
|
|
50
|
+
message,
|
|
48
51
|
causes
|
|
49
52
|
});
|
|
50
53
|
|
|
@@ -381,7 +384,7 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
381
384
|
}
|
|
382
385
|
|
|
383
386
|
export function sourceASTs(...elts: ({ sourceAST?: ASTNode } | undefined)[]): ASTNode[] {
|
|
384
|
-
return elts.map(elt => elt?.sourceAST).filter(elt => elt !== undefined)
|
|
387
|
+
return elts.map(elt => elt?.sourceAST).filter((elt): elt is ASTNode => elt !== undefined);
|
|
385
388
|
}
|
|
386
389
|
|
|
387
390
|
// Not exposed: mostly about avoid code duplication between SchemaElement and Directive (which is not a SchemaElement as it can't
|
|
@@ -490,7 +493,8 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
490
493
|
|
|
491
494
|
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
492
495
|
nameOrDefOrDirective: Directive<TOwnType, TApplicationArgs> | DirectiveDefinition<TApplicationArgs> | string,
|
|
493
|
-
args?: TApplicationArgs
|
|
496
|
+
args?: TApplicationArgs,
|
|
497
|
+
asFirstDirective: boolean = false,
|
|
494
498
|
): Directive<TOwnType, TApplicationArgs> {
|
|
495
499
|
let toAdd: Directive<TOwnType, TApplicationArgs>;
|
|
496
500
|
if (nameOrDefOrDirective instanceof Directive) {
|
|
@@ -503,9 +507,15 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
503
507
|
let name: string;
|
|
504
508
|
if (typeof nameOrDefOrDirective === 'string') {
|
|
505
509
|
this.checkUpdate();
|
|
506
|
-
const def = this.schema().directive(nameOrDefOrDirective) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDefOrDirective);
|
|
510
|
+
const def = this.schema().directive(nameOrDefOrDirective) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDefOrDirective, args);
|
|
507
511
|
if (!def) {
|
|
508
|
-
throw
|
|
512
|
+
throw this.schema().blueprint.onGraphQLJSValidationError(
|
|
513
|
+
this.schema(),
|
|
514
|
+
new GraphQLError(`Unknown directive "@${nameOrDefOrDirective}".`)
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
if (Array.isArray(def)) {
|
|
518
|
+
throw ErrGraphQLValidationFailed(def);
|
|
509
519
|
}
|
|
510
520
|
name = nameOrDefOrDirective;
|
|
511
521
|
} else {
|
|
@@ -516,7 +526,11 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
516
526
|
Element.prototype['setParent'].call(toAdd, this);
|
|
517
527
|
}
|
|
518
528
|
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
519
|
-
|
|
529
|
+
if (asFirstDirective) {
|
|
530
|
+
this._appliedDirectives.unshift(toAdd);
|
|
531
|
+
} else {
|
|
532
|
+
this._appliedDirectives.push(toAdd);
|
|
533
|
+
}
|
|
520
534
|
DirectiveDefinition.prototype['addReferencer'].call(toAdd.definition!, toAdd);
|
|
521
535
|
this.onModification();
|
|
522
536
|
return toAdd;
|
|
@@ -823,13 +837,14 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
|
|
|
823
837
|
}
|
|
824
838
|
|
|
825
839
|
export class SchemaBlueprint {
|
|
826
|
-
onMissingDirectiveDefinition(_schema: Schema, _name: string): DirectiveDefinition | undefined {
|
|
840
|
+
onMissingDirectiveDefinition(_schema: Schema, _name: string, _args?: {[key: string]: any}): DirectiveDefinition | GraphQLError[] | undefined {
|
|
827
841
|
// No-op by default, but used for federation.
|
|
828
842
|
return undefined;
|
|
829
843
|
}
|
|
830
844
|
|
|
831
|
-
onDirectiveDefinitionAndSchemaParsed(_: Schema) {
|
|
845
|
+
onDirectiveDefinitionAndSchemaParsed(_: Schema): GraphQLError[] {
|
|
832
846
|
// No-op by default, but used for federation.
|
|
847
|
+
return [];
|
|
833
848
|
}
|
|
834
849
|
|
|
835
850
|
ignoreParsedField(_type: NamedType, _fieldName: string): boolean {
|
|
@@ -857,6 +872,38 @@ export class SchemaBlueprint {
|
|
|
857
872
|
validationRules(): readonly SDLValidationRule[] {
|
|
858
873
|
return specifiedSDLRules;
|
|
859
874
|
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Allows to intercept some graphQL-js error messages when we can provide additional guidance to users.
|
|
878
|
+
*/
|
|
879
|
+
onGraphQLJSValidationError(schema: Schema, error: GraphQLError): GraphQLError {
|
|
880
|
+
// For now, the main additional guidance we provide is around directives, where we could provide additional help in 2 main ways:
|
|
881
|
+
// - if a directive name is likely misspelled (somehow, graphQL-js has methods to offer suggestions on likely mispelling, but don't use this (at the
|
|
882
|
+
// time of this writting) for directive names).
|
|
883
|
+
// - for fed 2 schema, if a federation directive is refered under it's "default" naming but is not properly imported (not enforced
|
|
884
|
+
// in the method but rather in the `FederationBlueprint`).
|
|
885
|
+
//
|
|
886
|
+
// Note that intercepting/parsing error messages to modify them is never ideal, but pragmatically, it's probably better than rewriting the relevant
|
|
887
|
+
// rules entirely (in that later case, our "copied" rule would stop getting any potential graphQL-js made improvements for instance). And while such
|
|
888
|
+
// parsing is fragile, in that it'll break if the original message change, we have unit tests to surface any such breakage so it's not really a risk.
|
|
889
|
+
const matcher = /^Unknown directive "@(?<directive>[_A-Za-z][_0-9A-Za-z]*)"\.$/.exec(error.message);
|
|
890
|
+
const name = matcher?.groups?.directive;
|
|
891
|
+
if (!name) {
|
|
892
|
+
return error;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const allDefinedDirectiveNames = schema.allDirectives().map((d) => d.name);
|
|
896
|
+
const suggestions = suggestionList(name, allDefinedDirectiveNames);
|
|
897
|
+
if (suggestions.length === 0) {
|
|
898
|
+
return this.onUnknownDirectiveValidationError(schema, name, error);
|
|
899
|
+
} else {
|
|
900
|
+
return withModifiedErrorMessage(error, `${error.message}${didYouMean(suggestions.map((s) => '@' + s))}`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
onUnknownDirectiveValidationError(_schema: Schema, _unknownDirectiveName: string, error: GraphQLError): GraphQLError {
|
|
905
|
+
return error;
|
|
906
|
+
}
|
|
860
907
|
}
|
|
861
908
|
|
|
862
909
|
export const defaultSchemaBlueprint = new SchemaBlueprint();
|
|
@@ -873,7 +920,8 @@ export class CoreFeature {
|
|
|
873
920
|
|
|
874
921
|
isFeatureDefinition(element: NamedType | DirectiveDefinition): boolean {
|
|
875
922
|
return element.name.startsWith(this.nameInSchema + '__')
|
|
876
|
-
|| (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
|
|
923
|
+
|| (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
|
|
924
|
+
|| !!this.imports.find((i) => element.name === (i.as ?? i.name));
|
|
877
925
|
}
|
|
878
926
|
|
|
879
927
|
directiveNameInSchema(name: string): string {
|
|
@@ -931,7 +979,7 @@ export class CoreFeatures {
|
|
|
931
979
|
if (existing) {
|
|
932
980
|
throw error(`Duplicate inclusion of feature ${url.identity}`);
|
|
933
981
|
}
|
|
934
|
-
const imports = extractCoreFeatureImports(typedDirective);
|
|
982
|
+
const imports = extractCoreFeatureImports(url, typedDirective);
|
|
935
983
|
const feature = new CoreFeature(url, args.as ?? url.name, directive, imports, args.for);
|
|
936
984
|
this.add(feature);
|
|
937
985
|
directive.schema().blueprint.onAddedCoreFeature(directive.schema(), feature);
|
|
@@ -976,25 +1024,29 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
|
|
|
976
1024
|
createDirectiveSpecification({
|
|
977
1025
|
name: 'include',
|
|
978
1026
|
locations: [DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
|
|
979
|
-
argumentFct: (schema) => [{ name: 'if', type: new NonNullType(schema.booleanType()) }]
|
|
1027
|
+
argumentFct: (schema) => ({ args: [{ name: 'if', type: new NonNullType(schema.booleanType()) }], errors: [] })
|
|
980
1028
|
}),
|
|
981
1029
|
createDirectiveSpecification({
|
|
982
1030
|
name: 'skip',
|
|
983
1031
|
locations: [DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
|
|
984
|
-
argumentFct: (schema) => [{ name: 'if', type: new NonNullType(schema.booleanType()) }]
|
|
1032
|
+
argumentFct: (schema) => ({ args: [{ name: 'if', type: new NonNullType(schema.booleanType()) }], errors: [] })
|
|
985
1033
|
}),
|
|
986
1034
|
createDirectiveSpecification({
|
|
987
1035
|
name: 'deprecated',
|
|
988
1036
|
locations: [DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.ENUM_VALUE, DirectiveLocation.ARGUMENT_DEFINITION, DirectiveLocation.INPUT_FIELD_DEFINITION],
|
|
989
|
-
argumentFct: (schema) => [{ name: 'reason', type: schema.stringType(), defaultValue: 'No longer supported' }]
|
|
1037
|
+
argumentFct: (schema) => ({ args: [{ name: 'reason', type: schema.stringType(), defaultValue: 'No longer supported' }], errors: [] })
|
|
990
1038
|
}),
|
|
991
1039
|
createDirectiveSpecification({
|
|
992
1040
|
name: 'specifiedBy',
|
|
993
1041
|
locations: [DirectiveLocation.SCALAR],
|
|
994
|
-
argumentFct: (schema) => [{ name: 'url', type: new NonNullType(schema.stringType()) }]
|
|
1042
|
+
argumentFct: (schema) => ({ args: [{ name: 'url', type: new NonNullType(schema.stringType()) }], errors: [] })
|
|
995
1043
|
}),
|
|
996
1044
|
];
|
|
997
1045
|
|
|
1046
|
+
|
|
1047
|
+
// A coordinate is up to 3 "graphQL name" ([_A-Za-z][_0-9A-Za-z]*).
|
|
1048
|
+
const coordinateRegexp = /^@?[_A-Za-z][_0-9A-Za-z]*(\.[_A-Za-z][_0-9A-Za-z]*)?(\([_A-Za-z][_0-9A-Za-z]*:\))?$/;
|
|
1049
|
+
|
|
998
1050
|
export class Schema {
|
|
999
1051
|
private _schemaDefinition: SchemaDefinition;
|
|
1000
1052
|
private readonly _builtInTypes = new MapWithCachedArrays<string, NamedType>();
|
|
@@ -1311,7 +1363,7 @@ export class Schema {
|
|
|
1311
1363
|
|
|
1312
1364
|
// TODO: we check that all types are properly set (aren't undefined) in `validateSchema`, but `validateSDL` will error out beforehand. We should
|
|
1313
1365
|
// probably extract that part of `validateSchema` and run `validateSDL` conditionally on that first check.
|
|
1314
|
-
let errors = validateSDL(this.toAST(), undefined, this.blueprint.validationRules());
|
|
1366
|
+
let errors = validateSDL(this.toAST(), undefined, this.blueprint.validationRules()).map((e) => this.blueprint.onGraphQLJSValidationError(this, e));
|
|
1315
1367
|
errors = errors.concat(validateSchema(this));
|
|
1316
1368
|
|
|
1317
1369
|
// We avoid adding federation-specific validations if the base schema is not proper graphQL as the later can easily trigger
|
|
@@ -1364,6 +1416,56 @@ export class Schema {
|
|
|
1364
1416
|
specifiedByDirective(schema: Schema): DirectiveDefinition<{url: string}> {
|
|
1365
1417
|
return this.getBuiltInDirective(schema, 'specifiedBy');
|
|
1366
1418
|
}
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* Gets an element of the schema given its "schema coordinate".
|
|
1422
|
+
*
|
|
1423
|
+
* Note that the syntax for schema coordinates is the one from the upcoming GraphQL spec: https://github.com/graphql/graphql-spec/pull/794.
|
|
1424
|
+
*/
|
|
1425
|
+
elementByCoordinate(coordinate: string): NamedSchemaElement<any, any, any> | undefined {
|
|
1426
|
+
if (!coordinate.match(coordinateRegexp)) {
|
|
1427
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
const argStartIdx = coordinate.indexOf('(');
|
|
1431
|
+
const start = argStartIdx < 0 ? coordinate : coordinate.slice(0, argStartIdx);
|
|
1432
|
+
// Argument syntax is `foo(argName:)`, so the arg name start after the open parenthesis and go until the final ':)'.
|
|
1433
|
+
const argName = argStartIdx < 0 ? undefined : coordinate.slice(argStartIdx + 1, coordinate.length - 2);
|
|
1434
|
+
const splittedStart = start.split('.');
|
|
1435
|
+
const typeOrDirectiveName = splittedStart[0];
|
|
1436
|
+
const fieldOrEnumName = splittedStart[1];
|
|
1437
|
+
const isDirective = typeOrDirectiveName.startsWith('@');
|
|
1438
|
+
if (isDirective) {
|
|
1439
|
+
if (fieldOrEnumName) {
|
|
1440
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1441
|
+
}
|
|
1442
|
+
const directive = this.directive(typeOrDirectiveName.slice(1));
|
|
1443
|
+
return argName ? directive?.argument(argName) : directive;
|
|
1444
|
+
} else {
|
|
1445
|
+
const type = this.type(typeOrDirectiveName);
|
|
1446
|
+
if (!type || !fieldOrEnumName) {
|
|
1447
|
+
return type;
|
|
1448
|
+
}
|
|
1449
|
+
switch (type.kind) {
|
|
1450
|
+
case 'ObjectType':
|
|
1451
|
+
case 'InterfaceType':
|
|
1452
|
+
const field = type.field(fieldOrEnumName);
|
|
1453
|
+
return argName ? field?.argument(argName) : field;
|
|
1454
|
+
case 'InputObjectType':
|
|
1455
|
+
if (argName) {
|
|
1456
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1457
|
+
}
|
|
1458
|
+
return type.field(fieldOrEnumName);
|
|
1459
|
+
case 'EnumType':
|
|
1460
|
+
if (argName) {
|
|
1461
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1462
|
+
}
|
|
1463
|
+
return type.value(fieldOrEnumName);
|
|
1464
|
+
default:
|
|
1465
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1367
1469
|
}
|
|
1368
1470
|
|
|
1369
1471
|
export class RootType extends BaseExtensionMember<SchemaDefinition> {
|
|
@@ -1391,9 +1493,10 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1391
1493
|
|
|
1392
1494
|
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
1393
1495
|
nameOrDefOrDirective: Directive<SchemaDefinition, TApplicationArgs> | DirectiveDefinition<TApplicationArgs> | string,
|
|
1394
|
-
args?: TApplicationArgs
|
|
1496
|
+
args?: TApplicationArgs,
|
|
1497
|
+
asFirstDirective: boolean = false,
|
|
1395
1498
|
): Directive<SchemaDefinition, TApplicationArgs> {
|
|
1396
|
-
const applied = super.applyDirective(nameOrDefOrDirective, args) as Directive<SchemaDefinition, TApplicationArgs>;
|
|
1499
|
+
const applied = super.applyDirective(nameOrDefOrDirective, args, asFirstDirective) as Directive<SchemaDefinition, TApplicationArgs>;
|
|
1397
1500
|
const schema = this.schema();
|
|
1398
1501
|
const coreFeatures = schema.coreFeatures;
|
|
1399
1502
|
if (isCoreSpecDirectiveApplication(applied)) {
|
|
@@ -1403,9 +1506,13 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1403
1506
|
const schemaDirective = applied as Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>;
|
|
1404
1507
|
const args = schemaDirective.arguments();
|
|
1405
1508
|
const url = FeatureUrl.parse((args.url ?? args.feature)!);
|
|
1406
|
-
const imports = extractCoreFeatureImports(schemaDirective);
|
|
1509
|
+
const imports = extractCoreFeatureImports(url, schemaDirective);
|
|
1407
1510
|
const core = new CoreFeature(url, args.as ?? url.name, schemaDirective, imports, args.for);
|
|
1408
1511
|
Schema.prototype['markAsCoreSchema'].call(schema, core);
|
|
1512
|
+
// We also any core features that may have been added before we saw the @link for link itself
|
|
1513
|
+
this.appliedDirectives
|
|
1514
|
+
.filter((a) => a !== applied)
|
|
1515
|
+
.forEach((other) => CoreFeatures.prototype['maybeAddFeature'].call(schema.coreFeatures, other));
|
|
1409
1516
|
} else if (coreFeatures) {
|
|
1410
1517
|
CoreFeatures.prototype['maybeAddFeature'].call(coreFeatures, applied);
|
|
1411
1518
|
}
|
|
@@ -2711,9 +2818,9 @@ export class Directive<
|
|
|
2711
2818
|
this.onModification();
|
|
2712
2819
|
const coreFeatures = this.schema().coreFeatures;
|
|
2713
2820
|
if (coreFeatures && this.name === coreFeatures.coreItself.nameInSchema) {
|
|
2714
|
-
// We're removing a @core directive application, so we remove it from the list of core features. And
|
|
2821
|
+
// We're removing a @core/@link directive application, so we remove it from the list of core features. And
|
|
2715
2822
|
// if it is @core itself, we clean all features (to avoid having things too inconsistent).
|
|
2716
|
-
const url = FeatureUrl.parse(this._args[
|
|
2823
|
+
const url = FeatureUrl.parse(this._args[coreFeatures.coreDefinition.urlArgName()]!);
|
|
2717
2824
|
if (url.identity === coreFeatures.coreItself.url.identity) {
|
|
2718
2825
|
// Note that we unmark first because the loop after that will nuke our parent.
|
|
2719
2826
|
Schema.prototype['unmarkAsCoreSchema'].call(this.schema());
|