@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +5 -4
  2. package/dist/buildSchema.d.ts.map +1 -1
  3. package/dist/buildSchema.js +14 -4
  4. package/dist/buildSchema.js.map +1 -1
  5. package/dist/coreSpec.d.ts +1 -4
  6. package/dist/coreSpec.d.ts.map +1 -1
  7. package/dist/coreSpec.js +1 -5
  8. package/dist/coreSpec.js.map +1 -1
  9. package/dist/definitions.d.ts +66 -34
  10. package/dist/definitions.d.ts.map +1 -1
  11. package/dist/definitions.js +309 -165
  12. package/dist/definitions.js.map +1 -1
  13. package/dist/error.d.ts +5 -0
  14. package/dist/error.d.ts.map +1 -1
  15. package/dist/error.js +47 -1
  16. package/dist/error.js.map +1 -1
  17. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  18. package/dist/extractSubgraphsFromSupergraph.js +135 -5
  19. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  20. package/dist/federation.d.ts +3 -2
  21. package/dist/federation.d.ts.map +1 -1
  22. package/dist/federation.js +12 -7
  23. package/dist/federation.js.map +1 -1
  24. package/dist/inaccessibleSpec.js +1 -2
  25. package/dist/inaccessibleSpec.js.map +1 -1
  26. package/dist/operations.d.ts +44 -20
  27. package/dist/operations.d.ts.map +1 -1
  28. package/dist/operations.js +287 -27
  29. package/dist/operations.js.map +1 -1
  30. package/dist/print.d.ts +1 -1
  31. package/dist/print.d.ts.map +1 -1
  32. package/dist/print.js +1 -1
  33. package/dist/print.js.map +1 -1
  34. package/dist/schemaUpgrader.d.ts.map +1 -1
  35. package/dist/schemaUpgrader.js +6 -6
  36. package/dist/schemaUpgrader.js.map +1 -1
  37. package/dist/supergraphs.d.ts +1 -3
  38. package/dist/supergraphs.d.ts.map +1 -1
  39. package/dist/supergraphs.js +9 -22
  40. package/dist/supergraphs.js.map +1 -1
  41. package/dist/utils.d.ts +10 -1
  42. package/dist/utils.d.ts.map +1 -1
  43. package/dist/utils.js +40 -1
  44. package/dist/utils.js.map +1 -1
  45. package/package.json +3 -4
  46. package/src/__tests__/coreSpec.test.ts +1 -1
  47. package/src/__tests__/definitions.test.ts +27 -0
  48. package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +9 -5
  49. package/src/__tests__/operations.test.ts +36 -0
  50. package/src/__tests__/removeInaccessibleElements.test.ts +1 -3
  51. package/src/__tests__/schemaUpgrader.test.ts +0 -1
  52. package/src/__tests__/subgraphValidation.test.ts +1 -2
  53. package/src/buildSchema.ts +20 -7
  54. package/src/coreSpec.ts +2 -7
  55. package/src/definitions.ts +355 -155
  56. package/src/error.ts +62 -0
  57. package/src/extractSubgraphsFromSupergraph.ts +198 -7
  58. package/src/federation.ts +11 -4
  59. package/src/inaccessibleSpec.ts +2 -5
  60. package/src/operations.ts +428 -40
  61. package/src/print.ts +3 -3
  62. package/src/schemaUpgrader.ts +7 -6
  63. package/src/supergraphs.ts +16 -25
  64. package/src/utils.ts +49 -0
  65. package/tsconfig.test.tsbuildinfo +1 -1
  66. package/tsconfig.tsbuildinfo +1 -1
@@ -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, setValues } from "./utils";
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
- err(validationErrorCode, {
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
- err(apiSchemaValidationErrorCode, {
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
- public readonly appliedDirectives: Directive<T>[] = [];
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.appliedDirectives.push(toAdd);
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 readonly _appliedDirectives: Directive<TOwnType>[] = [];
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._appliedDirectives.filter(d => d.name == directiveName) as Directive<TOwnType, TApplicationArgs>[];
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 (asFirstDirective) {
550
- this._appliedDirectives.unshift(toAdd);
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.push(toAdd);
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 readonly _referencers: Set<TReferencer> = new Set();
635
- protected readonly _extensions: Set<Extension<TOwnType>> = new Set();
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.add(referencer);
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.delete(referencer);
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(): ReadonlySet<Extension<TOwnType>> {
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._extensions.has(extension)) {
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.add(extension);
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.size === 0) {
733
+ if (!this._extensions) {
681
734
  return;
682
735
  }
683
736
 
684
- this._extensions.clear();
685
- for (const directive of this._appliedDirectives) {
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.size > 0;
749
+ return !!this._extensions;
697
750
  }
698
751
 
699
752
  hasNonExtensionElements(): boolean {
700
753
  return this.preserveEmptyDefinition
701
- || this._appliedDirectives.some(d => d.ofExtension() === undefined)
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 = setValues(this._referencers).map(r => {
800
+ const toReturn = this._referencers?.map(r => {
748
801
  SchemaElement.prototype['removeTypeReferenceInternal'].call(r, this);
749
802
  return r;
750
- });
751
- this._referencers.clear();
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 setValues(this._referencers);
832
+ return this._referencers ?? [];
780
833
  }
781
834
 
782
835
  isReferenced(): boolean {
783
- return this._referencers.size > 0;
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
- // See similar comment on FieldDefinition.setOfExtension for why we have to cast.
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(readonly blueprint: SchemaBlueprint = defaultSchemaBlueprint) {
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
- this.forceSetCachedDocument(parse(printSchema(this), { noLocation: true }));
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(): TypeSystemDefinitionNode[] {
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 = schema.directive(name);
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(schema: Schema): DirectiveDefinition<{if: boolean}> {
1489
- return this.getBuiltInDirective(schema, 'include');
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
- skipDirective(schema: Schema): DirectiveDefinition<{if: boolean}> {
1493
- return this.getBuiltInDirective(schema, 'skip');
1610
+ deprecatedDirective(): DirectiveDefinition<{reason?: string}> {
1611
+ return this.getBuiltInDirective('deprecated');
1494
1612
  }
1495
1613
 
1496
- deprecatedDirective(schema: Schema): DirectiveDefinition<{reason?: string}> {
1497
- return this.getBuiltInDirective(schema, 'deprecated');
1614
+ specifiedByDirective(): DirectiveDefinition<{url: string}> {
1615
+ return this.getBuiltInDirective('specifiedBy');
1498
1616
  }
1499
1617
 
1500
- specifiedByDirective(schema: Schema): DirectiveDefinition<{url: string}> {
1501
- return this.getBuiltInDirective(schema, 'specifiedBy');
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 readonly _extensions = new Set<Extension<SchemaDefinition>>();
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(): ReadonlySet<Extension<SchemaDefinition>> {
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._extensions.has(extension)) {
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.add(extension);
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.size > 0;
1795
+ return !!this._extensions;
1666
1796
  }
1667
1797
 
1668
1798
  hasNonExtensionElements(): boolean {
1669
1799
  return this.preserveEmptyDefinition
1670
- || this._appliedDirectives.some((d) => d.ofExtension() === undefined)
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 readonly _interfaceImplementations: MapWithCachedArrays<string, InterfaceImplementation<T>> = new MapWithCachedArrays();
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.values();
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.has(typeof type === 'string' ? type : type.name);
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.get(toAdd.interface.name);
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.delete(itf.name);
2020
+ this._interfaceImplementations?.delete(itf.name);
1888
2021
  removeReferenceToType(this, itf);
1889
2022
  }
1890
2023
 
1891
2024
  protected removeTypeReference(type: NamedType) {
1892
- this._interfaceImplementations.delete(type.name);
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 setValues(this._referencers).filter(ref => ref.kind === 'ObjectType' || ref.kind === 'InterfaceType') as (ObjectType | InterfaceType)[];
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
- const index = this._values.indexOf(value);
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 = Array.from(this._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 readonly _args: MapWithCachedArrays<string, ArgumentDefinition<FieldDefinition<TParent>>> = new MapWithCachedArrays();
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.values();
2476
+ return this._args?.values() ?? [];
2347
2477
  }
2348
2478
 
2349
2479
  argument(name: string): ArgumentDefinition<FieldDefinition<TParent>> | undefined {
2350
- return this._args.get(name);
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?.extensions().has(extension as any),
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.delete(name);
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._args.size == 0
2481
- ? ""
2482
- : '(' + this.arguments().map(arg => arg.toString()).join(', ') + ')';
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?.extensions().has(extension as any),
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?.extensions().has(extension as any),
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 readonly _args: MapWithCachedArrays<string, ArgumentDefinition<DirectiveDefinition>> = new MapWithCachedArrays();
2852
+ private _args?: MapWithCachedArrays<string, ArgumentDefinition<DirectiveDefinition>>;
2722
2853
  repeatable: boolean = false;
2723
2854
  private readonly _locations: DirectiveLocation[] = [];
2724
- private readonly _referencers: Set<Directive<SchemaElement<any, any>, TApplicationArgs>> = new Set();
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.values();
2866
+ return this._args?.values() ?? [];
2736
2867
  }
2737
2868
 
2738
2869
  argument(name: string): ArgumentDefinition<DirectiveDefinition> | undefined {
2739
- return this._args.get(name);
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.delete(name);
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
- const index = this._locations.indexOf(location);
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 setValues(this._referencers);
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.add(referencer);
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.delete(referencer);
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 = setValues(this._referencers);
2860
- this._referencers.clear();
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.extensions().has(extension), () => `Cannot mark directive ${this.name} as part of the provided extension: it is not an extension of parent ${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 index = parentDirectives.indexOf(this);
3033
- assert(index >= 0, () => `Directive ${this} lists ${this._parent} as parent, but that parent doesn't list it as applied directive`);
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>(source: ArgumentDefinition<P>, dest: ArgumentDefinition<P>) {
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
- copyAppliedDirectives(source, dest);
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(source: DirectiveDefinition, dest: DirectiveDefinition) {
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(...source.locations);
3689
+ dest.addLocations(...locations);
3490
3690
  dest.sourceAST = source.sourceAST;
3491
3691
  dest.description = source.description;
3492
3692
  }