@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/definitions.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
FeatureUrl,
|
|
25
25
|
findCoreSpecVersion,
|
|
26
26
|
isCoreSpecDirectiveApplication,
|
|
27
|
-
|
|
27
|
+
removeAllCoreFeatures,
|
|
28
28
|
} from "./coreSpec";
|
|
29
29
|
import { assert, mapValues, MapWithCachedArrays, setValues } from "./utils";
|
|
30
30
|
import { withDefaultValues, valueEquals, valueToString, valueToAST, variablesInValue, valueFromAST, valueNodeToConstValueNode } from "./values";
|
|
@@ -39,12 +39,23 @@ 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: message + '. Caused by:\n' + causes.map((c) => c.toString()).join('\n\n'),
|
|
51
|
+
causes
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const apiSchemaValidationErrorCode = 'GraphQLAPISchemaValidationFailed';
|
|
55
|
+
|
|
56
|
+
export const ErrGraphQLAPISchemaValidationFailed = (causes: GraphQLError[]) =>
|
|
57
|
+
err(apiSchemaValidationErrorCode, {
|
|
58
|
+
message: 'The supergraph schema failed to produce a valid API schema',
|
|
48
59
|
causes
|
|
49
60
|
});
|
|
50
61
|
|
|
@@ -54,7 +65,7 @@ export const ErrGraphQLValidationFailed = (causes: GraphQLError[]) =>
|
|
|
54
65
|
*/
|
|
55
66
|
export function errorCauses(e: Error): GraphQLError[] | undefined {
|
|
56
67
|
if (e instanceof GraphQLErrorExt) {
|
|
57
|
-
if (e.code === validationErrorCode) {
|
|
68
|
+
if (e.code === validationErrorCode || e.code === apiSchemaValidationErrorCode) {
|
|
58
69
|
return ((e as any).causes) as GraphQLError[];
|
|
59
70
|
}
|
|
60
71
|
return [e];
|
|
@@ -211,6 +222,22 @@ export function isInputType(type: Type): type is InputType {
|
|
|
211
222
|
}
|
|
212
223
|
}
|
|
213
224
|
|
|
225
|
+
export function isTypeOfKind<T extends Type>(type: Type, kind: T['kind']): type is T {
|
|
226
|
+
return type.kind === kind;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function filterTypesOfKind<T extends Type>(types: readonly Type[], kind: T['kind']): T[] {
|
|
230
|
+
return types.reduce(
|
|
231
|
+
(acc: T[], type: Type) => {
|
|
232
|
+
if (isTypeOfKind(type, kind)) {
|
|
233
|
+
acc.push(type);
|
|
234
|
+
}
|
|
235
|
+
return acc;
|
|
236
|
+
},
|
|
237
|
+
[],
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
214
241
|
export function baseType(type: Type): NamedType {
|
|
215
242
|
return isWrapperType(type) ? type.baseType() : type;
|
|
216
243
|
}
|
|
@@ -380,8 +407,8 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
380
407
|
}
|
|
381
408
|
}
|
|
382
409
|
|
|
383
|
-
export function sourceASTs(...elts: ({ sourceAST?:
|
|
384
|
-
return elts.map(elt => elt?.sourceAST).filter(elt => elt !== undefined)
|
|
410
|
+
export function sourceASTs<TNode extends ASTNode = ASTNode>(...elts: ({ sourceAST?: TNode } | undefined)[]): TNode[] {
|
|
411
|
+
return elts.map(elt => elt?.sourceAST).filter((elt): elt is TNode => elt !== undefined);
|
|
385
412
|
}
|
|
386
413
|
|
|
387
414
|
// Not exposed: mostly about avoid code duplication between SchemaElement and Directive (which is not a SchemaElement as it can't
|
|
@@ -489,34 +516,36 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
489
516
|
}
|
|
490
517
|
|
|
491
518
|
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
492
|
-
|
|
493
|
-
args?: TApplicationArgs
|
|
519
|
+
nameOrDef: DirectiveDefinition<TApplicationArgs> | string,
|
|
520
|
+
args?: TApplicationArgs,
|
|
521
|
+
asFirstDirective: boolean = false,
|
|
494
522
|
): Directive<TOwnType, TApplicationArgs> {
|
|
495
|
-
let
|
|
496
|
-
if (
|
|
497
|
-
this.checkUpdate(
|
|
498
|
-
|
|
499
|
-
if (
|
|
500
|
-
|
|
523
|
+
let name: string;
|
|
524
|
+
if (typeof nameOrDef === 'string') {
|
|
525
|
+
this.checkUpdate();
|
|
526
|
+
const def = this.schema().directive(nameOrDef) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDef, args);
|
|
527
|
+
if (!def) {
|
|
528
|
+
throw this.schema().blueprint.onGraphQLJSValidationError(
|
|
529
|
+
this.schema(),
|
|
530
|
+
new GraphQLError(`Unknown directive "@${nameOrDef}".`)
|
|
531
|
+
);
|
|
501
532
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
if (typeof nameOrDefOrDirective === 'string') {
|
|
505
|
-
this.checkUpdate();
|
|
506
|
-
const def = this.schema().directive(nameOrDefOrDirective) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDefOrDirective);
|
|
507
|
-
if (!def) {
|
|
508
|
-
throw new GraphQLError(`Cannot apply unknown directive "@${nameOrDefOrDirective}"`);
|
|
509
|
-
}
|
|
510
|
-
name = nameOrDefOrDirective;
|
|
511
|
-
} else {
|
|
512
|
-
this.checkUpdate(nameOrDefOrDirective);
|
|
513
|
-
name = nameOrDefOrDirective.name;
|
|
533
|
+
if (Array.isArray(def)) {
|
|
534
|
+
throw ErrGraphQLValidationFailed(def);
|
|
514
535
|
}
|
|
515
|
-
|
|
516
|
-
|
|
536
|
+
name = nameOrDef;
|
|
537
|
+
} else {
|
|
538
|
+
this.checkUpdate(nameOrDef);
|
|
539
|
+
name = nameOrDef.name;
|
|
517
540
|
}
|
|
541
|
+
const toAdd = new Directive<TOwnType, TApplicationArgs>(name, args ?? Object.create(null));
|
|
542
|
+
Element.prototype['setParent'].call(toAdd, this);
|
|
518
543
|
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
519
|
-
|
|
544
|
+
if (asFirstDirective) {
|
|
545
|
+
this._appliedDirectives.unshift(toAdd);
|
|
546
|
+
} else {
|
|
547
|
+
this._appliedDirectives.push(toAdd);
|
|
548
|
+
}
|
|
520
549
|
DirectiveDefinition.prototype['addReferencer'].call(toAdd.definition!, toAdd);
|
|
521
550
|
this.onModification();
|
|
522
551
|
return toAdd;
|
|
@@ -694,7 +723,7 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
694
723
|
/**
|
|
695
724
|
* Removes this type definition from its parent schema.
|
|
696
725
|
*
|
|
697
|
-
* After calling this method, this type will be "detached": it
|
|
726
|
+
* After calling this method, this type will be "detached": it will have no parent, schema, fields,
|
|
698
727
|
* values, directives, etc...
|
|
699
728
|
*
|
|
700
729
|
* Note that it is always allowed to remove a type, but this may make a valid schema
|
|
@@ -710,15 +739,18 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
710
739
|
}
|
|
711
740
|
this.checkRemoval();
|
|
712
741
|
this.onModification();
|
|
713
|
-
this.
|
|
714
|
-
Schema.prototype['removeTypeInternal'].call(this._parent, this);
|
|
715
|
-
this.removeAppliedDirectives();
|
|
742
|
+
// Remove this type's children.
|
|
716
743
|
this.sourceAST = undefined;
|
|
744
|
+
this.removeAppliedDirectives();
|
|
745
|
+
this.removeInnerElements();
|
|
746
|
+
// Remove this type's references.
|
|
717
747
|
const toReturn = setValues(this._referencers).map(r => {
|
|
718
748
|
SchemaElement.prototype['removeTypeReferenceInternal'].call(r, this);
|
|
719
749
|
return r;
|
|
720
750
|
});
|
|
721
751
|
this._referencers.clear();
|
|
752
|
+
// Remove this type from its parent schema.
|
|
753
|
+
Schema.prototype['removeTypeInternal'].call(this._parent, this);
|
|
722
754
|
this._parent = undefined;
|
|
723
755
|
return toReturn;
|
|
724
756
|
}
|
|
@@ -823,13 +855,14 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
|
|
|
823
855
|
}
|
|
824
856
|
|
|
825
857
|
export class SchemaBlueprint {
|
|
826
|
-
onMissingDirectiveDefinition(_schema: Schema, _name: string): DirectiveDefinition | undefined {
|
|
858
|
+
onMissingDirectiveDefinition(_schema: Schema, _name: string, _args?: {[key: string]: any}): DirectiveDefinition | GraphQLError[] | undefined {
|
|
827
859
|
// No-op by default, but used for federation.
|
|
828
860
|
return undefined;
|
|
829
861
|
}
|
|
830
862
|
|
|
831
|
-
onDirectiveDefinitionAndSchemaParsed(_: Schema) {
|
|
863
|
+
onDirectiveDefinitionAndSchemaParsed(_: Schema): GraphQLError[] {
|
|
832
864
|
// No-op by default, but used for federation.
|
|
865
|
+
return [];
|
|
833
866
|
}
|
|
834
867
|
|
|
835
868
|
ignoreParsedField(_type: NamedType, _fieldName: string): boolean {
|
|
@@ -857,6 +890,38 @@ export class SchemaBlueprint {
|
|
|
857
890
|
validationRules(): readonly SDLValidationRule[] {
|
|
858
891
|
return specifiedSDLRules;
|
|
859
892
|
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Allows to intercept some graphQL-js error messages when we can provide additional guidance to users.
|
|
896
|
+
*/
|
|
897
|
+
onGraphQLJSValidationError(schema: Schema, error: GraphQLError): GraphQLError {
|
|
898
|
+
// For now, the main additional guidance we provide is around directives, where we could provide additional help in 2 main ways:
|
|
899
|
+
// - 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
|
|
900
|
+
// time of this writting) for directive names).
|
|
901
|
+
// - for fed 2 schema, if a federation directive is refered under it's "default" naming but is not properly imported (not enforced
|
|
902
|
+
// in the method but rather in the `FederationBlueprint`).
|
|
903
|
+
//
|
|
904
|
+
// Note that intercepting/parsing error messages to modify them is never ideal, but pragmatically, it's probably better than rewriting the relevant
|
|
905
|
+
// rules entirely (in that later case, our "copied" rule would stop getting any potential graphQL-js made improvements for instance). And while such
|
|
906
|
+
// 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.
|
|
907
|
+
const matcher = /^Unknown directive "@(?<directive>[_A-Za-z][_0-9A-Za-z]*)"\.$/.exec(error.message);
|
|
908
|
+
const name = matcher?.groups?.directive;
|
|
909
|
+
if (!name) {
|
|
910
|
+
return error;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const allDefinedDirectiveNames = schema.allDirectives().map((d) => d.name);
|
|
914
|
+
const suggestions = suggestionList(name, allDefinedDirectiveNames);
|
|
915
|
+
if (suggestions.length === 0) {
|
|
916
|
+
return this.onUnknownDirectiveValidationError(schema, name, error);
|
|
917
|
+
} else {
|
|
918
|
+
return withModifiedErrorMessage(error, `${error.message}${didYouMean(suggestions.map((s) => '@' + s))}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
onUnknownDirectiveValidationError(_schema: Schema, _unknownDirectiveName: string, error: GraphQLError): GraphQLError {
|
|
923
|
+
return error;
|
|
924
|
+
}
|
|
860
925
|
}
|
|
861
926
|
|
|
862
927
|
export const defaultSchemaBlueprint = new SchemaBlueprint();
|
|
@@ -872,8 +937,12 @@ export class CoreFeature {
|
|
|
872
937
|
}
|
|
873
938
|
|
|
874
939
|
isFeatureDefinition(element: NamedType | DirectiveDefinition): boolean {
|
|
940
|
+
const importName = element.kind === 'DirectiveDefinition'
|
|
941
|
+
? '@' + element.name
|
|
942
|
+
: element.name;
|
|
875
943
|
return element.name.startsWith(this.nameInSchema + '__')
|
|
876
|
-
|| (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
|
|
944
|
+
|| (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
|
|
945
|
+
|| !!this.imports.find((i) => importName === (i.as ?? i.name));
|
|
877
946
|
}
|
|
878
947
|
|
|
879
948
|
directiveNameInSchema(name: string): string {
|
|
@@ -931,7 +1000,7 @@ export class CoreFeatures {
|
|
|
931
1000
|
if (existing) {
|
|
932
1001
|
throw error(`Duplicate inclusion of feature ${url.identity}`);
|
|
933
1002
|
}
|
|
934
|
-
const imports = extractCoreFeatureImports(typedDirective);
|
|
1003
|
+
const imports = extractCoreFeatureImports(url, typedDirective);
|
|
935
1004
|
const feature = new CoreFeature(url, args.as ?? url.name, directive, imports, args.for);
|
|
936
1005
|
this.add(feature);
|
|
937
1006
|
directive.schema().blueprint.onAddedCoreFeature(directive.schema(), feature);
|
|
@@ -976,25 +1045,29 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
|
|
|
976
1045
|
createDirectiveSpecification({
|
|
977
1046
|
name: 'include',
|
|
978
1047
|
locations: [DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
|
|
979
|
-
argumentFct: (schema) => [{ name: 'if', type: new NonNullType(schema.booleanType()) }]
|
|
1048
|
+
argumentFct: (schema) => ({ args: [{ name: 'if', type: new NonNullType(schema.booleanType()) }], errors: [] })
|
|
980
1049
|
}),
|
|
981
1050
|
createDirectiveSpecification({
|
|
982
1051
|
name: 'skip',
|
|
983
1052
|
locations: [DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
|
|
984
|
-
argumentFct: (schema) => [{ name: 'if', type: new NonNullType(schema.booleanType()) }]
|
|
1053
|
+
argumentFct: (schema) => ({ args: [{ name: 'if', type: new NonNullType(schema.booleanType()) }], errors: [] })
|
|
985
1054
|
}),
|
|
986
1055
|
createDirectiveSpecification({
|
|
987
1056
|
name: 'deprecated',
|
|
988
1057
|
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' }]
|
|
1058
|
+
argumentFct: (schema) => ({ args: [{ name: 'reason', type: schema.stringType(), defaultValue: 'No longer supported' }], errors: [] })
|
|
990
1059
|
}),
|
|
991
1060
|
createDirectiveSpecification({
|
|
992
1061
|
name: 'specifiedBy',
|
|
993
1062
|
locations: [DirectiveLocation.SCALAR],
|
|
994
|
-
argumentFct: (schema) => [{ name: 'url', type: new NonNullType(schema.stringType()) }]
|
|
1063
|
+
argumentFct: (schema) => ({ args: [{ name: 'url', type: new NonNullType(schema.stringType()) }], errors: [] })
|
|
995
1064
|
}),
|
|
996
1065
|
];
|
|
997
1066
|
|
|
1067
|
+
|
|
1068
|
+
// A coordinate is up to 3 "graphQL name" ([_A-Za-z][_0-9A-Za-z]*).
|
|
1069
|
+
const coordinateRegexp = /^@?[_A-Za-z][_0-9A-Za-z]*(\.[_A-Za-z][_0-9A-Za-z]*)?(\([_A-Za-z][_0-9A-Za-z]*:\))?$/;
|
|
1070
|
+
|
|
998
1071
|
export class Schema {
|
|
999
1072
|
private _schemaDefinition: SchemaDefinition;
|
|
1000
1073
|
private readonly _builtInTypes = new MapWithCachedArrays<string, NamedType>();
|
|
@@ -1084,13 +1157,7 @@ export class Schema {
|
|
|
1084
1157
|
|
|
1085
1158
|
const apiSchema = this.clone();
|
|
1086
1159
|
removeInaccessibleElements(apiSchema);
|
|
1087
|
-
|
|
1088
|
-
if (coreFeatures) {
|
|
1089
|
-
// Note that core being a feature itself, this will remove core itself and mark apiSchema as 'not core'
|
|
1090
|
-
for (const coreFeature of coreFeatures.allFeatures()) {
|
|
1091
|
-
removeFeatureElements(apiSchema, coreFeature);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1160
|
+
removeAllCoreFeatures(apiSchema);
|
|
1094
1161
|
assert(!apiSchema.isCoreSchema(), "The API schema shouldn't be a core schema")
|
|
1095
1162
|
apiSchema.validate();
|
|
1096
1163
|
this.apiSchema = apiSchema;
|
|
@@ -1118,20 +1185,42 @@ export class Schema {
|
|
|
1118
1185
|
/**
|
|
1119
1186
|
* All the types defined on this schema, excluding the built-in types.
|
|
1120
1187
|
*/
|
|
1121
|
-
types
|
|
1122
|
-
|
|
1123
|
-
|
|
1188
|
+
types(): readonly NamedType[] {
|
|
1189
|
+
return this._types.values();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
interfaceTypes(): readonly InterfaceType[] {
|
|
1193
|
+
return filterTypesOfKind<InterfaceType>(this.types(), 'InterfaceType');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
objectTypes(): readonly ObjectType[] {
|
|
1197
|
+
return filterTypesOfKind<ObjectType>(this.types(), 'ObjectType');
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
unionTypes(): readonly UnionType[] {
|
|
1201
|
+
return filterTypesOfKind<UnionType>(this.types(), 'UnionType');
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
scalarTypes(): readonly ScalarType[] {
|
|
1205
|
+
return filterTypesOfKind<ScalarType>(this.types(), 'ScalarType');
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
inputTypes(): readonly InputObjectType[] {
|
|
1209
|
+
return filterTypesOfKind<InputObjectType>(this.types(), 'InputObjectType');
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
enumTypes(): readonly EnumType[] {
|
|
1213
|
+
return filterTypesOfKind<EnumType>(this.types(), 'EnumType');
|
|
1124
1214
|
}
|
|
1125
1215
|
|
|
1126
1216
|
/**
|
|
1127
1217
|
* All the built-in types for this schema (those that are not displayed when printing the schema).
|
|
1128
1218
|
*/
|
|
1129
|
-
builtInTypes
|
|
1219
|
+
builtInTypes(includeShadowed: boolean = false): readonly NamedType[] {
|
|
1130
1220
|
const allBuiltIns = this._builtInTypes.values();
|
|
1131
|
-
const forKind = (kind ? allBuiltIns.filter(t => t.kind === kind) : allBuiltIns) as readonly T[];
|
|
1132
1221
|
return includeShadowed
|
|
1133
|
-
?
|
|
1134
|
-
:
|
|
1222
|
+
? allBuiltIns
|
|
1223
|
+
: allBuiltIns.filter(t => !this.isShadowedBuiltInType(t));
|
|
1135
1224
|
}
|
|
1136
1225
|
|
|
1137
1226
|
private isShadowedBuiltInType(type: NamedType) {
|
|
@@ -1141,8 +1230,8 @@ export class Schema {
|
|
|
1141
1230
|
/**
|
|
1142
1231
|
* All the types, including the built-in ones.
|
|
1143
1232
|
*/
|
|
1144
|
-
allTypes
|
|
1145
|
-
return this.builtInTypes(
|
|
1233
|
+
allTypes(): readonly NamedType[] {
|
|
1234
|
+
return this.builtInTypes().concat(this.types());
|
|
1146
1235
|
}
|
|
1147
1236
|
|
|
1148
1237
|
/**
|
|
@@ -1311,7 +1400,7 @@ export class Schema {
|
|
|
1311
1400
|
|
|
1312
1401
|
// TODO: we check that all types are properly set (aren't undefined) in `validateSchema`, but `validateSDL` will error out beforehand. We should
|
|
1313
1402
|
// probably extract that part of `validateSchema` and run `validateSDL` conditionally on that first check.
|
|
1314
|
-
let errors = validateSDL(this.toAST(), undefined, this.blueprint.validationRules());
|
|
1403
|
+
let errors = validateSDL(this.toAST(), undefined, this.blueprint.validationRules()).map((e) => this.blueprint.onGraphQLJSValidationError(this, e));
|
|
1315
1404
|
errors = errors.concat(validateSchema(this));
|
|
1316
1405
|
|
|
1317
1406
|
// We avoid adding federation-specific validations if the base schema is not proper graphQL as the later can easily trigger
|
|
@@ -1364,6 +1453,56 @@ export class Schema {
|
|
|
1364
1453
|
specifiedByDirective(schema: Schema): DirectiveDefinition<{url: string}> {
|
|
1365
1454
|
return this.getBuiltInDirective(schema, 'specifiedBy');
|
|
1366
1455
|
}
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* Gets an element of the schema given its "schema coordinate".
|
|
1459
|
+
*
|
|
1460
|
+
* Note that the syntax for schema coordinates is the one from the upcoming GraphQL spec: https://github.com/graphql/graphql-spec/pull/794.
|
|
1461
|
+
*/
|
|
1462
|
+
elementByCoordinate(coordinate: string): NamedSchemaElement<any, any, any> | undefined {
|
|
1463
|
+
if (!coordinate.match(coordinateRegexp)) {
|
|
1464
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const argStartIdx = coordinate.indexOf('(');
|
|
1468
|
+
const start = argStartIdx < 0 ? coordinate : coordinate.slice(0, argStartIdx);
|
|
1469
|
+
// Argument syntax is `foo(argName:)`, so the arg name start after the open parenthesis and go until the final ':)'.
|
|
1470
|
+
const argName = argStartIdx < 0 ? undefined : coordinate.slice(argStartIdx + 1, coordinate.length - 2);
|
|
1471
|
+
const splittedStart = start.split('.');
|
|
1472
|
+
const typeOrDirectiveName = splittedStart[0];
|
|
1473
|
+
const fieldOrEnumName = splittedStart[1];
|
|
1474
|
+
const isDirective = typeOrDirectiveName.startsWith('@');
|
|
1475
|
+
if (isDirective) {
|
|
1476
|
+
if (fieldOrEnumName) {
|
|
1477
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1478
|
+
}
|
|
1479
|
+
const directive = this.directive(typeOrDirectiveName.slice(1));
|
|
1480
|
+
return argName ? directive?.argument(argName) : directive;
|
|
1481
|
+
} else {
|
|
1482
|
+
const type = this.type(typeOrDirectiveName);
|
|
1483
|
+
if (!type || !fieldOrEnumName) {
|
|
1484
|
+
return type;
|
|
1485
|
+
}
|
|
1486
|
+
switch (type.kind) {
|
|
1487
|
+
case 'ObjectType':
|
|
1488
|
+
case 'InterfaceType':
|
|
1489
|
+
const field = type.field(fieldOrEnumName);
|
|
1490
|
+
return argName ? field?.argument(argName) : field;
|
|
1491
|
+
case 'InputObjectType':
|
|
1492
|
+
if (argName) {
|
|
1493
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1494
|
+
}
|
|
1495
|
+
return type.field(fieldOrEnumName);
|
|
1496
|
+
case 'EnumType':
|
|
1497
|
+
if (argName) {
|
|
1498
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1499
|
+
}
|
|
1500
|
+
return type.value(fieldOrEnumName);
|
|
1501
|
+
default:
|
|
1502
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1367
1506
|
}
|
|
1368
1507
|
|
|
1369
1508
|
export class RootType extends BaseExtensionMember<SchemaDefinition> {
|
|
@@ -1390,10 +1529,11 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1390
1529
|
}
|
|
1391
1530
|
|
|
1392
1531
|
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
1393
|
-
|
|
1394
|
-
args?: TApplicationArgs
|
|
1532
|
+
nameOrDef: DirectiveDefinition<TApplicationArgs> | string,
|
|
1533
|
+
args?: TApplicationArgs,
|
|
1534
|
+
asFirstDirective: boolean = false,
|
|
1395
1535
|
): Directive<SchemaDefinition, TApplicationArgs> {
|
|
1396
|
-
const applied = super.applyDirective(
|
|
1536
|
+
const applied = super.applyDirective(nameOrDef, args, asFirstDirective) as Directive<SchemaDefinition, TApplicationArgs>;
|
|
1397
1537
|
const schema = this.schema();
|
|
1398
1538
|
const coreFeatures = schema.coreFeatures;
|
|
1399
1539
|
if (isCoreSpecDirectiveApplication(applied)) {
|
|
@@ -1403,9 +1543,13 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1403
1543
|
const schemaDirective = applied as Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>;
|
|
1404
1544
|
const args = schemaDirective.arguments();
|
|
1405
1545
|
const url = FeatureUrl.parse((args.url ?? args.feature)!);
|
|
1406
|
-
const imports = extractCoreFeatureImports(schemaDirective);
|
|
1546
|
+
const imports = extractCoreFeatureImports(url, schemaDirective);
|
|
1407
1547
|
const core = new CoreFeature(url, args.as ?? url.name, schemaDirective, imports, args.for);
|
|
1408
1548
|
Schema.prototype['markAsCoreSchema'].call(schema, core);
|
|
1549
|
+
// We also any core features that may have been added before we saw the @link for link itself
|
|
1550
|
+
this.appliedDirectives
|
|
1551
|
+
.filter((a) => a !== applied)
|
|
1552
|
+
.forEach((other) => CoreFeatures.prototype['maybeAddFeature'].call(schema.coreFeatures, other));
|
|
1409
1553
|
} else if (coreFeatures) {
|
|
1410
1554
|
CoreFeatures.prototype['maybeAddFeature'].call(coreFeatures, applied);
|
|
1411
1555
|
}
|
|
@@ -1914,7 +2058,12 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
|
|
|
1914
2058
|
protected readonly _values: EnumValue[] = [];
|
|
1915
2059
|
|
|
1916
2060
|
get values(): readonly EnumValue[] {
|
|
1917
|
-
|
|
2061
|
+
// Because our abstractions are mutable, and removal is done by calling
|
|
2062
|
+
// `remove()` on the element to remove, it's not unlikely someone mauy
|
|
2063
|
+
// try to iterate on the result of this method and call `remove()` on
|
|
2064
|
+
// some of the return value based on some condition. But this will break
|
|
2065
|
+
// in an error-prone way if we don't copy, so we do.
|
|
2066
|
+
return Array.from(this._values);
|
|
1918
2067
|
}
|
|
1919
2068
|
|
|
1920
2069
|
value(name: string): EnumValue | undefined {
|
|
@@ -2213,23 +2362,36 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2213
2362
|
/**
|
|
2214
2363
|
* Removes this field definition from its parent type.
|
|
2215
2364
|
*
|
|
2216
|
-
* After calling this method, this field definition will be "detached": it
|
|
2217
|
-
* arguments or directives.
|
|
2365
|
+
* After calling this method, this field definition will be "detached": it will have no parent, schema, type,
|
|
2366
|
+
* arguments, or directives.
|
|
2218
2367
|
*/
|
|
2219
2368
|
remove(): never[] {
|
|
2220
2369
|
if (!this._parent) {
|
|
2221
2370
|
return [];
|
|
2222
2371
|
}
|
|
2372
|
+
this.checkRemoval();
|
|
2223
2373
|
this.onModification();
|
|
2224
|
-
this.
|
|
2374
|
+
// Remove this field's children.
|
|
2375
|
+
this.sourceAST = undefined;
|
|
2225
2376
|
this.type = undefined;
|
|
2226
|
-
this.
|
|
2377
|
+
this.removeAppliedDirectives();
|
|
2227
2378
|
for (const arg of this.arguments()) {
|
|
2228
2379
|
arg.remove();
|
|
2229
2380
|
}
|
|
2381
|
+
// Note that we don't track field references outside of parents, so no
|
|
2382
|
+
// removal needed there.
|
|
2383
|
+
//
|
|
2384
|
+
// TODO: One could consider interface fields as references to implementing
|
|
2385
|
+
// object/interface fields, in the sense that removing an implementing
|
|
2386
|
+
// object/interface field breaks the validity of the implementing
|
|
2387
|
+
// interface field. Being aware that an object/interface field is being
|
|
2388
|
+
// referenced in such a way would be useful for understanding breakages
|
|
2389
|
+
// that need to be resolved as a consequence of removal.
|
|
2390
|
+
//
|
|
2391
|
+
// Remove this field from its parent object/interface type.
|
|
2230
2392
|
FieldBasedType.prototype['removeFieldInternal'].call(this._parent, this);
|
|
2231
2393
|
this._parent = undefined;
|
|
2232
|
-
|
|
2394
|
+
this._extension = undefined;
|
|
2233
2395
|
return [];
|
|
2234
2396
|
}
|
|
2235
2397
|
|
|
@@ -2292,20 +2454,38 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
|
|
|
2292
2454
|
}
|
|
2293
2455
|
|
|
2294
2456
|
/**
|
|
2295
|
-
* Removes this field definition from its parent type.
|
|
2457
|
+
* Removes this input field definition from its parent type.
|
|
2296
2458
|
*
|
|
2297
|
-
* After calling this method, this field definition will be "detached": it
|
|
2298
|
-
*
|
|
2459
|
+
* After calling this method, this input field definition will be "detached": it will have no parent, schema,
|
|
2460
|
+
* type, default value, or directives.
|
|
2299
2461
|
*/
|
|
2300
2462
|
remove(): never[] {
|
|
2301
2463
|
if (!this._parent) {
|
|
2302
2464
|
return [];
|
|
2303
2465
|
}
|
|
2466
|
+
this.checkRemoval();
|
|
2304
2467
|
this.onModification();
|
|
2468
|
+
// Remove this input field's children.
|
|
2469
|
+
this.sourceAST = undefined;
|
|
2470
|
+
this.type = undefined;
|
|
2471
|
+
this.defaultValue = undefined;
|
|
2472
|
+
this.removeAppliedDirectives();
|
|
2473
|
+
// Note that we don't track input field references outside of parents, so no
|
|
2474
|
+
// removal needed there.
|
|
2475
|
+
//
|
|
2476
|
+
// TODO: One could consider default values (in field arguments, input
|
|
2477
|
+
// fields, or directive definitions) as references to input fields they
|
|
2478
|
+
// use, in the sense that removing the input field breaks the validity of
|
|
2479
|
+
// the default value. Being aware that an input field is being referenced
|
|
2480
|
+
// in such a way would be useful for understanding breakages that need to
|
|
2481
|
+
// be resolved as a consequence of removal. (The reference is indirect
|
|
2482
|
+
// though, as input field usages are currently represented as strings
|
|
2483
|
+
// within GraphQL values).
|
|
2484
|
+
//
|
|
2485
|
+
// Remove this input field from its parent input object type.
|
|
2305
2486
|
InputObjectType.prototype['removeFieldInternal'].call(this._parent, this);
|
|
2306
2487
|
this._parent = undefined;
|
|
2307
|
-
this.
|
|
2308
|
-
// Fields have nothing that can reference them outside of their parents
|
|
2488
|
+
this._extension = undefined;
|
|
2309
2489
|
return [];
|
|
2310
2490
|
}
|
|
2311
2491
|
|
|
@@ -2350,22 +2530,39 @@ export class ArgumentDefinition<TParent extends FieldDefinition<any> | Directive
|
|
|
2350
2530
|
/**
|
|
2351
2531
|
* Removes this argument definition from its parent element (field or directive).
|
|
2352
2532
|
*
|
|
2353
|
-
* After calling this method, this argument definition will be "detached": it
|
|
2354
|
-
* default value or directives.
|
|
2533
|
+
* After calling this method, this argument definition will be "detached": it will have no parent, schema, type,
|
|
2534
|
+
* default value, or directives.
|
|
2355
2535
|
*/
|
|
2356
2536
|
remove(): never[] {
|
|
2357
2537
|
if (!this._parent) {
|
|
2358
2538
|
return [];
|
|
2359
2539
|
}
|
|
2540
|
+
this.checkRemoval();
|
|
2360
2541
|
this.onModification();
|
|
2542
|
+
// Remove this argument's children.
|
|
2543
|
+
this.sourceAST = undefined;
|
|
2544
|
+
this.type = undefined;
|
|
2545
|
+
this.defaultValue = undefined;
|
|
2546
|
+
this.removeAppliedDirectives();
|
|
2547
|
+
// Note that we don't track argument references outside of parents, so no
|
|
2548
|
+
// removal needed there.
|
|
2549
|
+
//
|
|
2550
|
+
// TODO: One could consider the arguments of directive applications as
|
|
2551
|
+
// references to the arguments of directive definitions, in the sense that
|
|
2552
|
+
// removing a directive definition argument can break the validity of the
|
|
2553
|
+
// directive application. Being aware that a directive definition argument
|
|
2554
|
+
// is being referenced in such a way would be useful for understanding
|
|
2555
|
+
// breakages that need to be resolved as a consequence of removal. (You
|
|
2556
|
+
// could make a similar claim about interface field arguments being
|
|
2557
|
+
// references to object field arguments.)
|
|
2558
|
+
//
|
|
2559
|
+
// Remove this argument from its parent field or directive definition.
|
|
2361
2560
|
if (this._parent instanceof FieldDefinition) {
|
|
2362
2561
|
FieldDefinition.prototype['removeArgumentInternal'].call(this._parent, this.name);
|
|
2363
2562
|
} else {
|
|
2364
2563
|
DirectiveDefinition.prototype['removeArgumentInternal'].call(this._parent, this.name);
|
|
2365
2564
|
}
|
|
2366
2565
|
this._parent = undefined;
|
|
2367
|
-
this.type = undefined;
|
|
2368
|
-
this.defaultValue = undefined;
|
|
2369
2566
|
return [];
|
|
2370
2567
|
}
|
|
2371
2568
|
|
|
@@ -2406,23 +2603,36 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2406
2603
|
}
|
|
2407
2604
|
|
|
2408
2605
|
/**
|
|
2409
|
-
* Removes this
|
|
2606
|
+
* Removes this enum value definition from its parent type.
|
|
2410
2607
|
*
|
|
2411
|
-
* After calling this method, this
|
|
2412
|
-
* arguments or directives.
|
|
2608
|
+
* After calling this method, this enum value definition will be "detached": it will have no parent, schema, type,
|
|
2609
|
+
* arguments, or directives.
|
|
2413
2610
|
*/
|
|
2414
2611
|
remove(): never[] {
|
|
2415
2612
|
if (!this._parent) {
|
|
2416
2613
|
return [];
|
|
2417
2614
|
}
|
|
2615
|
+
this.checkRemoval();
|
|
2418
2616
|
this.onModification();
|
|
2617
|
+
// Remove this enum value's children.
|
|
2618
|
+
this.sourceAST = undefined;
|
|
2619
|
+
this.removeAppliedDirectives();
|
|
2620
|
+
// Note that we don't track enum value references outside of parents, so no
|
|
2621
|
+
// removal needed there.
|
|
2622
|
+
//
|
|
2623
|
+
// TODO: One could consider default values (in field arguments, input
|
|
2624
|
+
// fields, or directive definitions) as references to enum values they
|
|
2625
|
+
// use, in the sense that removing the enum value breaks the validity of
|
|
2626
|
+
// the default value. Being aware that an enum value is being referenced
|
|
2627
|
+
// in such a way would be useful for understanding breakages that need to
|
|
2628
|
+
// be resolved as a consequence of removal. (The reference is indirect
|
|
2629
|
+
// though, as enum value usages are currently represented as strings
|
|
2630
|
+
// within GraphQL values).
|
|
2631
|
+
//
|
|
2632
|
+
// Remove this enum value from its parent enum type.
|
|
2419
2633
|
EnumType.prototype['removeValueInternal'].call(this._parent, this);
|
|
2420
2634
|
this._parent = undefined;
|
|
2421
|
-
|
|
2422
|
-
// TODO: that's actually only semi-true if you include arguments, because default values in args and concrete directive applications can
|
|
2423
|
-
// indirectly refer to enum value. It's indirect though as we currently keep enum value as string in values. That said, it would
|
|
2424
|
-
// probably be really nice to be able to known if an enum value is used or not, rather then removing it and not knowing if we broke
|
|
2425
|
-
// something).
|
|
2635
|
+
this._extension = undefined;
|
|
2426
2636
|
return [];
|
|
2427
2637
|
}
|
|
2428
2638
|
|
|
@@ -2552,22 +2762,35 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2552
2762
|
assert(false, `Directive definition ${this} can't reference other types (it's arguments can); shouldn't be asked to remove reference to ${type}`);
|
|
2553
2763
|
}
|
|
2554
2764
|
|
|
2765
|
+
/**
|
|
2766
|
+
* Removes this directive definition from its parent schema.
|
|
2767
|
+
*
|
|
2768
|
+
* After calling this method, this directive definition will be "detached": it will have no parent, schema, or
|
|
2769
|
+
* arguments.
|
|
2770
|
+
*/
|
|
2555
2771
|
remove(): Directive[] {
|
|
2556
2772
|
if (!this._parent) {
|
|
2557
2773
|
return [];
|
|
2558
2774
|
}
|
|
2775
|
+
this.checkRemoval();
|
|
2559
2776
|
this.onModification();
|
|
2560
|
-
|
|
2561
|
-
this.
|
|
2777
|
+
// Remove this directive definition's children.
|
|
2778
|
+
this.sourceAST = undefined;
|
|
2562
2779
|
assert(this._appliedDirectives.length === 0, "Directive definition should not have directive applied to it");
|
|
2563
2780
|
for (const arg of this.arguments()) {
|
|
2564
2781
|
arg.remove();
|
|
2565
2782
|
}
|
|
2566
|
-
//
|
|
2567
|
-
//
|
|
2568
|
-
//
|
|
2783
|
+
// Remove this directive definition's references.
|
|
2784
|
+
//
|
|
2785
|
+
// Note that while a directive application references its definition, it
|
|
2786
|
+
// doesn't store a link to that definition. Instead, we fetch the definition
|
|
2787
|
+
// from the schema when requested. So we don't have to do anything on the
|
|
2788
|
+
// referencers other than clear them (and return the pre-cleared set).
|
|
2569
2789
|
const toReturn = setValues(this._referencers);
|
|
2570
2790
|
this._referencers.clear();
|
|
2791
|
+
// Remove this directive definition from its parent schema.
|
|
2792
|
+
Schema.prototype['removeDirectiveInternal'].call(this._parent, this);
|
|
2793
|
+
this._parent = undefined;
|
|
2571
2794
|
return toReturn;
|
|
2572
2795
|
}
|
|
2573
2796
|
|
|
@@ -2711,9 +2934,9 @@ export class Directive<
|
|
|
2711
2934
|
this.onModification();
|
|
2712
2935
|
const coreFeatures = this.schema().coreFeatures;
|
|
2713
2936
|
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
|
|
2937
|
+
// We're removing a @core/@link directive application, so we remove it from the list of core features. And
|
|
2715
2938
|
// if it is @core itself, we clean all features (to avoid having things too inconsistent).
|
|
2716
|
-
const url = FeatureUrl.parse(this._args[
|
|
2939
|
+
const url = FeatureUrl.parse(this._args[coreFeatures.coreDefinition.urlArgName()]!);
|
|
2717
2940
|
if (url.identity === coreFeatures.coreItself.url.identity) {
|
|
2718
2941
|
// Note that we unmark first because the loop after that will nuke our parent.
|
|
2719
2942
|
Schema.prototype['unmarkAsCoreSchema'].call(this.schema());
|
|
@@ -2733,10 +2956,12 @@ export class Directive<
|
|
|
2733
2956
|
if (!this._parent) {
|
|
2734
2957
|
return false;
|
|
2735
2958
|
}
|
|
2959
|
+
// Remove this directive application's reference to its definition.
|
|
2736
2960
|
const definition = this.definition;
|
|
2737
2961
|
if (definition && this.isAttachedToSchemaElement()) {
|
|
2738
2962
|
DirectiveDefinition.prototype['removeReferencer'].call(definition, this as Directive<SchemaElement<any, any>>);
|
|
2739
2963
|
}
|
|
2964
|
+
// Remove this directive application from its parent schema element.
|
|
2740
2965
|
const parentDirectives = this._parent.appliedDirectives as Directive<TParent>[];
|
|
2741
2966
|
const index = parentDirectives.indexOf(this);
|
|
2742
2967
|
assert(index >= 0, () => `Directive ${this} lists ${this._parent} as parent, but that parent doesn't list it as applied directive`);
|