@apollo/federation-internals 2.8.2 → 2.8.3-beta.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.
@@ -59,6 +59,8 @@ import { coreFeatureDefinitionIfKnown } from "./knownCoreFeatures";
59
59
  const validationErrorCode = 'GraphQLValidationFailed';
60
60
  const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL schema.';
61
61
 
62
+ const EMPTY_SET = new Set<never>();
63
+
62
64
  export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: string = DEFAULT_VALIDATION_ERROR_MESSAGE) =>
63
65
  aggregateError(validationErrorCode, message, causes);
64
66
 
@@ -660,7 +662,7 @@ export abstract class NamedSchemaElement<TOwnType extends NamedSchemaElement<TOw
660
662
  }
661
663
 
662
664
  abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSchemaElement<TOwnType, Schema, TReferencer>> extends NamedSchemaElement<TOwnType, Schema, TReferencer> {
663
- protected _referencers?: TReferencer[];
665
+ protected _referencers?: Set<TReferencer>;
664
666
  protected _extensions?: Extension<TOwnType>[];
665
667
  public preserveEmptyDefinition: boolean = false;
666
668
 
@@ -669,19 +671,12 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
669
671
  }
670
672
 
671
673
  private addReferencer(referencer: TReferencer) {
672
- if (this._referencers) {
673
- if (!this._referencers.includes(referencer)) {
674
- this._referencers.push(referencer);
675
- }
676
- } else {
677
- this._referencers = [ referencer ];
678
- }
674
+ this._referencers ??= new Set();
675
+ this._referencers.add(referencer);
679
676
  }
680
677
 
681
678
  private removeReferencer(referencer: TReferencer) {
682
- if (this._referencers) {
683
- removeArrayElement(referencer, this._referencers);
684
- }
679
+ this._referencers?.delete(referencer)
685
680
  }
686
681
 
687
682
  get coordinate(): string {
@@ -789,10 +784,11 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
789
784
  this.removeAppliedDirectives();
790
785
  this.removeInnerElements();
791
786
  // Remove this type's references.
792
- const toReturn = this._referencers?.map(r => {
787
+ const toReturn: TReferencer[] = [];
788
+ this._referencers?.forEach(r => {
793
789
  SchemaElement.prototype['removeTypeReferenceInternal'].call(r, this);
794
- return r;
795
- }) ?? [];
790
+ toReturn.push(r);
791
+ });
796
792
  this._referencers = undefined;
797
793
  // Remove this type from its parent schema.
798
794
  Schema.prototype['removeTypeInternal'].call(this._parent, this);
@@ -820,8 +816,8 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
820
816
 
821
817
  protected abstract removeReferenceRecursive(ref: TReferencer): void;
822
818
 
823
- referencers(): readonly TReferencer[] {
824
- return this._referencers ?? [];
819
+ referencers(): ReadonlySet<TReferencer> {
820
+ return this._referencers ?? EMPTY_SET;
825
821
  }
826
822
 
827
823
  isReferenced(): boolean {
@@ -1261,7 +1257,7 @@ export class Schema {
1261
1257
  if (!this.apiSchema) {
1262
1258
  this.validate();
1263
1259
 
1264
- const apiSchema = this.clone();
1260
+ const apiSchema = this.clone(undefined, false);
1265
1261
 
1266
1262
  // As we compute the API schema of a supergraph, we want to ignore explicit definitions of `@defer` and `@stream` because
1267
1263
  // those correspond to the merging of potential definitions from the subgraphs, but whether the supergraph API schema
@@ -1611,9 +1607,9 @@ export class Schema {
1611
1607
  this.isValidated = true;
1612
1608
  }
1613
1609
 
1614
- clone(builtIns?: SchemaBlueprint): Schema {
1610
+ clone(builtIns?: SchemaBlueprint, cloneJoinDirectives: boolean = true): Schema {
1615
1611
  const cloned = new Schema(builtIns ?? this.blueprint);
1616
- copy(this, cloned);
1612
+ copy(this, cloned, cloneJoinDirectives);
1617
1613
  if (this.isValidated) {
1618
1614
  cloned.assumeValid();
1619
1615
  }
@@ -2124,7 +2120,13 @@ export class ObjectType extends FieldBasedType<ObjectType, ObjectTypeReferencer>
2124
2120
  }
2125
2121
 
2126
2122
  unionsWhereMember(): readonly UnionType[] {
2127
- return this._referencers?.filter<UnionType>((r): r is UnionType => r instanceof BaseNamedType && isUnionType(r)) ?? [];
2123
+ const unions: UnionType[] = [];
2124
+ this._referencers?.forEach((r) => {
2125
+ if (r instanceof BaseNamedType && isUnionType(r)) {
2126
+ unions.push(r);
2127
+ }
2128
+ });
2129
+ return unions;
2128
2130
  }
2129
2131
  }
2130
2132
 
@@ -2133,7 +2135,13 @@ export class InterfaceType extends FieldBasedType<InterfaceType, InterfaceTypeRe
2133
2135
  readonly astDefinitionKind = Kind.INTERFACE_TYPE_DEFINITION;
2134
2136
 
2135
2137
  allImplementations(): (ObjectType | InterfaceType)[] {
2136
- return this.referencers().filter(ref => ref.kind === 'ObjectType' || ref.kind === 'InterfaceType') as (ObjectType | InterfaceType)[];
2138
+ const implementations: (ObjectType | InterfaceType)[] = [];
2139
+ this.referencers().forEach(ref => {
2140
+ if (ref.kind === 'ObjectType' || ref.kind === 'InterfaceType') {
2141
+ implementations.push(ref);
2142
+ }
2143
+ });
2144
+ return implementations;
2137
2145
  }
2138
2146
 
2139
2147
  possibleRuntimeTypes(): readonly ObjectType[] {
@@ -2895,7 +2903,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
2895
2903
  private _args?: MapWithCachedArrays<string, ArgumentDefinition<DirectiveDefinition>>;
2896
2904
  repeatable: boolean = false;
2897
2905
  private readonly _locations: DirectiveLocation[] = [];
2898
- private _referencers?: Directive<SchemaElement<any, any>, TApplicationArgs>[];
2906
+ private _referencers?: Set<Directive<SchemaElement<any, any>, TApplicationArgs>>;
2899
2907
 
2900
2908
  constructor(name: string, readonly isBuiltIn: boolean = false) {
2901
2909
  super(name);
@@ -2999,25 +3007,19 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
2999
3007
  return this.locations.some((loc) => isTypeSystemDirectiveLocation(loc));
3000
3008
  }
3001
3009
 
3002
- applications(): readonly Directive<SchemaElement<any, any>, TApplicationArgs>[] {
3003
- return this._referencers ?? [];
3010
+ applications(): ReadonlySet<Directive<SchemaElement<any, any>, TApplicationArgs>> {
3011
+ this._referencers ??= new Set();
3012
+ return this._referencers;
3004
3013
  }
3005
3014
 
3006
3015
  private addReferencer(referencer: Directive<SchemaElement<any, any>, TApplicationArgs>) {
3007
3016
  assert(referencer, 'Referencer should exists');
3008
- if (this._referencers) {
3009
- if (!this._referencers.includes(referencer)) {
3010
- this._referencers.push(referencer);
3011
- }
3012
- } else {
3013
- this._referencers = [ referencer ];
3014
- }
3017
+ this._referencers ??= new Set();
3018
+ this._referencers.add(referencer);
3015
3019
  }
3016
3020
 
3017
3021
  private removeReferencer(referencer: Directive<SchemaElement<any, any>, TApplicationArgs>) {
3018
- if (this._referencers) {
3019
- removeArrayElement(referencer, this._referencers);
3020
- }
3022
+ this._referencers?.delete(referencer);
3021
3023
  }
3022
3024
 
3023
3025
  protected removeTypeReference(type: NamedType) {
@@ -3048,7 +3050,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
3048
3050
  // doesn't store a link to that definition. Instead, we fetch the definition
3049
3051
  // from the schema when requested. So we don't have to do anything on the
3050
3052
  // referencers other than clear them (and return the pre-cleared set).
3051
- const toReturn = this._referencers ?? [];
3053
+ const toReturn = Array.from(this._referencers ?? []);
3052
3054
  this._referencers = undefined;
3053
3055
  // Remove this directive definition from its parent schema.
3054
3056
  Schema.prototype['removeDirectiveInternal'].call(this._parent, this);
@@ -3598,7 +3600,7 @@ export function copyDirectiveDefinitionToSchema({
3598
3600
  );
3599
3601
  }
3600
3602
 
3601
- function copy(source: Schema, dest: Schema) {
3603
+ function copy(source: Schema, dest: Schema, cloneJoinDirectives: boolean) {
3602
3604
  // We shallow copy types first so any future reference to any of them can be dereferenced.
3603
3605
  for (const type of typesToCopy(source, dest)) {
3604
3606
  dest.addType(newNamedType(type.kind, type.name));
@@ -3615,7 +3617,7 @@ function copy(source: Schema, dest: Schema) {
3615
3617
 
3616
3618
  copySchemaDefinitionInner(source.schemaDefinition, dest.schemaDefinition);
3617
3619
  for (const type of typesToCopy(source, dest)) {
3618
- copyNamedTypeInner(type, dest.type(type.name)!);
3620
+ copyNamedTypeInner(type, dest.type(type.name)!, cloneJoinDirectives);
3619
3621
  }
3620
3622
  }
3621
3623
 
@@ -3655,7 +3657,7 @@ function copySchemaDefinitionInner(source: SchemaDefinition, dest: SchemaDefinit
3655
3657
  dest.sourceAST = source.sourceAST;
3656
3658
  }
3657
3659
 
3658
- function copyNamedTypeInner(source: NamedType, dest: NamedType) {
3660
+ function copyNamedTypeInner(source: NamedType, dest: NamedType, cloneJoinDirectives: boolean) {
3659
3661
  dest.preserveEmptyDefinition = source.preserveEmptyDefinition;
3660
3662
  const extensionsMap = copyExtensions(source, dest);
3661
3663
  // Same as copyAppliedDirectives, but as the directive applies to the type, we need to remember if the application
@@ -3672,7 +3674,7 @@ function copyNamedTypeInner(source: NamedType, dest: NamedType) {
3672
3674
  for (const sourceField of source.fields()) {
3673
3675
  const destField = destFieldBasedType.addField(new FieldDefinition(sourceField.name));
3674
3676
  copyOfExtension(extensionsMap, sourceField, destField);
3675
- copyFieldDefinitionInner(sourceField, destField);
3677
+ copyFieldDefinitionInner(sourceField, destField, cloneJoinDirectives);
3676
3678
  }
3677
3679
  for (const sourceImpl of source.interfaceImplementations()) {
3678
3680
  const destImpl = destFieldBasedType.addImplementedInterface(sourceImpl.interface.name);
@@ -3692,7 +3694,7 @@ function copyNamedTypeInner(source: NamedType, dest: NamedType) {
3692
3694
  const destValue = destEnumType.addValue(sourceValue.name);
3693
3695
  destValue.description = sourceValue.description;
3694
3696
  copyOfExtension(extensionsMap, sourceValue, destValue);
3695
- copyAppliedDirectives(sourceValue, destValue);
3697
+ copyAppliedDirectives(sourceValue, destValue, cloneJoinDirectives);
3696
3698
  }
3697
3699
  break
3698
3700
  case 'InputObjectType':
@@ -3700,13 +3702,13 @@ function copyNamedTypeInner(source: NamedType, dest: NamedType) {
3700
3702
  for (const sourceField of source.fields()) {
3701
3703
  const destField = destInputType.addField(new InputFieldDefinition(sourceField.name));
3702
3704
  copyOfExtension(extensionsMap, sourceField, destField);
3703
- copyInputFieldDefinitionInner(sourceField, destField);
3705
+ copyInputFieldDefinitionInner(sourceField, destField, cloneJoinDirectives);
3704
3706
  }
3705
3707
  }
3706
3708
  }
3707
3709
 
3708
- function copyAppliedDirectives(source: SchemaElement<any, any>, dest: SchemaElement<any, any>) {
3709
- source.appliedDirectives.forEach((d) => copyAppliedDirective(d, dest));
3710
+ function copyAppliedDirectives(source: SchemaElement<any, any>, dest: SchemaElement<any, any>, cloneJoinDirectives: boolean) {
3711
+ source.appliedDirectives.filter(d => cloneJoinDirectives || !d.name.startsWith('join__')).forEach((d) => copyAppliedDirective(d, dest));
3710
3712
  }
3711
3713
 
3712
3714
  function copyAppliedDirective(source: Directive<any, any>, dest: SchemaElement<any, any>): Directive<any, any> {
@@ -3715,23 +3717,27 @@ function copyAppliedDirective(source: Directive<any, any>, dest: SchemaElement<a
3715
3717
  return res;
3716
3718
  }
3717
3719
 
3718
- function copyFieldDefinitionInner<P extends ObjectType | InterfaceType>(source: FieldDefinition<P>, dest: FieldDefinition<P>) {
3720
+ function copyFieldDefinitionInner<P extends ObjectType | InterfaceType>(source: FieldDefinition<P>, dest: FieldDefinition<P>, cloneJoinDirectives: boolean) {
3719
3721
  const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()) as OutputType;
3720
3722
  dest.type = type;
3721
3723
  for (const arg of source.arguments()) {
3722
3724
  const argType = copyWrapperTypeOrTypeRef(arg.type, dest.schema());
3723
- copyArgumentDefinitionInner(arg, dest.addArgument(arg.name, argType as InputType));
3725
+ copyArgumentDefinitionInner({
3726
+ source: arg,
3727
+ dest: dest.addArgument(arg.name, argType as InputType),
3728
+ cloneJoinDirectives,
3729
+ });
3724
3730
  }
3725
- copyAppliedDirectives(source, dest);
3731
+ copyAppliedDirectives(source, dest, cloneJoinDirectives);
3726
3732
  dest.description = source.description;
3727
3733
  dest.sourceAST = source.sourceAST;
3728
3734
  }
3729
3735
 
3730
- function copyInputFieldDefinitionInner(source: InputFieldDefinition, dest: InputFieldDefinition) {
3736
+ function copyInputFieldDefinitionInner(source: InputFieldDefinition, dest: InputFieldDefinition, cloneJoinDirectives: boolean) {
3731
3737
  const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()) as InputType;
3732
3738
  dest.type = type;
3733
3739
  dest.defaultValue = source.defaultValue;
3734
- copyAppliedDirectives(source, dest);
3740
+ copyAppliedDirectives(source, dest, cloneJoinDirectives);
3735
3741
  dest.description = source.description;
3736
3742
  dest.sourceAST = source.sourceAST;
3737
3743
  }
@@ -3750,16 +3756,22 @@ function copyWrapperTypeOrTypeRef(source: Type | undefined, destParent: Schema):
3750
3756
  }
3751
3757
  }
3752
3758
 
3753
- function copyArgumentDefinitionInner<P extends FieldDefinition<any> | DirectiveDefinition>(
3759
+ function copyArgumentDefinitionInner<P extends FieldDefinition<any> | DirectiveDefinition>({
3760
+ source,
3761
+ dest,
3762
+ copyDirectiveApplications = true,
3763
+ cloneJoinDirectives,
3764
+ }: {
3754
3765
  source: ArgumentDefinition<P>,
3755
3766
  dest: ArgumentDefinition<P>,
3756
- copyDirectiveApplications: boolean = true,
3757
- ) {
3767
+ copyDirectiveApplications?: boolean,
3768
+ cloneJoinDirectives: boolean,
3769
+ }) {
3758
3770
  const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()) as InputType;
3759
3771
  dest.type = type;
3760
3772
  dest.defaultValue = source.defaultValue;
3761
3773
  if (copyDirectiveApplications) {
3762
- copyAppliedDirectives(source, dest);
3774
+ copyAppliedDirectives(source, dest, cloneJoinDirectives);
3763
3775
  }
3764
3776
  dest.description = source.description;
3765
3777
  dest.sourceAST = source.sourceAST;
@@ -3781,7 +3793,12 @@ function copyDirectiveDefinitionInner(
3781
3793
 
3782
3794
  for (const arg of source.arguments()) {
3783
3795
  const type = copyWrapperTypeOrTypeRef(arg.type, dest.schema());
3784
- copyArgumentDefinitionInner(arg, dest.addArgument(arg.name, type as InputType), copyDirectiveApplicationsInArguments);
3796
+ copyArgumentDefinitionInner({
3797
+ source: arg,
3798
+ dest: dest.addArgument(arg.name, type as InputType),
3799
+ copyDirectiveApplications: copyDirectiveApplicationsInArguments,
3800
+ cloneJoinDirectives: true,
3801
+ });
3785
3802
  }
3786
3803
  dest.repeatable = source.repeatable;
3787
3804
  dest.addLocations(...locations);
package/src/federation.ts CHANGED
@@ -1195,7 +1195,7 @@ export class FederationMetadata {
1195
1195
  ): Post20FederationDirectiveDefinition<TApplicationArgs> {
1196
1196
  return this.getFederationDirective<TApplicationArgs>(name) ?? {
1197
1197
  name,
1198
- applications: () => new Array<Directive<any, TApplicationArgs>>(),
1198
+ applications: () => new Set<Directive<any, TApplicationArgs>>(),
1199
1199
  };
1200
1200
  }
1201
1201
 
@@ -1391,7 +1391,7 @@ export class FederationMetadata {
1391
1391
 
1392
1392
  export type FederationDirectiveNotDefinedInSchema<TApplicationArgs extends {[key: string]: any}> = {
1393
1393
  name: string,
1394
- applications: () => readonly Directive<any, TApplicationArgs>[],
1394
+ applications: () => ReadonlySet<Directive<any, TApplicationArgs>>,
1395
1395
  }
1396
1396
 
1397
1397
  export type Post20FederationDirectiveDefinition<TApplicationArgs extends {[key: string]: any}> =
@@ -2040,7 +2040,7 @@ function completeFed1SubgraphSchema(schema: Schema): GraphQLError[] {
2040
2040
  // definition to re-add the "correct" version, we'd have to re-attach existing applications (doable but not
2041
2041
  // done). This assert is so we notice it quickly if that ever happens (again, unlikely, because fed1 schema
2042
2042
  // is a backward compatibility thing and there is no reason to expand that too much in the future).
2043
- assert(directive.applications().length === 0, `${directive} shouldn't have had validation at that places`);
2043
+ assert(directive.applications().size === 0, `${directive} shouldn't have had validation at that places`);
2044
2044
 
2045
2045
  // The patterns we recognize and "correct" (by essentially ignoring the definition)
2046
2046
  // are:
@@ -233,7 +233,7 @@ export function upgradeSubgraphsIfNecessary(inputs: Subgraphs): UpgradeResult {
233
233
  for (const subgraph of inputs.values()) {
234
234
  if (subgraph.isFed2Subgraph()) {
235
235
  subgraphs.add(subgraph);
236
- if (subgraph.metadata().interfaceObjectDirective().applications().length > 0) {
236
+ if (subgraph.metadata().interfaceObjectDirective().applications().size > 0) {
237
237
  subgraphsUsingInterfaceObject.push(subgraph.name);
238
238
  }
239
239
  } else {