@apollo/federation-internals 2.0.0-alpha.6 → 2.0.0-preview.10
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 +43 -3
- package/dist/buildSchema.d.ts +7 -3
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +94 -61
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +39 -9
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +232 -42
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +71 -51
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +326 -231
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +48 -0
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -0
- package/dist/directiveAndTypeSpecification.js +253 -0
- package/dist/directiveAndTypeSpecification.js.map +1 -0
- package/dist/error.d.ts +21 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +63 -3
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +42 -97
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +102 -46
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +762 -234
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +23 -0
- package/dist/federationSpec.d.ts.map +1 -0
- package/dist/federationSpec.js +117 -0
- package/dist/federationSpec.js.map +1 -0
- 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 +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +8 -3
- package/dist/introspection.js.map +1 -1
- package/dist/joinSpec.d.ts +6 -1
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +22 -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 +9 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +27 -5
- package/dist/operations.js.map +1 -1
- package/dist/precompute.d.ts +3 -0
- package/dist/precompute.d.ts.map +1 -0
- package/dist/precompute.js +51 -0
- package/dist/precompute.js.map +1 -0
- package/dist/print.d.ts +11 -9
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +32 -22
- package/dist/print.js.map +1 -1
- package/dist/schemaUpgrader.d.ts +108 -0
- package/dist/schemaUpgrader.d.ts.map +1 -0
- package/dist/schemaUpgrader.js +497 -0
- package/dist/schemaUpgrader.js.map +1 -0
- 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 +3 -3
- 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 +36 -16
- package/dist/tagSpec.js.map +1 -1
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +34 -1
- package/dist/utils.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +19 -11
- package/dist/validate.js.map +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.js +1 -2
- package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
- package/dist/values.d.ts +1 -0
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +3 -2
- package/dist/values.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/coreSpec.test.ts +100 -0
- package/src/__tests__/definitions.test.ts +98 -17
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +64 -0
- package/src/__tests__/federation.test.ts +31 -0
- package/src/__tests__/operations.test.ts +2 -3
- package/src/__tests__/removeInaccessibleElements.test.ts +59 -6
- package/src/__tests__/schemaUpgrader.test.ts +169 -0
- package/src/__tests__/subgraphValidation.test.ts +422 -21
- package/src/__tests__/values.test.ts +2 -4
- package/src/buildSchema.ts +154 -84
- package/src/coreSpec.ts +294 -55
- package/src/definitions.ts +415 -275
- package/src/directiveAndTypeSpecification.ts +353 -0
- package/src/error.ts +143 -5
- package/src/extractSubgraphsFromSupergraph.ts +56 -122
- package/src/federation.ts +991 -302
- package/src/federationSpec.ts +146 -0
- package/src/inaccessibleSpec.ts +39 -11
- package/src/index.ts +4 -0
- package/src/introspection.ts +8 -3
- package/src/joinSpec.ts +35 -4
- package/src/knownCoreFeatures.ts +13 -0
- package/src/operations.ts +37 -7
- package/src/precompute.ts +65 -0
- package/src/print.ts +63 -48
- package/src/schemaUpgrader.ts +653 -0
- package/src/suggestions.ts +1 -1
- package/src/supergraphs.ts +4 -3
- package/src/tagSpec.ts +50 -18
- package/src/utils.ts +63 -0
- package/src/validate.ts +27 -16
- package/src/validation/KnownTypeNamesInFederationRule.ts +1 -7
- package/src/values.ts +7 -3
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/definitions.ts
CHANGED
|
@@ -12,16 +12,24 @@ import {
|
|
|
12
12
|
ListTypeNode,
|
|
13
13
|
NamedTypeNode,
|
|
14
14
|
parse,
|
|
15
|
-
printError,
|
|
16
15
|
TypeNode,
|
|
17
16
|
VariableDefinitionNode,
|
|
18
17
|
VariableNode
|
|
19
18
|
} from "graphql";
|
|
20
|
-
import {
|
|
21
|
-
|
|
19
|
+
import {
|
|
20
|
+
CoreImport,
|
|
21
|
+
CoreOrLinkDirectiveArgs,
|
|
22
|
+
CoreSpecDefinition,
|
|
23
|
+
extractCoreFeatureImports,
|
|
24
|
+
FeatureUrl,
|
|
25
|
+
findCoreSpecVersion,
|
|
26
|
+
isCoreSpecDirectiveApplication,
|
|
27
|
+
removeFeatureElements,
|
|
28
|
+
} from "./coreSpec";
|
|
29
|
+
import { assert, mapValues, MapWithCachedArrays, setValues } from "./utils";
|
|
22
30
|
import { withDefaultValues, valueEquals, valueToString, valueToAST, variablesInValue, valueFromAST, valueNodeToConstValueNode } from "./values";
|
|
23
31
|
import { removeInaccessibleElements } from "./inaccessibleSpec";
|
|
24
|
-
import {
|
|
32
|
+
import { defaultPrintOptions, printSchema } from './print';
|
|
25
33
|
import { sameType } from './types';
|
|
26
34
|
import { addIntrospectionFields, introspectionFieldNames, isIntrospectionName } from "./introspection";
|
|
27
35
|
import { err } from '@apollo/core-schema';
|
|
@@ -30,12 +38,16 @@ import { validateSDL } from "graphql/validation/validate";
|
|
|
30
38
|
import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
31
39
|
import { specifiedSDLRules } from "graphql/validation/specifiedRules";
|
|
32
40
|
import { validateSchema } from "./validate";
|
|
41
|
+
import { createDirectiveSpecification, createScalarTypeSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
|
|
42
|
+
import { didYouMean, suggestionList } from "./suggestions";
|
|
43
|
+
import { withModifiedErrorMessage } from "./error";
|
|
33
44
|
|
|
34
45
|
const validationErrorCode = 'GraphQLValidationFailed';
|
|
46
|
+
const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL schema.';
|
|
35
47
|
|
|
36
|
-
export const ErrGraphQLValidationFailed = (causes: GraphQLError[]) =>
|
|
48
|
+
export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: string = DEFAULT_VALIDATION_ERROR_MESSAGE) =>
|
|
37
49
|
err(validationErrorCode, {
|
|
38
|
-
message
|
|
50
|
+
message,
|
|
39
51
|
causes
|
|
40
52
|
});
|
|
41
53
|
|
|
@@ -61,11 +73,11 @@ export function printGraphQLErrorsOrRethrow(e: Error): string {
|
|
|
61
73
|
if (!causes) {
|
|
62
74
|
throw e;
|
|
63
75
|
}
|
|
64
|
-
return causes.map(e =>
|
|
76
|
+
return causes.map(e => e.toString()).join('\n\n');
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
export function printErrors(errors: GraphQLError[]): string {
|
|
68
|
-
return errors.map(e =>
|
|
80
|
+
return errors.map(e => e.toString()).join('\n\n');
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
export const typenameFieldName = '__typename';
|
|
@@ -93,6 +105,10 @@ function checkDefaultSchemaRoot(type: NamedType): SchemaRootKind | undefined {
|
|
|
93
105
|
}
|
|
94
106
|
}
|
|
95
107
|
|
|
108
|
+
export function isSchemaRootType(type: NamedType): boolean {
|
|
109
|
+
return isObjectType(type) && type.isRootType();
|
|
110
|
+
}
|
|
111
|
+
|
|
96
112
|
export type Type = NamedType | WrapperType;
|
|
97
113
|
export type NamedType = ScalarType | ObjectType | InterfaceType | UnionType | EnumType | InputObjectType;
|
|
98
114
|
export type OutputType = ScalarType | ObjectType | InterfaceType | UnionType | EnumType | ListType<any> | NonNullType<any>;
|
|
@@ -131,7 +147,7 @@ export function isScalarType(type: Type): type is ScalarType {
|
|
|
131
147
|
}
|
|
132
148
|
|
|
133
149
|
export function isCustomScalarType(type: Type): boolean {
|
|
134
|
-
return isScalarType(type) && !
|
|
150
|
+
return isScalarType(type) && !graphQLBuiltInTypes.includes(type.name);
|
|
135
151
|
}
|
|
136
152
|
|
|
137
153
|
export function isIntType(type: Type): boolean {
|
|
@@ -198,6 +214,22 @@ export function isInputType(type: Type): type is InputType {
|
|
|
198
214
|
}
|
|
199
215
|
}
|
|
200
216
|
|
|
217
|
+
export function isTypeOfKind<T extends Type>(type: Type, kind: T['kind']): type is T {
|
|
218
|
+
return type.kind === kind;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function filterTypesOfKind<T extends Type>(types: readonly Type[], kind: T['kind']): T[] {
|
|
222
|
+
return types.reduce(
|
|
223
|
+
(acc: T[], type: Type) => {
|
|
224
|
+
if (isTypeOfKind(type, kind)) {
|
|
225
|
+
acc.push(type);
|
|
226
|
+
}
|
|
227
|
+
return acc;
|
|
228
|
+
},
|
|
229
|
+
[],
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
201
233
|
export function baseType(type: Type): NamedType {
|
|
202
234
|
return isWrapperType(type) ? type.baseType() : type;
|
|
203
235
|
}
|
|
@@ -367,8 +399,8 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
367
399
|
}
|
|
368
400
|
}
|
|
369
401
|
|
|
370
|
-
export function sourceASTs(...elts: ({ sourceAST?:
|
|
371
|
-
return elts.map(elt => elt?.sourceAST).filter(elt => elt !== undefined)
|
|
402
|
+
export function sourceASTs<TNode extends ASTNode = ASTNode>(...elts: ({ sourceAST?: TNode } | undefined)[]): TNode[] {
|
|
403
|
+
return elts.map(elt => elt?.sourceAST).filter((elt): elt is TNode => elt !== undefined);
|
|
372
404
|
}
|
|
373
405
|
|
|
374
406
|
// Not exposed: mostly about avoid code duplication between SchemaElement and Directive (which is not a SchemaElement as it can't
|
|
@@ -477,7 +509,8 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
477
509
|
|
|
478
510
|
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
479
511
|
nameOrDefOrDirective: Directive<TOwnType, TApplicationArgs> | DirectiveDefinition<TApplicationArgs> | string,
|
|
480
|
-
args?: TApplicationArgs
|
|
512
|
+
args?: TApplicationArgs,
|
|
513
|
+
asFirstDirective: boolean = false,
|
|
481
514
|
): Directive<TOwnType, TApplicationArgs> {
|
|
482
515
|
let toAdd: Directive<TOwnType, TApplicationArgs>;
|
|
483
516
|
if (nameOrDefOrDirective instanceof Directive) {
|
|
@@ -490,9 +523,15 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
490
523
|
let name: string;
|
|
491
524
|
if (typeof nameOrDefOrDirective === 'string') {
|
|
492
525
|
this.checkUpdate();
|
|
493
|
-
const def = this.schema().directive(nameOrDefOrDirective);
|
|
526
|
+
const def = this.schema().directive(nameOrDefOrDirective) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDefOrDirective, args);
|
|
494
527
|
if (!def) {
|
|
495
|
-
throw
|
|
528
|
+
throw this.schema().blueprint.onGraphQLJSValidationError(
|
|
529
|
+
this.schema(),
|
|
530
|
+
new GraphQLError(`Unknown directive "@${nameOrDefOrDirective}".`)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
if (Array.isArray(def)) {
|
|
534
|
+
throw ErrGraphQLValidationFailed(def);
|
|
496
535
|
}
|
|
497
536
|
name = nameOrDefOrDirective;
|
|
498
537
|
} else {
|
|
@@ -503,7 +542,11 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
503
542
|
Element.prototype['setParent'].call(toAdd, this);
|
|
504
543
|
}
|
|
505
544
|
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
506
|
-
|
|
545
|
+
if (asFirstDirective) {
|
|
546
|
+
this._appliedDirectives.unshift(toAdd);
|
|
547
|
+
} else {
|
|
548
|
+
this._appliedDirectives.push(toAdd);
|
|
549
|
+
}
|
|
507
550
|
DirectiveDefinition.prototype['addReferencer'].call(toAdd.definition!, toAdd);
|
|
508
551
|
this.onModification();
|
|
509
552
|
return toAdd;
|
|
@@ -636,6 +679,18 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
636
679
|
return extension;
|
|
637
680
|
}
|
|
638
681
|
|
|
682
|
+
removeExtensions() {
|
|
683
|
+
if (this._extensions.size === 0) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
this._extensions.clear();
|
|
688
|
+
for (const directive of this._appliedDirectives) {
|
|
689
|
+
directive.removeOfExtension();
|
|
690
|
+
}
|
|
691
|
+
this.removeInnerElementsExtensions();
|
|
692
|
+
}
|
|
693
|
+
|
|
639
694
|
isIntrospectionType(): boolean {
|
|
640
695
|
return isIntrospectionName(this.name);
|
|
641
696
|
}
|
|
@@ -649,6 +704,7 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
649
704
|
}
|
|
650
705
|
|
|
651
706
|
protected abstract hasNonExtensionInnerElements(): boolean;
|
|
707
|
+
protected abstract removeInnerElementsExtensions(): void;
|
|
652
708
|
|
|
653
709
|
protected isElementBuiltIn(): boolean {
|
|
654
710
|
return this.isBuiltIn;
|
|
@@ -773,6 +829,10 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
|
|
|
773
829
|
return this._extension;
|
|
774
830
|
}
|
|
775
831
|
|
|
832
|
+
removeOfExtension() {
|
|
833
|
+
this._extension = undefined;
|
|
834
|
+
}
|
|
835
|
+
|
|
776
836
|
setOfExtension(extension: Extension<TExtended> | undefined) {
|
|
777
837
|
this.checkUpdate();
|
|
778
838
|
// See similar comment on FieldDefinition.setOfExtension for why we have to cast.
|
|
@@ -792,231 +852,105 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
|
|
|
792
852
|
protected abstract removeInner(): void;
|
|
793
853
|
}
|
|
794
854
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
export class BuiltIns {
|
|
800
|
-
readonly defaultGraphQLBuiltInTypes: readonly string[] = [ 'Int', 'Float', 'String', 'Boolean', 'ID' ];
|
|
801
|
-
private readonly defaultGraphQLBuiltInDirectives: readonly string[] = [ 'include', 'skip', 'deprecated', 'specifiedBy' ];
|
|
802
|
-
|
|
803
|
-
addBuiltInTypes(schema: Schema) {
|
|
804
|
-
this.defaultGraphQLBuiltInTypes.forEach(t => this.addBuiltInScalar(schema, t));
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
addBuiltInDirectives(schema: Schema) {
|
|
808
|
-
for (const name of ['include', 'skip']) {
|
|
809
|
-
this.addBuiltInDirective(schema, name)
|
|
810
|
-
.addLocations(DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT)
|
|
811
|
-
.addArgument('if', new NonNullType(schema.booleanType()));
|
|
812
|
-
}
|
|
813
|
-
this.addBuiltInDirective(schema, 'deprecated')
|
|
814
|
-
.addLocations(
|
|
815
|
-
DirectiveLocation.FIELD_DEFINITION,
|
|
816
|
-
DirectiveLocation.ENUM_VALUE,
|
|
817
|
-
DirectiveLocation.ARGUMENT_DEFINITION,
|
|
818
|
-
DirectiveLocation.INPUT_FIELD_DEFINITION,
|
|
819
|
-
).addArgument('reason', schema.stringType(), 'No longer supported');
|
|
820
|
-
this.addBuiltInDirective(schema, 'specifiedBy')
|
|
821
|
-
.addLocations(DirectiveLocation.SCALAR)
|
|
822
|
-
.addArgument('url', new NonNullType(schema.stringType()));
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
isGraphQLBuiltIn(element: NamedType | DirectiveDefinition | FieldDefinition<any>): boolean {
|
|
826
|
-
if (isIntrospectionName(element.name)) {
|
|
827
|
-
return true;
|
|
828
|
-
}
|
|
829
|
-
if (element instanceof FieldDefinition) {
|
|
830
|
-
return false;
|
|
831
|
-
} else if (element instanceof DirectiveDefinition) {
|
|
832
|
-
return this.defaultGraphQLBuiltInDirectives.includes(element.name);
|
|
833
|
-
} else {
|
|
834
|
-
return this.defaultGraphQLBuiltInTypes.includes(element.name);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
prepareValidation(_: Schema) {
|
|
839
|
-
// No-op for graphQL built-ins, but overriden for federation built-ins.
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
onValidation(schema: Schema, unvalidatedDirectives?: string[]): GraphQLError[] {
|
|
843
|
-
const errors: GraphQLError[] = [];
|
|
844
|
-
// We make sure that if any of the built-ins has been redefined, then the redefinition is
|
|
845
|
-
// the same as the built-in one.
|
|
846
|
-
for (const type of schema.builtInTypes(undefined, true)) {
|
|
847
|
-
const maybeRedefined = schema.type(type.name)!;
|
|
848
|
-
if (!maybeRedefined.isBuiltIn) {
|
|
849
|
-
this.ensureSameTypeStructure(type, maybeRedefined, errors);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
for (const directive of schema.builtInDirectives(true)) {
|
|
854
|
-
if (unvalidatedDirectives && unvalidatedDirectives.includes(directive.name)) {
|
|
855
|
-
continue;
|
|
856
|
-
}
|
|
857
|
-
const maybeRedefined = schema.directive(directive.name)!;
|
|
858
|
-
if (!maybeRedefined.isBuiltIn) {
|
|
859
|
-
this.ensureSameDirectiveStructure(directive, maybeRedefined, errors);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
return errors;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
validationRules(): readonly SDLValidationRule[] {
|
|
866
|
-
return specifiedSDLRules;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
maybeUpdateSubgraphDocument(_: Schema, document: DocumentNode): DocumentNode {
|
|
870
|
-
return document;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
private ensureSameDirectiveStructure(builtIn: DirectiveDefinition<any>, manuallyDefined: DirectiveDefinition<any>, errors: GraphQLError[]) {
|
|
874
|
-
this.ensureSameArguments(builtIn, manuallyDefined, `directive ${builtIn}`, errors);
|
|
875
|
-
// It's ok to say you'll never repeat a built-in that is repeatable. It's not ok to repeat one that isn't.
|
|
876
|
-
if (!builtIn.repeatable && manuallyDefined.repeatable) {
|
|
877
|
-
errors.push(error(`Invalid redefinition of built-in directive ${builtIn}: ${builtIn} should${builtIn.repeatable ? "" : " not"} be repeatable`));
|
|
878
|
-
}
|
|
879
|
-
// Similarly, it's ok to say that you will never use a directive in some locations, but not that you will use it in places not allowed by the built-in.
|
|
880
|
-
if (!manuallyDefined.locations.every(loc => builtIn.locations.includes(loc))) {
|
|
881
|
-
errors.push(error(`Invalid redefinition of built-in directive ${builtIn}: ${builtIn} should have locations ${builtIn.locations.join(', ')}, but found (non-subset) ${manuallyDefined.locations.join(', ')}`));
|
|
882
|
-
}
|
|
855
|
+
export class SchemaBlueprint {
|
|
856
|
+
onMissingDirectiveDefinition(_schema: Schema, _name: string, _args?: {[key: string]: any}): DirectiveDefinition | GraphQLError[] | undefined {
|
|
857
|
+
// No-op by default, but used for federation.
|
|
858
|
+
return undefined;
|
|
883
859
|
}
|
|
884
860
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
what: string,
|
|
889
|
-
errors: GraphQLError[]
|
|
890
|
-
) {
|
|
891
|
-
const expectedArguments = builtIn.arguments();
|
|
892
|
-
const foundArguments = manuallyDefined.arguments();
|
|
893
|
-
if (expectedArguments.length !== foundArguments.length) {
|
|
894
|
-
errors.push(error(`Invalid redefinition of built-in ${what}: should have ${expectedArguments.length} arguments but ${foundArguments.length} found in redefinition`));
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
for (const expectedArgument of expectedArguments) {
|
|
898
|
-
const foundArgument = manuallyDefined.argument(expectedArgument.name)!;
|
|
899
|
-
const expectedType = expectedArgument.type!;
|
|
900
|
-
let actualType = foundArgument.type!;
|
|
901
|
-
if (isNonNullType(actualType) && !isNonNullType(expectedType)) {
|
|
902
|
-
// It's ok to redefine an optional argument as mandatory. For instance, if you want to force people on your team to provide a "deprecation reason", you can
|
|
903
|
-
// redefine @deprecated as `directive @deprecated(reason: String!)...` to get validation. In other words, you are allowed to always pass an argument that
|
|
904
|
-
// is optional if you so wish.
|
|
905
|
-
actualType = actualType.ofType;
|
|
906
|
-
}
|
|
907
|
-
if (!sameType(expectedType, actualType)) {
|
|
908
|
-
errors.push(error(`Invalid redefinition of built-in ${what}: ${expectedArgument.coordinate} should have type ${expectedArgument.type!} but found type ${foundArgument.type!}`));
|
|
909
|
-
} else if (!isNonNullType(actualType) && !valueEquals(expectedArgument.defaultValue, foundArgument.defaultValue)) {
|
|
910
|
-
errors.push(error(`Invalid redefinition of built-in ${what}: ${expectedArgument.coordinate} should have default value ${valueToString(expectedArgument.defaultValue)} but found default value ${valueToString(foundArgument.defaultValue)}`));
|
|
911
|
-
}
|
|
912
|
-
}
|
|
861
|
+
onDirectiveDefinitionAndSchemaParsed(_: Schema): GraphQLError[] {
|
|
862
|
+
// No-op by default, but used for federation.
|
|
863
|
+
return [];
|
|
913
864
|
}
|
|
914
865
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
switch (builtIn.kind) {
|
|
922
|
-
case 'ScalarType':
|
|
923
|
-
// Nothing more to check for scalars.
|
|
924
|
-
return;
|
|
925
|
-
case 'ObjectType':
|
|
926
|
-
const redefinedObject = manuallyDefined as ObjectType;
|
|
927
|
-
for (const builtInField of builtIn.fields()) {
|
|
928
|
-
const redefinedField = redefinedObject.field(builtInField.name);
|
|
929
|
-
if (!redefinedField) {
|
|
930
|
-
errors.push(error(`Invalid redefinition of built-in type ${builtIn}: redefinition is missing field ${builtInField}`));
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
// We allow adding non-nullability because we've seen redefinition of the federation _Service type with type String! for the `sdl` field
|
|
934
|
-
// and we don't want to break backward compatibility as this doesn't feel too harmful.
|
|
935
|
-
let rType = redefinedField.type!;
|
|
936
|
-
if (!isNonNullType(builtInField.type!) && isNonNullType(rType)) {
|
|
937
|
-
rType = rType.ofType;
|
|
938
|
-
}
|
|
939
|
-
if (!sameType(builtInField.type!, rType)) {
|
|
940
|
-
errors.push(error(`Invalid redefinition of field ${builtInField} of built-in type ${builtIn}: should have type ${builtInField.type} but redefined with type ${redefinedField.type}`));
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
this.ensureSameArguments(builtInField, redefinedField, `field ${builtInField.coordinate}`, errors);
|
|
944
|
-
}
|
|
945
|
-
break;
|
|
946
|
-
case 'UnionType':
|
|
947
|
-
const redefinedUnion = manuallyDefined as UnionType;
|
|
948
|
-
const builtInMembers = sortedMemberNames(builtIn);
|
|
949
|
-
const redefinedMembers = sortedMemberNames(redefinedUnion);
|
|
950
|
-
if (!arrayEquals(builtInMembers, redefinedMembers)) {
|
|
951
|
-
errors.push(error(`Invalid redefinition of built-in type ${builtIn}: redefinition has members [${redefinedMembers}] but should have members [${builtInMembers}]`));
|
|
952
|
-
}
|
|
953
|
-
break;
|
|
954
|
-
default:
|
|
955
|
-
// Let's not bother with the rest until we actually need it.
|
|
956
|
-
errors.push(error(`Invalid redefinition of built-in type ${builtIn}: cannot redefine ${builtIn.kind} built-in types`));
|
|
957
|
-
}
|
|
866
|
+
ignoreParsedField(_type: NamedType, _fieldName: string): boolean {
|
|
867
|
+
// No-op by default, but used for federation.
|
|
868
|
+
return false;
|
|
958
869
|
}
|
|
959
870
|
|
|
960
|
-
|
|
961
|
-
|
|
871
|
+
onConstructed(_: Schema) {
|
|
872
|
+
// No-op by default, but used for federation.
|
|
962
873
|
}
|
|
963
874
|
|
|
964
|
-
|
|
965
|
-
|
|
875
|
+
onAddedCoreFeature(_schema: Schema, _feature: CoreFeature) {
|
|
876
|
+
// No-op by default, but used for federation.
|
|
966
877
|
}
|
|
967
878
|
|
|
968
|
-
|
|
969
|
-
|
|
879
|
+
onInvalidation(_: Schema) {
|
|
880
|
+
// No-op by default, but used for federation.
|
|
970
881
|
}
|
|
971
882
|
|
|
972
|
-
|
|
973
|
-
|
|
883
|
+
onValidation(_schema: Schema): GraphQLError[] {
|
|
884
|
+
// No-op by default, but used for federation.
|
|
885
|
+
return []
|
|
974
886
|
}
|
|
975
887
|
|
|
976
|
-
|
|
977
|
-
return
|
|
888
|
+
validationRules(): readonly SDLValidationRule[] {
|
|
889
|
+
return specifiedSDLRules;
|
|
978
890
|
}
|
|
979
891
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
):
|
|
984
|
-
|
|
985
|
-
if
|
|
986
|
-
|
|
892
|
+
/**
|
|
893
|
+
* Allows to intercept some graphQL-js error messages when we can provide additional guidance to users.
|
|
894
|
+
*/
|
|
895
|
+
onGraphQLJSValidationError(schema: Schema, error: GraphQLError): GraphQLError {
|
|
896
|
+
// For now, the main additional guidance we provide is around directives, where we could provide additional help in 2 main ways:
|
|
897
|
+
// - 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
|
|
898
|
+
// time of this writting) for directive names).
|
|
899
|
+
// - for fed 2 schema, if a federation directive is refered under it's "default" naming but is not properly imported (not enforced
|
|
900
|
+
// in the method but rather in the `FederationBlueprint`).
|
|
901
|
+
//
|
|
902
|
+
// Note that intercepting/parsing error messages to modify them is never ideal, but pragmatically, it's probably better than rewriting the relevant
|
|
903
|
+
// rules entirely (in that later case, our "copied" rule would stop getting any potential graphQL-js made improvements for instance). And while such
|
|
904
|
+
// 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.
|
|
905
|
+
const matcher = /^Unknown directive "@(?<directive>[_A-Za-z][_0-9A-Za-z]*)"\.$/.exec(error.message);
|
|
906
|
+
const name = matcher?.groups?.directive;
|
|
907
|
+
if (!name) {
|
|
908
|
+
return error;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const allDefinedDirectiveNames = schema.allDirectives().map((d) => d.name);
|
|
912
|
+
const suggestions = suggestionList(name, allDefinedDirectiveNames);
|
|
913
|
+
if (suggestions.length === 0) {
|
|
914
|
+
return this.onUnknownDirectiveValidationError(schema, name, error);
|
|
915
|
+
} else {
|
|
916
|
+
return withModifiedErrorMessage(error, `${error.message}${didYouMean(suggestions.map((s) => '@' + s))}`);
|
|
987
917
|
}
|
|
988
|
-
return directive as DirectiveDefinition<TApplicationArgs>;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
includeDirective(schema: Schema): DirectiveDefinition<{if: boolean}> {
|
|
992
|
-
return this.getTypedDirective(schema, 'include');
|
|
993
918
|
}
|
|
994
919
|
|
|
995
|
-
|
|
996
|
-
return
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
deprecatedDirective(schema: Schema): DirectiveDefinition<{reason?: string}> {
|
|
1000
|
-
return this.getTypedDirective(schema, 'deprecated');
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
specifiedByDirective(schema: Schema): DirectiveDefinition<{url: string}> {
|
|
1004
|
-
return this.getTypedDirective(schema, 'specifiedBy');
|
|
920
|
+
onUnknownDirectiveValidationError(_schema: Schema, _unknownDirectiveName: string, error: GraphQLError): GraphQLError {
|
|
921
|
+
return error;
|
|
1005
922
|
}
|
|
1006
923
|
}
|
|
1007
924
|
|
|
925
|
+
export const defaultSchemaBlueprint = new SchemaBlueprint();
|
|
926
|
+
|
|
1008
927
|
export class CoreFeature {
|
|
1009
928
|
constructor(
|
|
1010
929
|
readonly url: FeatureUrl,
|
|
1011
930
|
readonly nameInSchema: string,
|
|
1012
931
|
readonly directive: Directive<SchemaDefinition>,
|
|
932
|
+
readonly imports: CoreImport[],
|
|
1013
933
|
readonly purpose?: string,
|
|
1014
934
|
) {
|
|
1015
935
|
}
|
|
1016
936
|
|
|
1017
937
|
isFeatureDefinition(element: NamedType | DirectiveDefinition): boolean {
|
|
1018
938
|
return element.name.startsWith(this.nameInSchema + '__')
|
|
1019
|
-
|| (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
|
|
939
|
+
|| (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
|
|
940
|
+
|| !!this.imports.find((i) => element.name === (i.as ?? i.name));
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
directiveNameInSchema(name: string): string {
|
|
944
|
+
if (name === this.url.name) {
|
|
945
|
+
return this.nameInSchema;
|
|
946
|
+
}
|
|
947
|
+
const elementImport = this.imports.find((i) => i.name.charAt(0) === '@' && i.name.slice(1) === name);
|
|
948
|
+
return elementImport ? (elementImport.as?.slice(1) ?? name) : this.nameInSchema + '__' + name;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
typeNameInSchema(name: string): string {
|
|
952
|
+
const elementImport = this.imports.find((i) => i.name === name);
|
|
953
|
+
return elementImport ? (elementImport.as ?? name) : this.nameInSchema + '__' + name;
|
|
1020
954
|
}
|
|
1021
955
|
}
|
|
1022
956
|
|
|
@@ -1027,9 +961,9 @@ export class CoreFeatures {
|
|
|
1027
961
|
|
|
1028
962
|
constructor(readonly coreItself: CoreFeature) {
|
|
1029
963
|
this.add(coreItself);
|
|
1030
|
-
const coreDef =
|
|
964
|
+
const coreDef = findCoreSpecVersion(coreItself.url);
|
|
1031
965
|
if (!coreDef) {
|
|
1032
|
-
throw error(`Schema uses unknown version ${coreItself.url.version} of the
|
|
966
|
+
throw error(`Schema uses unknown version ${coreItself.url.version} of the ${coreItself.url.name} spec`);
|
|
1033
967
|
}
|
|
1034
968
|
this.coreDefinition = coreDef;
|
|
1035
969
|
}
|
|
@@ -1054,14 +988,17 @@ export class CoreFeatures {
|
|
|
1054
988
|
if (directive.definition?.name !== this.coreItself.nameInSchema) {
|
|
1055
989
|
return undefined;
|
|
1056
990
|
}
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
991
|
+
const typedDirective = directive as Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>
|
|
992
|
+
const args = typedDirective.arguments();
|
|
993
|
+
const url = this.coreDefinition.extractFeatureUrl(args);
|
|
1059
994
|
const existing = this.byIdentity.get(url.identity);
|
|
1060
995
|
if (existing) {
|
|
1061
996
|
throw error(`Duplicate inclusion of feature ${url.identity}`);
|
|
1062
997
|
}
|
|
1063
|
-
const
|
|
998
|
+
const imports = extractCoreFeatureImports(url, typedDirective);
|
|
999
|
+
const feature = new CoreFeature(url, args.as ?? url.name, directive, imports, args.for);
|
|
1064
1000
|
this.add(feature);
|
|
1001
|
+
directive.schema().blueprint.onAddedCoreFeature(directive.schema(), feature);
|
|
1065
1002
|
return feature;
|
|
1066
1003
|
}
|
|
1067
1004
|
|
|
@@ -1069,9 +1006,62 @@ export class CoreFeatures {
|
|
|
1069
1006
|
this.byAlias.set(feature.nameInSchema, feature);
|
|
1070
1007
|
this.byIdentity.set(feature.url.identity, feature);
|
|
1071
1008
|
}
|
|
1009
|
+
|
|
1010
|
+
sourceFeature(element: DirectiveDefinition | NamedType): CoreFeature | undefined {
|
|
1011
|
+
const isDirective = element instanceof DirectiveDefinition;
|
|
1012
|
+
const splitted = element.name.split('__');
|
|
1013
|
+
if (splitted.length > 1) {
|
|
1014
|
+
return this.byAlias.get(splitted[0]);
|
|
1015
|
+
} else {
|
|
1016
|
+
const directFeature = this.byAlias.get(element.name);
|
|
1017
|
+
if (directFeature && isDirective) {
|
|
1018
|
+
return directFeature;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Let's see if it's an import. If not, it's not associated to a declared feature.
|
|
1022
|
+
const importName = isDirective ? '@' + element.name : element.name;
|
|
1023
|
+
const allFeatures = [this.coreItself, ...this.byIdentity.values()];
|
|
1024
|
+
for (const feature of allFeatures) {
|
|
1025
|
+
for (const { as } of feature.imports) {
|
|
1026
|
+
if (as === importName) {
|
|
1027
|
+
return feature;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return undefined;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1072
1034
|
}
|
|
1073
1035
|
|
|
1074
|
-
const
|
|
1036
|
+
const graphQLBuiltInTypes: readonly string[] = [ 'Int', 'Float', 'String', 'Boolean', 'ID' ];
|
|
1037
|
+
const graphQLBuiltInTypesSpecifications: readonly TypeSpecification[] = graphQLBuiltInTypes.map((name) => createScalarTypeSpecification({ name }));
|
|
1038
|
+
|
|
1039
|
+
const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[] = [
|
|
1040
|
+
createDirectiveSpecification({
|
|
1041
|
+
name: 'include',
|
|
1042
|
+
locations: [DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
|
|
1043
|
+
argumentFct: (schema) => ({ args: [{ name: 'if', type: new NonNullType(schema.booleanType()) }], errors: [] })
|
|
1044
|
+
}),
|
|
1045
|
+
createDirectiveSpecification({
|
|
1046
|
+
name: 'skip',
|
|
1047
|
+
locations: [DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
|
|
1048
|
+
argumentFct: (schema) => ({ args: [{ name: 'if', type: new NonNullType(schema.booleanType()) }], errors: [] })
|
|
1049
|
+
}),
|
|
1050
|
+
createDirectiveSpecification({
|
|
1051
|
+
name: 'deprecated',
|
|
1052
|
+
locations: [DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.ENUM_VALUE, DirectiveLocation.ARGUMENT_DEFINITION, DirectiveLocation.INPUT_FIELD_DEFINITION],
|
|
1053
|
+
argumentFct: (schema) => ({ args: [{ name: 'reason', type: schema.stringType(), defaultValue: 'No longer supported' }], errors: [] })
|
|
1054
|
+
}),
|
|
1055
|
+
createDirectiveSpecification({
|
|
1056
|
+
name: 'specifiedBy',
|
|
1057
|
+
locations: [DirectiveLocation.SCALAR],
|
|
1058
|
+
argumentFct: (schema) => ({ args: [{ name: 'url', type: new NonNullType(schema.stringType()) }], errors: [] })
|
|
1059
|
+
}),
|
|
1060
|
+
];
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
// A coordinate is up to 3 "graphQL name" ([_A-Za-z][_0-9A-Za-z]*).
|
|
1064
|
+
const coordinateRegexp = /^@?[_A-Za-z][_0-9A-Za-z]*(\.[_A-Za-z][_0-9A-Za-z]*)?(\([_A-Za-z][_0-9A-Za-z]*:\))?$/;
|
|
1075
1065
|
|
|
1076
1066
|
export class Schema {
|
|
1077
1067
|
private _schemaDefinition: SchemaDefinition;
|
|
@@ -1081,16 +1071,17 @@ export class Schema {
|
|
|
1081
1071
|
private readonly _directives = new MapWithCachedArrays<string, DirectiveDefinition>();
|
|
1082
1072
|
private _coreFeatures?: CoreFeatures;
|
|
1083
1073
|
private isConstructed: boolean = false;
|
|
1084
|
-
|
|
1074
|
+
public isValidated: boolean = false;
|
|
1085
1075
|
|
|
1086
1076
|
private cachedDocument?: DocumentNode;
|
|
1087
1077
|
private apiSchema?: Schema;
|
|
1088
1078
|
|
|
1089
|
-
constructor(readonly
|
|
1079
|
+
constructor(readonly blueprint: SchemaBlueprint = defaultSchemaBlueprint) {
|
|
1090
1080
|
this._schemaDefinition = new SchemaDefinition();
|
|
1091
1081
|
Element.prototype['setParent'].call(this._schemaDefinition, this);
|
|
1092
|
-
|
|
1093
|
-
|
|
1082
|
+
graphQLBuiltInTypesSpecifications.forEach((spec) => spec.checkOrAdd(this, undefined, true));
|
|
1083
|
+
graphQLBuiltInDirectivesSpecifications.forEach((spec) => spec.checkOrAdd(this, undefined, true));
|
|
1084
|
+
blueprint.onConstructed(this);
|
|
1094
1085
|
this.isConstructed = true;
|
|
1095
1086
|
}
|
|
1096
1087
|
|
|
@@ -1135,10 +1126,8 @@ export class Schema {
|
|
|
1135
1126
|
}
|
|
1136
1127
|
}
|
|
1137
1128
|
|
|
1138
|
-
private forceSetCachedDocument(document: DocumentNode
|
|
1139
|
-
this.cachedDocument =
|
|
1140
|
-
? this.builtIns.maybeUpdateSubgraphDocument(this, document)
|
|
1141
|
-
: document;
|
|
1129
|
+
private forceSetCachedDocument(document: DocumentNode) {
|
|
1130
|
+
this.cachedDocument = document;
|
|
1142
1131
|
}
|
|
1143
1132
|
|
|
1144
1133
|
isCoreSchema(): boolean {
|
|
@@ -1152,7 +1141,7 @@ export class Schema {
|
|
|
1152
1141
|
toAST(): DocumentNode {
|
|
1153
1142
|
if (!this.cachedDocument) {
|
|
1154
1143
|
// As we're not building the document from a file, having locations info might be more confusing that not.
|
|
1155
|
-
this.forceSetCachedDocument(parse(printSchema(this
|
|
1144
|
+
this.forceSetCachedDocument(parse(printSchema(this), { noLocation: true }));
|
|
1156
1145
|
}
|
|
1157
1146
|
return this.cachedDocument!;
|
|
1158
1147
|
}
|
|
@@ -1185,8 +1174,8 @@ export class Schema {
|
|
|
1185
1174
|
|
|
1186
1175
|
// Some subgraphs, especially federation 1 ones, cannot be properly converted to a GraphQLSchema because they are invalid graphQL.
|
|
1187
1176
|
// And the main culprit is type extensions that don't have a corresponding definition. So to avoid that problem, we print
|
|
1188
|
-
// up the AST without extensions.
|
|
1189
|
-
const ast = parse(printSchema(this, { ...
|
|
1177
|
+
// up the AST without extensions.
|
|
1178
|
+
const ast = parse(printSchema(this, { ...defaultPrintOptions, mergeTypesAndExtensions: true }), { noLocation: true });
|
|
1190
1179
|
return buildGraphqlSchemaFromAST(ast);
|
|
1191
1180
|
}
|
|
1192
1181
|
|
|
@@ -1197,23 +1186,42 @@ export class Schema {
|
|
|
1197
1186
|
/**
|
|
1198
1187
|
* All the types defined on this schema, excluding the built-in types.
|
|
1199
1188
|
*/
|
|
1200
|
-
types
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1189
|
+
types(): readonly NamedType[] {
|
|
1190
|
+
return this._types.values();
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
interfaceTypes(): readonly InterfaceType[] {
|
|
1194
|
+
return filterTypesOfKind<InterfaceType>(this.types(), 'InterfaceType');
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
objectTypes(): readonly ObjectType[] {
|
|
1198
|
+
return filterTypesOfKind<ObjectType>(this.types(), 'ObjectType');
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
unionTypes(): readonly UnionType[] {
|
|
1202
|
+
return filterTypesOfKind<UnionType>(this.types(), 'UnionType');
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
scalarTypes(): readonly ScalarType[] {
|
|
1206
|
+
return filterTypesOfKind<ScalarType>(this.types(), 'ScalarType');
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
inputTypes(): readonly InputObjectType[] {
|
|
1210
|
+
return filterTypesOfKind<InputObjectType>(this.types(), 'InputObjectType');
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
enumTypes(): readonly EnumType[] {
|
|
1214
|
+
return filterTypesOfKind<EnumType>(this.types(), 'EnumType');
|
|
1206
1215
|
}
|
|
1207
1216
|
|
|
1208
1217
|
/**
|
|
1209
1218
|
* All the built-in types for this schema (those that are not displayed when printing the schema).
|
|
1210
1219
|
*/
|
|
1211
|
-
builtInTypes
|
|
1220
|
+
builtInTypes(includeShadowed: boolean = false): readonly NamedType[] {
|
|
1212
1221
|
const allBuiltIns = this._builtInTypes.values();
|
|
1213
|
-
const forKind = (kind ? allBuiltIns.filter(t => t.kind === kind) : allBuiltIns) as readonly T[];
|
|
1214
1222
|
return includeShadowed
|
|
1215
|
-
?
|
|
1216
|
-
:
|
|
1223
|
+
? allBuiltIns
|
|
1224
|
+
: allBuiltIns.filter(t => !this.isShadowedBuiltInType(t));
|
|
1217
1225
|
}
|
|
1218
1226
|
|
|
1219
1227
|
private isShadowedBuiltInType(type: NamedType) {
|
|
@@ -1223,8 +1231,8 @@ export class Schema {
|
|
|
1223
1231
|
/**
|
|
1224
1232
|
* All the types, including the built-in ones.
|
|
1225
1233
|
*/
|
|
1226
|
-
allTypes
|
|
1227
|
-
return this.builtInTypes(
|
|
1234
|
+
allTypes(): readonly NamedType[] {
|
|
1235
|
+
return this.builtInTypes().concat(this.types());
|
|
1228
1236
|
}
|
|
1229
1237
|
|
|
1230
1238
|
/**
|
|
@@ -1262,9 +1270,12 @@ export class Schema {
|
|
|
1262
1270
|
|
|
1263
1271
|
addType<T extends NamedType>(type: T): T {
|
|
1264
1272
|
const existing = this.type(type.name);
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1273
|
+
if (existing) {
|
|
1274
|
+
// Like for directive, we let user shadow built-in types, but the definition must be valid.
|
|
1275
|
+
if (existing.isBuiltIn) {
|
|
1276
|
+
} else {
|
|
1277
|
+
throw error(`Type ${type} already exists in this schema`);
|
|
1278
|
+
}
|
|
1268
1279
|
}
|
|
1269
1280
|
if (type.isAttached()) {
|
|
1270
1281
|
// For convenience, let's not error out on adding an already added type.
|
|
@@ -1297,10 +1308,8 @@ export class Schema {
|
|
|
1297
1308
|
/**
|
|
1298
1309
|
* All the directive defined on this schema, excluding the built-in directives.
|
|
1299
1310
|
*/
|
|
1300
|
-
directives(
|
|
1301
|
-
return
|
|
1302
|
-
? this.builtInDirectives().filter(d => !graphQLBuiltIns.isGraphQLBuiltIn(d)).concat(this._directives.values())
|
|
1303
|
-
: this._directives.values();
|
|
1311
|
+
directives(): readonly DirectiveDefinition[] {
|
|
1312
|
+
return this._directives.values();
|
|
1304
1313
|
}
|
|
1305
1314
|
|
|
1306
1315
|
/**
|
|
@@ -1322,7 +1331,11 @@ export class Schema {
|
|
|
1322
1331
|
|
|
1323
1332
|
directive(name: string): DirectiveDefinition | undefined {
|
|
1324
1333
|
const directive = this._directives.get(name);
|
|
1325
|
-
return directive ? directive : this.
|
|
1334
|
+
return directive ? directive : this.builtInDirective(name);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
builtInDirective(name: string): DirectiveDefinition | undefined {
|
|
1338
|
+
return this._builtInDirectives.get(name);
|
|
1326
1339
|
}
|
|
1327
1340
|
|
|
1328
1341
|
*allNamedSchemaElement(): Generator<NamedSchemaElement<any, any, any>, void, undefined> {
|
|
@@ -1374,6 +1387,7 @@ export class Schema {
|
|
|
1374
1387
|
|
|
1375
1388
|
invalidate() {
|
|
1376
1389
|
this.isValidated = false;
|
|
1390
|
+
this.blueprint.onInvalidation(this);
|
|
1377
1391
|
}
|
|
1378
1392
|
|
|
1379
1393
|
validate() {
|
|
@@ -1381,22 +1395,20 @@ export class Schema {
|
|
|
1381
1395
|
return;
|
|
1382
1396
|
}
|
|
1383
1397
|
|
|
1384
|
-
// This needs to run _before_ graphQL validation: otherwise, the _Entity union will be empty and fail validation.
|
|
1385
1398
|
this.runWithBuiltInModificationAllowed(() => {
|
|
1386
|
-
this.builtIns.prepareValidation(this);
|
|
1387
1399
|
addIntrospectionFields(this);
|
|
1388
1400
|
});
|
|
1389
1401
|
|
|
1390
|
-
// TODO: we
|
|
1391
|
-
// and
|
|
1392
|
-
let errors = validateSDL(this.toAST(), undefined, this.
|
|
1402
|
+
// TODO: we check that all types are properly set (aren't undefined) in `validateSchema`, but `validateSDL` will error out beforehand. We should
|
|
1403
|
+
// probably extract that part of `validateSchema` and run `validateSDL` conditionally on that first check.
|
|
1404
|
+
let errors = validateSDL(this.toAST(), undefined, this.blueprint.validationRules()).map((e) => this.blueprint.onGraphQLJSValidationError(this, e));
|
|
1393
1405
|
errors = errors.concat(validateSchema(this));
|
|
1394
1406
|
|
|
1395
1407
|
// We avoid adding federation-specific validations if the base schema is not proper graphQL as the later can easily trigger
|
|
1396
1408
|
// the former (for instance, someone mistyping the 'fields' argument name of a @key).
|
|
1397
1409
|
if (errors.length === 0) {
|
|
1398
1410
|
this.runWithBuiltInModificationAllowed(() => {
|
|
1399
|
-
errors = this.
|
|
1411
|
+
errors = this.blueprint.onValidation(this);
|
|
1400
1412
|
});
|
|
1401
1413
|
}
|
|
1402
1414
|
|
|
@@ -1407,8 +1419,8 @@ export class Schema {
|
|
|
1407
1419
|
this.isValidated = true;
|
|
1408
1420
|
}
|
|
1409
1421
|
|
|
1410
|
-
clone(builtIns?:
|
|
1411
|
-
const cloned = new Schema(builtIns ?? this.
|
|
1422
|
+
clone(builtIns?: SchemaBlueprint): Schema {
|
|
1423
|
+
const cloned = new Schema(builtIns ?? this.blueprint);
|
|
1412
1424
|
copy(this, cloned);
|
|
1413
1425
|
if (this.isValidated) {
|
|
1414
1426
|
// TODO: when we do actual validation, no point in redoing it, but we should
|
|
@@ -1417,6 +1429,81 @@ export class Schema {
|
|
|
1417
1429
|
}
|
|
1418
1430
|
return cloned;
|
|
1419
1431
|
}
|
|
1432
|
+
|
|
1433
|
+
private getBuiltInDirective<TApplicationArgs extends {[key: string]: any}>(
|
|
1434
|
+
schema: Schema,
|
|
1435
|
+
name: string
|
|
1436
|
+
): DirectiveDefinition<TApplicationArgs> {
|
|
1437
|
+
const directive = schema.directive(name);
|
|
1438
|
+
assert(directive, `The provided schema has not be built with the ${name} directive built-in`);
|
|
1439
|
+
return directive as DirectiveDefinition<TApplicationArgs>;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
includeDirective(schema: Schema): DirectiveDefinition<{if: boolean}> {
|
|
1443
|
+
return this.getBuiltInDirective(schema, 'include');
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
skipDirective(schema: Schema): DirectiveDefinition<{if: boolean}> {
|
|
1447
|
+
return this.getBuiltInDirective(schema, 'skip');
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
deprecatedDirective(schema: Schema): DirectiveDefinition<{reason?: string}> {
|
|
1451
|
+
return this.getBuiltInDirective(schema, 'deprecated');
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
specifiedByDirective(schema: Schema): DirectiveDefinition<{url: string}> {
|
|
1455
|
+
return this.getBuiltInDirective(schema, 'specifiedBy');
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Gets an element of the schema given its "schema coordinate".
|
|
1460
|
+
*
|
|
1461
|
+
* Note that the syntax for schema coordinates is the one from the upcoming GraphQL spec: https://github.com/graphql/graphql-spec/pull/794.
|
|
1462
|
+
*/
|
|
1463
|
+
elementByCoordinate(coordinate: string): NamedSchemaElement<any, any, any> | undefined {
|
|
1464
|
+
if (!coordinate.match(coordinateRegexp)) {
|
|
1465
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
const argStartIdx = coordinate.indexOf('(');
|
|
1469
|
+
const start = argStartIdx < 0 ? coordinate : coordinate.slice(0, argStartIdx);
|
|
1470
|
+
// Argument syntax is `foo(argName:)`, so the arg name start after the open parenthesis and go until the final ':)'.
|
|
1471
|
+
const argName = argStartIdx < 0 ? undefined : coordinate.slice(argStartIdx + 1, coordinate.length - 2);
|
|
1472
|
+
const splittedStart = start.split('.');
|
|
1473
|
+
const typeOrDirectiveName = splittedStart[0];
|
|
1474
|
+
const fieldOrEnumName = splittedStart[1];
|
|
1475
|
+
const isDirective = typeOrDirectiveName.startsWith('@');
|
|
1476
|
+
if (isDirective) {
|
|
1477
|
+
if (fieldOrEnumName) {
|
|
1478
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1479
|
+
}
|
|
1480
|
+
const directive = this.directive(typeOrDirectiveName.slice(1));
|
|
1481
|
+
return argName ? directive?.argument(argName) : directive;
|
|
1482
|
+
} else {
|
|
1483
|
+
const type = this.type(typeOrDirectiveName);
|
|
1484
|
+
if (!type || !fieldOrEnumName) {
|
|
1485
|
+
return type;
|
|
1486
|
+
}
|
|
1487
|
+
switch (type.kind) {
|
|
1488
|
+
case 'ObjectType':
|
|
1489
|
+
case 'InterfaceType':
|
|
1490
|
+
const field = type.field(fieldOrEnumName);
|
|
1491
|
+
return argName ? field?.argument(argName) : field;
|
|
1492
|
+
case 'InputObjectType':
|
|
1493
|
+
if (argName) {
|
|
1494
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1495
|
+
}
|
|
1496
|
+
return type.field(fieldOrEnumName);
|
|
1497
|
+
case 'EnumType':
|
|
1498
|
+
if (argName) {
|
|
1499
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1500
|
+
}
|
|
1501
|
+
return type.value(fieldOrEnumName);
|
|
1502
|
+
default:
|
|
1503
|
+
throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1420
1507
|
}
|
|
1421
1508
|
|
|
1422
1509
|
export class RootType extends BaseExtensionMember<SchemaDefinition> {
|
|
@@ -1444,20 +1531,26 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1444
1531
|
|
|
1445
1532
|
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
1446
1533
|
nameOrDefOrDirective: Directive<SchemaDefinition, TApplicationArgs> | DirectiveDefinition<TApplicationArgs> | string,
|
|
1447
|
-
args?: TApplicationArgs
|
|
1534
|
+
args?: TApplicationArgs,
|
|
1535
|
+
asFirstDirective: boolean = false,
|
|
1448
1536
|
): Directive<SchemaDefinition, TApplicationArgs> {
|
|
1449
|
-
const applied = super.applyDirective(nameOrDefOrDirective, args) as Directive<SchemaDefinition, TApplicationArgs>;
|
|
1537
|
+
const applied = super.applyDirective(nameOrDefOrDirective, args, asFirstDirective) as Directive<SchemaDefinition, TApplicationArgs>;
|
|
1450
1538
|
const schema = this.schema();
|
|
1451
1539
|
const coreFeatures = schema.coreFeatures;
|
|
1452
1540
|
if (isCoreSpecDirectiveApplication(applied)) {
|
|
1453
1541
|
if (coreFeatures) {
|
|
1454
|
-
throw error(`Invalid duplicate application of
|
|
1542
|
+
throw error(`Invalid duplicate application of @core/@link`);
|
|
1455
1543
|
}
|
|
1456
|
-
const schemaDirective = applied as Directive<SchemaDefinition,
|
|
1544
|
+
const schemaDirective = applied as Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>;
|
|
1457
1545
|
const args = schemaDirective.arguments();
|
|
1458
|
-
const url = FeatureUrl.parse(args.feature);
|
|
1459
|
-
const
|
|
1546
|
+
const url = FeatureUrl.parse((args.url ?? args.feature)!);
|
|
1547
|
+
const imports = extractCoreFeatureImports(url, schemaDirective);
|
|
1548
|
+
const core = new CoreFeature(url, args.as ?? url.name, schemaDirective, imports, args.for);
|
|
1460
1549
|
Schema.prototype['markAsCoreSchema'].call(schema, core);
|
|
1550
|
+
// We also any core features that may have been added before we saw the @link for link itself
|
|
1551
|
+
this.appliedDirectives
|
|
1552
|
+
.filter((a) => a !== applied)
|
|
1553
|
+
.forEach((other) => CoreFeatures.prototype['maybeAddFeature'].call(schema.coreFeatures, other));
|
|
1461
1554
|
} else if (coreFeatures) {
|
|
1462
1555
|
CoreFeatures.prototype['maybeAddFeature'].call(coreFeatures, applied);
|
|
1463
1556
|
}
|
|
@@ -1551,6 +1644,10 @@ export class ScalarType extends BaseNamedType<OutputTypeReferencer | InputTypeRe
|
|
|
1551
1644
|
return false; // No inner elements
|
|
1552
1645
|
}
|
|
1553
1646
|
|
|
1647
|
+
protected removeInnerElementsExtensions(): void {
|
|
1648
|
+
// No inner elements
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1554
1651
|
protected removeInnerElements(): void {
|
|
1555
1652
|
// No inner elements
|
|
1556
1653
|
}
|
|
@@ -1657,18 +1754,15 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1657
1754
|
/**
|
|
1658
1755
|
* All the fields of this type, excluding the built-in ones.
|
|
1659
1756
|
*/
|
|
1660
|
-
fields(
|
|
1661
|
-
if (includeNonGraphQLBuiltIns) {
|
|
1662
|
-
return this.allFields().filter(f => !graphQLBuiltIns.isGraphQLBuiltIn(f));
|
|
1663
|
-
}
|
|
1757
|
+
fields(): readonly FieldDefinition<T>[] {
|
|
1664
1758
|
if (!this._cachedNonBuiltInFields) {
|
|
1665
1759
|
this._cachedNonBuiltInFields = this._fields.values().filter(f => !f.isBuiltIn);
|
|
1666
1760
|
}
|
|
1667
1761
|
return this._cachedNonBuiltInFields;
|
|
1668
1762
|
}
|
|
1669
1763
|
|
|
1670
|
-
hasFields(
|
|
1671
|
-
return this.fields(
|
|
1764
|
+
hasFields(): boolean {
|
|
1765
|
+
return this.fields().length > 0;
|
|
1672
1766
|
}
|
|
1673
1767
|
|
|
1674
1768
|
/**
|
|
@@ -1761,6 +1855,11 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1761
1855
|
return this.interfaceImplementations().some(itf => itf.ofExtension() === undefined)
|
|
1762
1856
|
|| this.fields().some(f => f.ofExtension() === undefined);
|
|
1763
1857
|
}
|
|
1858
|
+
|
|
1859
|
+
protected removeInnerElementsExtensions(): void {
|
|
1860
|
+
this.interfaceImplementations().forEach(itf => itf.removeOfExtension());
|
|
1861
|
+
this.fields().forEach(f => f.removeOfExtension());
|
|
1862
|
+
}
|
|
1764
1863
|
}
|
|
1765
1864
|
|
|
1766
1865
|
export class ObjectType extends FieldBasedType<ObjectType, ObjectTypeReferencer> {
|
|
@@ -1949,6 +2048,10 @@ export class UnionType extends BaseNamedType<OutputTypeReferencer, UnionType> {
|
|
|
1949
2048
|
protected removeReferenceRecursive(ref: OutputTypeReferencer): void {
|
|
1950
2049
|
ref.removeRecursive();
|
|
1951
2050
|
}
|
|
2051
|
+
|
|
2052
|
+
protected removeInnerElementsExtensions(): void {
|
|
2053
|
+
this.members().forEach(m => m.removeOfExtension());
|
|
2054
|
+
}
|
|
1952
2055
|
}
|
|
1953
2056
|
|
|
1954
2057
|
export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
|
|
@@ -1956,11 +2059,16 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
|
|
|
1956
2059
|
protected readonly _values: EnumValue[] = [];
|
|
1957
2060
|
|
|
1958
2061
|
get values(): readonly EnumValue[] {
|
|
1959
|
-
|
|
2062
|
+
// Because our abstractions are mutable, and removal is done by calling
|
|
2063
|
+
// `remove()` on the element to remove, it's not unlikely someone mauy
|
|
2064
|
+
// try to iterate on the result of this method and call `remove()` on
|
|
2065
|
+
// some of the return value based on some condition. But this will break
|
|
2066
|
+
// in an error-prone way if we don't copy, so we do.
|
|
2067
|
+
return Array.from(this._values);
|
|
1960
2068
|
}
|
|
1961
2069
|
|
|
1962
2070
|
value(name: string): EnumValue | undefined {
|
|
1963
|
-
return this._values.find(v => v.name
|
|
2071
|
+
return this._values.find(v => v.name === name);
|
|
1964
2072
|
}
|
|
1965
2073
|
|
|
1966
2074
|
addValue(value: EnumValue): EnumValue;
|
|
@@ -2007,6 +2115,10 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
|
|
|
2007
2115
|
protected removeReferenceRecursive(ref: OutputTypeReferencer): void {
|
|
2008
2116
|
ref.removeRecursive();
|
|
2009
2117
|
}
|
|
2118
|
+
|
|
2119
|
+
protected removeInnerElementsExtensions(): void {
|
|
2120
|
+
this._values.forEach(v => v.removeOfExtension());
|
|
2121
|
+
}
|
|
2010
2122
|
}
|
|
2011
2123
|
|
|
2012
2124
|
export class InputObjectType extends BaseNamedType<InputTypeReferencer, InputObjectType> {
|
|
@@ -2090,6 +2202,10 @@ export class InputObjectType extends BaseNamedType<InputTypeReferencer, InputObj
|
|
|
2090
2202
|
ref.removeRecursive();
|
|
2091
2203
|
}
|
|
2092
2204
|
}
|
|
2205
|
+
|
|
2206
|
+
protected removeInnerElementsExtensions(): void {
|
|
2207
|
+
this.fields().forEach(f => f.removeOfExtension());
|
|
2208
|
+
}
|
|
2093
2209
|
}
|
|
2094
2210
|
|
|
2095
2211
|
class BaseWrapperType<T extends Type> {
|
|
@@ -2208,6 +2324,10 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2208
2324
|
return this._extension;
|
|
2209
2325
|
}
|
|
2210
2326
|
|
|
2327
|
+
removeOfExtension() {
|
|
2328
|
+
this._extension = undefined;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2211
2331
|
setOfExtension(extension: Extension<TParent> | undefined) {
|
|
2212
2332
|
this.checkUpdate();
|
|
2213
2333
|
// It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
|
|
@@ -2302,6 +2422,10 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
|
|
|
2302
2422
|
return this._extension;
|
|
2303
2423
|
}
|
|
2304
2424
|
|
|
2425
|
+
removeOfExtension() {
|
|
2426
|
+
this._extension = undefined;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2305
2429
|
setOfExtension(extension: Extension<InputObjectType> | undefined) {
|
|
2306
2430
|
this.checkUpdate();
|
|
2307
2431
|
// It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
|
|
@@ -2328,6 +2452,7 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
|
|
|
2328
2452
|
return [];
|
|
2329
2453
|
}
|
|
2330
2454
|
this.onModification();
|
|
2455
|
+
this.removeAppliedDirectives();
|
|
2331
2456
|
InputObjectType.prototype['removeFieldInternal'].call(this._parent, this);
|
|
2332
2457
|
this._parent = undefined;
|
|
2333
2458
|
this.type = undefined;
|
|
@@ -2384,6 +2509,7 @@ export class ArgumentDefinition<TParent extends FieldDefinition<any> | Directive
|
|
|
2384
2509
|
return [];
|
|
2385
2510
|
}
|
|
2386
2511
|
this.onModification();
|
|
2512
|
+
this.removeAppliedDirectives();
|
|
2387
2513
|
if (this._parent instanceof FieldDefinition) {
|
|
2388
2514
|
FieldDefinition.prototype['removeArgumentInternal'].call(this._parent, this.name);
|
|
2389
2515
|
} else {
|
|
@@ -2414,6 +2540,10 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2414
2540
|
return this._extension;
|
|
2415
2541
|
}
|
|
2416
2542
|
|
|
2543
|
+
removeOfExtension() {
|
|
2544
|
+
this._extension = undefined;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2417
2547
|
setOfExtension(extension: Extension<EnumType> | undefined) {
|
|
2418
2548
|
this.checkUpdate();
|
|
2419
2549
|
if (extension && !this._parent?.extensions().has(extension)) {
|
|
@@ -2438,6 +2568,7 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2438
2568
|
return [];
|
|
2439
2569
|
}
|
|
2440
2570
|
this.onModification();
|
|
2571
|
+
this.removeAppliedDirectives();
|
|
2441
2572
|
EnumType.prototype['removeValueInternal'].call(this._parent, this);
|
|
2442
2573
|
this._parent = undefined;
|
|
2443
2574
|
// Enum values have nothing that can reference them outside of their parents
|
|
@@ -2622,6 +2753,9 @@ export class Directive<
|
|
|
2622
2753
|
}
|
|
2623
2754
|
|
|
2624
2755
|
get definition(): DirectiveDefinition | undefined {
|
|
2756
|
+
if (!this.isAttached()) {
|
|
2757
|
+
return undefined;
|
|
2758
|
+
}
|
|
2625
2759
|
const doc = this.schema();
|
|
2626
2760
|
return doc.directive(this.name);
|
|
2627
2761
|
}
|
|
@@ -2681,6 +2815,10 @@ export class Directive<
|
|
|
2681
2815
|
return this._extension;
|
|
2682
2816
|
}
|
|
2683
2817
|
|
|
2818
|
+
removeOfExtension() {
|
|
2819
|
+
this._extension = undefined;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2684
2822
|
setOfExtension(extension: Extension<any> | undefined) {
|
|
2685
2823
|
this.checkUpdate();
|
|
2686
2824
|
if (extension) {
|
|
@@ -2726,9 +2864,9 @@ export class Directive<
|
|
|
2726
2864
|
this.onModification();
|
|
2727
2865
|
const coreFeatures = this.schema().coreFeatures;
|
|
2728
2866
|
if (coreFeatures && this.name === coreFeatures.coreItself.nameInSchema) {
|
|
2729
|
-
// We're removing a @core directive application, so we remove it from the list of core features. And
|
|
2867
|
+
// We're removing a @core/@link directive application, so we remove it from the list of core features. And
|
|
2730
2868
|
// if it is @core itself, we clean all features (to avoid having things too inconsistent).
|
|
2731
|
-
const url = FeatureUrl.parse(this._args[
|
|
2869
|
+
const url = FeatureUrl.parse(this._args[coreFeatures.coreDefinition.urlArgName()]!);
|
|
2732
2870
|
if (url.identity === coreFeatures.coreItself.url.identity) {
|
|
2733
2871
|
// Note that we unmark first because the loop after that will nuke our parent.
|
|
2734
2872
|
Schema.prototype['unmarkAsCoreSchema'].call(this.schema());
|
|
@@ -2933,8 +3071,6 @@ export function variableDefinitionFromAST(schema: Schema, definitionNode: Variab
|
|
|
2933
3071
|
return def;
|
|
2934
3072
|
}
|
|
2935
3073
|
|
|
2936
|
-
export const graphQLBuiltIns = new BuiltIns();
|
|
2937
|
-
|
|
2938
3074
|
function addReferenceToType(referencer: SchemaElement<any, any>, type: Type) {
|
|
2939
3075
|
switch (type.kind) {
|
|
2940
3076
|
case 'ListType':
|
|
@@ -3043,7 +3179,7 @@ function copySchemaDefinitionInner(source: SchemaDefinition, dest: SchemaDefinit
|
|
|
3043
3179
|
// Same as copyAppliedDirectives, but as the directive applies to the schema definition, we need to remember if the application
|
|
3044
3180
|
// is for the extension or not.
|
|
3045
3181
|
for (const directive of source.appliedDirectives) {
|
|
3046
|
-
copyOfExtension(extensionsMap, directive,
|
|
3182
|
+
copyOfExtension(extensionsMap, directive, copyAppliedDirective(directive, dest));
|
|
3047
3183
|
}
|
|
3048
3184
|
dest.description = source.description;
|
|
3049
3185
|
dest.sourceAST = source.sourceAST;
|
|
@@ -3054,7 +3190,7 @@ function copyNamedTypeInner(source: NamedType, dest: NamedType) {
|
|
|
3054
3190
|
// Same as copyAppliedDirectives, but as the directive applies to the type, we need to remember if the application
|
|
3055
3191
|
// is for the extension or not.
|
|
3056
3192
|
for (const directive of source.appliedDirectives) {
|
|
3057
|
-
copyOfExtension(extensionsMap, directive,
|
|
3193
|
+
copyOfExtension(extensionsMap, directive, copyAppliedDirective(directive, dest));
|
|
3058
3194
|
}
|
|
3059
3195
|
dest.description = source.description;
|
|
3060
3196
|
dest.sourceAST = source.sourceAST;
|
|
@@ -3099,9 +3235,13 @@ function copyNamedTypeInner(source: NamedType, dest: NamedType) {
|
|
|
3099
3235
|
}
|
|
3100
3236
|
|
|
3101
3237
|
function copyAppliedDirectives(source: SchemaElement<any, any>, dest: SchemaElement<any, any>) {
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3238
|
+
source.appliedDirectives.forEach((d) => copyAppliedDirective(d, dest));
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
function copyAppliedDirective(source: Directive<any, any>, dest: SchemaElement<any, any>): Directive<any, any> {
|
|
3242
|
+
const res = dest.applyDirective(source.name, { ...source.arguments() });
|
|
3243
|
+
res.sourceAST = source.sourceAST
|
|
3244
|
+
return res;
|
|
3105
3245
|
}
|
|
3106
3246
|
|
|
3107
3247
|
function copyFieldDefinitionInner<P extends ObjectType | InterfaceType>(source: FieldDefinition<P>, dest: FieldDefinition<P>) {
|