@apollo/federation-internals 2.1.0-alpha.4 → 2.1.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 +1 -10
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +1 -1
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +1 -4
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +1 -5
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +26 -29
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +202 -158
- package/dist/definitions.js.map +1 -1
- package/dist/error.d.ts +5 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +47 -1
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +126 -5
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +2 -2
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +6 -6
- package/dist/federation.js.map +1 -1
- package/dist/operations.d.ts +2 -2
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +23 -20
- package/dist/operations.js.map +1 -1
- package/dist/print.js.map +1 -1
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +4 -4
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/supergraphs.d.ts +1 -3
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +9 -22
- package/dist/supergraphs.js.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +12 -1
- package/dist/utils.js.map +1 -1
- package/package.json +2 -3
- package/src/__tests__/coreSpec.test.ts +1 -1
- package/src/__tests__/definitions.test.ts +13 -4
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +9 -5
- package/src/__tests__/removeInaccessibleElements.test.ts +1 -3
- package/src/__tests__/schemaUpgrader.test.ts +0 -1
- package/src/__tests__/subgraphValidation.test.ts +1 -2
- package/src/buildSchema.ts +1 -2
- package/src/coreSpec.ts +2 -7
- package/src/definitions.ts +173 -148
- package/src/error.ts +62 -0
- package/src/extractSubgraphsFromSupergraph.ts +178 -7
- package/src/federation.ts +4 -3
- package/src/operations.ts +39 -28
- package/src/print.ts +2 -2
- package/src/schemaUpgrader.ts +5 -4
- package/src/supergraphs.ts +16 -25
- package/src/utils.ts +15 -0
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/definitions.ts
CHANGED
|
@@ -31,67 +31,30 @@ import {
|
|
|
31
31
|
isCoreSpecDirectiveApplication,
|
|
32
32
|
removeAllCoreFeatures,
|
|
33
33
|
} from "./coreSpec";
|
|
34
|
-
import { assert, mapValues, MapWithCachedArrays,
|
|
34
|
+
import { assert, mapValues, MapWithCachedArrays, removeArrayElement } from "./utils";
|
|
35
35
|
import { withDefaultValues, valueEquals, valueToString, valueToAST, variablesInValue, valueFromAST, valueNodeToConstValueNode, argumentsEquals } from "./values";
|
|
36
36
|
import { removeInaccessibleElements } from "./inaccessibleSpec";
|
|
37
37
|
import { printDirectiveDefinition, printSchema } from './print';
|
|
38
38
|
import { sameType } from './types';
|
|
39
39
|
import { addIntrospectionFields, introspectionFieldNames, isIntrospectionName } from "./introspection";
|
|
40
|
-
import { err } from '@apollo/core-schema';
|
|
41
|
-
import { GraphQLErrorExt } from "@apollo/core-schema/dist/error";
|
|
42
40
|
import { validateSDL } from "graphql/validation/validate";
|
|
43
41
|
import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
44
42
|
import { specifiedSDLRules } from "graphql/validation/specifiedRules";
|
|
45
43
|
import { validateSchema } from "./validate";
|
|
46
44
|
import { createDirectiveSpecification, createScalarTypeSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
|
|
47
45
|
import { didYouMean, suggestionList } from "./suggestions";
|
|
48
|
-
import { ERRORS, withModifiedErrorMessage } from "./error";
|
|
46
|
+
import { aggregateError, ERRORS, withModifiedErrorMessage } from "./error";
|
|
49
47
|
|
|
50
48
|
const validationErrorCode = 'GraphQLValidationFailed';
|
|
51
49
|
const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL schema.';
|
|
52
50
|
|
|
53
51
|
export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: string = DEFAULT_VALIDATION_ERROR_MESSAGE) =>
|
|
54
|
-
|
|
55
|
-
message: message + '. Caused by:\n' + causes.map((c) => c.toString()).join('\n\n'),
|
|
56
|
-
causes
|
|
57
|
-
});
|
|
52
|
+
aggregateError(validationErrorCode, message, causes);
|
|
58
53
|
|
|
59
54
|
const apiSchemaValidationErrorCode = 'GraphQLAPISchemaValidationFailed';
|
|
60
55
|
|
|
61
56
|
export const ErrGraphQLAPISchemaValidationFailed = (causes: GraphQLError[]) =>
|
|
62
|
-
|
|
63
|
-
message: 'The supergraph schema failed to produce a valid API schema',
|
|
64
|
-
causes
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Given an error that may have been thrown during schema validation, extract the causes of validation failure.
|
|
69
|
-
* If the error is not a graphQL error, undefined is returned.
|
|
70
|
-
*/
|
|
71
|
-
export function errorCauses(e: Error): GraphQLError[] | undefined {
|
|
72
|
-
if (e instanceof GraphQLErrorExt) {
|
|
73
|
-
if (e.code === validationErrorCode || e.code === apiSchemaValidationErrorCode) {
|
|
74
|
-
return ((e as any).causes) as GraphQLError[];
|
|
75
|
-
}
|
|
76
|
-
return [e];
|
|
77
|
-
}
|
|
78
|
-
if (e instanceof GraphQLError) {
|
|
79
|
-
return [e];
|
|
80
|
-
}
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function printGraphQLErrorsOrRethrow(e: Error): string {
|
|
85
|
-
const causes = errorCauses(e);
|
|
86
|
-
if (!causes) {
|
|
87
|
-
throw e;
|
|
88
|
-
}
|
|
89
|
-
return causes.map(e => e.toString()).join('\n\n');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function printErrors(errors: GraphQLError[]): string {
|
|
93
|
-
return errors.map(e => e.toString()).join('\n\n');
|
|
94
|
-
}
|
|
57
|
+
aggregateError(apiSchemaValidationErrorCode, 'The supergraph schema failed to produce a valid API schema', causes);
|
|
95
58
|
|
|
96
59
|
export const typenameFieldName = '__typename';
|
|
97
60
|
|
|
@@ -372,7 +335,7 @@ export interface Named {
|
|
|
372
335
|
export type ExtendableElement = SchemaDefinition | NamedType;
|
|
373
336
|
|
|
374
337
|
export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
375
|
-
|
|
338
|
+
private _appliedDirectives: Directive<T>[] | undefined;
|
|
376
339
|
|
|
377
340
|
constructor(private readonly _schema: Schema) {}
|
|
378
341
|
|
|
@@ -387,6 +350,10 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
387
350
|
return this.appliedDirectives.filter(d => d.name == directiveName);
|
|
388
351
|
}
|
|
389
352
|
|
|
353
|
+
get appliedDirectives(): readonly Directive<T>[] {
|
|
354
|
+
return this._appliedDirectives ?? [];
|
|
355
|
+
}
|
|
356
|
+
|
|
390
357
|
hasAppliedDirective(nameOrDefinition: string | DirectiveDefinition): boolean {
|
|
391
358
|
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
|
|
392
359
|
return this.appliedDirectives.some(d => d.name == directiveName);
|
|
@@ -410,7 +377,11 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
410
377
|
}
|
|
411
378
|
Element.prototype['setParent'].call(toAdd, this);
|
|
412
379
|
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
413
|
-
this.
|
|
380
|
+
if (this._appliedDirectives) {
|
|
381
|
+
this._appliedDirectives.push(toAdd);
|
|
382
|
+
} else {
|
|
383
|
+
this._appliedDirectives = [ toAdd ];
|
|
384
|
+
}
|
|
414
385
|
return toAdd;
|
|
415
386
|
}
|
|
416
387
|
|
|
@@ -532,35 +503,40 @@ type UnappliedDirective = {
|
|
|
532
503
|
|
|
533
504
|
// TODO: ideally, we should hide the ctor of this class as we rely in places on the fact the no-one external defines new implementations.
|
|
534
505
|
export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>, TParent extends SchemaElement<any, any> | Schema> extends Element<TParent> {
|
|
535
|
-
protected
|
|
536
|
-
protected _unappliedDirectives: UnappliedDirective[]
|
|
506
|
+
protected _appliedDirectives: Directive<TOwnType>[] | undefined;
|
|
507
|
+
protected _unappliedDirectives: UnappliedDirective[] | undefined;
|
|
537
508
|
description?: string;
|
|
538
509
|
|
|
539
510
|
addUnappliedDirective({ nameOrDef, args, extension, directive }: UnappliedDirective) {
|
|
540
|
-
|
|
511
|
+
const toAdd = {
|
|
541
512
|
nameOrDef,
|
|
542
513
|
args: args ?? {},
|
|
543
514
|
extension,
|
|
544
515
|
directive,
|
|
545
|
-
}
|
|
516
|
+
};
|
|
517
|
+
if (this._unappliedDirectives) {
|
|
518
|
+
this._unappliedDirectives.push(toAdd);
|
|
519
|
+
} else {
|
|
520
|
+
this._unappliedDirectives = [toAdd];
|
|
521
|
+
}
|
|
546
522
|
}
|
|
547
523
|
|
|
548
524
|
processUnappliedDirectives() {
|
|
549
|
-
for (const { nameOrDef, args, extension, directive } of this._unappliedDirectives) {
|
|
525
|
+
for (const { nameOrDef, args, extension, directive } of this._unappliedDirectives ?? []) {
|
|
550
526
|
const d = this.applyDirective(nameOrDef, args);
|
|
551
527
|
d.setOfExtension(extension);
|
|
552
528
|
d.sourceAST = directive;
|
|
553
529
|
}
|
|
554
|
-
this._unappliedDirectives =
|
|
530
|
+
this._unappliedDirectives = undefined;
|
|
555
531
|
}
|
|
556
532
|
|
|
557
533
|
get appliedDirectives(): readonly Directive<TOwnType>[] {
|
|
558
|
-
return this._appliedDirectives;
|
|
534
|
+
return this._appliedDirectives ?? [];
|
|
559
535
|
}
|
|
560
536
|
|
|
561
537
|
appliedDirectivesOf<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(nameOrDefinition: string | DirectiveDefinition<TApplicationArgs>): Directive<TOwnType, TApplicationArgs>[] {
|
|
562
538
|
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
|
|
563
|
-
return this.
|
|
539
|
+
return this.appliedDirectives.filter(d => d.name == directiveName) as Directive<TOwnType, TApplicationArgs>[];
|
|
564
540
|
}
|
|
565
541
|
|
|
566
542
|
hasAppliedDirective(nameOrDefinition: string | DirectiveDefinition<any>): boolean {
|
|
@@ -600,10 +576,14 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
600
576
|
const toAdd = new Directive<TOwnType, TApplicationArgs>(name, args ?? Object.create(null));
|
|
601
577
|
Element.prototype['setParent'].call(toAdd, this);
|
|
602
578
|
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
603
|
-
if (
|
|
604
|
-
|
|
579
|
+
if (this._appliedDirectives) {
|
|
580
|
+
if (asFirstDirective) {
|
|
581
|
+
this._appliedDirectives.unshift(toAdd);
|
|
582
|
+
} else {
|
|
583
|
+
this._appliedDirectives.push(toAdd);
|
|
584
|
+
}
|
|
605
585
|
} else {
|
|
606
|
-
this._appliedDirectives
|
|
586
|
+
this._appliedDirectives = [toAdd];
|
|
607
587
|
}
|
|
608
588
|
DirectiveDefinition.prototype['addReferencer'].call(toAdd.definition!, toAdd);
|
|
609
589
|
this.onModification();
|
|
@@ -612,6 +592,9 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
612
592
|
|
|
613
593
|
protected removeAppliedDirectives() {
|
|
614
594
|
// We copy the array because this._appliedDirectives is modified in-place by `directive.remove()`
|
|
595
|
+
if (!this._appliedDirectives) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
615
598
|
const applied = this._appliedDirectives.concat();
|
|
616
599
|
applied.forEach(d => d.remove());
|
|
617
600
|
}
|
|
@@ -685,8 +668,8 @@ export abstract class NamedSchemaElement<TOwnType extends NamedSchemaElement<TOw
|
|
|
685
668
|
}
|
|
686
669
|
|
|
687
670
|
abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSchemaElement<TOwnType, Schema, TReferencer>> extends NamedSchemaElement<TOwnType, Schema, TReferencer> {
|
|
688
|
-
protected
|
|
689
|
-
protected
|
|
671
|
+
protected _referencers?: TReferencer[];
|
|
672
|
+
protected _extensions?: Extension<TOwnType>[];
|
|
690
673
|
public preserveEmptyDefinition: boolean = false;
|
|
691
674
|
|
|
692
675
|
constructor(name: string, readonly isBuiltIn: boolean = false) {
|
|
@@ -694,11 +677,19 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
694
677
|
}
|
|
695
678
|
|
|
696
679
|
private addReferencer(referencer: TReferencer) {
|
|
697
|
-
this._referencers
|
|
680
|
+
if (this._referencers) {
|
|
681
|
+
if (!this._referencers.includes(referencer)) {
|
|
682
|
+
this._referencers.push(referencer);
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
this._referencers = [ referencer ];
|
|
686
|
+
}
|
|
698
687
|
}
|
|
699
688
|
|
|
700
689
|
private removeReferencer(referencer: TReferencer) {
|
|
701
|
-
this._referencers
|
|
690
|
+
if (this._referencers) {
|
|
691
|
+
removeArrayElement(referencer, this._referencers);
|
|
692
|
+
}
|
|
702
693
|
}
|
|
703
694
|
|
|
704
695
|
get coordinate(): string {
|
|
@@ -709,8 +700,12 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
709
700
|
// Overriden by those types that do have children
|
|
710
701
|
}
|
|
711
702
|
|
|
712
|
-
extensions():
|
|
713
|
-
return this._extensions;
|
|
703
|
+
extensions(): readonly Extension<TOwnType>[] {
|
|
704
|
+
return this._extensions ?? [];
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
hasExtension(extension: Extension<any>): boolean {
|
|
708
|
+
return this._extensions?.includes(extension) ?? false;
|
|
714
709
|
}
|
|
715
710
|
|
|
716
711
|
newExtension(): Extension<TOwnType> {
|
|
@@ -720,23 +715,27 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
720
715
|
addExtension(extension: Extension<TOwnType>): Extension<TOwnType> {
|
|
721
716
|
this.checkUpdate();
|
|
722
717
|
// Let's be nice and not complaint if we add an extension already added.
|
|
723
|
-
if (this.
|
|
718
|
+
if (this.hasExtension(extension)) {
|
|
724
719
|
return extension;
|
|
725
720
|
}
|
|
726
721
|
assert(!extension.extendedElement, () => `Cannot add extension to type ${this}: it is already added to another type`);
|
|
727
|
-
this._extensions
|
|
722
|
+
if (this._extensions) {
|
|
723
|
+
this._extensions.push(extension);
|
|
724
|
+
} else {
|
|
725
|
+
this._extensions = [ extension ];
|
|
726
|
+
}
|
|
728
727
|
Extension.prototype['setExtendedElement'].call(extension, this);
|
|
729
728
|
this.onModification();
|
|
730
729
|
return extension;
|
|
731
730
|
}
|
|
732
731
|
|
|
733
732
|
removeExtensions() {
|
|
734
|
-
if (this._extensions
|
|
733
|
+
if (!this._extensions) {
|
|
735
734
|
return;
|
|
736
735
|
}
|
|
737
736
|
|
|
738
|
-
this._extensions
|
|
739
|
-
for (const directive of this.
|
|
737
|
+
this._extensions = undefined;
|
|
738
|
+
for (const directive of this.appliedDirectives) {
|
|
740
739
|
directive.removeOfExtension();
|
|
741
740
|
}
|
|
742
741
|
this.removeInnerElementsExtensions();
|
|
@@ -747,12 +746,12 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
747
746
|
}
|
|
748
747
|
|
|
749
748
|
hasExtensionElements(): boolean {
|
|
750
|
-
return this._extensions
|
|
749
|
+
return !!this._extensions;
|
|
751
750
|
}
|
|
752
751
|
|
|
753
752
|
hasNonExtensionElements(): boolean {
|
|
754
753
|
return this.preserveEmptyDefinition
|
|
755
|
-
|| this.
|
|
754
|
+
|| this.appliedDirectives.some(d => d.ofExtension() === undefined)
|
|
756
755
|
|| this.hasNonExtensionInnerElements();
|
|
757
756
|
}
|
|
758
757
|
|
|
@@ -798,11 +797,11 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
798
797
|
this.removeAppliedDirectives();
|
|
799
798
|
this.removeInnerElements();
|
|
800
799
|
// Remove this type's references.
|
|
801
|
-
const toReturn =
|
|
800
|
+
const toReturn = this._referencers?.map(r => {
|
|
802
801
|
SchemaElement.prototype['removeTypeReferenceInternal'].call(r, this);
|
|
803
802
|
return r;
|
|
804
|
-
});
|
|
805
|
-
this._referencers
|
|
803
|
+
}) ?? [];
|
|
804
|
+
this._referencers = undefined;
|
|
806
805
|
// Remove this type from its parent schema.
|
|
807
806
|
Schema.prototype['removeTypeInternal'].call(this._parent, this);
|
|
808
807
|
this._parent = undefined;
|
|
@@ -830,11 +829,11 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
830
829
|
protected abstract removeReferenceRecursive(ref: TReferencer): void;
|
|
831
830
|
|
|
832
831
|
referencers(): readonly TReferencer[] {
|
|
833
|
-
return
|
|
832
|
+
return this._referencers ?? [];
|
|
834
833
|
}
|
|
835
834
|
|
|
836
835
|
isReferenced(): boolean {
|
|
837
|
-
return this._referencers
|
|
836
|
+
return !!this._referencers;
|
|
838
837
|
}
|
|
839
838
|
|
|
840
839
|
protected abstract removeInnerElements(): void;
|
|
@@ -887,8 +886,7 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
|
|
|
887
886
|
|
|
888
887
|
setOfExtension(extension: Extension<TExtended> | undefined) {
|
|
889
888
|
this.checkUpdate();
|
|
890
|
-
|
|
891
|
-
assert(!extension || this._parent?.extensions().has(extension as any), () => `Cannot set object as part of the provided extension: it is not an extension of parent ${this.parent}`);
|
|
889
|
+
assert(!extension || this._parent?.hasExtension(extension), () => `Cannot set object as part of the provided extension: it is not an extension of parent ${this.parent}`);
|
|
892
890
|
this._extension = extension;
|
|
893
891
|
}
|
|
894
892
|
|
|
@@ -1130,15 +1128,16 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
|
|
|
1130
1128
|
locations: [DirectiveLocation.SCALAR],
|
|
1131
1129
|
argumentFct: (schema) => ({ args: [{ name: 'url', type: new NonNullType(schema.stringType()) }], errors: [] })
|
|
1132
1130
|
}),
|
|
1133
|
-
//
|
|
1134
|
-
//
|
|
1131
|
+
// Note that @defer and @stream are unconditionally added to `Schema` even if they are technically "optional" built-in. _But_,
|
|
1132
|
+
// the `Schema#toGraphQLJSSchema` method has an option to decide if @defer/@stream should be included or not in the resulting
|
|
1133
|
+
// schema, which is how the gateway and router can, at runtime, decide to include or not include them based on actual support.
|
|
1135
1134
|
createDirectiveSpecification({
|
|
1136
1135
|
name: 'defer',
|
|
1137
1136
|
locations: [DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
|
|
1138
1137
|
argumentFct: (schema) => ({
|
|
1139
1138
|
args: [
|
|
1140
1139
|
{ name: 'label', type: schema.stringType() },
|
|
1141
|
-
{ name: 'if', type: schema.booleanType() },
|
|
1140
|
+
{ name: 'if', type: new NonNullType(schema.booleanType()), defaultValue: true },
|
|
1142
1141
|
],
|
|
1143
1142
|
errors: [],
|
|
1144
1143
|
})
|
|
@@ -1153,7 +1152,7 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
|
|
|
1153
1152
|
args: [
|
|
1154
1153
|
{ name: 'label', type: schema.stringType() },
|
|
1155
1154
|
{ name: 'initialCount', type: schema.intType(), defaultValue: 0 },
|
|
1156
|
-
{ name: 'if', type: schema.booleanType() },
|
|
1155
|
+
{ name: 'if', type: new NonNullType(schema.booleanType()), defaultValue: true },
|
|
1157
1156
|
],
|
|
1158
1157
|
errors: [],
|
|
1159
1158
|
})
|
|
@@ -1161,9 +1160,6 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
|
|
|
1161
1160
|
];
|
|
1162
1161
|
|
|
1163
1162
|
export type DeferDirectiveArgs = {
|
|
1164
|
-
// TODO: we currently do not support variables for the defer label. Passing a label in a variable
|
|
1165
|
-
// feels like a weird use case in the first place, but we should probably fix this nonetheless (or
|
|
1166
|
-
// if we decide to have it be a known limitations, we should at least reject it cleanly).
|
|
1167
1163
|
label?: string,
|
|
1168
1164
|
if?: boolean | Variable,
|
|
1169
1165
|
}
|
|
@@ -1178,6 +1174,10 @@ export type StreamDirectiveArgs = {
|
|
|
1178
1174
|
// A coordinate is up to 3 "graphQL name" ([_A-Za-z][_0-9A-Za-z]*).
|
|
1179
1175
|
const coordinateRegexp = /^@?[_A-Za-z][_0-9A-Za-z]*(\.[_A-Za-z][_0-9A-Za-z]*)?(\([_A-Za-z][_0-9A-Za-z]*:\))?$/;
|
|
1180
1176
|
|
|
1177
|
+
export type SchemaConfig = {
|
|
1178
|
+
cacheAST?: boolean,
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
1181
|
export class Schema {
|
|
1182
1182
|
private _schemaDefinition: SchemaDefinition;
|
|
1183
1183
|
private readonly _builtInTypes = new MapWithCachedArrays<string, NamedType>();
|
|
@@ -1191,7 +1191,10 @@ export class Schema {
|
|
|
1191
1191
|
private cachedDocument?: DocumentNode;
|
|
1192
1192
|
private apiSchema?: Schema;
|
|
1193
1193
|
|
|
1194
|
-
constructor(
|
|
1194
|
+
constructor(
|
|
1195
|
+
readonly blueprint: SchemaBlueprint = defaultSchemaBlueprint,
|
|
1196
|
+
readonly config: SchemaConfig = {},
|
|
1197
|
+
) {
|
|
1195
1198
|
this._schemaDefinition = new SchemaDefinition();
|
|
1196
1199
|
Element.prototype['setParent'].call(this._schemaDefinition, this);
|
|
1197
1200
|
graphQLBuiltInTypesSpecifications.forEach((spec) => spec.checkOrAdd(this, undefined, true));
|
|
@@ -1241,10 +1244,6 @@ export class Schema {
|
|
|
1241
1244
|
}
|
|
1242
1245
|
}
|
|
1243
1246
|
|
|
1244
|
-
private forceSetCachedDocument(document: DocumentNode) {
|
|
1245
|
-
this.cachedDocument = document;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
1247
|
isCoreSchema(): boolean {
|
|
1249
1248
|
return this.coreFeatures !== undefined;
|
|
1250
1249
|
}
|
|
@@ -1256,7 +1255,12 @@ export class Schema {
|
|
|
1256
1255
|
toAST(): DocumentNode {
|
|
1257
1256
|
if (!this.cachedDocument) {
|
|
1258
1257
|
// As we're not building the document from a file, having locations info might be more confusing that not.
|
|
1259
|
-
|
|
1258
|
+
const ast = parse(printSchema(this), { noLocation: true });
|
|
1259
|
+
const shouldCache = this.config.cacheAST ?? false;
|
|
1260
|
+
if (!shouldCache) {
|
|
1261
|
+
return ast;
|
|
1262
|
+
}
|
|
1263
|
+
this.cachedDocument = ast;
|
|
1260
1264
|
}
|
|
1261
1265
|
return this.cachedDocument!;
|
|
1262
1266
|
}
|
|
@@ -1293,8 +1297,9 @@ export class Schema {
|
|
|
1293
1297
|
return nodes;
|
|
1294
1298
|
}
|
|
1295
1299
|
|
|
1296
|
-
toGraphQLJSSchema(config?: { includeDefer?: boolean }): GraphQLSchema {
|
|
1300
|
+
toGraphQLJSSchema(config?: { includeDefer?: boolean, includeStream?: boolean }): GraphQLSchema {
|
|
1297
1301
|
const includeDefer = config?.includeDefer ?? false;
|
|
1302
|
+
const includeStream = config?.includeStream ?? false;
|
|
1298
1303
|
|
|
1299
1304
|
let ast = this.toAST();
|
|
1300
1305
|
|
|
@@ -1306,6 +1311,9 @@ export class Schema {
|
|
|
1306
1311
|
if (includeDefer) {
|
|
1307
1312
|
additionalNodes.push(this.deferDirective().toAST());
|
|
1308
1313
|
}
|
|
1314
|
+
if (includeStream) {
|
|
1315
|
+
additionalNodes.push(this.streamDirective().toAST());
|
|
1316
|
+
}
|
|
1309
1317
|
if (additionalNodes.length > 0) {
|
|
1310
1318
|
ast = {
|
|
1311
1319
|
kind: Kind.DOCUMENT,
|
|
@@ -1537,8 +1545,10 @@ export class Schema {
|
|
|
1537
1545
|
}
|
|
1538
1546
|
|
|
1539
1547
|
invalidate() {
|
|
1548
|
+
if (this.isValidated) {
|
|
1549
|
+
this.blueprint.onInvalidation(this);
|
|
1550
|
+
}
|
|
1540
1551
|
this.isValidated = false;
|
|
1541
|
-
this.blueprint.onInvalidation(this);
|
|
1542
1552
|
}
|
|
1543
1553
|
|
|
1544
1554
|
validate() {
|
|
@@ -1682,7 +1692,7 @@ export class RootType extends BaseExtensionMember<SchemaDefinition> {
|
|
|
1682
1692
|
export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
1683
1693
|
readonly kind = 'SchemaDefinition' as const;
|
|
1684
1694
|
protected readonly _roots = new MapWithCachedArrays<SchemaRootKind, RootType>();
|
|
1685
|
-
protected
|
|
1695
|
+
protected _extensions: Extension<SchemaDefinition>[] | undefined;
|
|
1686
1696
|
public preserveEmptyDefinition: boolean = false;
|
|
1687
1697
|
|
|
1688
1698
|
roots(): readonly RootType[] {
|
|
@@ -1752,8 +1762,12 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1752
1762
|
return toSet;
|
|
1753
1763
|
}
|
|
1754
1764
|
|
|
1755
|
-
extensions():
|
|
1756
|
-
return this._extensions;
|
|
1765
|
+
extensions(): Extension<SchemaDefinition>[] {
|
|
1766
|
+
return this._extensions ?? [];
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
hasExtension(extension: Extension<any>): boolean {
|
|
1770
|
+
return this._extensions?.includes(extension) ?? false;
|
|
1757
1771
|
}
|
|
1758
1772
|
|
|
1759
1773
|
newExtension(): Extension<SchemaDefinition> {
|
|
@@ -1763,23 +1777,27 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1763
1777
|
addExtension(extension: Extension<SchemaDefinition>): Extension<SchemaDefinition> {
|
|
1764
1778
|
this.checkUpdate();
|
|
1765
1779
|
// Let's be nice and not complaint if we add an extension already added.
|
|
1766
|
-
if (this.
|
|
1780
|
+
if (this.hasExtension(extension)) {
|
|
1767
1781
|
return extension;
|
|
1768
1782
|
}
|
|
1769
1783
|
assert(!extension.extendedElement, 'Cannot add extension to this schema: extension is already added to another schema');
|
|
1770
|
-
this._extensions
|
|
1784
|
+
if (this._extensions) {
|
|
1785
|
+
this._extensions.push(extension);
|
|
1786
|
+
} else {
|
|
1787
|
+
this._extensions = [extension];
|
|
1788
|
+
}
|
|
1771
1789
|
Extension.prototype['setExtendedElement'].call(extension, this);
|
|
1772
1790
|
this.onModification();
|
|
1773
1791
|
return extension;
|
|
1774
1792
|
}
|
|
1775
1793
|
|
|
1776
1794
|
hasExtensionElements(): boolean {
|
|
1777
|
-
return this._extensions
|
|
1795
|
+
return !!this._extensions;
|
|
1778
1796
|
}
|
|
1779
1797
|
|
|
1780
1798
|
hasNonExtensionElements(): boolean {
|
|
1781
1799
|
return this.preserveEmptyDefinition
|
|
1782
|
-
|| this.
|
|
1800
|
+
|| this.appliedDirectives.some((d) => d.ofExtension() === undefined)
|
|
1783
1801
|
|| this.roots().some((r) => r.ofExtension() === undefined);
|
|
1784
1802
|
}
|
|
1785
1803
|
|
|
@@ -1853,7 +1871,7 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1853
1871
|
// either to the main type definition _or_ to a single extension. In theory, a document could have `implements X`
|
|
1854
1872
|
// in both of those places (or on 2 distinct extensions). We don't preserve that level of detail, but this
|
|
1855
1873
|
// feels like a very minor limitation with little practical impact, and it avoids additional complexity.
|
|
1856
|
-
private
|
|
1874
|
+
private _interfaceImplementations: MapWithCachedArrays<string, InterfaceImplementation<T>> | undefined;
|
|
1857
1875
|
private readonly _fields: MapWithCachedArrays<string, FieldDefinition<T>> = new MapWithCachedArrays();
|
|
1858
1876
|
private _cachedNonBuiltInFields?: readonly FieldDefinition<T>[];
|
|
1859
1877
|
|
|
@@ -1872,11 +1890,11 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1872
1890
|
}
|
|
1873
1891
|
|
|
1874
1892
|
interfaceImplementations(): readonly InterfaceImplementation<T>[] {
|
|
1875
|
-
return this._interfaceImplementations
|
|
1893
|
+
return this._interfaceImplementations?.values() ?? [];
|
|
1876
1894
|
}
|
|
1877
1895
|
|
|
1878
1896
|
interfaceImplementation(type: string | InterfaceType): InterfaceImplementation<T> | undefined {
|
|
1879
|
-
return this._interfaceImplementations.get(typeof type === 'string' ? type : type.name);
|
|
1897
|
+
return this._interfaceImplementations ? this._interfaceImplementations.get(typeof type === 'string' ? type : type.name) : undefined;
|
|
1880
1898
|
}
|
|
1881
1899
|
|
|
1882
1900
|
interfaces(): readonly InterfaceType[] {
|
|
@@ -1884,7 +1902,7 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1884
1902
|
}
|
|
1885
1903
|
|
|
1886
1904
|
implementsInterface(type: string | InterfaceType): boolean {
|
|
1887
|
-
return this._interfaceImplementations
|
|
1905
|
+
return this._interfaceImplementations?.has(typeof type === 'string' ? type : type.name) ?? false;
|
|
1888
1906
|
}
|
|
1889
1907
|
|
|
1890
1908
|
addImplementedInterface(nameOrItfOrItfImpl: InterfaceImplementation<T> | InterfaceType | string): InterfaceImplementation<T> {
|
|
@@ -1908,8 +1926,11 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1908
1926
|
}
|
|
1909
1927
|
toAdd = new InterfaceImplementation<T>(itf);
|
|
1910
1928
|
}
|
|
1911
|
-
const existing = this._interfaceImplementations
|
|
1929
|
+
const existing = this._interfaceImplementations?.get(toAdd.interface.name);
|
|
1912
1930
|
if (!existing) {
|
|
1931
|
+
if (!this._interfaceImplementations) {
|
|
1932
|
+
this._interfaceImplementations = new MapWithCachedArrays();
|
|
1933
|
+
}
|
|
1913
1934
|
this._interfaceImplementations.set(toAdd.interface.name, toAdd);
|
|
1914
1935
|
addReferenceToType(this, toAdd.interface);
|
|
1915
1936
|
Element.prototype['setParent'].call(toAdd, this);
|
|
@@ -1996,12 +2017,12 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1996
2017
|
}
|
|
1997
2018
|
|
|
1998
2019
|
private removeInterfaceImplementation(itf: InterfaceType) {
|
|
1999
|
-
this._interfaceImplementations
|
|
2020
|
+
this._interfaceImplementations?.delete(itf.name);
|
|
2000
2021
|
removeReferenceToType(this, itf);
|
|
2001
2022
|
}
|
|
2002
2023
|
|
|
2003
2024
|
protected removeTypeReference(type: NamedType) {
|
|
2004
|
-
this._interfaceImplementations
|
|
2025
|
+
this._interfaceImplementations?.delete(type.name);
|
|
2005
2026
|
}
|
|
2006
2027
|
|
|
2007
2028
|
protected removeInnerElements(): void {
|
|
@@ -2071,7 +2092,7 @@ export class InterfaceType extends FieldBasedType<InterfaceType, InterfaceTypeRe
|
|
|
2071
2092
|
readonly astDefinitionKind = Kind.INTERFACE_TYPE_DEFINITION;
|
|
2072
2093
|
|
|
2073
2094
|
allImplementations(): (ObjectType | InterfaceType)[] {
|
|
2074
|
-
return
|
|
2095
|
+
return this.referencers().filter(ref => ref.kind === 'ObjectType' || ref.kind === 'InterfaceType') as (ObjectType | InterfaceType)[];
|
|
2075
2096
|
}
|
|
2076
2097
|
|
|
2077
2098
|
possibleRuntimeTypes(): readonly ObjectType[] {
|
|
@@ -2271,15 +2292,12 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
|
|
|
2271
2292
|
}
|
|
2272
2293
|
|
|
2273
2294
|
private removeValueInternal(value: EnumValue) {
|
|
2274
|
-
|
|
2275
|
-
if (index >= 0) {
|
|
2276
|
-
this._values.splice(index, 1);
|
|
2277
|
-
}
|
|
2295
|
+
removeArrayElement(value, this._values);
|
|
2278
2296
|
}
|
|
2279
2297
|
|
|
2280
2298
|
protected removeInnerElements(): void {
|
|
2281
|
-
// Make a copy, since EnumValue.remove() will modify this._values.
|
|
2282
|
-
const values =
|
|
2299
|
+
// Make a copy (indirectly), since EnumValue.remove() will modify this._values.
|
|
2300
|
+
const values = this.values;
|
|
2283
2301
|
for (const value of values) {
|
|
2284
2302
|
value.remove();
|
|
2285
2303
|
}
|
|
@@ -2434,7 +2452,7 @@ export class NonNullType<T extends NullableType> extends BaseWrapperType<T> {
|
|
|
2434
2452
|
|
|
2435
2453
|
export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaElementWithType<OutputType, FieldDefinition<TParent>, TParent, never> {
|
|
2436
2454
|
readonly kind = 'FieldDefinition' as const;
|
|
2437
|
-
private
|
|
2455
|
+
private _args: MapWithCachedArrays<string, ArgumentDefinition<FieldDefinition<TParent>>> | undefined;
|
|
2438
2456
|
private _extension?: Extension<TParent>;
|
|
2439
2457
|
|
|
2440
2458
|
constructor(name: string, readonly isBuiltIn: boolean = false) {
|
|
@@ -2451,15 +2469,15 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2451
2469
|
}
|
|
2452
2470
|
|
|
2453
2471
|
hasArguments(): boolean {
|
|
2454
|
-
return this._args.size > 0;
|
|
2472
|
+
return !!this._args && this._args.size > 0;
|
|
2455
2473
|
}
|
|
2456
2474
|
|
|
2457
2475
|
arguments(): readonly ArgumentDefinition<FieldDefinition<TParent>>[] {
|
|
2458
|
-
return this._args
|
|
2476
|
+
return this._args?.values() ?? [];
|
|
2459
2477
|
}
|
|
2460
2478
|
|
|
2461
2479
|
argument(name: string): ArgumentDefinition<FieldDefinition<TParent>> | undefined {
|
|
2462
|
-
return this._args
|
|
2480
|
+
return this._args?.get(name);
|
|
2463
2481
|
}
|
|
2464
2482
|
|
|
2465
2483
|
addArgument(arg: ArgumentDefinition<FieldDefinition<TParent>>): ArgumentDefinition<FieldDefinition<TParent>>;
|
|
@@ -2489,6 +2507,9 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2489
2507
|
if (type && !isInputType(type)) {
|
|
2490
2508
|
throw ERRORS.INVALID_GRAPHQL.err(`Invalid output type ${type} for argument ${toAdd.name} of ${this}: arguments should be input types.`);
|
|
2491
2509
|
}
|
|
2510
|
+
if (!this._args) {
|
|
2511
|
+
this._args = new MapWithCachedArrays();
|
|
2512
|
+
}
|
|
2492
2513
|
this._args.set(toAdd.name, toAdd);
|
|
2493
2514
|
Element.prototype['setParent'].call(toAdd, this);
|
|
2494
2515
|
if (typeof nameOrArg === 'string') {
|
|
@@ -2508,10 +2529,8 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2508
2529
|
|
|
2509
2530
|
setOfExtension(extension: Extension<TParent> | undefined) {
|
|
2510
2531
|
this.checkUpdate();
|
|
2511
|
-
// It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
|
|
2512
|
-
// the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
|
|
2513
2532
|
assert(
|
|
2514
|
-
!extension || this._parent?.
|
|
2533
|
+
!extension || this._parent?.hasExtension(extension),
|
|
2515
2534
|
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`
|
|
2516
2535
|
);
|
|
2517
2536
|
this._extension = extension;
|
|
@@ -2527,7 +2546,9 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2527
2546
|
}
|
|
2528
2547
|
|
|
2529
2548
|
private removeArgumentInternal(name: string) {
|
|
2530
|
-
this._args
|
|
2549
|
+
if (this._args) {
|
|
2550
|
+
this._args.delete(name);
|
|
2551
|
+
}
|
|
2531
2552
|
}
|
|
2532
2553
|
|
|
2533
2554
|
// Only called through the prototype from FieldBasedType.removeInnerElements because we don't want to expose it.
|
|
@@ -2589,9 +2610,9 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2589
2610
|
}
|
|
2590
2611
|
|
|
2591
2612
|
toString(): string {
|
|
2592
|
-
const args = this.
|
|
2593
|
-
?
|
|
2594
|
-
:
|
|
2613
|
+
const args = this.hasArguments()
|
|
2614
|
+
? '(' + this.arguments().map(arg => arg.toString()).join(', ') + ')'
|
|
2615
|
+
: "";
|
|
2595
2616
|
return `${this.name}${args}: ${this.type}`;
|
|
2596
2617
|
}
|
|
2597
2618
|
}
|
|
@@ -2620,10 +2641,8 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
|
|
|
2620
2641
|
|
|
2621
2642
|
setOfExtension(extension: Extension<InputObjectType> | undefined) {
|
|
2622
2643
|
this.checkUpdate();
|
|
2623
|
-
// It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
|
|
2624
|
-
// the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
|
|
2625
2644
|
assert(
|
|
2626
|
-
!extension || this._parent?.
|
|
2645
|
+
!extension || this._parent?.hasExtension(extension),
|
|
2627
2646
|
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`,
|
|
2628
2647
|
);
|
|
2629
2648
|
this._extension = extension;
|
|
@@ -2773,7 +2792,7 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2773
2792
|
setOfExtension(extension: Extension<EnumType> | undefined) {
|
|
2774
2793
|
this.checkUpdate();
|
|
2775
2794
|
assert(
|
|
2776
|
-
!extension || this._parent?.
|
|
2795
|
+
!extension || this._parent?.hasExtension(extension),
|
|
2777
2796
|
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of enum value parent type ${this.parent}`,
|
|
2778
2797
|
);
|
|
2779
2798
|
this._extension = extension;
|
|
@@ -2830,10 +2849,10 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2830
2849
|
export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}> extends NamedSchemaElement<DirectiveDefinition<TApplicationArgs>, Schema, Directive> {
|
|
2831
2850
|
readonly kind = 'DirectiveDefinition' as const;
|
|
2832
2851
|
|
|
2833
|
-
private
|
|
2852
|
+
private _args?: MapWithCachedArrays<string, ArgumentDefinition<DirectiveDefinition>>;
|
|
2834
2853
|
repeatable: boolean = false;
|
|
2835
2854
|
private readonly _locations: DirectiveLocation[] = [];
|
|
2836
|
-
private
|
|
2855
|
+
private _referencers?: Directive<SchemaElement<any, any>, TApplicationArgs>[];
|
|
2837
2856
|
|
|
2838
2857
|
constructor(name: string, readonly isBuiltIn: boolean = false) {
|
|
2839
2858
|
super(name);
|
|
@@ -2844,11 +2863,11 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2844
2863
|
}
|
|
2845
2864
|
|
|
2846
2865
|
arguments(): readonly ArgumentDefinition<DirectiveDefinition>[] {
|
|
2847
|
-
return this._args
|
|
2866
|
+
return this._args?.values() ?? [];
|
|
2848
2867
|
}
|
|
2849
2868
|
|
|
2850
2869
|
argument(name: string): ArgumentDefinition<DirectiveDefinition> | undefined {
|
|
2851
|
-
return this._args
|
|
2870
|
+
return this._args?.get(name);
|
|
2852
2871
|
}
|
|
2853
2872
|
|
|
2854
2873
|
addArgument(arg: ArgumentDefinition<DirectiveDefinition>): ArgumentDefinition<DirectiveDefinition>;
|
|
@@ -2866,6 +2885,9 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2866
2885
|
if (this.argument(toAdd.name)) {
|
|
2867
2886
|
throw ERRORS.INVALID_GRAPHQL.err(`Argument ${toAdd.name} already exists on field ${this.name}`);
|
|
2868
2887
|
}
|
|
2888
|
+
if (!this._args) {
|
|
2889
|
+
this._args = new MapWithCachedArrays();
|
|
2890
|
+
}
|
|
2869
2891
|
this._args.set(toAdd.name, toAdd);
|
|
2870
2892
|
Element.prototype['setParent'].call(toAdd, this);
|
|
2871
2893
|
if (typeof nameOrArg === 'string') {
|
|
@@ -2876,7 +2898,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2876
2898
|
}
|
|
2877
2899
|
|
|
2878
2900
|
private removeArgumentInternal(name: string) {
|
|
2879
|
-
this._args
|
|
2901
|
+
this._args?.delete(name);
|
|
2880
2902
|
}
|
|
2881
2903
|
|
|
2882
2904
|
get locations(): readonly DirectiveLocation[] {
|
|
@@ -2918,11 +2940,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2918
2940
|
removeLocations(...locations: DirectiveLocation[]): DirectiveDefinition {
|
|
2919
2941
|
let modified = false;
|
|
2920
2942
|
for (const location of locations) {
|
|
2921
|
-
|
|
2922
|
-
if (index >= 0) {
|
|
2923
|
-
this._locations.splice(index, 1);
|
|
2924
|
-
modified = true;
|
|
2925
|
-
}
|
|
2943
|
+
modified ||= removeArrayElement(location, this._locations);
|
|
2926
2944
|
}
|
|
2927
2945
|
if (modified) {
|
|
2928
2946
|
this.onModification();
|
|
@@ -2939,16 +2957,24 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2939
2957
|
}
|
|
2940
2958
|
|
|
2941
2959
|
applications(): readonly Directive<SchemaElement<any, any>, TApplicationArgs>[] {
|
|
2942
|
-
return
|
|
2960
|
+
return this._referencers ?? [];
|
|
2943
2961
|
}
|
|
2944
2962
|
|
|
2945
2963
|
private addReferencer(referencer: Directive<SchemaElement<any, any>, TApplicationArgs>) {
|
|
2946
2964
|
assert(referencer, 'Referencer should exists');
|
|
2947
|
-
this._referencers
|
|
2965
|
+
if (this._referencers) {
|
|
2966
|
+
if (!this._referencers.includes(referencer)) {
|
|
2967
|
+
this._referencers.push(referencer);
|
|
2968
|
+
}
|
|
2969
|
+
} else {
|
|
2970
|
+
this._referencers = [ referencer ];
|
|
2971
|
+
}
|
|
2948
2972
|
}
|
|
2949
2973
|
|
|
2950
2974
|
private removeReferencer(referencer: Directive<SchemaElement<any, any>, TApplicationArgs>) {
|
|
2951
|
-
this._referencers
|
|
2975
|
+
if (this._referencers) {
|
|
2976
|
+
removeArrayElement(referencer, this._referencers);
|
|
2977
|
+
}
|
|
2952
2978
|
}
|
|
2953
2979
|
|
|
2954
2980
|
protected removeTypeReference(type: NamedType) {
|
|
@@ -2969,7 +2995,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2969
2995
|
this.onModification();
|
|
2970
2996
|
// Remove this directive definition's children.
|
|
2971
2997
|
this.sourceAST = undefined;
|
|
2972
|
-
assert(this._appliedDirectives.length === 0, "Directive definition should not have directive applied to it");
|
|
2998
|
+
assert(!this._appliedDirectives || this._appliedDirectives.length === 0, "Directive definition should not have directive applied to it");
|
|
2973
2999
|
for (const arg of this.arguments()) {
|
|
2974
3000
|
arg.remove();
|
|
2975
3001
|
}
|
|
@@ -2979,8 +3005,8 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2979
3005
|
// doesn't store a link to that definition. Instead, we fetch the definition
|
|
2980
3006
|
// from the schema when requested. So we don't have to do anything on the
|
|
2981
3007
|
// referencers other than clear them (and return the pre-cleared set).
|
|
2982
|
-
const toReturn =
|
|
2983
|
-
this._referencers
|
|
3008
|
+
const toReturn = this._referencers ?? [];
|
|
3009
|
+
this._referencers = undefined;
|
|
2984
3010
|
// Remove this directive definition from its parent schema.
|
|
2985
3011
|
Schema.prototype['removeDirectiveInternal'].call(this._parent, this);
|
|
2986
3012
|
this._parent = undefined;
|
|
@@ -3093,7 +3119,7 @@ export class Directive<
|
|
|
3093
3119
|
parent instanceof SchemaDefinition || parent instanceof BaseNamedType,
|
|
3094
3120
|
'Can only mark directive parts of extensions when directly apply to type or schema definition.'
|
|
3095
3121
|
);
|
|
3096
|
-
assert(parent.
|
|
3122
|
+
assert(parent.hasExtension(extension), () => `Cannot mark directive ${this.name} as part of the provided extension: it is not an extension of parent ${parent}`);
|
|
3097
3123
|
}
|
|
3098
3124
|
this._extension = extension;
|
|
3099
3125
|
this.onModification();
|
|
@@ -3157,9 +3183,8 @@ export class Directive<
|
|
|
3157
3183
|
}
|
|
3158
3184
|
// Remove this directive application from its parent schema element.
|
|
3159
3185
|
const parentDirectives = this._parent.appliedDirectives as Directive<TParent>[];
|
|
3160
|
-
const
|
|
3161
|
-
assert(
|
|
3162
|
-
parentDirectives.splice(index, 1);
|
|
3186
|
+
const removed = removeArrayElement(this, parentDirectives);
|
|
3187
|
+
assert(removed, () => `Directive ${this} lists ${this._parent} as parent, but that parent doesn't list it as applied directive`);
|
|
3163
3188
|
this._parent = undefined;
|
|
3164
3189
|
this._extension = undefined;
|
|
3165
3190
|
return true;
|
|
@@ -3179,7 +3204,7 @@ export function sameDirectiveApplication(application1: Directive<any, any>, appl
|
|
|
3179
3204
|
/**
|
|
3180
3205
|
* Checks whether the 2 provided "set" of directive applications are the same (same applications, regardless or order).
|
|
3181
3206
|
*/
|
|
3182
|
-
export function sameDirectiveApplications(applications1: Directive<any, any>[], applications2: Directive<any, any>[]): boolean {
|
|
3207
|
+
export function sameDirectiveApplications(applications1: readonly Directive<any, any>[], applications2: readonly Directive<any, any>[]): boolean {
|
|
3183
3208
|
if (applications1.length !== applications2.length) {
|
|
3184
3209
|
return false;
|
|
3185
3210
|
}
|
|
@@ -3197,7 +3222,7 @@ export function sameDirectiveApplications(applications1: Directive<any, any>[],
|
|
|
3197
3222
|
*
|
|
3198
3223
|
* Sub-set here means that all of the applications in `maybeSubset` appears in `applications`.
|
|
3199
3224
|
*/
|
|
3200
|
-
export function isDirectiveApplicationsSubset(applications: Directive<any, any>[], maybeSubset: Directive<any, any>[]): boolean {
|
|
3225
|
+
export function isDirectiveApplicationsSubset(applications: readonly Directive<any, any>[], maybeSubset: readonly Directive<any, any>[]): boolean {
|
|
3201
3226
|
if (maybeSubset.length > applications.length) {
|
|
3202
3227
|
return false;
|
|
3203
3228
|
}
|
|
@@ -3213,7 +3238,7 @@ export function isDirectiveApplicationsSubset(applications: Directive<any, any>[
|
|
|
3213
3238
|
/**
|
|
3214
3239
|
* Computes the difference between the set of directives applications `baseApplications` and the `toRemove` one.
|
|
3215
3240
|
*/
|
|
3216
|
-
export function directiveApplicationsSubstraction(baseApplications: Directive<any, any>[], toRemove: Directive<any, any>[]): Directive<any, any>[] {
|
|
3241
|
+
export function directiveApplicationsSubstraction(baseApplications: readonly Directive<any, any>[], toRemove: readonly Directive<any, any>[]): Directive<any, any>[] {
|
|
3217
3242
|
return baseApplications.filter((application) => !toRemove.some((other) => sameDirectiveApplication(application, other)));
|
|
3218
3243
|
}
|
|
3219
3244
|
|