@apollo/federation-internals 2.0.0-alpha.1 → 2.0.0-alpha.2

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 (63) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/buildSchema.js.map +1 -1
  3. package/dist/coreSpec.d.ts +1 -1
  4. package/dist/coreSpec.d.ts.map +1 -1
  5. package/dist/coreSpec.js +2 -0
  6. package/dist/coreSpec.js.map +1 -1
  7. package/dist/debug.js +5 -5
  8. package/dist/debug.js.map +1 -1
  9. package/dist/definitions.d.ts +10 -6
  10. package/dist/definitions.d.ts.map +1 -1
  11. package/dist/definitions.js +52 -45
  12. package/dist/definitions.js.map +1 -1
  13. package/dist/extractSubgraphsFromSupergraph.js +20 -5
  14. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  15. package/dist/federation.d.ts +3 -5
  16. package/dist/federation.d.ts.map +1 -1
  17. package/dist/federation.js +31 -24
  18. package/dist/federation.js.map +1 -1
  19. package/dist/inaccessibleSpec.d.ts +1 -1
  20. package/dist/inaccessibleSpec.d.ts.map +1 -1
  21. package/dist/inaccessibleSpec.js +5 -5
  22. package/dist/inaccessibleSpec.js.map +1 -1
  23. package/dist/joinSpec.d.ts.map +1 -1
  24. package/dist/joinSpec.js.map +1 -1
  25. package/dist/operations.js +1 -1
  26. package/dist/operations.js.map +1 -1
  27. package/dist/print.d.ts +1 -1
  28. package/dist/print.d.ts.map +1 -1
  29. package/dist/print.js +4 -4
  30. package/dist/print.js.map +1 -1
  31. package/dist/suggestions.js +1 -1
  32. package/dist/suggestions.js.map +1 -1
  33. package/dist/tagSpec.js.map +1 -1
  34. package/dist/utils.d.ts +15 -0
  35. package/dist/utils.d.ts.map +1 -1
  36. package/dist/utils.js +64 -1
  37. package/dist/utils.js.map +1 -1
  38. package/dist/validate.js.map +1 -1
  39. package/dist/values.js +2 -3
  40. package/dist/values.js.map +1 -1
  41. package/package.json +5 -6
  42. package/src/__tests__/definitions.test.ts +3 -3
  43. package/src/__tests__/matchers/toMatchString.ts +2 -2
  44. package/src/__tests__/removeInaccessibleElements.test.ts +8 -8
  45. package/src/__tests__/utils.test.ts +92 -0
  46. package/src/buildSchema.ts +5 -5
  47. package/src/coreSpec.ts +12 -10
  48. package/src/debug.ts +6 -6
  49. package/src/definitions.ts +98 -75
  50. package/src/extractSubgraphsFromSupergraph.ts +31 -15
  51. package/src/federation.ts +56 -50
  52. package/src/inaccessibleSpec.ts +7 -7
  53. package/src/joinSpec.ts +0 -3
  54. package/src/operations.ts +20 -20
  55. package/src/print.ts +8 -8
  56. package/src/suggestions.ts +1 -1
  57. package/src/tagSpec.ts +1 -1
  58. package/src/types.ts +1 -1
  59. package/src/utils.ts +82 -0
  60. package/src/validate.ts +4 -4
  61. package/src/values.ts +4 -4
  62. package/tsconfig.test.tsbuildinfo +1 -1
  63. package/tsconfig.tsbuildinfo +1 -1
@@ -135,23 +135,23 @@ export function isCustomScalarType(type: Type): boolean {
135
135
  }
136
136
 
137
137
  export function isIntType(type: Type): boolean {
138
- return type === type.schema()?.intType();
138
+ return type === type.schema().intType();
139
139
  }
140
140
 
141
141
  export function isStringType(type: Type): boolean {
142
- return type === type.schema()?.stringType();
142
+ return type === type.schema().stringType();
143
143
  }
144
144
 
145
145
  export function isFloatType(type: Type): boolean {
146
- return type === type.schema()?.floatType();
146
+ return type === type.schema().floatType();
147
147
  }
148
148
 
149
149
  export function isBooleanType(type: Type): boolean {
150
- return type === type.schema()?.booleanType();
150
+ return type === type.schema().booleanType();
151
151
  }
152
152
 
153
153
  export function isIDType(type: Type): boolean {
154
- return type === type.schema()?.idType();
154
+ return type === type.schema().idType();
155
155
  }
156
156
 
157
157
  export function isObjectType(type: Type): type is ObjectType {
@@ -323,7 +323,7 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
323
323
  ): Directive<T, TApplicationArgs> {
324
324
  let toAdd: Directive<T, TApplicationArgs>;
325
325
  if (defOrDirective instanceof Directive) {
326
- if (defOrDirective.schema() && defOrDirective.schema() != this.schema()) {
326
+ if (defOrDirective.schema() != this.schema()) {
327
327
  throw new Error(`Cannot add directive ${defOrDirective} to ${this} as it is attached to another schema`);
328
328
  }
329
329
  toAdd = defOrDirective;
@@ -377,24 +377,41 @@ abstract class Element<TParent extends SchemaElement<any, any> | Schema | Direct
377
377
  protected _parent?: TParent;
378
378
  sourceAST?: ASTNode;
379
379
 
380
- schema(): Schema | undefined {
380
+ schema(): Schema {
381
+ const schema = this.schemaInternal();
382
+ assert(schema, 'requested schema does not exist. Probably because the element is unattached');
383
+ return schema;
384
+ }
385
+
386
+ // this function exists because sometimes we can have an element that will be attached soon even though the current state is unattached
387
+ // (mainly for callbacks). Sometimes these intermediate states need to get the schema if it exists, but it may not.
388
+ // all external clients should use schema()
389
+ protected schemaInternal(): Schema | undefined {
381
390
  if (!this._parent) {
382
391
  return undefined;
383
392
  } else if (this._parent instanceof Schema) {
384
- // Note: at the time of this writing, it seems like typescript type-checking breaks a bit around generics.
393
+ // Note: at the time of this writing, it seems like typescript type-checking breaks a bit around generics.
385
394
  // At this point of the code, `this._parent` is typed as 'TParent & Schema', but for some reason this is
386
395
  // "not assignable to type 'Schema | undefined'" (which sounds wrong: if my type theory is not too broken,
387
396
  // 'A & B' should always be assignable to both 'A' and 'B').
388
397
  return this._parent as any;
389
- } else {
390
- return (this._parent as SchemaElement<any, any> | DirectiveTargetElement<any>).schema();
398
+ } else if (this._parent instanceof SchemaElement) {
399
+ return this._parent.schemaInternal();
400
+ } else if (this._parent instanceof DirectiveTargetElement) {
401
+ return this._parent.schema();
391
402
  }
403
+ assert(false, 'unreachable code. parent is of unknown type');
392
404
  }
393
405
 
394
- get parent(): TParent | undefined {
406
+ get parent(): TParent {
407
+ assert(this._parent, 'trying to access non-existent parent');
395
408
  return this._parent;
396
409
  }
397
410
 
411
+ isAttached(): boolean {
412
+ return !!this._parent;
413
+ }
414
+
398
415
  // Accessed only through Element.prototype['setParent'] (so we don't mark it protected as an override wouldn't be properly called).
399
416
  private setParent(parent: TParent) {
400
417
  assert(!this._parent, "Cannot set parent of an already attached element");
@@ -410,10 +427,10 @@ abstract class Element<TParent extends SchemaElement<any, any> | Schema | Direct
410
427
  // Allowing to add element to a detached element would get hairy. Because that would mean that when you do attach an element,
411
428
  // you have to recurse within that element to all children elements to check whether they are attached or not and to which
412
429
  // schema. And if they aren't attached, attaching them as side-effect could be surprising (think that adding a single field
413
- // to a schema could bring a whole hierachy of types and directives for instance). If they are attached, it only work if
430
+ // to a schema could bring a whole hierarchy of types and directives for instance). If they are attached, it only work if
414
431
  // it's to the same schema, but you have to check.
415
432
  // Overall, it's simpler to force attaching elements before you add other elements to them.
416
- if (!this.schema()) {
433
+ if (!this.isAttached()) {
417
434
  throw error(`Cannot modify detached element ${this}`);
418
435
  }
419
436
  }
@@ -449,8 +466,8 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
449
466
 
450
467
  hasAppliedDirective(nameOrDefinition: string | DirectiveDefinition<any>): boolean {
451
468
  // From the type-system point of view, there is no `appliedDirectivesOf(_: string | DirectiveDefinition)` function, but rather 2 overloads, neither of
452
- // which can take 'string | DirectiveDefinition', hence the need for this suprisingly looking code. And we don't really want to remove the overloading
453
- // on `applieddDirectivesOf` because that would lose us the type-checking of arguments in the case where we pass a defintion (or rather, we could
469
+ // which can take 'string | DirectiveDefinition', hence the need for this surprisingly looking code. And we don't really want to remove the overloading
470
+ // on `appliedDirectivesOf` because that would lose us the type-checking of arguments in the case where we pass a definition (or rather, we could
454
471
  // preserve it, but it would make is a bit too easy to mess up calls with the 'string' argument).
455
472
  return (typeof nameOrDefinition === 'string'
456
473
  ? this.appliedDirectivesOf(nameOrDefinition)
@@ -473,9 +490,9 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
473
490
  let name: string;
474
491
  if (typeof nameOrDefOrDirective === 'string') {
475
492
  this.checkUpdate();
476
- const def = this.schema()!.directive(nameOrDefOrDirective);
493
+ const def = this.schema().directive(nameOrDefOrDirective);
477
494
  if (!def) {
478
- throw new GraphQLError(`Cannot apply unkown directive "@${nameOrDefOrDirective}"`);
495
+ throw new GraphQLError(`Cannot apply unknown directive "@${nameOrDefOrDirective}"`);
479
496
  }
480
497
  name = nameOrDefOrDirective;
481
498
  } else {
@@ -499,7 +516,7 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
499
516
  }
500
517
 
501
518
  protected onModification() {
502
- const schema = this.schema();
519
+ const schema = this.schemaInternal();
503
520
  if (schema) {
504
521
  Schema.prototype['onModification'].call(schema);
505
522
  }
@@ -511,7 +528,7 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
511
528
 
512
529
  protected removeTypeReferenceInternal(type: BaseNamedType<any, any>) {
513
530
  // This method is a bit of a hack: we don't want to expose it and we call it from an other class, so we call it though
514
- // `SchemaElement.prototype`, but we also want this to abstract as it can only be impemented by each concrete subclass.
531
+ // `SchemaElement.prototype`, but we also want this to abstract as it can only be implemented by each concrete subclass.
515
532
  // As we can't have both at the same time, this method just delegate to `remoteTypeReference` which is genuinely
516
533
  // abstract. This also allow to work around the typing issue that the type checker cannot tell that every BaseNamedType
517
534
  // is a NamedType (because in theory, someone could extend BaseNamedType without listing it in NamedType; but as
@@ -522,16 +539,16 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
522
539
  protected abstract removeTypeReference(type: NamedType): void;
523
540
 
524
541
  protected checkRemoval() {
525
- if (this.isElementBuiltIn() && !Schema.prototype['canModifyBuiltIn'].call(this.schema()!)) {
542
+ if (this.isElementBuiltIn() && !Schema.prototype['canModifyBuiltIn'].call(this.schema())) {
526
543
  throw error(`Cannot modify built-in ${this}`);
527
544
  }
528
545
  // We allow removals even on detached element because that doesn't particularly create issues (and we happen to do such
529
546
  // removals on detached internally; though of course we could refactor the code if we wanted).
530
547
  }
531
548
 
532
- protected checkUpdate(addedElement?: { schema(): Schema | undefined }) {
549
+ protected checkUpdate(addedElement?: { schema(): Schema, isAttached(): boolean }) {
533
550
  super.checkUpdate();
534
- if (!Schema.prototype['canModifyBuiltIn'].call(this.schema()!)) {
551
+ if (!Schema.prototype['canModifyBuiltIn'].call(this.schema())) {
535
552
  // Ensure this element (the modified one), is not a built-in, or part of one.
536
553
  let thisElement: SchemaElement<TOwnType, any> | Schema | undefined = this;
537
554
  while (thisElement && thisElement instanceof SchemaElement) {
@@ -541,7 +558,7 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
541
558
  thisElement = thisElement.parent;
542
559
  }
543
560
  }
544
- if (addedElement) {
561
+ if (addedElement && addedElement.isAttached()) {
545
562
  const thatSchema = addedElement.schema();
546
563
  if (thatSchema && thatSchema != this.schema()) {
547
564
  throw error(`Cannot add element ${addedElement} to ${this} as it is attached to another schema`);
@@ -593,7 +610,7 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
593
610
  }
594
611
 
595
612
  *allChildElements(): Generator<NamedSchemaElement<any, TOwnType, any>, void, undefined> {
596
- // Overriden by those types that do have chidrens
613
+ // Overriden by those types that do have children
597
614
  }
598
615
 
599
616
  extensions(): ReadonlySet<Extension<TOwnType>> {
@@ -659,7 +676,7 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
659
676
  * reference.
660
677
  *
661
678
  * @returns an array of all the elements in the schema of this type (before the removal) that were
662
- * referening this type (and have thus now an undefined reference).
679
+ * referencing this type (and have thus now an undefined reference).
663
680
  */
664
681
  remove(): TReferencer[] {
665
682
  if (!this._parent) {
@@ -739,7 +756,7 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
739
756
  setOfExtension(extension: Extension<TExtended> | undefined) {
740
757
  this.checkUpdate();
741
758
  // See similar comment on FieldDefinition.setOfExtension for why we have to cast.
742
- if (extension && !this.parent?.extensions().has(extension as any)) {
759
+ if (extension && !this._parent?.extensions().has(extension as any)) {
743
760
  throw error(`Cannot set object as part of the provided extension: it is not an extension of parent ${this.parent}`);
744
761
  }
745
762
  this._extension = extension;
@@ -800,7 +817,7 @@ export class BuiltIns {
800
817
 
801
818
  onValidation(schema: Schema, unvalidatedDirectives?: string[]): GraphQLError[] {
802
819
  const errors: GraphQLError[] = [];
803
- // We make sure that if any of the built-ins has been redefined, then the redifinition is
820
+ // We make sure that if any of the built-ins has been redefined, then the redefinition is
804
821
  // the same as the built-in one.
805
822
  for (const type of schema.builtInTypes(undefined, true)) {
806
823
  const maybeRedefined = schema.type(type.name)!;
@@ -1136,9 +1153,17 @@ export class Schema {
1136
1153
  return this.apiSchema;
1137
1154
  }
1138
1155
 
1139
- toGraphQLJSSchema(): GraphQLSchema {
1156
+ toGraphQLJSSchema(isSubgraph: boolean = false): GraphQLSchema {
1140
1157
  // Obviously not super fast, but as long as we don't use this often on a hot path, that's probably ok.
1141
- return buildGraphqlSchemaFromAST(this.toAST());
1158
+ if (!isSubgraph) {
1159
+ return buildGraphqlSchemaFromAST(this.toAST());
1160
+ }
1161
+
1162
+ // Some subgraphs, especially federation 1 ones, cannot be properly converted to a GraphQLSchema because they are invalid graphQL.
1163
+ // And the main culprit is type extensions that don't have a corresponding definition. So to avoid that problem, we print
1164
+ // up the AST without extensions. Another issue is the non graph built-ins which needs to be explicitely defined.
1165
+ const ast = parse(printSchema(this, { ...toASTPrintOptions, mergeTypesAndExtensions: true }), { noLocation: true });
1166
+ return buildGraphqlSchemaFromAST(ast);
1142
1167
  }
1143
1168
 
1144
1169
  get schemaDefinition(): SchemaDefinition {
@@ -1217,7 +1242,7 @@ export class Schema {
1217
1242
  if (existing && !existing.isBuiltIn) {
1218
1243
  throw error(`Type ${type} already exists in this schema`);
1219
1244
  }
1220
- if (type.parent) {
1245
+ if (type.isAttached()) {
1221
1246
  // For convenience, let's not error out on adding an already added type.
1222
1247
  if (type.parent == this) {
1223
1248
  return type;
@@ -1302,7 +1327,7 @@ export class Schema {
1302
1327
  if (existing && !existing.isBuiltIn) {
1303
1328
  throw error(`Directive ${definition} already exists in this schema`);
1304
1329
  }
1305
- if (definition.parent) {
1330
+ if (definition.isAttached()) {
1306
1331
  // For convenience, let's not error out on adding an already added directive.
1307
1332
  if (definition.parent == this) {
1308
1333
  return definition;
@@ -1398,7 +1423,7 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
1398
1423
  args?: TApplicationArgs
1399
1424
  ): Directive<SchemaDefinition, TApplicationArgs> {
1400
1425
  const applied = super.applyDirective(nameOrDefOrDirective, args) as Directive<SchemaDefinition, TApplicationArgs>;
1401
- const schema = this.schema()!;
1426
+ const schema = this.schema();
1402
1427
  const coreFeatures = schema.coreFeatures;
1403
1428
  if (isCoreSpecDirectiveApplication(applied)) {
1404
1429
  if (coreFeatures) {
@@ -1428,7 +1453,7 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
1428
1453
  let toSet: RootType;
1429
1454
  if (typeof nameOrType === 'string') {
1430
1455
  this.checkUpdate();
1431
- const obj = this.schema()!.type(nameOrType);
1456
+ const obj = this.schema().type(nameOrType);
1432
1457
  if (!obj) {
1433
1458
  throw new GraphQLError(`Cannot set schema ${rootKind} root to unknown type ${nameOrType}`);
1434
1459
  } else if (obj.kind != 'ObjectType') {
@@ -1542,8 +1567,8 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
1542
1567
  // Note that we can only add the __typename built-in field when we're attached, because we need access to the
1543
1568
  // schema string type. Also, we're effectively modifying a built-in (to add the type), so we
1544
1569
  // need to let the schema accept it.
1545
- Schema.prototype['runWithBuiltInModificationAllowed'].call(this.schema()!, () => {
1546
- this.addField(new FieldDefinition(typenameFieldName, true), new NonNullType(this.schema()!.stringType()));
1570
+ Schema.prototype['runWithBuiltInModificationAllowed'].call(this.schema(), () => {
1571
+ this.addField(new FieldDefinition(typenameFieldName, true), new NonNullType(this.schema().stringType()));
1547
1572
  });
1548
1573
  }
1549
1574
 
@@ -1577,9 +1602,9 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
1577
1602
  let itf: InterfaceType;
1578
1603
  if (typeof nameOrItfOrItfImpl === 'string') {
1579
1604
  this.checkUpdate();
1580
- const maybeItf = this.schema()!.type(nameOrItfOrItfImpl);
1605
+ const maybeItf = this.schema().type(nameOrItfOrItfImpl);
1581
1606
  if (!maybeItf) {
1582
- throw new GraphQLError(`Cannot implement unkown type ${nameOrItfOrItfImpl}`);
1607
+ throw new GraphQLError(`Cannot implement unknown type ${nameOrItfOrItfImpl}`);
1583
1608
  } else if (maybeItf.kind != 'InterfaceType') {
1584
1609
  throw new GraphQLError(`Cannot implement non-interface type ${nameOrItfOrItfImpl} (of type ${maybeItf.kind})`);
1585
1610
  }
@@ -1718,9 +1743,6 @@ export class ObjectType extends FieldBasedType<ObjectType, ObjectTypeReferencer>
1718
1743
  */
1719
1744
  isRootType(): boolean {
1720
1745
  const schema = this.schema();
1721
- if (!schema) {
1722
- return false;
1723
- }
1724
1746
  return schema.schemaDefinition.roots().some(rt => rt.type == this);
1725
1747
  }
1726
1748
 
@@ -1729,9 +1751,6 @@ export class ObjectType extends FieldBasedType<ObjectType, ObjectTypeReferencer>
1729
1751
  */
1730
1752
  isQueryRootType(): boolean {
1731
1753
  const schema = this.schema();
1732
- if (!schema) {
1733
- return false;
1734
- }
1735
1754
  return schema.schemaDefinition.root('query')?.type === this;
1736
1755
  }
1737
1756
  }
@@ -1773,10 +1792,10 @@ export class UnionType extends BaseNamedType<OutputTypeReferencer, UnionType> {
1773
1792
  // Note that we can only create the __typename built-in field when we're attached, because we need access to the
1774
1793
  // schema string type. Also, we're effectively modifying a built-in (to add the type), so we
1775
1794
  // need to let the schema accept it.
1776
- Schema.prototype['runWithBuiltInModificationAllowed'].call(this.schema()!, () => {
1795
+ Schema.prototype['runWithBuiltInModificationAllowed'].call(this.schema(), () => {
1777
1796
  this._typenameField = new FieldDefinition(typenameFieldName, true);
1778
1797
  Element.prototype['setParent'].call(this._typenameField, this);
1779
- this._typenameField.type = new NonNullType(this.schema()!.stringType());
1798
+ this._typenameField.type = new NonNullType(this.schema().stringType());
1780
1799
  });
1781
1800
  }
1782
1801
 
@@ -1805,9 +1824,9 @@ export class UnionType extends BaseNamedType<OutputTypeReferencer, UnionType> {
1805
1824
  let obj: ObjectType;
1806
1825
  if (typeof nameOrTypeOrMember === 'string') {
1807
1826
  this.checkUpdate();
1808
- const maybeObj = this.schema()!.type(nameOrTypeOrMember);
1827
+ const maybeObj = this.schema().type(nameOrTypeOrMember);
1809
1828
  if (!maybeObj) {
1810
- throw new GraphQLError(`Cannot add unkown type ${nameOrTypeOrMember} as member of union type ${this.name}`);
1829
+ throw new GraphQLError(`Cannot add unknown type ${nameOrTypeOrMember} as member of union type ${this.name}`);
1811
1830
  } else if (maybeObj.kind != 'ObjectType') {
1812
1831
  throw new GraphQLError(`Cannot add non-object type ${nameOrTypeOrMember} (of type ${maybeObj.kind}) as member of union type ${this.name}`);
1813
1832
  }
@@ -1839,7 +1858,7 @@ export class UnionType extends BaseNamedType<OutputTypeReferencer, UnionType> {
1839
1858
 
1840
1859
  /**
1841
1860
  * Access a field of the union by name.
1842
- * As the only field that can be acessed on an union is the __typename one, this method will always return undefined unless called
1861
+ * As the only field that can be accessed on an union is the __typename one, this method will always return undefined unless called
1843
1862
  * on "__typename". However, this exists to allow code working on CompositeType to be more generic.
1844
1863
  */
1845
1864
  field(name: string): FieldDefinition<UnionType> | undefined {
@@ -1960,7 +1979,7 @@ export class InputObjectType extends BaseNamedType<InputTypeReferencer, InputObj
1960
1979
  throw error(`Field ${toAdd.name} already exists on ${this}`);
1961
1980
  }
1962
1981
  if (type && !isInputType(type)) {
1963
- throw error(`Invalid ouptut type ${type} for field ${toAdd.name}: input field types should be input types.`);
1982
+ throw error(`Invalid output type ${type} for field ${toAdd.name}: input field types should be input types.`);
1964
1983
  }
1965
1984
  this._fields.set(toAdd.name, toAdd);
1966
1985
  this._cachedFieldsArray = undefined;
@@ -2007,10 +2026,14 @@ class BaseWrapperType<T extends Type> {
2007
2026
  assert(this._type, 'Cannot wrap an undefined/null type');
2008
2027
  }
2009
2028
 
2010
- schema(): Schema | undefined {
2029
+ schema(): Schema {
2011
2030
  return this.baseType().schema();
2012
2031
  }
2013
2032
 
2033
+ isAttached(): boolean {
2034
+ return this.baseType().isAttached();
2035
+ }
2036
+
2014
2037
  get ofType(): T {
2015
2038
  return this._type;
2016
2039
  }
@@ -2058,7 +2081,7 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
2058
2081
  }
2059
2082
 
2060
2083
  get coordinate(): string {
2061
- const parent = this.parent;
2084
+ const parent = this._parent;
2062
2085
  return `${parent == undefined ? '<detached>' : parent.coordinate}.${this.name}`;
2063
2086
  }
2064
2087
 
@@ -2099,7 +2122,7 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
2099
2122
  return existing;
2100
2123
  }
2101
2124
  if (type && !isInputType(type)) {
2102
- throw error(`Invalid ouptut type ${type} for argument ${toAdd.name} of ${this}: arguments should be input types.`);
2125
+ throw error(`Invalid output type ${type} for argument ${toAdd.name} of ${this}: arguments should be input types.`);
2103
2126
  }
2104
2127
  this._args.set(toAdd.name, toAdd);
2105
2128
  Element.prototype['setParent'].call(toAdd, this);
@@ -2116,9 +2139,9 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
2116
2139
 
2117
2140
  setOfExtension(extension: Extension<TParent> | undefined) {
2118
2141
  this.checkUpdate();
2119
- // It seems typscript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
2142
+ // It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
2120
2143
  // the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
2121
- if (extension && !this.parent?.extensions().has(extension as any)) {
2144
+ if (extension && !this._parent?.extensions().has(extension as any)) {
2122
2145
  throw error(`Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`);
2123
2146
  }
2124
2147
  this._extension = extension;
@@ -2171,7 +2194,7 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
2171
2194
 
2172
2195
  toString(): string {
2173
2196
  const args = this._args.size == 0
2174
- ? ""
2197
+ ? ""
2175
2198
  : '(' + this.arguments().map(arg => arg.toString()).join(', ') + ')';
2176
2199
  return `${this.name}${args}: ${this.type}`;
2177
2200
  }
@@ -2183,7 +2206,7 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
2183
2206
  defaultValue?: any
2184
2207
 
2185
2208
  get coordinate(): string {
2186
- const parent = this.parent;
2209
+ const parent = this._parent;
2187
2210
  return `${parent == undefined ? '<detached>' : parent.coordinate}.${this.name}`;
2188
2211
  }
2189
2212
 
@@ -2197,9 +2220,9 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
2197
2220
 
2198
2221
  setOfExtension(extension: Extension<InputObjectType> | undefined) {
2199
2222
  this.checkUpdate();
2200
- // It seems typscript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
2223
+ // It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
2201
2224
  // the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
2202
- if (extension && !this.parent?.extensions().has(extension as any)) {
2225
+ if (extension && !this._parent?.extensions().has(extension as any)) {
2203
2226
  throw error(`Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`);
2204
2227
  }
2205
2228
  this._extension = extension;
@@ -2243,7 +2266,7 @@ export class ArgumentDefinition<TParent extends FieldDefinition<any> | Directive
2243
2266
  }
2244
2267
 
2245
2268
  get coordinate(): string {
2246
- const parent = this.parent;
2269
+ const parent = this._parent;
2247
2270
  return `${parent == undefined ? '<detached>' : parent.coordinate}(${this.name}:)`;
2248
2271
  }
2249
2272
 
@@ -2288,7 +2311,7 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
2288
2311
  private _extension?: Extension<EnumType>;
2289
2312
 
2290
2313
  get coordinate(): string {
2291
- const parent = this.parent;
2314
+ const parent = this._parent;
2292
2315
  return `${parent == undefined ? '<detached>' : parent.coordinate}.${this.name}`;
2293
2316
  }
2294
2317
 
@@ -2298,7 +2321,7 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
2298
2321
 
2299
2322
  setOfExtension(extension: Extension<EnumType> | undefined) {
2300
2323
  this.checkUpdate();
2301
- if (extension && !this.parent?.extensions().has(extension)) {
2324
+ if (extension && !this._parent?.extensions().has(extension)) {
2302
2325
  throw error(`Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`);
2303
2326
  }
2304
2327
  this._extension = extension;
@@ -2485,13 +2508,13 @@ export class Directive<
2485
2508
  super();
2486
2509
  }
2487
2510
 
2488
- schema(): Schema | undefined {
2489
- return this._parent?.schema();
2511
+ schema(): Schema {
2512
+ return this.parent.schema();
2490
2513
  }
2491
2514
 
2492
2515
  get definition(): DirectiveDefinition | undefined {
2493
2516
  const doc = this.schema();
2494
- return doc?.directive(this.name);
2517
+ return doc.directive(this.name);
2495
2518
  }
2496
2519
 
2497
2520
  arguments(includeDefaultValues: boolean = false) : Readonly<TArgs> {
@@ -2516,7 +2539,7 @@ export class Directive<
2516
2539
  }
2517
2540
 
2518
2541
  private isAttachedToSchemaElement(): boolean {
2519
- return this.parent !== undefined && this.parent instanceof SchemaElement;
2542
+ return this.isAttached();
2520
2543
  }
2521
2544
 
2522
2545
  setArguments(args: TArgs) {
@@ -2533,7 +2556,7 @@ export class Directive<
2533
2556
  if (entries.length !== Object.keys(expectedArgs).length) {
2534
2557
  return false;
2535
2558
  }
2536
- for (var [key, val] of entries) {
2559
+ for (const [key, val] of entries) {
2537
2560
  if (!(key in expectedArgs)) {
2538
2561
  return false;
2539
2562
  }
@@ -2552,7 +2575,7 @@ export class Directive<
2552
2575
  setOfExtension(extension: Extension<any> | undefined) {
2553
2576
  this.checkUpdate();
2554
2577
  if (extension) {
2555
- const parent = this.parent!;
2578
+ const parent = this.parent;
2556
2579
  if (parent instanceof SchemaDefinition || parent instanceof BaseNamedType) {
2557
2580
  if (!parent.extensions().has(extension)) {
2558
2581
  throw error(`Cannot mark directive ${this.name} as part of the provided extension: it is not an extension of parent ${parent}`);
@@ -2592,15 +2615,15 @@ export class Directive<
2592
2615
  return false;
2593
2616
  }
2594
2617
  this.onModification();
2595
- const coreFeatures = this.schema()?.coreFeatures;
2618
+ const coreFeatures = this.schema().coreFeatures;
2596
2619
  if (coreFeatures && this.name === coreFeatures.coreItself.nameInSchema) {
2597
2620
  // We're removing a @core directive application, so we remove it from the list of core features. And
2598
2621
  // if it is @core itself, we clean all features (to avoid having things too inconsistent).
2599
2622
  const url = FeatureUrl.parse(this._args['feature']!);
2600
2623
  if (url.identity === coreFeatures.coreItself.url.identity) {
2601
2624
  // Note that we unmark first because the loop after that will nuke our parent.
2602
- Schema.prototype['unmarkAsCoreSchema'].call(this.schema()!);
2603
- for (const d of this.schema()!.schemaDefinition.appliedDirectivesOf(coreFeatures.coreItself.nameInSchema)) {
2625
+ Schema.prototype['unmarkAsCoreSchema'].call(this.schema());
2626
+ for (const d of this.schema().schemaDefinition.appliedDirectivesOf(coreFeatures.coreItself.nameInSchema)) {
2604
2627
  d.removeInternal();
2605
2628
  }
2606
2629
  // The loop above will already have call removeInternal on this instance, so we can return
@@ -2971,10 +2994,10 @@ function copyAppliedDirectives(source: SchemaElement<any, any>, dest: SchemaElem
2971
2994
  }
2972
2995
 
2973
2996
  function copyFieldDefinitionInner<P extends ObjectType | InterfaceType>(source: FieldDefinition<P>, dest: FieldDefinition<P>) {
2974
- const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()!) as OutputType;
2997
+ const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()) as OutputType;
2975
2998
  dest.type = type;
2976
2999
  for (const arg of source.arguments()) {
2977
- const argType = copyWrapperTypeOrTypeRef(arg.type, dest.schema()!);
3000
+ const argType = copyWrapperTypeOrTypeRef(arg.type, dest.schema());
2978
3001
  copyArgumentDefinitionInner(arg, dest.addArgument(arg.name, argType as InputType));
2979
3002
  }
2980
3003
  copyAppliedDirectives(source, dest);
@@ -2983,7 +3006,7 @@ function copyFieldDefinitionInner<P extends ObjectType | InterfaceType>(source:
2983
3006
  }
2984
3007
 
2985
3008
  function copyInputFieldDefinitionInner(source: InputFieldDefinition, dest: InputFieldDefinition) {
2986
- const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()!) as InputType;
3009
+ const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()) as InputType;
2987
3010
  dest.type = type;
2988
3011
  dest.defaultValue = source.defaultValue;
2989
3012
  copyAppliedDirectives(source, dest);
@@ -3006,7 +3029,7 @@ function copyWrapperTypeOrTypeRef(source: Type | undefined, destParent: Schema):
3006
3029
  }
3007
3030
 
3008
3031
  function copyArgumentDefinitionInner<P extends FieldDefinition<any> | DirectiveDefinition>(source: ArgumentDefinition<P>, dest: ArgumentDefinition<P>) {
3009
- const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()!) as InputType;
3032
+ const type = copyWrapperTypeOrTypeRef(source.type, dest.schema()) as InputType;
3010
3033
  dest.type = type;
3011
3034
  dest.defaultValue = source.defaultValue;
3012
3035
  copyAppliedDirectives(source, dest);
@@ -3016,7 +3039,7 @@ function copyArgumentDefinitionInner<P extends FieldDefinition<any> | DirectiveD
3016
3039
 
3017
3040
  function copyDirectiveDefinitionInner(source: DirectiveDefinition, dest: DirectiveDefinition) {
3018
3041
  for (const arg of source.arguments()) {
3019
- const type = copyWrapperTypeOrTypeRef(arg.type, dest.schema()!);
3042
+ const type = copyWrapperTypeOrTypeRef(arg.type, dest.schema());
3020
3043
  copyArgumentDefinitionInner(arg, dest.addArgument(arg.name, type as InputType));
3021
3044
  }
3022
3045
  dest.repeatable = source.repeatable;