@apollo/federation-internals 2.1.0-alpha.0 → 2.1.0-alpha.1

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 (57) hide show
  1. package/dist/buildSchema.d.ts.map +1 -1
  2. package/dist/buildSchema.js +11 -10
  3. package/dist/buildSchema.js.map +1 -1
  4. package/dist/coreSpec.d.ts.map +1 -1
  5. package/dist/coreSpec.js +15 -42
  6. package/dist/coreSpec.js.map +1 -1
  7. package/dist/definitions.d.ts.map +1 -1
  8. package/dist/definitions.js +49 -98
  9. package/dist/definitions.js.map +1 -1
  10. package/dist/directiveAndTypeSpecification.js +14 -48
  11. package/dist/directiveAndTypeSpecification.js.map +1 -1
  12. package/dist/error.d.ts +19 -17
  13. package/dist/error.d.ts.map +1 -1
  14. package/dist/error.js +40 -7
  15. package/dist/error.js.map +1 -1
  16. package/dist/federation.d.ts.map +1 -1
  17. package/dist/federation.js +70 -123
  18. package/dist/federation.js.map +1 -1
  19. package/dist/inaccessibleSpec.js +43 -65
  20. package/dist/inaccessibleSpec.js.map +1 -1
  21. package/dist/operations.d.ts.map +1 -1
  22. package/dist/operations.js +5 -4
  23. package/dist/operations.js.map +1 -1
  24. package/dist/schemaUpgrader.js +2 -8
  25. package/dist/schemaUpgrader.js.map +1 -1
  26. package/dist/supergraphs.d.ts.map +1 -1
  27. package/dist/supergraphs.js +4 -4
  28. package/dist/supergraphs.js.map +1 -1
  29. package/dist/tagSpec.d.ts.map +1 -1
  30. package/dist/tagSpec.js +1 -3
  31. package/dist/tagSpec.js.map +1 -1
  32. package/dist/validate.d.ts.map +1 -1
  33. package/dist/validate.js +26 -22
  34. package/dist/validate.js.map +1 -1
  35. package/dist/validation/KnownTypeNamesInFederationRule.js +1 -1
  36. package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
  37. package/dist/values.d.ts.map +1 -1
  38. package/dist/values.js +19 -18
  39. package/dist/values.js.map +1 -1
  40. package/package.json +2 -2
  41. package/src/__tests__/subgraphValidation.test.ts +75 -0
  42. package/src/buildSchema.ts +11 -18
  43. package/src/coreSpec.ts +47 -43
  44. package/src/definitions.ts +64 -96
  45. package/src/directiveAndTypeSpecification.ts +50 -48
  46. package/src/error.ts +87 -41
  47. package/src/federation.ts +142 -131
  48. package/src/inaccessibleSpec.ts +207 -191
  49. package/src/operations.ts +5 -5
  50. package/src/schemaUpgrader.ts +8 -8
  51. package/src/supergraphs.ts +5 -4
  52. package/src/tagSpec.ts +2 -3
  53. package/src/validate.ts +60 -52
  54. package/src/validation/KnownTypeNamesInFederationRule.ts +1 -1
  55. package/src/values.ts +19 -18
  56. package/tsconfig.test.tsbuildinfo +1 -1
  57. package/tsconfig.tsbuildinfo +1 -1
package/src/coreSpec.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ASTNode, DirectiveLocation, GraphQLError, StringValueNode } from "graphql";
2
2
  import { URL } from "url";
3
- import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement } from "./definitions";
3
+ import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement, sourceASTs } from "./definitions";
4
4
  import { sameType } from "./types";
5
5
  import { err } from '@apollo/core-schema';
6
6
  import { assert, firstOf } from './utils';
@@ -185,10 +185,10 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
185
185
  continue;
186
186
  }
187
187
  if (typeof elt !== 'object') {
188
- errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
189
- message: `Invalid sub-value ${valueToString(elt)} for @link(import:) argument: values should be either strings or input object values of the form { name: "<importedElement>", as: "<alias>" }.`,
190
- nodes: directive.sourceAST
191
- }));
188
+ errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
189
+ `Invalid sub-value ${valueToString(elt)} for @link(import:) argument: values should be either strings or input object values of the form { name: "<importedElement>", as: "<alias>" }.`,
190
+ { nodes: directive.sourceAST },
191
+ ));
192
192
  continue;
193
193
  }
194
194
  let name: string | undefined;
@@ -196,28 +196,28 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
196
196
  switch (key) {
197
197
  case 'name':
198
198
  if (typeof value !== 'string') {
199
- errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
200
- message: `Invalid value for the "name" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
201
- nodes: directive.sourceAST
202
- }));
199
+ errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
200
+ `Invalid value for the "name" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
201
+ { nodes: directive.sourceAST },
202
+ ));
203
203
  continue importArgLoop;
204
204
  }
205
205
  name = value;
206
206
  break;
207
207
  case 'as':
208
208
  if (typeof value !== 'string') {
209
- errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
210
- message: `Invalid value for the "as" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
211
- nodes: directive.sourceAST
212
- }));
209
+ errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
210
+ `Invalid value for the "as" field for sub-value ${valueToString(elt)} of @link(import:) argument: must be a string.`,
211
+ { nodes: directive.sourceAST },
212
+ ));
213
213
  continue importArgLoop;
214
214
  }
215
215
  break;
216
216
  default:
217
- errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
218
- message: `Unknown field "${key}" for sub-value ${valueToString(elt)} of @link(import:) argument.`,
219
- nodes: directive.sourceAST
220
- }));
217
+ errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
218
+ `Unknown field "${key}" for sub-value ${valueToString(elt)} of @link(import:) argument.`,
219
+ { nodes: directive.sourceAST },
220
+ ));
221
221
  continue importArgLoop;
222
222
  }
223
223
  }
@@ -226,24 +226,24 @@ export function extractCoreFeatureImports(url: FeatureUrl, directive: Directive<
226
226
  imports.push(i);
227
227
  if (i.as) {
228
228
  if (i.name.charAt(0) === '@' && i.as.charAt(0) !== '@') {
229
- errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
230
- message: `Invalid @link import renaming: directive "${i.name}" imported name should start with a '@' character, but got "${i.as}".`,
231
- nodes: directive.sourceAST
232
- }));
229
+ errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
230
+ `Invalid @link import renaming: directive "${i.name}" imported name should start with a '@' character, but got "${i.as}".`,
231
+ { nodes: directive.sourceAST },
232
+ ));
233
233
  }
234
234
  else if (i.name.charAt(0) !== '@' && i.as.charAt(0) === '@') {
235
- errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
236
- message: `Invalid @link import renaming: type "${i.name}" imported name should not start with a '@' character, but got "${i.as}" (or, if @${i.name} is a directive, then it should be referred to with a '@').`,
237
- nodes: directive.sourceAST
238
- }));
235
+ errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
236
+ `Invalid @link import renaming: type "${i.name}" imported name should not start with a '@' character, but got "${i.as}" (or, if @${i.name} is a directive, then it should be referred to with a '@').`,
237
+ { nodes: directive.sourceAST },
238
+ ));
239
239
  }
240
240
  }
241
241
  validateImportedName(name, knownElements, errors, directive);
242
242
  } else {
243
- errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
244
- message: `Invalid sub-value ${valueToString(elt)} for @link(import:) argument: missing mandatory "name" field.`,
245
- nodes: directive.sourceAST
246
- }));
243
+ errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
244
+ `Invalid sub-value ${valueToString(elt)} for @link(import:) argument: missing mandatory "name" field.`,
245
+ { nodes: directive.sourceAST },
246
+ ));
247
247
  }
248
248
  }
249
249
 
@@ -264,10 +264,10 @@ function validateImportedName(name: string, knownElements: string[] | undefined,
264
264
  details = didYouMean(suggestions);
265
265
  }
266
266
  }
267
- errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
268
- message: `Cannot import unknown element "${name}".${details}`,
269
- nodes: directive.sourceAST
270
- }));
267
+ errors.push(ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
268
+ `Cannot import unknown element "${name}".${details}`,
269
+ { nodes: directive.sourceAST },
270
+ ));
271
271
  }
272
272
  }
273
273
 
@@ -419,9 +419,9 @@ export class CoreSpecDefinition extends FeatureDefinition {
419
419
  // Already exists with the same version, let it be.
420
420
  return [];
421
421
  } else {
422
- return [ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err({
423
- message: `Cannot add feature ${this} to the schema, it already uses ${existingCore.coreItself.url}`
424
- })];
422
+ return [ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(
423
+ `Cannot add feature ${this} to the schema, it already uses ${existingCore.coreItself.url}`
424
+ )];
425
425
  }
426
426
  }
427
427
 
@@ -555,7 +555,7 @@ export class FeatureVersion {
555
555
  public static parse(input: string): FeatureVersion {
556
556
  const match = input.match(this.VERSION_RE)
557
557
  if (!match) {
558
- throw new GraphQLError(`Expected a version string (of the form v1.2), got ${input}`);
558
+ throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Expected a version string (of the form v1.2), got ${input}`);
559
559
  }
560
560
  return new this(+match[1], +match[2])
561
561
  }
@@ -663,17 +663,17 @@ export class FeatureUrl {
663
663
  public static parse(input: string, node?: ASTNode): FeatureUrl {
664
664
  const url = new URL(input)
665
665
  if (!url.pathname || url.pathname === '/') {
666
- throw new GraphQLError(`Missing path in feature url '${url}'`, node)
666
+ throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing path in feature url '${url}'`, { nodes: node })
667
667
  }
668
668
  const path = url.pathname.split('/')
669
669
  const verStr = path.pop()
670
670
  if (!verStr) {
671
- throw new GraphQLError(`Missing version component in feature url '${url}'`, node)
671
+ throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing version component in feature url '${url}'`, { nodes: node })
672
672
  }
673
673
  const version = FeatureVersion.parse(verStr)
674
674
  const name = path[path.length - 1]
675
675
  if (!name) {
676
- throw new GraphQLError(`Missing feature name component in feature url '${url}'`, node)
676
+ throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing feature name component in feature url '${url}'`, { nodes: node })
677
677
  }
678
678
  const element = url.hash ? url.hash.slice(1): undefined
679
679
  url.hash = ''
@@ -804,11 +804,15 @@ export function removeAllCoreFeatures(schema: Schema) {
804
804
  for (const { feature, type, references } of typeReferences) {
805
805
  const referencesInSchema = references.filter(r => r.isAttached());
806
806
  if (referencesInSchema.length > 0) {
807
- errors.push(new GraphQLError(
807
+ // Note: using REFERENCED_INACCESSIBLE is slightly abusive because the reference element is not marked
808
+ // @inacessible exactly. Instead, it is inacessible due to core elements being removed, but that's very
809
+ // very close semantically. Overall, adding a publicly documented error code just to minor difference
810
+ // doesn't feel worth it, especially since that case is super unlikely in the first place (and, as
811
+ // the prior comment says, may one day be removed too).
812
+ errors.push(ERRORS.REFERENCED_INACCESSIBLE.err(
808
813
  `Cannot remove elements of feature ${feature} as feature type ${type}` +
809
814
  ` is referenced by elements: ${referencesInSchema.join(', ')}`,
810
- references.map(r => r.sourceAST)
811
- .filter(n => n !== undefined) as ASTNode[]
815
+ { nodes: sourceASTs(...references) },
812
816
  ));
813
817
  }
814
818
  }
@@ -43,7 +43,7 @@ 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 { withModifiedErrorMessage } from "./error";
46
+ import { 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.';
@@ -325,7 +325,7 @@ export function typeFromAST(schema: Schema, node: TypeNode): Type {
325
325
  default:
326
326
  const type = schema.type(node.name.value);
327
327
  if (!type) {
328
- throw new GraphQLError(`Unknown type "${node.name.value}"`, node);
328
+ throw ERRORS.INVALID_GRAPHQL.err(`Unknown type "${node.name.value}"`, { nodes: node });
329
329
  }
330
330
  return type;
331
331
  }
@@ -477,9 +477,7 @@ abstract class Element<TParent extends SchemaElement<any, any> | Schema | Direct
477
477
  // to a schema could bring a whole hierarchy of types and directives for instance). If they are attached, it only work if
478
478
  // it's to the same schema, but you have to check.
479
479
  // Overall, it's simpler to force attaching elements before you add other elements to them.
480
- if (!this.isAttached()) {
481
- throw error(`Cannot modify detached element ${this}`);
482
- }
480
+ assert(this.isAttached(), () => `Cannot modify detached element ${this}`);
483
481
  }
484
482
  }
485
483
 
@@ -534,7 +532,7 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
534
532
  if (!def) {
535
533
  throw this.schema().blueprint.onGraphQLJSValidationError(
536
534
  this.schema(),
537
- new GraphQLError(`Unknown directive "@${nameOrDef}".`)
535
+ ERRORS.INVALID_GRAPHQL.err(`Unknown directive "@${nameOrDef}".`)
538
536
  );
539
537
  }
540
538
  if (Array.isArray(def)) {
@@ -588,9 +586,7 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
588
586
  protected abstract removeTypeReference(type: NamedType): void;
589
587
 
590
588
  protected checkRemoval() {
591
- if (this.isElementBuiltIn() && !Schema.prototype['canModifyBuiltIn'].call(this.schema())) {
592
- throw error(`Cannot modify built-in ${this}`);
593
- }
589
+ assert(!this.isElementBuiltIn() || Schema.prototype['canModifyBuiltIn'].call(this.schema()), () => `Cannot modify built-in ${this}`);
594
590
  // We allow removals even on detached element because that doesn't particularly create issues (and we happen to do such
595
591
  // removals on detached internally; though of course we could refactor the code if we wanted).
596
592
  }
@@ -601,17 +597,13 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
601
597
  // Ensure this element (the modified one), is not a built-in, or part of one.
602
598
  let thisElement: SchemaElement<TOwnType, any> | Schema | undefined = this;
603
599
  while (thisElement && thisElement instanceof SchemaElement) {
604
- if (thisElement.isElementBuiltIn()) {
605
- throw error(`Cannot modify built-in (or part of built-in) ${this}`);
606
- }
600
+ assert(!thisElement.isElementBuiltIn(), () => `Cannot modify built-in (or part of built-in) ${this}`);
607
601
  thisElement = thisElement.parent;
608
602
  }
609
603
  }
610
604
  if (addedElement && addedElement.isAttached()) {
611
605
  const thatSchema = addedElement.schema();
612
- if (thatSchema && thatSchema != this.schema()) {
613
- throw error(`Cannot add element ${addedElement} to ${this} as it is attached to another schema`);
614
- }
606
+ assert(!thatSchema || thatSchema === this.schema(), () => `Cannot add element ${addedElement} to ${this} as it is attached to another schema`);
615
607
  }
616
608
  }
617
609
  }
@@ -677,9 +669,7 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
677
669
  if (this._extensions.has(extension)) {
678
670
  return extension;
679
671
  }
680
- if (extension.extendedElement) {
681
- throw error(`Cannot add extension to type ${this}: it is already added to another type`);
682
- }
672
+ assert(!extension.extendedElement, () => `Cannot add extension to type ${this}: it is already added to another type`);
683
673
  this._extensions.add(extension);
684
674
  Extension.prototype['setExtendedElement'].call(extension, this);
685
675
  this.onModification();
@@ -830,10 +820,6 @@ export abstract class NamedSchemaElementWithType<TType extends Type, TOwnType ex
830
820
  }
831
821
  }
832
822
 
833
- function error(message: string): GraphQLError {
834
- return new GraphQLError(message);
835
- }
836
-
837
823
  abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends Element<TExtended> {
838
824
  private _extension?: Extension<TExtended>;
839
825
 
@@ -848,9 +834,7 @@ abstract class BaseExtensionMember<TExtended extends ExtendableElement> extends
848
834
  setOfExtension(extension: Extension<TExtended> | undefined) {
849
835
  this.checkUpdate();
850
836
  // See similar comment on FieldDefinition.setOfExtension for why we have to cast.
851
- if (extension && !this._parent?.extensions().has(extension as any)) {
852
- throw error(`Cannot set object as part of the provided extension: it is not an extension of parent ${this.parent}`);
853
- }
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}`);
854
838
  this._extension = extension;
855
839
  }
856
840
 
@@ -980,7 +964,7 @@ export class CoreFeatures {
980
964
  this.add(coreItself);
981
965
  const coreDef = findCoreSpecVersion(coreItself.url);
982
966
  if (!coreDef) {
983
- throw error(`Schema uses unknown version ${coreItself.url.version} of the ${coreItself.url.name} spec`);
967
+ throw ERRORS.UNKNOWN_LINK_VERSION.err(`Schema uses unknown version ${coreItself.url.version} of the ${coreItself.url.name} spec`);
984
968
  }
985
969
  this.coreDefinition = coreDef;
986
970
  }
@@ -1010,7 +994,8 @@ export class CoreFeatures {
1010
994
  const url = this.coreDefinition.extractFeatureUrl(args);
1011
995
  const existing = this.byIdentity.get(url.identity);
1012
996
  if (existing) {
1013
- throw error(`Duplicate inclusion of feature ${url.identity}`);
997
+ // TODO: we may want to lossen that limitation at some point. Including the same feature for 2 different major versions should be ok.
998
+ throw ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Duplicate inclusion of feature ${url.identity}`);
1014
999
  }
1015
1000
  const imports = extractCoreFeatureImports(url, typedDirective);
1016
1001
  const feature = new CoreFeature(url, args.as ?? url.name, directive, imports, args.for);
@@ -1335,24 +1320,16 @@ export class Schema {
1335
1320
  const existing = this.type(type.name);
1336
1321
  if (existing) {
1337
1322
  // Like for directive, we let user shadow built-in types, but the definition must be valid.
1338
- if (existing.isBuiltIn) {
1339
- } else {
1340
- throw error(`Type ${type} already exists in this schema`);
1341
- }
1323
+ assert(existing.isBuiltIn, () => `Type ${type} already exists in this schema`);
1342
1324
  }
1343
1325
  if (type.isAttached()) {
1344
1326
  // For convenience, let's not error out on adding an already added type.
1345
- if (type.parent == this) {
1346
- return type;
1347
- }
1348
- throw error(`Cannot add type ${type} to this schema; it is already attached to another schema`);
1327
+ assert(type.parent == this, () => `Cannot add type ${type} to this schema; it is already attached to another schema`);
1328
+ return type;
1349
1329
  }
1350
1330
  if (type.isBuiltIn) {
1351
- if (!this.isConstructed) {
1352
- this._builtInTypes.set(type.name, type);
1353
- } else {
1354
- throw error(`Cannot add built-in ${type} to this schema (built-ins can only be added at schema construction time)`);
1355
- }
1331
+ assert(!this.isConstructed, `Cannot add built-in ${type} to this schema (built-ins can only be added at schema construction time)`);
1332
+ this._builtInTypes.set(type.name, type);
1356
1333
  } else {
1357
1334
  this._types.set(type.name, type);
1358
1335
  }
@@ -1424,22 +1401,15 @@ export class Schema {
1424
1401
  const existing = this.directive(definition.name);
1425
1402
  // Note that we allow the schema to define a built-in manually (and the manual definition will shadow the
1426
1403
  // built-in one). It's just that validation will ensure the definition ends up the one expected.
1427
- if (existing && !existing.isBuiltIn) {
1428
- throw error(`Directive ${definition} already exists in this schema`);
1429
- }
1404
+ assert(!existing || existing.isBuiltIn, () => `Directive ${definition} already exists in this schema`);
1430
1405
  if (definition.isAttached()) {
1431
1406
  // For convenience, let's not error out on adding an already added directive.
1432
- if (definition.parent == this) {
1433
- return definition;
1434
- }
1435
- throw error(`Cannot add directive ${definition} to this schema; it is already attached to another schema`);
1407
+ assert(definition.parent == this, () => `Cannot add directive ${definition} to this schema; it is already attached to another schema`);
1408
+ return definition;
1436
1409
  }
1437
1410
  if (definition.isBuiltIn) {
1438
- if (!this.isConstructed) {
1439
- this._builtInDirectives.set(definition.name, definition);
1440
- } else {
1441
- throw error(`Cannot add built-in ${definition} to this schema (built-ins can only be added at schema construction time)`);
1442
- }
1411
+ assert(!this.isConstructed, () => `Cannot add built-in ${definition} to this schema (built-ins can only be added at schema construction time)`);
1412
+ this._builtInDirectives.set(definition.name, definition);
1443
1413
  } else {
1444
1414
  this._directives.set(definition.name, definition);
1445
1415
  }
@@ -1525,7 +1495,8 @@ export class Schema {
1525
1495
  */
1526
1496
  elementByCoordinate(coordinate: string): NamedSchemaElement<any, any, any> | undefined {
1527
1497
  if (!coordinate.match(coordinateRegexp)) {
1528
- throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1498
+ // To be fair, graphQL coordinate is not yet officially part of the spec but well...
1499
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1529
1500
  }
1530
1501
 
1531
1502
  const argStartIdx = coordinate.indexOf('(');
@@ -1538,7 +1509,7 @@ export class Schema {
1538
1509
  const isDirective = typeOrDirectiveName.startsWith('@');
1539
1510
  if (isDirective) {
1540
1511
  if (fieldOrEnumName) {
1541
- throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1512
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1542
1513
  }
1543
1514
  const directive = this.directive(typeOrDirectiveName.slice(1));
1544
1515
  return argName ? directive?.argument(argName) : directive;
@@ -1554,16 +1525,16 @@ export class Schema {
1554
1525
  return argName ? field?.argument(argName) : field;
1555
1526
  case 'InputObjectType':
1556
1527
  if (argName) {
1557
- throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1528
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1558
1529
  }
1559
1530
  return type.field(fieldOrEnumName);
1560
1531
  case 'EnumType':
1561
1532
  if (argName) {
1562
- throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1533
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1563
1534
  }
1564
1535
  return type.value(fieldOrEnumName);
1565
1536
  default:
1566
- throw error(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1537
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid argument "${coordinate}: it is not a syntactically valid graphQL coordinate."`);
1567
1538
  }
1568
1539
  }
1569
1540
  }
@@ -1603,7 +1574,7 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
1603
1574
  const coreFeatures = schema.coreFeatures;
1604
1575
  if (isCoreSpecDirectiveApplication(applied)) {
1605
1576
  if (coreFeatures) {
1606
- throw error(`Invalid duplicate application of @core/@link`);
1577
+ throw ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Invalid duplicate application of @core/@link`);
1607
1578
  }
1608
1579
  const schemaDirective = applied as Directive<SchemaDefinition, CoreOrLinkDirectiveArgs>;
1609
1580
  const args = schemaDirective.arguments();
@@ -1636,9 +1607,9 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
1636
1607
  this.checkUpdate();
1637
1608
  const obj = this.schema().type(nameOrType);
1638
1609
  if (!obj) {
1639
- throw new GraphQLError(`Cannot set schema ${rootKind} root to unknown type ${nameOrType}`);
1610
+ throw ERRORS.INVALID_GRAPHQL.err(`Cannot set schema ${rootKind} root to unknown type ${nameOrType}`);
1640
1611
  } else if (obj.kind != 'ObjectType') {
1641
- throw new GraphQLError(`${defaultRootName(rootKind)} root type must be an Object type${rootKind === 'query' ? '' : ' if provided'}, it cannot be set to ${nameOrType} (an ${obj.kind}).`);
1612
+ throw ERRORS.INVALID_GRAPHQL.err(`${defaultRootName(rootKind)} root type must be an Object type${rootKind === 'query' ? '' : ' if provided'}, it cannot be set to ${nameOrType} (an ${obj.kind}).`);
1642
1613
  }
1643
1614
  toSet = new RootType(rootKind, obj);
1644
1615
  } else {
@@ -1670,9 +1641,7 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
1670
1641
  if (this._extensions.has(extension)) {
1671
1642
  return extension;
1672
1643
  }
1673
- if (extension.extendedElement) {
1674
- throw error(`Cannot add extension to this schema: extension is already added to another schema`);
1675
- }
1644
+ assert(!extension.extendedElement, 'Cannot add extension to this schema: extension is already added to another schema');
1676
1645
  this._extensions.add(extension);
1677
1646
  Extension.prototype['setExtendedElement'].call(extension, this);
1678
1647
  this.onModification();
@@ -1804,9 +1773,9 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
1804
1773
  this.checkUpdate();
1805
1774
  const maybeItf = this.schema().type(nameOrItfOrItfImpl);
1806
1775
  if (!maybeItf) {
1807
- throw new GraphQLError(`Cannot implement unknown type ${nameOrItfOrItfImpl}`);
1776
+ throw ERRORS.INVALID_GRAPHQL.err(`Cannot implement unknown type ${nameOrItfOrItfImpl}`);
1808
1777
  } else if (maybeItf.kind != 'InterfaceType') {
1809
- throw new GraphQLError(`Cannot implement non-interface type ${nameOrItfOrItfImpl} (of type ${maybeItf.kind})`);
1778
+ throw ERRORS.INVALID_GRAPHQL.err(`Cannot implement non-interface type ${nameOrItfOrItfImpl} (of type ${maybeItf.kind})`);
1810
1779
  }
1811
1780
  itf = maybeItf;
1812
1781
  } else {
@@ -1878,10 +1847,10 @@ abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSche
1878
1847
  toAdd = nameOrField;
1879
1848
  }
1880
1849
  if (this.field(toAdd.name)) {
1881
- throw error(`Field ${toAdd.name} already exists on ${this}`);
1850
+ throw ERRORS.INVALID_GRAPHQL.err(`Field ${toAdd.name} already exists on ${this}`);
1882
1851
  }
1883
1852
  if (type && !isOutputType(type)) {
1884
- throw error(`Invalid input type ${type} for field ${toAdd.name}: object and interface field types should be output types.`);
1853
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid input type ${type} for field ${toAdd.name}: object and interface field types should be output types.`);
1885
1854
  }
1886
1855
  this._fields.set(toAdd.name, toAdd);
1887
1856
  this._cachedNonBuiltInFields = undefined;
@@ -2053,9 +2022,9 @@ export class UnionType extends BaseNamedType<OutputTypeReferencer, UnionType> {
2053
2022
  this.checkUpdate();
2054
2023
  const maybeObj = this.schema().type(nameOrTypeOrMember);
2055
2024
  if (!maybeObj) {
2056
- throw new GraphQLError(`Cannot add unknown type ${nameOrTypeOrMember} as member of union type ${this.name}`);
2025
+ throw ERRORS.INVALID_GRAPHQL.err(`Cannot add unknown type ${nameOrTypeOrMember} as member of union type ${this.name}`);
2057
2026
  } else if (maybeObj.kind != 'ObjectType') {
2058
- throw new GraphQLError(`Cannot add non-object type ${nameOrTypeOrMember} (of type ${maybeObj.kind}) as member of union type ${this.name}`);
2027
+ throw ERRORS.INVALID_GRAPHQL.err(`Cannot add non-object type ${nameOrTypeOrMember} (of type ${maybeObj.kind}) as member of union type ${this.name}`);
2059
2028
  }
2060
2029
  obj = maybeObj;
2061
2030
  } else {
@@ -2230,10 +2199,10 @@ export class InputObjectType extends BaseNamedType<InputTypeReferencer, InputObj
2230
2199
  const toAdd = typeof nameOrField === 'string' ? new InputFieldDefinition(nameOrField) : nameOrField;
2231
2200
  this.checkUpdate(toAdd);
2232
2201
  if (this.field(toAdd.name)) {
2233
- throw error(`Field ${toAdd.name} already exists on ${this}`);
2202
+ throw ERRORS.INVALID_GRAPHQL.err(`Field ${toAdd.name} already exists on ${this}`);
2234
2203
  }
2235
2204
  if (type && !isInputType(type)) {
2236
- throw error(`Invalid output type ${type} for field ${toAdd.name}: input field types should be input types.`);
2205
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid output type ${type} for field ${toAdd.name}: input field types should be input types.`);
2237
2206
  }
2238
2207
  this._fields.set(toAdd.name, toAdd);
2239
2208
  this._cachedFieldsArray = undefined;
@@ -2385,15 +2354,15 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
2385
2354
  // For some reason (bad codegen, maybe?), some users have field where a arg is defined more than one. And this doesn't seem rejected by
2386
2355
  // graphQL (?). So we accept it, but ensure the types/default values are the same.
2387
2356
  if (type && existing.type && !sameType(type, existing.type)) {
2388
- throw error(`Argument ${toAdd.name} already exists on field ${this.name} with a different type (${existing.type})`);
2357
+ throw ERRORS.INVALID_GRAPHQL.err(`Argument ${toAdd.name} already exists on field ${this.name} with a different type (${existing.type})`);
2389
2358
  }
2390
2359
  if (defaultValue && (!existing.defaultValue || !valueEquals(defaultValue, existing.defaultValue))) {
2391
- throw error(`Argument ${toAdd.name} already exists on field ${this.name} with a different default value (${valueToString(existing.defaultValue)})`);
2360
+ throw ERRORS.INVALID_GRAPHQL.err(`Argument ${toAdd.name} already exists on field ${this.name} with a different default value (${valueToString(existing.defaultValue)})`);
2392
2361
  }
2393
2362
  return existing;
2394
2363
  }
2395
2364
  if (type && !isInputType(type)) {
2396
- throw error(`Invalid output type ${type} for argument ${toAdd.name} of ${this}: arguments should be input types.`);
2365
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid output type ${type} for argument ${toAdd.name} of ${this}: arguments should be input types.`);
2397
2366
  }
2398
2367
  this._args.set(toAdd.name, toAdd);
2399
2368
  Element.prototype['setParent'].call(toAdd, this);
@@ -2416,9 +2385,10 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
2416
2385
  this.checkUpdate();
2417
2386
  // It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
2418
2387
  // the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
2419
- if (extension && !this._parent?.extensions().has(extension as any)) {
2420
- throw error(`Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`);
2421
- }
2388
+ assert(
2389
+ !extension || this._parent?.extensions().has(extension as any),
2390
+ () => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`
2391
+ );
2422
2392
  this._extension = extension;
2423
2393
  this.onModification();
2424
2394
  }
@@ -2527,9 +2497,10 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
2527
2497
  this.checkUpdate();
2528
2498
  // It seems typescript "expand" `TParent` below into `ObjectType | Interface`, so it essentially lose the context that
2529
2499
  // the `TParent` in `Extension<TParent>` will always match. Hence the `as any`.
2530
- if (extension && !this._parent?.extensions().has(extension as any)) {
2531
- throw error(`Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`);
2532
- }
2500
+ assert(
2501
+ !extension || this._parent?.extensions().has(extension as any),
2502
+ () => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`,
2503
+ );
2533
2504
  this._extension = extension;
2534
2505
  this.onModification();
2535
2506
  }
@@ -2676,9 +2647,10 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
2676
2647
 
2677
2648
  setOfExtension(extension: Extension<EnumType> | undefined) {
2678
2649
  this.checkUpdate();
2679
- if (extension && !this._parent?.extensions().has(extension)) {
2680
- throw error(`Cannot mark field ${this.name} as part of the provided extension: it is not an extension of field parent type ${this.parent}`);
2681
- }
2650
+ assert(
2651
+ !extension || this._parent?.extensions().has(extension as any),
2652
+ () => `Cannot mark field ${this.name} as part of the provided extension: it is not an extension of enum value parent type ${this.parent}`,
2653
+ );
2682
2654
  this._extension = extension;
2683
2655
  this.onModification();
2684
2656
  }
@@ -2767,7 +2739,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
2767
2739
  toAdd = nameOrArg;
2768
2740
  }
2769
2741
  if (this.argument(toAdd.name)) {
2770
- throw error(`Argument ${toAdd.name} already exists on field ${this.name}`);
2742
+ throw ERRORS.INVALID_GRAPHQL.err(`Argument ${toAdd.name} already exists on field ${this.name}`);
2771
2743
  }
2772
2744
  this._args.set(toAdd.name, toAdd);
2773
2745
  Element.prototype['setParent'].call(toAdd, this);
@@ -2920,9 +2892,7 @@ export class Directive<
2920
2892
  return this._args;
2921
2893
  }
2922
2894
  const definition = this.definition;
2923
- if (!definition) {
2924
- throw error(`Cannot include default values for arguments: cannot find directive definition for ${this.name}`);
2925
- }
2895
+ assert(definition, () => `Cannot include default values for arguments: cannot find directive definition for ${this.name}`);
2926
2896
  const updated = Object.create(null);
2927
2897
  for (const argDef of definition.arguments()) {
2928
2898
  updated[argDef.name] = withDefaultValues(this._args[argDef.name], argDef);
@@ -2978,13 +2948,11 @@ export class Directive<
2978
2948
  this.checkUpdate();
2979
2949
  if (extension) {
2980
2950
  const parent = this.parent;
2981
- if (parent instanceof SchemaDefinition || parent instanceof BaseNamedType) {
2982
- if (!parent.extensions().has(extension)) {
2983
- throw error(`Cannot mark directive ${this.name} as part of the provided extension: it is not an extension of parent ${parent}`);
2984
- }
2985
- } else {
2986
- throw error(`Can only mark directive parts of extensions when directly apply to type or schema definition.`);
2987
- }
2951
+ assert(
2952
+ parent instanceof SchemaDefinition || parent instanceof BaseNamedType,
2953
+ 'Can only mark directive parts of extensions when directly apply to type or schema definition.'
2954
+ );
2955
+ assert(parent.extensions().has(extension), () => `Cannot mark directive ${this.name} as part of the provided extension: it is not an extension of parent ${parent}`);
2988
2956
  }
2989
2957
  this._extension = extension;
2990
2958
  this.onModification();
@@ -3252,7 +3220,7 @@ export function variableDefinitionsFromAST(schema: Schema, definitionNodes: read
3252
3220
  for (const definitionNode of definitionNodes) {
3253
3221
  if (!definitions.add(variableDefinitionFromAST(schema, definitionNode))) {
3254
3222
  const name = definitionNode.variable.name.value;
3255
- throw new GraphQLError(`Duplicate definition for variable ${name}`, definitionNodes.filter(n => n.variable.name.value === name));
3223
+ throw ERRORS.INVALID_GRAPHQL.err(`Duplicate definition for variable ${name}`, { nodes: definitionNodes.filter(n => n.variable.name.value === name) });
3256
3224
  }
3257
3225
  }
3258
3226
  return definitions;
@@ -3262,7 +3230,7 @@ export function variableDefinitionFromAST(schema: Schema, definitionNode: Variab
3262
3230
  const variable = new Variable(definitionNode.variable.name.value);
3263
3231
  const type = typeFromAST(schema, definitionNode.type);
3264
3232
  if (!isInputType(type)) {
3265
- throw new GraphQLError(`Invalid type "${type}" for variable $${variable}: not an input type`, definitionNode.type);
3233
+ throw ERRORS.INVALID_GRAPHQL.err(`Invalid type "${type}" for variable $${variable}: not an input type`, { nodes: definitionNode.type });
3266
3234
  }
3267
3235
  const def = new VariableDefinition(
3268
3236
  schema,