@apollo/federation-internals 2.0.6-rc.1 → 2.1.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 (72) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/buildSchema.d.ts.map +1 -1
  3. package/dist/buildSchema.js +11 -10
  4. package/dist/buildSchema.js.map +1 -1
  5. package/dist/coreSpec.d.ts.map +1 -1
  6. package/dist/coreSpec.js +15 -42
  7. package/dist/coreSpec.js.map +1 -1
  8. package/dist/definitions.d.ts +5 -1
  9. package/dist/definitions.d.ts.map +1 -1
  10. package/dist/definitions.js +68 -103
  11. package/dist/definitions.js.map +1 -1
  12. package/dist/directiveAndTypeSpecification.js +14 -48
  13. package/dist/directiveAndTypeSpecification.js.map +1 -1
  14. package/dist/error.d.ts +20 -17
  15. package/dist/error.d.ts.map +1 -1
  16. package/dist/error.js +42 -7
  17. package/dist/error.js.map +1 -1
  18. package/dist/federation.d.ts +4 -1
  19. package/dist/federation.d.ts.map +1 -1
  20. package/dist/federation.js +77 -127
  21. package/dist/federation.js.map +1 -1
  22. package/dist/federationSpec.d.ts +13 -9
  23. package/dist/federationSpec.d.ts.map +1 -1
  24. package/dist/federationSpec.js +27 -5
  25. package/dist/federationSpec.js.map +1 -1
  26. package/dist/inaccessibleSpec.js +43 -65
  27. package/dist/inaccessibleSpec.js.map +1 -1
  28. package/dist/operations.d.ts +7 -4
  29. package/dist/operations.d.ts.map +1 -1
  30. package/dist/operations.js +54 -29
  31. package/dist/operations.js.map +1 -1
  32. package/dist/schemaUpgrader.js +2 -8
  33. package/dist/schemaUpgrader.js.map +1 -1
  34. package/dist/supergraphs.d.ts.map +1 -1
  35. package/dist/supergraphs.js +4 -4
  36. package/dist/supergraphs.js.map +1 -1
  37. package/dist/tagSpec.d.ts.map +1 -1
  38. package/dist/tagSpec.js +1 -3
  39. package/dist/tagSpec.js.map +1 -1
  40. package/dist/utils.d.ts +1 -0
  41. package/dist/utils.d.ts.map +1 -1
  42. package/dist/utils.js +3 -1
  43. package/dist/utils.js.map +1 -1
  44. package/dist/validate.d.ts.map +1 -1
  45. package/dist/validate.js +26 -22
  46. package/dist/validate.js.map +1 -1
  47. package/dist/validation/KnownTypeNamesInFederationRule.js +1 -1
  48. package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
  49. package/dist/values.d.ts.map +1 -1
  50. package/dist/values.js +19 -18
  51. package/dist/values.js.map +1 -1
  52. package/package.json +3 -3
  53. package/src/__tests__/operations.test.ts +181 -99
  54. package/src/__tests__/subgraphValidation.test.ts +77 -0
  55. package/src/buildSchema.ts +11 -18
  56. package/src/coreSpec.ts +47 -43
  57. package/src/definitions.ts +86 -105
  58. package/src/directiveAndTypeSpecification.ts +50 -48
  59. package/src/error.ts +94 -41
  60. package/src/federation.ts +151 -135
  61. package/src/federationSpec.ts +32 -5
  62. package/src/inaccessibleSpec.ts +207 -191
  63. package/src/operations.ts +115 -43
  64. package/src/schemaUpgrader.ts +8 -8
  65. package/src/supergraphs.ts +5 -4
  66. package/src/tagSpec.ts +2 -3
  67. package/src/utils.ts +6 -0
  68. package/src/validate.ts +60 -52
  69. package/src/validation/KnownTypeNamesInFederationRule.ts +1 -1
  70. package/src/values.ts +19 -18
  71. package/tsconfig.test.tsbuildinfo +1 -1
  72. package/tsconfig.tsbuildinfo +1 -1
@@ -13,133 +13,215 @@ function parseSchema(schema: string): Schema {
13
13
  }
14
14
  }
15
15
 
16
- test('fragments optimization of selection sets', () => {
17
- const schema = parseSchema(`
18
- type Query {
19
- t: T1
20
- }
21
-
22
- interface I {
23
- b: Int
24
- }
16
+ describe('fragments optimization', () => {
17
+ test('handles fragments using other fragments', () => {
18
+ const schema = parseSchema(`
19
+ type Query {
20
+ t: T1
21
+ }
25
22
 
26
- type T1 {
27
- a: Int
28
- b: Int
29
- u: U
30
- }
23
+ interface I {
24
+ b: Int
25
+ }
31
26
 
32
- type T2 {
33
- x: String
34
- y: String
35
- b: Int
36
- u: U
37
- }
27
+ type T1 {
28
+ a: Int
29
+ b: Int
30
+ u: U
31
+ }
38
32
 
39
- union U = T1 | T2
40
- `);
33
+ type T2 {
34
+ x: String
35
+ y: String
36
+ b: Int
37
+ u: U
38
+ }
41
39
 
42
- const operation = parseOperation(schema, `
43
- fragment OnT1 on T1 {
44
- a
45
- b
46
- }
40
+ union U = T1 | T2
41
+ `);
47
42
 
48
- fragment OnT2 on T2 {
49
- x
50
- y
51
- }
43
+ const operation = parseOperation(schema, `
44
+ fragment OnT1 on T1 {
45
+ a
46
+ b
47
+ }
52
48
 
53
- fragment OnI on I {
54
- b
55
- }
49
+ fragment OnT2 on T2 {
50
+ x
51
+ y
52
+ }
56
53
 
57
- fragment OnU on U {
58
- ...OnI
59
- ...OnT1
60
- ...OnT2
61
- }
54
+ fragment OnI on I {
55
+ b
56
+ }
62
57
 
63
- query {
64
- t {
58
+ fragment OnU on U {
59
+ ...OnI
65
60
  ...OnT1
66
61
  ...OnT2
67
- ...OnI
68
- u {
69
- ...OnU
62
+ }
63
+
64
+ query {
65
+ t {
66
+ ...OnT1
67
+ ...OnT2
68
+ ...OnI
69
+ u {
70
+ ...OnU
71
+ }
70
72
  }
71
73
  }
72
- }
73
- `);
74
+ `);
75
+
76
+ const withoutFragments = parseOperation(schema, operation.toString(true, true));
77
+ expect(withoutFragments.toString()).toMatchString(`
78
+ {
79
+ t {
80
+ ... on T1 {
81
+ a
82
+ b
83
+ }
84
+ ... on T2 {
85
+ x
86
+ y
87
+ }
88
+ ... on I {
89
+ b
90
+ }
91
+ u {
92
+ ... on U {
93
+ ... on I {
94
+ b
95
+ }
96
+ ... on T1 {
97
+ a
98
+ b
99
+ }
100
+ ... on T2 {
101
+ x
102
+ y
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ `);
109
+
110
+ const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
111
+ // Note that we expect onU to *not* be recreated because, by default, optimize only
112
+ // add add back a fragment if it is used at least twice (otherwise, the fragment just
113
+ // make the query bigger).
114
+ expect(optimized.toString()).toMatchString(`
115
+ fragment OnT1 on T1 {
116
+ a
117
+ b
118
+ }
74
119
 
75
- const withoutFragments = parseOperation(schema, operation.toString(true, true));
76
- expect(withoutFragments.toString()).toMatchString(`
77
- {
78
- t {
79
- ... on T1 {
80
- a
81
- b
120
+ fragment OnT2 on T2 {
121
+ x
122
+ y
123
+ }
124
+
125
+ fragment OnI on I {
126
+ b
127
+ }
128
+
129
+ {
130
+ t {
131
+ ...OnT1
132
+ ...OnT2
133
+ ...OnI
134
+ u {
135
+ ...OnI
136
+ ...OnT1
137
+ ...OnT2
138
+ }
82
139
  }
83
- ... on T2 {
140
+ }
141
+ `);
142
+ });
143
+
144
+ test('handles fragments with nested selections', () => {
145
+ const schema = parseSchema(`
146
+ type Query {
147
+ t1a: T1
148
+ t2a: T1
149
+ }
150
+
151
+ type T1 {
152
+ t2: T2
153
+ }
154
+
155
+ type T2 {
156
+ x: String
157
+ y: String
158
+ }
159
+ `);
160
+
161
+ const operation = parseOperation(schema, `
162
+ fragment OnT1 on T1 {
163
+ t2 {
84
164
  x
85
- y
86
165
  }
87
- ... on I {
88
- b
166
+ }
167
+
168
+ query {
169
+ t1a {
170
+ ...OnT1
171
+ t2 {
172
+ y
173
+ }
89
174
  }
90
- u {
91
- ... on U {
92
- ... on I {
93
- b
94
- }
95
- ... on T1 {
96
- a
97
- b
175
+ t2a {
176
+ ...OnT1
177
+ }
178
+ }
179
+ `);
180
+
181
+ const withoutFragments = parseOperation(schema, operation.toString(true, true));
182
+ expect(withoutFragments.toString()).toMatchString(`
183
+ {
184
+ t1a {
185
+ ... on T1 {
186
+ t2 {
187
+ x
98
188
  }
99
- ... on T2 {
189
+ }
190
+ t2 {
191
+ y
192
+ }
193
+ }
194
+ t2a {
195
+ ... on T1 {
196
+ t2 {
100
197
  x
101
- y
102
198
  }
103
199
  }
104
200
  }
105
201
  }
106
- }
107
- `);
202
+ `);
108
203
 
109
- const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
110
- // Note that we expect onU to *not* be recreated because, by default, optimize only
111
- // add add back a fragment if it is used at least twice (otherwise, the fragment just
112
- // make the query bigger).
113
- expect(optimized.toString()).toMatchString(`
114
- fragment OnT1 on T1 {
115
- a
116
- b
117
- }
118
-
119
- fragment OnT2 on T2 {
120
- x
121
- y
122
- }
123
-
124
- fragment OnI on I {
125
- b
126
- }
204
+ const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
205
+ expect(optimized.toString()).toMatchString(`
206
+ fragment OnT1 on T1 {
207
+ t2 {
208
+ x
209
+ }
210
+ }
127
211
 
128
- {
129
- t {
130
- ...OnT1
131
- ...OnT2
132
- ...OnI
133
- u {
134
- ... on U {
135
- ...OnI
136
- ...OnT1
137
- ...OnT2
212
+ {
213
+ t1a {
214
+ ...OnT1
215
+ t2 {
216
+ y
138
217
  }
139
218
  }
219
+ t2a {
220
+ ...OnT1
221
+ }
140
222
  }
141
- }
142
- `);
223
+ `);
224
+ });
143
225
  });
144
226
 
145
227
  describe('selection set freezing', () => {
@@ -385,6 +385,81 @@ describe('fieldset-based directives', () => {
385
385
  ['KEY_FIELDS_SELECT_INVALID_TYPE', '[S] On type "T", for @key(fields: "f"): field "T.f" is a Union type which is not allowed in @key'],
386
386
  ]);
387
387
  });
388
+
389
+ it('rejects directive applications in @key', () => {
390
+ const subgraph = gql`
391
+ type Query {
392
+ t: T
393
+ }
394
+
395
+ type T @key(fields: "v { x ... @include(if: false) { y }}") {
396
+ v: V
397
+ }
398
+
399
+ type V {
400
+ x: Int
401
+ y: Int
402
+ }
403
+ `
404
+ expect(buildForErrors(subgraph)).toStrictEqual([
405
+ ['KEY_DIRECTIVE_IN_FIELDS_ARG', '[S] On type "T", for @key(fields: "v { x ... @include(if: false) { y }}"): cannot have directive applications in the @key(fields:) argument but found @include(if: false).'],
406
+ ]);
407
+ });
408
+
409
+ it('rejects directive applications in @provides', () => {
410
+ const subgraph = gql`
411
+ type Query {
412
+ t: T @provides(fields: "v { ... on V @skip(if: true) { x y } }")
413
+ }
414
+
415
+ type T @key(fields: "id") {
416
+ id: ID
417
+ v: V @external
418
+ }
419
+
420
+ type V {
421
+ x: Int
422
+ y: Int
423
+ }
424
+ `
425
+ expect(buildForErrors(subgraph)).toStrictEqual([
426
+ ['PROVIDES_DIRECTIVE_IN_FIELDS_ARG', '[S] On field "Query.t", for @provides(fields: "v { ... on V @skip(if: true) { x y } }"): cannot have directive applications in the @provides(fields:) argument but found @skip(if: true).'],
427
+ ]);
428
+ });
429
+
430
+ it('rejects directive applications in @requires', () => {
431
+ const subgraph = gql`
432
+ type Query {
433
+ t: T
434
+ }
435
+
436
+ type T @key(fields: "id") {
437
+ id: ID
438
+ a: Int @requires(fields: "... @skip(if: false) { b }")
439
+ b: Int @external
440
+ }
441
+ `
442
+ expect(buildForErrors(subgraph)).toStrictEqual([
443
+ ['REQUIRES_DIRECTIVE_IN_FIELDS_ARG', '[S] On field "T.a", for @requires(fields: "... @skip(if: false) { b }"): cannot have directive applications in the @requires(fields:) argument but found @skip(if: false).'],
444
+ ]);
445
+ });
446
+
447
+ it('can collect multiple errors in a single `fields` argument', () => {
448
+ const subgraph = gql`
449
+ type Query {
450
+ t: T @provides(fields: "f(x: 3)")
451
+ }
452
+
453
+ type T @key(fields: "id") {
454
+ id: ID
455
+ f(x: Int): Int
456
+ }
457
+ `
458
+ expect(buildForErrors(subgraph)).toStrictEqual([
459
+ ['PROVIDES_FIELDS_HAS_ARGS', '[S] On field "Query.t", for @provides(fields: "f(x: 3)"): field T.f cannot be included because it has arguments (fields with argument are not allowed in @provides)'],
460
+ ['PROVIDES_FIELDS_MISSING_EXTERNAL', '[S] On field "Query.t", for @provides(fields: "f(x: 3)"): field "T.f" should not be part of a @provides since it is already provided by this subgraph (it is not marked @external)'],
461
+ ]);
462
+ });
388
463
  });
389
464
 
390
465
  describe('root types', () => {
@@ -561,6 +636,8 @@ describe('@core/@link handling', () => {
561
636
 
562
637
  directive @federation__override(from: String!) on FIELD_DEFINITION
563
638
 
639
+ directive @federation__composeDirective(name: String) repeatable on SCHEMA
640
+
564
641
  type T
565
642
  @key(fields: "k")
566
643
  {
@@ -55,6 +55,7 @@ import {
55
55
  errorCauses,
56
56
  NamedSchemaElement,
57
57
  } from "./definitions";
58
+ import { ERRORS, withModifiedErrorNodes } from "./error";
58
59
 
59
60
  function buildValue(value?: ValueNode): any {
60
61
  return value ? valueFromASTUntyped(value) : undefined;
@@ -195,7 +196,7 @@ function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema:
195
196
  switch (definitionNode.kind) {
196
197
  case 'OperationDefinition':
197
198
  case 'FragmentDefinition':
198
- errors.push(new GraphQLError("Invalid executable definition found while building schema", definitionNode));
199
+ errors.push(ERRORS.INVALID_GRAPHQL.err("Invalid executable definition found while building schema", { nodes: definitionNode }));
199
200
  continue;
200
201
  case 'SchemaDefinition':
201
202
  schemaDefinitions.push(definitionNode);
@@ -220,7 +221,7 @@ function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema:
220
221
  type = schema.addType(newNamedType(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value));
221
222
  } else if (type.preserveEmptyDefinition) {
222
223
  // Note: we reuse the same error message than graphQL-js would output
223
- throw new GraphQLError(`There can be only one type named "${definitionNode.name.value}"`);
224
+ throw ERRORS.INVALID_GRAPHQL.err(`There can be only one type named "${definitionNode.name.value}"`);
224
225
  }
225
226
  // It's possible for the type definition to be empty, because it is valid graphQL to have:
226
227
  // type Foo
@@ -251,7 +252,7 @@ function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema:
251
252
  if (!existing) {
252
253
  schema.addType(newNamedType(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value));
253
254
  } else if (existing.isBuiltIn) {
254
- throw new GraphQLError(`Cannot extend built-in type "${definitionNode.name.value}"`);
255
+ throw ERRORS.INVALID_GRAPHQL.err(`Cannot extend built-in type "${definitionNode.name.value}"`);
255
256
  }
256
257
  break;
257
258
  case 'DirectiveDefinition':
@@ -281,7 +282,7 @@ function withoutTrailingDefinition(str: string): NamedTypeKind {
281
282
  function getReferencedType(node: NamedTypeNode, schema: Schema): NamedType {
282
283
  const type = schema.type(node.name.value);
283
284
  if (!type) {
284
- throw new GraphQLError(`Unknown type ${node.name.value}`, node);
285
+ throw ERRORS.INVALID_GRAPHQL.err(`Unknown type ${node.name.value}`, { nodes: node });
285
286
  }
286
287
  return type;
287
288
  }
@@ -294,15 +295,7 @@ function withNodeAttachedToError(operation: () => void, node: ASTNode, errors: G
294
295
  if (causes) {
295
296
  for (const cause of causes) {
296
297
  const allNodes: ASTNode | ASTNode[] = cause.nodes ? [node, ...cause.nodes] : node;
297
- errors.push(new GraphQLError(
298
- cause.message,
299
- allNodes,
300
- cause.source,
301
- cause.positions,
302
- cause.path,
303
- cause,
304
- cause.extensions
305
- ));
298
+ errors.push(withModifiedErrorNodes(cause, allNodes));
306
299
  }
307
300
  } else {
308
301
  throw e;
@@ -392,7 +385,7 @@ function buildNamedTypeInner(
392
385
  () => {
393
386
  const itfName = itfNode.name.value;
394
387
  if (fieldBasedType.implementsInterface(itfName)) {
395
- throw new GraphQLError(`Type "${type}" can only implement "${itfName}" once.`);
388
+ throw ERRORS.INVALID_GRAPHQL.err(`Type "${type}" can only implement "${itfName}" once.`);
396
389
  }
397
390
  fieldBasedType.addImplementedInterface(itfName).setOfExtension(extension);
398
391
  },
@@ -409,7 +402,7 @@ function buildNamedTypeInner(
409
402
  () => {
410
403
  const name = namedType.name.value;
411
404
  if (unionType.hasTypeMember(name)) {
412
- throw new GraphQLError(`Union type "${unionType}" can only include type "${name}" once.`);
405
+ throw ERRORS.INVALID_GRAPHQL.err(`Union type "${unionType}" can only include type "${name}" once.`);
413
406
  }
414
407
  unionType.addType(name).setOfExtension(extension);
415
408
  },
@@ -477,7 +470,7 @@ function validateOutputType(type: Type, what: string, node: ASTNode, errors: Gra
477
470
  if (isOutputType(type)) {
478
471
  return type;
479
472
  } else {
480
- errors.push(new GraphQLError(`The type of "${what}" must be Output Type but got "${type}", a ${type.kind}.`, node));
473
+ errors.push(ERRORS.INVALID_GRAPHQL.err(`The type of "${what}" must be Output Type but got "${type}", a ${type.kind}.`, { nodes: node }));
481
474
  return undefined;
482
475
  }
483
476
  }
@@ -486,7 +479,7 @@ function validateInputType(type: Type, what: string, node: ASTNode, errors: Grap
486
479
  if (isInputType(type)) {
487
480
  return type;
488
481
  } else {
489
- errors.push(new GraphQLError(`The type of "${what}" must be Input Type but got "${type}", a ${type.kind}.`, node));
482
+ errors.push(ERRORS.INVALID_GRAPHQL.err(`The type of "${what}" must be Input Type but got "${type}", a ${type.kind}.`, { nodes: node }));
490
483
  return undefined;
491
484
  }
492
485
  }
@@ -502,7 +495,7 @@ function buildTypeReferenceFromAST(typeNode: TypeNode, schema: Schema): Type {
502
495
  case Kind.NON_NULL_TYPE:
503
496
  const wrapped = buildTypeReferenceFromAST(typeNode.type, schema);
504
497
  if (wrapped.kind == Kind.NON_NULL_TYPE) {
505
- throw new GraphQLError(`Cannot apply the non-null operator (!) twice to the same type`, typeNode);
498
+ throw ERRORS.INVALID_GRAPHQL.err(`Cannot apply the non-null operator (!) twice to the same type`, { nodes: typeNode });
506
499
  }
507
500
  return new NonNullType(wrapped);
508
501
  default:
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
  }