@apollo/federation-internals 2.1.0-alpha.2 → 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 +5 -4
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +14 -4
- 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 +66 -34
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +309 -165
- 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 +135 -5
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +3 -2
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +12 -7
- package/dist/federation.js.map +1 -1
- package/dist/inaccessibleSpec.js +1 -2
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/operations.d.ts +44 -20
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +287 -27
- package/dist/operations.js.map +1 -1
- package/dist/print.d.ts +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +1 -1
- package/dist/print.js.map +1 -1
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +6 -6
- 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 +10 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +40 -1
- package/dist/utils.js.map +1 -1
- package/package.json +3 -4
- package/src/__tests__/coreSpec.test.ts +1 -1
- package/src/__tests__/definitions.test.ts +27 -0
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +9 -5
- package/src/__tests__/operations.test.ts +36 -0
- 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 +20 -7
- package/src/coreSpec.ts +2 -7
- package/src/definitions.ts +355 -155
- package/src/error.ts +62 -0
- package/src/extractSubgraphsFromSupergraph.ts +198 -7
- package/src/federation.ts +11 -4
- package/src/inaccessibleSpec.ts +2 -5
- package/src/operations.ts +428 -40
- package/src/print.ts +3 -3
- package/src/schemaUpgrader.ts +7 -6
- package/src/supergraphs.ts +16 -25
- package/src/utils.ts +49 -0
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/definitions.ts
CHANGED
|
@@ -15,9 +15,11 @@ import {
|
|
|
15
15
|
TypeNode,
|
|
16
16
|
VariableDefinitionNode,
|
|
17
17
|
VariableNode,
|
|
18
|
-
TypeSystemDefinitionNode,
|
|
19
18
|
SchemaDefinitionNode,
|
|
20
|
-
TypeDefinitionNode
|
|
19
|
+
TypeDefinitionNode,
|
|
20
|
+
DefinitionNode,
|
|
21
|
+
DirectiveDefinitionNode,
|
|
22
|
+
DirectiveNode,
|
|
21
23
|
} from "graphql";
|
|
22
24
|
import {
|
|
23
25
|
CoreImport,
|
|
@@ -29,67 +31,30 @@ import {
|
|
|
29
31
|
isCoreSpecDirectiveApplication,
|
|
30
32
|
removeAllCoreFeatures,
|
|
31
33
|
} from "./coreSpec";
|
|
32
|
-
import { assert, mapValues, MapWithCachedArrays,
|
|
34
|
+
import { assert, mapValues, MapWithCachedArrays, removeArrayElement } from "./utils";
|
|
33
35
|
import { withDefaultValues, valueEquals, valueToString, valueToAST, variablesInValue, valueFromAST, valueNodeToConstValueNode, argumentsEquals } from "./values";
|
|
34
36
|
import { removeInaccessibleElements } from "./inaccessibleSpec";
|
|
35
|
-
import { printSchema } from './print';
|
|
37
|
+
import { printDirectiveDefinition, printSchema } from './print';
|
|
36
38
|
import { sameType } from './types';
|
|
37
39
|
import { addIntrospectionFields, introspectionFieldNames, isIntrospectionName } from "./introspection";
|
|
38
|
-
import { err } from '@apollo/core-schema';
|
|
39
|
-
import { GraphQLErrorExt } from "@apollo/core-schema/dist/error";
|
|
40
40
|
import { validateSDL } from "graphql/validation/validate";
|
|
41
41
|
import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
42
42
|
import { specifiedSDLRules } from "graphql/validation/specifiedRules";
|
|
43
43
|
import { validateSchema } from "./validate";
|
|
44
44
|
import { createDirectiveSpecification, createScalarTypeSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
|
|
45
45
|
import { didYouMean, suggestionList } from "./suggestions";
|
|
46
|
-
import { ERRORS, withModifiedErrorMessage } from "./error";
|
|
46
|
+
import { aggregateError, ERRORS, withModifiedErrorMessage } from "./error";
|
|
47
47
|
|
|
48
48
|
const validationErrorCode = 'GraphQLValidationFailed';
|
|
49
49
|
const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL schema.';
|
|
50
50
|
|
|
51
51
|
export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: string = DEFAULT_VALIDATION_ERROR_MESSAGE) =>
|
|
52
|
-
|
|
53
|
-
message: message + '. Caused by:\n' + causes.map((c) => c.toString()).join('\n\n'),
|
|
54
|
-
causes
|
|
55
|
-
});
|
|
52
|
+
aggregateError(validationErrorCode, message, causes);
|
|
56
53
|
|
|
57
54
|
const apiSchemaValidationErrorCode = 'GraphQLAPISchemaValidationFailed';
|
|
58
55
|
|
|
59
56
|
export const ErrGraphQLAPISchemaValidationFailed = (causes: GraphQLError[]) =>
|
|
60
|
-
|
|
61
|
-
message: 'The supergraph schema failed to produce a valid API schema',
|
|
62
|
-
causes
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Given an error that may have been thrown during schema validation, extract the causes of validation failure.
|
|
67
|
-
* If the error is not a graphQL error, undefined is returned.
|
|
68
|
-
*/
|
|
69
|
-
export function errorCauses(e: Error): GraphQLError[] | undefined {
|
|
70
|
-
if (e instanceof GraphQLErrorExt) {
|
|
71
|
-
if (e.code === validationErrorCode || e.code === apiSchemaValidationErrorCode) {
|
|
72
|
-
return ((e as any).causes) as GraphQLError[];
|
|
73
|
-
}
|
|
74
|
-
return [e];
|
|
75
|
-
}
|
|
76
|
-
if (e instanceof GraphQLError) {
|
|
77
|
-
return [e];
|
|
78
|
-
}
|
|
79
|
-
return undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function printGraphQLErrorsOrRethrow(e: Error): string {
|
|
83
|
-
const causes = errorCauses(e);
|
|
84
|
-
if (!causes) {
|
|
85
|
-
throw e;
|
|
86
|
-
}
|
|
87
|
-
return causes.map(e => e.toString()).join('\n\n');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function printErrors(errors: GraphQLError[]): string {
|
|
91
|
-
return errors.map(e => e.toString()).join('\n\n');
|
|
92
|
-
}
|
|
57
|
+
aggregateError(apiSchemaValidationErrorCode, 'The supergraph schema failed to produce a valid API schema', causes);
|
|
93
58
|
|
|
94
59
|
export const typenameFieldName = '__typename';
|
|
95
60
|
|
|
@@ -291,6 +256,32 @@ export const executableDirectiveLocations: DirectiveLocation[] = [
|
|
|
291
256
|
DirectiveLocation.VARIABLE_DEFINITION,
|
|
292
257
|
];
|
|
293
258
|
|
|
259
|
+
const executableDirectiveLocationsSet = new Set(executableDirectiveLocations);
|
|
260
|
+
|
|
261
|
+
export function isExecutableDirectiveLocation(loc: DirectiveLocation): boolean {
|
|
262
|
+
return executableDirectiveLocationsSet.has(loc);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export const typeSystemDirectiveLocations: DirectiveLocation[] = [
|
|
266
|
+
DirectiveLocation.SCHEMA,
|
|
267
|
+
DirectiveLocation.SCALAR,
|
|
268
|
+
DirectiveLocation.OBJECT,
|
|
269
|
+
DirectiveLocation.FIELD_DEFINITION,
|
|
270
|
+
DirectiveLocation.ARGUMENT_DEFINITION,
|
|
271
|
+
DirectiveLocation.INTERFACE,
|
|
272
|
+
DirectiveLocation.UNION,
|
|
273
|
+
DirectiveLocation.ENUM,
|
|
274
|
+
DirectiveLocation.ENUM_VALUE,
|
|
275
|
+
DirectiveLocation.INPUT_OBJECT,
|
|
276
|
+
DirectiveLocation.INPUT_FIELD_DEFINITION,
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
const typeSystemDirectiveLocationsSet = new Set(typeSystemDirectiveLocations);
|
|
280
|
+
|
|
281
|
+
export function isTypeSystemDirectiveLocation(loc: DirectiveLocation): boolean {
|
|
282
|
+
return typeSystemDirectiveLocationsSet.has(loc);
|
|
283
|
+
}
|
|
284
|
+
|
|
294
285
|
/**
|
|
295
286
|
* Converts a type to an AST of a "reference" to that type, one corresponding to the type `toString()` (and thus never a type definition).
|
|
296
287
|
*
|
|
@@ -344,7 +335,7 @@ export interface Named {
|
|
|
344
335
|
export type ExtendableElement = SchemaDefinition | NamedType;
|
|
345
336
|
|
|
346
337
|
export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
347
|
-
|
|
338
|
+
private _appliedDirectives: Directive<T>[] | undefined;
|
|
348
339
|
|
|
349
340
|
constructor(private readonly _schema: Schema) {}
|
|
350
341
|
|
|
@@ -359,6 +350,10 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
359
350
|
return this.appliedDirectives.filter(d => d.name == directiveName);
|
|
360
351
|
}
|
|
361
352
|
|
|
353
|
+
get appliedDirectives(): readonly Directive<T>[] {
|
|
354
|
+
return this._appliedDirectives ?? [];
|
|
355
|
+
}
|
|
356
|
+
|
|
362
357
|
hasAppliedDirective(nameOrDefinition: string | DirectiveDefinition): boolean {
|
|
363
358
|
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
|
|
364
359
|
return this.appliedDirectives.some(d => d.name == directiveName);
|
|
@@ -382,7 +377,11 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
382
377
|
}
|
|
383
378
|
Element.prototype['setParent'].call(toAdd, this);
|
|
384
379
|
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
385
|
-
this.
|
|
380
|
+
if (this._appliedDirectives) {
|
|
381
|
+
this._appliedDirectives.push(toAdd);
|
|
382
|
+
} else {
|
|
383
|
+
this._appliedDirectives = [ toAdd ];
|
|
384
|
+
}
|
|
386
385
|
return toAdd;
|
|
387
386
|
}
|
|
388
387
|
|
|
@@ -495,18 +494,49 @@ export class Extension<TElement extends ExtendableElement> {
|
|
|
495
494
|
}
|
|
496
495
|
}
|
|
497
496
|
|
|
497
|
+
type UnappliedDirective = {
|
|
498
|
+
nameOrDef: DirectiveDefinition<Record<string, any>> | string,
|
|
499
|
+
args: Record<string, any>,
|
|
500
|
+
extension?: Extension<any>,
|
|
501
|
+
directive: DirectiveNode,
|
|
502
|
+
};
|
|
503
|
+
|
|
498
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.
|
|
499
505
|
export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>, TParent extends SchemaElement<any, any> | Schema> extends Element<TParent> {
|
|
500
|
-
protected
|
|
506
|
+
protected _appliedDirectives: Directive<TOwnType>[] | undefined;
|
|
507
|
+
protected _unappliedDirectives: UnappliedDirective[] | undefined;
|
|
501
508
|
description?: string;
|
|
502
509
|
|
|
510
|
+
addUnappliedDirective({ nameOrDef, args, extension, directive }: UnappliedDirective) {
|
|
511
|
+
const toAdd = {
|
|
512
|
+
nameOrDef,
|
|
513
|
+
args: args ?? {},
|
|
514
|
+
extension,
|
|
515
|
+
directive,
|
|
516
|
+
};
|
|
517
|
+
if (this._unappliedDirectives) {
|
|
518
|
+
this._unappliedDirectives.push(toAdd);
|
|
519
|
+
} else {
|
|
520
|
+
this._unappliedDirectives = [toAdd];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
processUnappliedDirectives() {
|
|
525
|
+
for (const { nameOrDef, args, extension, directive } of this._unappliedDirectives ?? []) {
|
|
526
|
+
const d = this.applyDirective(nameOrDef, args);
|
|
527
|
+
d.setOfExtension(extension);
|
|
528
|
+
d.sourceAST = directive;
|
|
529
|
+
}
|
|
530
|
+
this._unappliedDirectives = undefined;
|
|
531
|
+
}
|
|
532
|
+
|
|
503
533
|
get appliedDirectives(): readonly Directive<TOwnType>[] {
|
|
504
|
-
return this._appliedDirectives;
|
|
534
|
+
return this._appliedDirectives ?? [];
|
|
505
535
|
}
|
|
506
536
|
|
|
507
537
|
appliedDirectivesOf<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(nameOrDefinition: string | DirectiveDefinition<TApplicationArgs>): Directive<TOwnType, TApplicationArgs>[] {
|
|
508
538
|
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
|
|
509
|
-
return this.
|
|
539
|
+
return this.appliedDirectives.filter(d => d.name == directiveName) as Directive<TOwnType, TApplicationArgs>[];
|
|
510
540
|
}
|
|
511
541
|
|
|
512
542
|
hasAppliedDirective(nameOrDefinition: string | DirectiveDefinition<any>): boolean {
|
|
@@ -546,10 +576,14 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
546
576
|
const toAdd = new Directive<TOwnType, TApplicationArgs>(name, args ?? Object.create(null));
|
|
547
577
|
Element.prototype['setParent'].call(toAdd, this);
|
|
548
578
|
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
549
|
-
if (
|
|
550
|
-
|
|
579
|
+
if (this._appliedDirectives) {
|
|
580
|
+
if (asFirstDirective) {
|
|
581
|
+
this._appliedDirectives.unshift(toAdd);
|
|
582
|
+
} else {
|
|
583
|
+
this._appliedDirectives.push(toAdd);
|
|
584
|
+
}
|
|
551
585
|
} else {
|
|
552
|
-
this._appliedDirectives
|
|
586
|
+
this._appliedDirectives = [toAdd];
|
|
553
587
|
}
|
|
554
588
|
DirectiveDefinition.prototype['addReferencer'].call(toAdd.definition!, toAdd);
|
|
555
589
|
this.onModification();
|
|
@@ -558,6 +592,9 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
558
592
|
|
|
559
593
|
protected removeAppliedDirectives() {
|
|
560
594
|
// We copy the array because this._appliedDirectives is modified in-place by `directive.remove()`
|
|
595
|
+
if (!this._appliedDirectives) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
561
598
|
const applied = this._appliedDirectives.concat();
|
|
562
599
|
applied.forEach(d => d.remove());
|
|
563
600
|
}
|
|
@@ -631,8 +668,8 @@ export abstract class NamedSchemaElement<TOwnType extends NamedSchemaElement<TOw
|
|
|
631
668
|
}
|
|
632
669
|
|
|
633
670
|
abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSchemaElement<TOwnType, Schema, TReferencer>> extends NamedSchemaElement<TOwnType, Schema, TReferencer> {
|
|
634
|
-
protected
|
|
635
|
-
protected
|
|
671
|
+
protected _referencers?: TReferencer[];
|
|
672
|
+
protected _extensions?: Extension<TOwnType>[];
|
|
636
673
|
public preserveEmptyDefinition: boolean = false;
|
|
637
674
|
|
|
638
675
|
constructor(name: string, readonly isBuiltIn: boolean = false) {
|
|
@@ -640,11 +677,19 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
640
677
|
}
|
|
641
678
|
|
|
642
679
|
private addReferencer(referencer: TReferencer) {
|
|
643
|
-
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
|
+
}
|
|
644
687
|
}
|
|
645
688
|
|
|
646
689
|
private removeReferencer(referencer: TReferencer) {
|
|
647
|
-
this._referencers
|
|
690
|
+
if (this._referencers) {
|
|
691
|
+
removeArrayElement(referencer, this._referencers);
|
|
692
|
+
}
|
|
648
693
|
}
|
|
649
694
|
|
|
650
695
|
get coordinate(): string {
|
|
@@ -655,8 +700,12 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
655
700
|
// Overriden by those types that do have children
|
|
656
701
|
}
|
|
657
702
|
|
|
658
|
-
extensions():
|
|
659
|
-
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;
|
|
660
709
|
}
|
|
661
710
|
|
|
662
711
|
newExtension(): Extension<TOwnType> {
|
|
@@ -666,23 +715,27 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
666
715
|
addExtension(extension: Extension<TOwnType>): Extension<TOwnType> {
|
|
667
716
|
this.checkUpdate();
|
|
668
717
|
// Let's be nice and not complaint if we add an extension already added.
|
|
669
|
-
if (this.
|
|
718
|
+
if (this.hasExtension(extension)) {
|
|
670
719
|
return extension;
|
|
671
720
|
}
|
|
672
721
|
assert(!extension.extendedElement, () => `Cannot add extension to type ${this}: it is already added to another type`);
|
|
673
|
-
this._extensions
|
|
722
|
+
if (this._extensions) {
|
|
723
|
+
this._extensions.push(extension);
|
|
724
|
+
} else {
|
|
725
|
+
this._extensions = [ extension ];
|
|
726
|
+
}
|
|
674
727
|
Extension.prototype['setExtendedElement'].call(extension, this);
|
|
675
728
|
this.onModification();
|
|
676
729
|
return extension;
|
|
677
730
|
}
|
|
678
731
|
|
|
679
732
|
removeExtensions() {
|
|
680
|
-
if (this._extensions
|
|
733
|
+
if (!this._extensions) {
|
|
681
734
|
return;
|
|
682
735
|
}
|
|
683
736
|
|
|
684
|
-
this._extensions
|
|
685
|
-
for (const directive of this.
|
|
737
|
+
this._extensions = undefined;
|
|
738
|
+
for (const directive of this.appliedDirectives) {
|
|
686
739
|
directive.removeOfExtension();
|
|
687
740
|
}
|
|
688
741
|
this.removeInnerElementsExtensions();
|
|
@@ -693,12 +746,12 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
693
746
|
}
|
|
694
747
|
|
|
695
748
|
hasExtensionElements(): boolean {
|
|
696
|
-
return this._extensions
|
|
749
|
+
return !!this._extensions;
|
|
697
750
|
}
|
|
698
751
|
|
|
699
752
|
hasNonExtensionElements(): boolean {
|
|
700
753
|
return this.preserveEmptyDefinition
|
|
701
|
-
|| this.
|
|
754
|
+
|| this.appliedDirectives.some(d => d.ofExtension() === undefined)
|
|
702
755
|
|| this.hasNonExtensionInnerElements();
|
|
703
756
|
}
|
|
704
757
|
|
|
@@ -744,11 +797,11 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
744
797
|
this.removeAppliedDirectives();
|
|
745
798
|
this.removeInnerElements();
|
|
746
799
|
// Remove this type's references.
|
|
747
|
-
const toReturn =
|
|
800
|
+
const toReturn = this._referencers?.map(r => {
|
|
748
801
|
SchemaElement.prototype['removeTypeReferenceInternal'].call(r, this);
|
|
749
802
|
return r;
|
|
750
|
-
});
|
|
751
|
-
this._referencers
|
|
803
|
+
}) ?? [];
|
|
804
|
+
this._referencers = undefined;
|
|
752
805
|
// Remove this type from its parent schema.
|
|
753
806
|
Schema.prototype['removeTypeInternal'].call(this._parent, this);
|
|
754
807
|
this._parent = undefined;
|
|
@@ -776,11 +829,11 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
776
829
|
protected abstract removeReferenceRecursive(ref: TReferencer): void;
|
|
777
830
|
|
|
778
831
|
referencers(): readonly TReferencer[] {
|
|
779
|
-
return
|
|
832
|
+
return this._referencers ?? [];
|
|
780
833
|
}
|
|
781
834
|
|
|
782
835
|
isReferenced(): boolean {
|
|
783
|
-
return this._referencers
|
|
836
|
+
return !!this._referencers;
|
|
784
837
|
}
|
|
785
838
|
|
|
786
839
|
protected abstract removeInnerElements(): void;
|
|
@@ -833,8 +886,7 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
|
|
|
833
886
|
|
|
834
887
|
setOfExtension(extension: Extension<TExtended> | undefined) {
|
|
835
888
|
this.checkUpdate();
|
|
836
|
-
|
|
837
|
-
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}`);
|
|
838
890
|
this._extension = extension;
|
|
839
891
|
}
|
|
840
892
|
|
|
@@ -916,6 +968,10 @@ export class SchemaBlueprint {
|
|
|
916
968
|
onUnknownDirectiveValidationError(_schema: Schema, _unknownDirectiveName: string, error: GraphQLError): GraphQLError {
|
|
917
969
|
return error;
|
|
918
970
|
}
|
|
971
|
+
|
|
972
|
+
applyDirectivesAfterParsing() {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
919
975
|
}
|
|
920
976
|
|
|
921
977
|
export const defaultSchemaBlueprint = new SchemaBlueprint();
|
|
@@ -1072,12 +1128,56 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
|
|
|
1072
1128
|
locations: [DirectiveLocation.SCALAR],
|
|
1073
1129
|
argumentFct: (schema) => ({ args: [{ name: 'url', type: new NonNullType(schema.stringType()) }], errors: [] })
|
|
1074
1130
|
}),
|
|
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.
|
|
1134
|
+
createDirectiveSpecification({
|
|
1135
|
+
name: 'defer',
|
|
1136
|
+
locations: [DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
|
|
1137
|
+
argumentFct: (schema) => ({
|
|
1138
|
+
args: [
|
|
1139
|
+
{ name: 'label', type: schema.stringType() },
|
|
1140
|
+
{ name: 'if', type: new NonNullType(schema.booleanType()), defaultValue: true },
|
|
1141
|
+
],
|
|
1142
|
+
errors: [],
|
|
1143
|
+
})
|
|
1144
|
+
}),
|
|
1145
|
+
// Adding @stream too so that it's know and we don't error out if it is queries. It feels like it would be weird to do so for @stream but not
|
|
1146
|
+
// @defer when both are defined in the same spec. That said, that does *not* mean we currently _implement_ @stream, we don't, and so putting
|
|
1147
|
+
// it in a query will be a no-op at the moment (which technically is valid according to the spec so ...).
|
|
1148
|
+
createDirectiveSpecification({
|
|
1149
|
+
name: 'stream',
|
|
1150
|
+
locations: [DirectiveLocation.FIELD],
|
|
1151
|
+
argumentFct: (schema) => ({
|
|
1152
|
+
args: [
|
|
1153
|
+
{ name: 'label', type: schema.stringType() },
|
|
1154
|
+
{ name: 'initialCount', type: schema.intType(), defaultValue: 0 },
|
|
1155
|
+
{ name: 'if', type: new NonNullType(schema.booleanType()), defaultValue: true },
|
|
1156
|
+
],
|
|
1157
|
+
errors: [],
|
|
1158
|
+
})
|
|
1159
|
+
}),
|
|
1075
1160
|
];
|
|
1076
1161
|
|
|
1162
|
+
export type DeferDirectiveArgs = {
|
|
1163
|
+
label?: string,
|
|
1164
|
+
if?: boolean | Variable,
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
export type StreamDirectiveArgs = {
|
|
1168
|
+
label?: string,
|
|
1169
|
+
initialCount: number,
|
|
1170
|
+
if?: boolean,
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1077
1173
|
|
|
1078
1174
|
// A coordinate is up to 3 "graphQL name" ([_A-Za-z][_0-9A-Za-z]*).
|
|
1079
1175
|
const coordinateRegexp = /^@?[_A-Za-z][_0-9A-Za-z]*(\.[_A-Za-z][_0-9A-Za-z]*)?(\([_A-Za-z][_0-9A-Za-z]*:\))?$/;
|
|
1080
1176
|
|
|
1177
|
+
export type SchemaConfig = {
|
|
1178
|
+
cacheAST?: boolean,
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1081
1181
|
export class Schema {
|
|
1082
1182
|
private _schemaDefinition: SchemaDefinition;
|
|
1083
1183
|
private readonly _builtInTypes = new MapWithCachedArrays<string, NamedType>();
|
|
@@ -1091,7 +1191,10 @@ export class Schema {
|
|
|
1091
1191
|
private cachedDocument?: DocumentNode;
|
|
1092
1192
|
private apiSchema?: Schema;
|
|
1093
1193
|
|
|
1094
|
-
constructor(
|
|
1194
|
+
constructor(
|
|
1195
|
+
readonly blueprint: SchemaBlueprint = defaultSchemaBlueprint,
|
|
1196
|
+
readonly config: SchemaConfig = {},
|
|
1197
|
+
) {
|
|
1095
1198
|
this._schemaDefinition = new SchemaDefinition();
|
|
1096
1199
|
Element.prototype['setParent'].call(this._schemaDefinition, this);
|
|
1097
1200
|
graphQLBuiltInTypesSpecifications.forEach((spec) => spec.checkOrAdd(this, undefined, true));
|
|
@@ -1141,10 +1244,6 @@ export class Schema {
|
|
|
1141
1244
|
}
|
|
1142
1245
|
}
|
|
1143
1246
|
|
|
1144
|
-
private forceSetCachedDocument(document: DocumentNode) {
|
|
1145
|
-
this.cachedDocument = document;
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
1247
|
isCoreSchema(): boolean {
|
|
1149
1248
|
return this.coreFeatures !== undefined;
|
|
1150
1249
|
}
|
|
@@ -1156,7 +1255,12 @@ export class Schema {
|
|
|
1156
1255
|
toAST(): DocumentNode {
|
|
1157
1256
|
if (!this.cachedDocument) {
|
|
1158
1257
|
// As we're not building the document from a file, having locations info might be more confusing that not.
|
|
1159
|
-
|
|
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;
|
|
1160
1264
|
}
|
|
1161
1265
|
return this.cachedDocument!;
|
|
1162
1266
|
}
|
|
@@ -1175,7 +1279,7 @@ export class Schema {
|
|
|
1175
1279
|
return this.apiSchema;
|
|
1176
1280
|
}
|
|
1177
1281
|
|
|
1178
|
-
private emptyASTDefinitionsForExtensionsWithoutDefinition():
|
|
1282
|
+
private emptyASTDefinitionsForExtensionsWithoutDefinition(): DefinitionNode[] {
|
|
1179
1283
|
const nodes = [];
|
|
1180
1284
|
if (this.schemaDefinition.hasExtensionElements() && !this.schemaDefinition.hasNonExtensionElements()) {
|
|
1181
1285
|
const node: SchemaDefinitionNode = { kind: Kind.SCHEMA_DEFINITION, operationTypes: [] };
|
|
@@ -1193,7 +1297,10 @@ export class Schema {
|
|
|
1193
1297
|
return nodes;
|
|
1194
1298
|
}
|
|
1195
1299
|
|
|
1196
|
-
toGraphQLJSSchema(): GraphQLSchema {
|
|
1300
|
+
toGraphQLJSSchema(config?: { includeDefer?: boolean, includeStream?: boolean }): GraphQLSchema {
|
|
1301
|
+
const includeDefer = config?.includeDefer ?? false;
|
|
1302
|
+
const includeStream = config?.includeStream ?? false;
|
|
1303
|
+
|
|
1197
1304
|
let ast = this.toAST();
|
|
1198
1305
|
|
|
1199
1306
|
// Note that AST generated by `this.toAST()` may not be fully graphQL valid because, in federation subgraphs, we accept
|
|
@@ -1201,6 +1308,12 @@ export class Schema {
|
|
|
1201
1308
|
// we need to "fix" that problem. For that, we add empty definitions for every element that has extensions without
|
|
1202
1309
|
// definitions (which is also what `fed1` was effectively doing).
|
|
1203
1310
|
const additionalNodes = this.emptyASTDefinitionsForExtensionsWithoutDefinition();
|
|
1311
|
+
if (includeDefer) {
|
|
1312
|
+
additionalNodes.push(this.deferDirective().toAST());
|
|
1313
|
+
}
|
|
1314
|
+
if (includeStream) {
|
|
1315
|
+
additionalNodes.push(this.streamDirective().toAST());
|
|
1316
|
+
}
|
|
1204
1317
|
if (additionalNodes.length > 0) {
|
|
1205
1318
|
ast = {
|
|
1206
1319
|
kind: Kind.DOCUMENT,
|
|
@@ -1432,8 +1545,10 @@ export class Schema {
|
|
|
1432
1545
|
}
|
|
1433
1546
|
|
|
1434
1547
|
invalidate() {
|
|
1548
|
+
if (this.isValidated) {
|
|
1549
|
+
this.blueprint.onInvalidation(this);
|
|
1550
|
+
}
|
|
1435
1551
|
this.isValidated = false;
|
|
1436
|
-
this.blueprint.onInvalidation(this);
|
|
1437
1552
|
}
|
|
1438
1553
|
|
|
1439
1554
|
validate() {
|
|
@@ -1477,28 +1592,35 @@ export class Schema {
|
|
|
1477
1592
|
}
|
|
1478
1593
|
|
|
1479
1594
|
private getBuiltInDirective<TApplicationArgs extends {[key: string]: any}>(
|
|
1480
|
-
schema: Schema,
|
|
1481
1595
|
name: string
|
|
1482
1596
|
): DirectiveDefinition<TApplicationArgs> {
|
|
1483
|
-
const directive =
|
|
1597
|
+
const directive = this.directive(name);
|
|
1484
1598
|
assert(directive, `The provided schema has not be built with the ${name} directive built-in`);
|
|
1485
1599
|
return directive as DirectiveDefinition<TApplicationArgs>;
|
|
1486
1600
|
}
|
|
1487
1601
|
|
|
1488
|
-
includeDirective(
|
|
1489
|
-
return this.getBuiltInDirective(
|
|
1602
|
+
includeDirective(): DirectiveDefinition<{if: boolean}> {
|
|
1603
|
+
return this.getBuiltInDirective('include');
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
skipDirective(): DirectiveDefinition<{if: boolean}> {
|
|
1607
|
+
return this.getBuiltInDirective('skip');
|
|
1490
1608
|
}
|
|
1491
1609
|
|
|
1492
|
-
|
|
1493
|
-
return this.getBuiltInDirective(
|
|
1610
|
+
deprecatedDirective(): DirectiveDefinition<{reason?: string}> {
|
|
1611
|
+
return this.getBuiltInDirective('deprecated');
|
|
1494
1612
|
}
|
|
1495
1613
|
|
|
1496
|
-
|
|
1497
|
-
return this.getBuiltInDirective(
|
|
1614
|
+
specifiedByDirective(): DirectiveDefinition<{url: string}> {
|
|
1615
|
+
return this.getBuiltInDirective('specifiedBy');
|
|
1498
1616
|
}
|
|
1499
1617
|
|
|
1500
|
-
|
|
1501
|
-
return this.getBuiltInDirective(
|
|
1618
|
+
deferDirective(): DirectiveDefinition<DeferDirectiveArgs> {
|
|
1619
|
+
return this.getBuiltInDirective('defer');
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
streamDirective(): DirectiveDefinition<StreamDirectiveArgs> {
|
|
1623
|
+
return this.getBuiltInDirective('stream');
|
|
1502
1624
|
}
|
|
1503
1625
|
|
|
1504
1626
|
/**
|
|
@@ -1570,7 +1692,7 @@ export class RootType extends BaseExtensionMember<SchemaDefinition> {
|
|
|
1570
1692
|
export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
1571
1693
|
readonly kind = 'SchemaDefinition' as const;
|
|
1572
1694
|
protected readonly _roots = new MapWithCachedArrays<SchemaRootKind, RootType>();
|
|
1573
|
-
protected
|
|
1695
|
+
protected _extensions: Extension<SchemaDefinition>[] | undefined;
|
|
1574
1696
|
public preserveEmptyDefinition: boolean = false;
|
|
1575
1697
|
|
|
1576
1698
|
roots(): readonly RootType[] {
|
|
@@ -1640,8 +1762,12 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1640
1762
|
return toSet;
|
|
1641
1763
|
}
|
|
1642
1764
|
|
|
1643
|
-
extensions():
|
|
1644
|
-
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;
|
|
1645
1771
|
}
|
|
1646
1772
|
|
|
1647
1773
|
newExtension(): Extension<SchemaDefinition> {
|
|
@@ -1651,23 +1777,27 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1651
1777
|
addExtension(extension: Extension<SchemaDefinition>): Extension<SchemaDefinition> {
|
|
1652
1778
|
this.checkUpdate();
|
|
1653
1779
|
// Let's be nice and not complaint if we add an extension already added.
|
|
1654
|
-
if (this.
|
|
1780
|
+
if (this.hasExtension(extension)) {
|
|
1655
1781
|
return extension;
|
|
1656
1782
|
}
|
|
1657
1783
|
assert(!extension.extendedElement, 'Cannot add extension to this schema: extension is already added to another schema');
|
|
1658
|
-
this._extensions
|
|
1784
|
+
if (this._extensions) {
|
|
1785
|
+
this._extensions.push(extension);
|
|
1786
|
+
} else {
|
|
1787
|
+
this._extensions = [extension];
|
|
1788
|
+
}
|
|
1659
1789
|
Extension.prototype['setExtendedElement'].call(extension, this);
|
|
1660
1790
|
this.onModification();
|
|
1661
1791
|
return extension;
|
|
1662
1792
|
}
|
|
1663
1793
|
|
|
1664
1794
|
hasExtensionElements(): boolean {
|
|
1665
|
-
return this._extensions
|
|
1795
|
+
return !!this._extensions;
|
|
1666
1796
|
}
|
|
1667
1797
|
|
|
1668
1798
|
hasNonExtensionElements(): boolean {
|
|
1669
1799
|
return this.preserveEmptyDefinition
|
|
1670
|
-
|| this.
|
|
1800
|
+
|| this.appliedDirectives.some((d) => d.ofExtension() === undefined)
|
|
1671
1801
|
|| this.roots().some((r) => r.ofExtension() === undefined);
|
|
1672
1802
|
}
|
|
1673
1803
|
|
|
@@ -1741,7 +1871,7 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1741
1871
|
// either to the main type definition _or_ to a single extension. In theory, a document could have `implements X`
|
|
1742
1872
|
// in both of those places (or on 2 distinct extensions). We don't preserve that level of detail, but this
|
|
1743
1873
|
// feels like a very minor limitation with little practical impact, and it avoids additional complexity.
|
|
1744
|
-
private
|
|
1874
|
+
private _interfaceImplementations: MapWithCachedArrays<string, InterfaceImplementation<T>> | undefined;
|
|
1745
1875
|
private readonly _fields: MapWithCachedArrays<string, FieldDefinition<T>> = new MapWithCachedArrays();
|
|
1746
1876
|
private _cachedNonBuiltInFields?: readonly FieldDefinition<T>[];
|
|
1747
1877
|
|
|
@@ -1760,11 +1890,11 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1760
1890
|
}
|
|
1761
1891
|
|
|
1762
1892
|
interfaceImplementations(): readonly InterfaceImplementation<T>[] {
|
|
1763
|
-
return this._interfaceImplementations
|
|
1893
|
+
return this._interfaceImplementations?.values() ?? [];
|
|
1764
1894
|
}
|
|
1765
1895
|
|
|
1766
1896
|
interfaceImplementation(type: string | InterfaceType): InterfaceImplementation<T> | undefined {
|
|
1767
|
-
return this._interfaceImplementations.get(typeof type === 'string' ? type : type.name);
|
|
1897
|
+
return this._interfaceImplementations ? this._interfaceImplementations.get(typeof type === 'string' ? type : type.name) : undefined;
|
|
1768
1898
|
}
|
|
1769
1899
|
|
|
1770
1900
|
interfaces(): readonly InterfaceType[] {
|
|
@@ -1772,7 +1902,7 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1772
1902
|
}
|
|
1773
1903
|
|
|
1774
1904
|
implementsInterface(type: string | InterfaceType): boolean {
|
|
1775
|
-
return this._interfaceImplementations
|
|
1905
|
+
return this._interfaceImplementations?.has(typeof type === 'string' ? type : type.name) ?? false;
|
|
1776
1906
|
}
|
|
1777
1907
|
|
|
1778
1908
|
addImplementedInterface(nameOrItfOrItfImpl: InterfaceImplementation<T> | InterfaceType | string): InterfaceImplementation<T> {
|
|
@@ -1796,8 +1926,11 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1796
1926
|
}
|
|
1797
1927
|
toAdd = new InterfaceImplementation<T>(itf);
|
|
1798
1928
|
}
|
|
1799
|
-
const existing = this._interfaceImplementations
|
|
1929
|
+
const existing = this._interfaceImplementations?.get(toAdd.interface.name);
|
|
1800
1930
|
if (!existing) {
|
|
1931
|
+
if (!this._interfaceImplementations) {
|
|
1932
|
+
this._interfaceImplementations = new MapWithCachedArrays();
|
|
1933
|
+
}
|
|
1801
1934
|
this._interfaceImplementations.set(toAdd.interface.name, toAdd);
|
|
1802
1935
|
addReferenceToType(this, toAdd.interface);
|
|
1803
1936
|
Element.prototype['setParent'].call(toAdd, this);
|
|
@@ -1884,12 +2017,12 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
|
|
|
1884
2017
|
}
|
|
1885
2018
|
|
|
1886
2019
|
private removeInterfaceImplementation(itf: InterfaceType) {
|
|
1887
|
-
this._interfaceImplementations
|
|
2020
|
+
this._interfaceImplementations?.delete(itf.name);
|
|
1888
2021
|
removeReferenceToType(this, itf);
|
|
1889
2022
|
}
|
|
1890
2023
|
|
|
1891
2024
|
protected removeTypeReference(type: NamedType) {
|
|
1892
|
-
this._interfaceImplementations
|
|
2025
|
+
this._interfaceImplementations?.delete(type.name);
|
|
1893
2026
|
}
|
|
1894
2027
|
|
|
1895
2028
|
protected removeInnerElements(): void {
|
|
@@ -1959,7 +2092,7 @@ export class InterfaceType extends FieldBasedType<InterfaceType, InterfaceTypeRe
|
|
|
1959
2092
|
readonly astDefinitionKind = Kind.INTERFACE_TYPE_DEFINITION;
|
|
1960
2093
|
|
|
1961
2094
|
allImplementations(): (ObjectType | InterfaceType)[] {
|
|
1962
|
-
return
|
|
2095
|
+
return this.referencers().filter(ref => ref.kind === 'ObjectType' || ref.kind === 'InterfaceType') as (ObjectType | InterfaceType)[];
|
|
1963
2096
|
}
|
|
1964
2097
|
|
|
1965
2098
|
possibleRuntimeTypes(): readonly ObjectType[] {
|
|
@@ -2159,15 +2292,12 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
|
|
|
2159
2292
|
}
|
|
2160
2293
|
|
|
2161
2294
|
private removeValueInternal(value: EnumValue) {
|
|
2162
|
-
|
|
2163
|
-
if (index >= 0) {
|
|
2164
|
-
this._values.splice(index, 1);
|
|
2165
|
-
}
|
|
2295
|
+
removeArrayElement(value, this._values);
|
|
2166
2296
|
}
|
|
2167
2297
|
|
|
2168
2298
|
protected removeInnerElements(): void {
|
|
2169
|
-
// Make a copy, since EnumValue.remove() will modify this._values.
|
|
2170
|
-
const values =
|
|
2299
|
+
// Make a copy (indirectly), since EnumValue.remove() will modify this._values.
|
|
2300
|
+
const values = this.values;
|
|
2171
2301
|
for (const value of values) {
|
|
2172
2302
|
value.remove();
|
|
2173
2303
|
}
|
|
@@ -2322,7 +2452,7 @@ export class NonNullType<T extends NullableType> extends BaseWrapperType<T> {
|
|
|
2322
2452
|
|
|
2323
2453
|
export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaElementWithType<OutputType, FieldDefinition<TParent>, TParent, never> {
|
|
2324
2454
|
readonly kind = 'FieldDefinition' as const;
|
|
2325
|
-
private
|
|
2455
|
+
private _args: MapWithCachedArrays<string, ArgumentDefinition<FieldDefinition<TParent>>> | undefined;
|
|
2326
2456
|
private _extension?: Extension<TParent>;
|
|
2327
2457
|
|
|
2328
2458
|
constructor(name: string, readonly isBuiltIn: boolean = false) {
|
|
@@ -2339,15 +2469,15 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2339
2469
|
}
|
|
2340
2470
|
|
|
2341
2471
|
hasArguments(): boolean {
|
|
2342
|
-
return this._args.size > 0;
|
|
2472
|
+
return !!this._args && this._args.size > 0;
|
|
2343
2473
|
}
|
|
2344
2474
|
|
|
2345
2475
|
arguments(): readonly ArgumentDefinition<FieldDefinition<TParent>>[] {
|
|
2346
|
-
return this._args
|
|
2476
|
+
return this._args?.values() ?? [];
|
|
2347
2477
|
}
|
|
2348
2478
|
|
|
2349
2479
|
argument(name: string): ArgumentDefinition<FieldDefinition<TParent>> | undefined {
|
|
2350
|
-
return this._args
|
|
2480
|
+
return this._args?.get(name);
|
|
2351
2481
|
}
|
|
2352
2482
|
|
|
2353
2483
|
addArgument(arg: ArgumentDefinition<FieldDefinition<TParent>>): ArgumentDefinition<FieldDefinition<TParent>>;
|
|
@@ -2377,6 +2507,9 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2377
2507
|
if (type && !isInputType(type)) {
|
|
2378
2508
|
throw ERRORS.INVALID_GRAPHQL.err(`Invalid output type ${type} for argument ${toAdd.name} of ${this}: arguments should be input types.`);
|
|
2379
2509
|
}
|
|
2510
|
+
if (!this._args) {
|
|
2511
|
+
this._args = new MapWithCachedArrays();
|
|
2512
|
+
}
|
|
2380
2513
|
this._args.set(toAdd.name, toAdd);
|
|
2381
2514
|
Element.prototype['setParent'].call(toAdd, this);
|
|
2382
2515
|
if (typeof nameOrArg === 'string') {
|
|
@@ -2396,10 +2529,8 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2396
2529
|
|
|
2397
2530
|
setOfExtension(extension: Extension<TParent> | undefined) {
|
|
2398
2531
|
this.checkUpdate();
|
|
2399
|
-
// It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
|
|
2400
|
-
// the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
|
|
2401
2532
|
assert(
|
|
2402
|
-
!extension || this._parent?.
|
|
2533
|
+
!extension || this._parent?.hasExtension(extension),
|
|
2403
2534
|
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`
|
|
2404
2535
|
);
|
|
2405
2536
|
this._extension = extension;
|
|
@@ -2415,7 +2546,9 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2415
2546
|
}
|
|
2416
2547
|
|
|
2417
2548
|
private removeArgumentInternal(name: string) {
|
|
2418
|
-
this._args
|
|
2549
|
+
if (this._args) {
|
|
2550
|
+
this._args.delete(name);
|
|
2551
|
+
}
|
|
2419
2552
|
}
|
|
2420
2553
|
|
|
2421
2554
|
// Only called through the prototype from FieldBasedType.removeInnerElements because we don't want to expose it.
|
|
@@ -2477,9 +2610,9 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2477
2610
|
}
|
|
2478
2611
|
|
|
2479
2612
|
toString(): string {
|
|
2480
|
-
const args = this.
|
|
2481
|
-
?
|
|
2482
|
-
:
|
|
2613
|
+
const args = this.hasArguments()
|
|
2614
|
+
? '(' + this.arguments().map(arg => arg.toString()).join(', ') + ')'
|
|
2615
|
+
: "";
|
|
2483
2616
|
return `${this.name}${args}: ${this.type}`;
|
|
2484
2617
|
}
|
|
2485
2618
|
}
|
|
@@ -2508,10 +2641,8 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
|
|
|
2508
2641
|
|
|
2509
2642
|
setOfExtension(extension: Extension<InputObjectType> | undefined) {
|
|
2510
2643
|
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
2644
|
assert(
|
|
2514
|
-
!extension || this._parent?.
|
|
2645
|
+
!extension || this._parent?.hasExtension(extension),
|
|
2515
2646
|
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`,
|
|
2516
2647
|
);
|
|
2517
2648
|
this._extension = extension;
|
|
@@ -2661,7 +2792,7 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2661
2792
|
setOfExtension(extension: Extension<EnumType> | undefined) {
|
|
2662
2793
|
this.checkUpdate();
|
|
2663
2794
|
assert(
|
|
2664
|
-
!extension || this._parent?.
|
|
2795
|
+
!extension || this._parent?.hasExtension(extension),
|
|
2665
2796
|
() => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of enum value parent type ${this.parent}`,
|
|
2666
2797
|
);
|
|
2667
2798
|
this._extension = extension;
|
|
@@ -2718,10 +2849,10 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2718
2849
|
export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}> extends NamedSchemaElement<DirectiveDefinition<TApplicationArgs>, Schema, Directive> {
|
|
2719
2850
|
readonly kind = 'DirectiveDefinition' as const;
|
|
2720
2851
|
|
|
2721
|
-
private
|
|
2852
|
+
private _args?: MapWithCachedArrays<string, ArgumentDefinition<DirectiveDefinition>>;
|
|
2722
2853
|
repeatable: boolean = false;
|
|
2723
2854
|
private readonly _locations: DirectiveLocation[] = [];
|
|
2724
|
-
private
|
|
2855
|
+
private _referencers?: Directive<SchemaElement<any, any>, TApplicationArgs>[];
|
|
2725
2856
|
|
|
2726
2857
|
constructor(name: string, readonly isBuiltIn: boolean = false) {
|
|
2727
2858
|
super(name);
|
|
@@ -2732,11 +2863,11 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2732
2863
|
}
|
|
2733
2864
|
|
|
2734
2865
|
arguments(): readonly ArgumentDefinition<DirectiveDefinition>[] {
|
|
2735
|
-
return this._args
|
|
2866
|
+
return this._args?.values() ?? [];
|
|
2736
2867
|
}
|
|
2737
2868
|
|
|
2738
2869
|
argument(name: string): ArgumentDefinition<DirectiveDefinition> | undefined {
|
|
2739
|
-
return this._args
|
|
2870
|
+
return this._args?.get(name);
|
|
2740
2871
|
}
|
|
2741
2872
|
|
|
2742
2873
|
addArgument(arg: ArgumentDefinition<DirectiveDefinition>): ArgumentDefinition<DirectiveDefinition>;
|
|
@@ -2754,6 +2885,9 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2754
2885
|
if (this.argument(toAdd.name)) {
|
|
2755
2886
|
throw ERRORS.INVALID_GRAPHQL.err(`Argument ${toAdd.name} already exists on field ${this.name}`);
|
|
2756
2887
|
}
|
|
2888
|
+
if (!this._args) {
|
|
2889
|
+
this._args = new MapWithCachedArrays();
|
|
2890
|
+
}
|
|
2757
2891
|
this._args.set(toAdd.name, toAdd);
|
|
2758
2892
|
Element.prototype['setParent'].call(toAdd, this);
|
|
2759
2893
|
if (typeof nameOrArg === 'string') {
|
|
@@ -2764,7 +2898,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2764
2898
|
}
|
|
2765
2899
|
|
|
2766
2900
|
private removeArgumentInternal(name: string) {
|
|
2767
|
-
this._args
|
|
2901
|
+
this._args?.delete(name);
|
|
2768
2902
|
}
|
|
2769
2903
|
|
|
2770
2904
|
get locations(): readonly DirectiveLocation[] {
|
|
@@ -2789,6 +2923,9 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2789
2923
|
return this.addLocations(...Object.values(DirectiveLocation));
|
|
2790
2924
|
}
|
|
2791
2925
|
|
|
2926
|
+
/**
|
|
2927
|
+
* Adds the subset of type system locations that correspond to type definitions.
|
|
2928
|
+
*/
|
|
2792
2929
|
addAllTypeLocations(): DirectiveDefinition {
|
|
2793
2930
|
return this.addLocations(
|
|
2794
2931
|
DirectiveLocation.SCALAR,
|
|
@@ -2803,11 +2940,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2803
2940
|
removeLocations(...locations: DirectiveLocation[]): DirectiveDefinition {
|
|
2804
2941
|
let modified = false;
|
|
2805
2942
|
for (const location of locations) {
|
|
2806
|
-
|
|
2807
|
-
if (index >= 0) {
|
|
2808
|
-
this._locations.splice(index, 1);
|
|
2809
|
-
modified = true;
|
|
2810
|
-
}
|
|
2943
|
+
modified ||= removeArrayElement(location, this._locations);
|
|
2811
2944
|
}
|
|
2812
2945
|
if (modified) {
|
|
2813
2946
|
this.onModification();
|
|
@@ -2815,17 +2948,33 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2815
2948
|
return this;
|
|
2816
2949
|
}
|
|
2817
2950
|
|
|
2951
|
+
hasExecutableLocations(): boolean {
|
|
2952
|
+
return this.locations.some((loc) => isExecutableDirectiveLocation(loc));
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
hasTypeSystemLocations(): boolean {
|
|
2956
|
+
return this.locations.some((loc) => isTypeSystemDirectiveLocation(loc));
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2818
2959
|
applications(): readonly Directive<SchemaElement<any, any>, TApplicationArgs>[] {
|
|
2819
|
-
return
|
|
2960
|
+
return this._referencers ?? [];
|
|
2820
2961
|
}
|
|
2821
2962
|
|
|
2822
2963
|
private addReferencer(referencer: Directive<SchemaElement<any, any>, TApplicationArgs>) {
|
|
2823
2964
|
assert(referencer, 'Referencer should exists');
|
|
2824
|
-
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
|
+
}
|
|
2825
2972
|
}
|
|
2826
2973
|
|
|
2827
2974
|
private removeReferencer(referencer: Directive<SchemaElement<any, any>, TApplicationArgs>) {
|
|
2828
|
-
this._referencers
|
|
2975
|
+
if (this._referencers) {
|
|
2976
|
+
removeArrayElement(referencer, this._referencers);
|
|
2977
|
+
}
|
|
2829
2978
|
}
|
|
2830
2979
|
|
|
2831
2980
|
protected removeTypeReference(type: NamedType) {
|
|
@@ -2846,7 +2995,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2846
2995
|
this.onModification();
|
|
2847
2996
|
// Remove this directive definition's children.
|
|
2848
2997
|
this.sourceAST = undefined;
|
|
2849
|
-
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");
|
|
2850
2999
|
for (const arg of this.arguments()) {
|
|
2851
3000
|
arg.remove();
|
|
2852
3001
|
}
|
|
@@ -2856,8 +3005,8 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2856
3005
|
// doesn't store a link to that definition. Instead, we fetch the definition
|
|
2857
3006
|
// from the schema when requested. So we don't have to do anything on the
|
|
2858
3007
|
// referencers other than clear them (and return the pre-cleared set).
|
|
2859
|
-
const toReturn =
|
|
2860
|
-
this._referencers
|
|
3008
|
+
const toReturn = this._referencers ?? [];
|
|
3009
|
+
this._referencers = undefined;
|
|
2861
3010
|
// Remove this directive definition from its parent schema.
|
|
2862
3011
|
Schema.prototype['removeDirectiveInternal'].call(this._parent, this);
|
|
2863
3012
|
this._parent = undefined;
|
|
@@ -2871,6 +3020,11 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2871
3020
|
this.remove().forEach(ref => ref.remove());
|
|
2872
3021
|
}
|
|
2873
3022
|
|
|
3023
|
+
toAST(): DirectiveDefinitionNode {
|
|
3024
|
+
const doc = parse(printDirectiveDefinition(this));
|
|
3025
|
+
return doc.definitions[0] as DirectiveDefinitionNode;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
2874
3028
|
toString(): string {
|
|
2875
3029
|
return `@${this.name}`;
|
|
2876
3030
|
}
|
|
@@ -2965,7 +3119,7 @@ export class Directive<
|
|
|
2965
3119
|
parent instanceof SchemaDefinition || parent instanceof BaseNamedType,
|
|
2966
3120
|
'Can only mark directive parts of extensions when directly apply to type or schema definition.'
|
|
2967
3121
|
);
|
|
2968
|
-
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}`);
|
|
2969
3123
|
}
|
|
2970
3124
|
this._extension = extension;
|
|
2971
3125
|
this.onModification();
|
|
@@ -3029,9 +3183,8 @@ export class Directive<
|
|
|
3029
3183
|
}
|
|
3030
3184
|
// Remove this directive application from its parent schema element.
|
|
3031
3185
|
const parentDirectives = this._parent.appliedDirectives as Directive<TParent>[];
|
|
3032
|
-
const
|
|
3033
|
-
assert(
|
|
3034
|
-
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`);
|
|
3035
3188
|
this._parent = undefined;
|
|
3036
3189
|
this._extension = undefined;
|
|
3037
3190
|
return true;
|
|
@@ -3051,7 +3204,7 @@ export function sameDirectiveApplication(application1: Directive<any, any>, appl
|
|
|
3051
3204
|
/**
|
|
3052
3205
|
* Checks whether the 2 provided "set" of directive applications are the same (same applications, regardless or order).
|
|
3053
3206
|
*/
|
|
3054
|
-
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 {
|
|
3055
3208
|
if (applications1.length !== applications2.length) {
|
|
3056
3209
|
return false;
|
|
3057
3210
|
}
|
|
@@ -3069,7 +3222,7 @@ export function sameDirectiveApplications(applications1: Directive<any, any>[],
|
|
|
3069
3222
|
*
|
|
3070
3223
|
* Sub-set here means that all of the applications in `maybeSubset` appears in `applications`.
|
|
3071
3224
|
*/
|
|
3072
|
-
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 {
|
|
3073
3226
|
if (maybeSubset.length > applications.length) {
|
|
3074
3227
|
return false;
|
|
3075
3228
|
}
|
|
@@ -3085,7 +3238,7 @@ export function isDirectiveApplicationsSubset(applications: Directive<any, any>[
|
|
|
3085
3238
|
/**
|
|
3086
3239
|
* Computes the difference between the set of directives applications `baseApplications` and the `toRemove` one.
|
|
3087
3240
|
*/
|
|
3088
|
-
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>[] {
|
|
3089
3242
|
return baseApplications.filter((application) => !toRemove.some((other) => sameDirectiveApplication(application, other)));
|
|
3090
3243
|
}
|
|
3091
3244
|
|
|
@@ -3319,6 +3472,34 @@ function *directivesToCopy(source: Schema, dest: Schema): Generator<DirectiveDef
|
|
|
3319
3472
|
yield* source.directives();
|
|
3320
3473
|
}
|
|
3321
3474
|
|
|
3475
|
+
/**
|
|
3476
|
+
* Creates, in the provided schema, a directive definition equivalent to the provided one.
|
|
3477
|
+
*
|
|
3478
|
+
* Note that this method assumes that:
|
|
3479
|
+
* - the provided schema does not already have a directive with the name of the definition to copy.
|
|
3480
|
+
* - if the copied definition has arguments, then the provided schema has existing types with
|
|
3481
|
+
* names matching any type used in copied definition.
|
|
3482
|
+
*/
|
|
3483
|
+
export function copyDirectiveDefinitionToSchema({
|
|
3484
|
+
definition,
|
|
3485
|
+
schema,
|
|
3486
|
+
copyDirectiveApplicationsInArguments = true,
|
|
3487
|
+
locationFilter,
|
|
3488
|
+
}: {
|
|
3489
|
+
definition: DirectiveDefinition,
|
|
3490
|
+
schema: Schema,
|
|
3491
|
+
copyDirectiveApplicationsInArguments: boolean,
|
|
3492
|
+
locationFilter?: (loc: DirectiveLocation) => boolean,
|
|
3493
|
+
}
|
|
3494
|
+
) {
|
|
3495
|
+
copyDirectiveDefinitionInner(
|
|
3496
|
+
definition,
|
|
3497
|
+
schema.addDirectiveDefinition(definition.name),
|
|
3498
|
+
copyDirectiveApplicationsInArguments,
|
|
3499
|
+
locationFilter,
|
|
3500
|
+
);
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3322
3503
|
function copy(source: Schema, dest: Schema) {
|
|
3323
3504
|
// We shallow copy types first so any future reference to any of them can be dereferenced.
|
|
3324
3505
|
for (const type of typesToCopy(source, dest)) {
|
|
@@ -3471,22 +3652,41 @@ function copyWrapperTypeOrTypeRef(source: Type | undefined, destParent: Schema):
|
|
|
3471
3652
|
}
|
|
3472
3653
|
}
|
|
3473
3654
|
|
|
3474
|
-
function copyArgumentDefinitionInner<P extends FieldDefinition<any> | DirectiveDefinition>(
|
|
3655
|
+
function copyArgumentDefinitionInner<P extends FieldDefinition<any> | DirectiveDefinition>(
|
|
3656
|
+
source: ArgumentDefinition<P>,
|
|
3657
|
+
dest: ArgumentDefinition<P>,
|
|
3658
|
+
copyDirectiveApplications: boolean = true,
|
|
3659
|
+
) {
|
|
3475
3660
|
const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()) as InputType;
|
|
3476
3661
|
dest.type = type;
|
|
3477
3662
|
dest.defaultValue = source.defaultValue;
|
|
3478
|
-
|
|
3663
|
+
if (copyDirectiveApplications) {
|
|
3664
|
+
copyAppliedDirectives(source, dest);
|
|
3665
|
+
}
|
|
3479
3666
|
dest.description = source.description;
|
|
3480
3667
|
dest.sourceAST = source.sourceAST;
|
|
3481
3668
|
}
|
|
3482
3669
|
|
|
3483
|
-
function copyDirectiveDefinitionInner(
|
|
3670
|
+
function copyDirectiveDefinitionInner(
|
|
3671
|
+
source: DirectiveDefinition,
|
|
3672
|
+
dest: DirectiveDefinition,
|
|
3673
|
+
copyDirectiveApplicationsInArguments: boolean = true,
|
|
3674
|
+
locationFilter?: (loc: DirectiveLocation) => boolean,
|
|
3675
|
+
) {
|
|
3676
|
+
let locations = source.locations;
|
|
3677
|
+
if (locationFilter) {
|
|
3678
|
+
locations = locations.filter((loc) => locationFilter(loc));
|
|
3679
|
+
}
|
|
3680
|
+
if (locations.length === 0) {
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3484
3684
|
for (const arg of source.arguments()) {
|
|
3485
3685
|
const type = copyWrapperTypeOrTypeRef(arg.type, dest.schema());
|
|
3486
|
-
copyArgumentDefinitionInner(arg, dest.addArgument(arg.name, type as InputType));
|
|
3686
|
+
copyArgumentDefinitionInner(arg, dest.addArgument(arg.name, type as InputType), copyDirectiveApplicationsInArguments);
|
|
3487
3687
|
}
|
|
3488
3688
|
dest.repeatable = source.repeatable;
|
|
3489
|
-
dest.addLocations(...
|
|
3689
|
+
dest.addLocations(...locations);
|
|
3490
3690
|
dest.sourceAST = source.sourceAST;
|
|
3491
3691
|
dest.description = source.description;
|
|
3492
3692
|
}
|