graphql 16.9.0 → 16.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![GraphQLConf 2024 Banner: September 10-12, San Francisco. Hosted by the GraphQL Foundation](https://github.com/user-attachments/assets/2d048502-e5b2-4e9d-a02a-50b841824de6)](https://graphql.org/conf/2024/?utm_source=github&utm_medium=graphql_js&utm_campaign=readme)
2
+
1
3
  # GraphQL.js
2
4
 
3
5
  The JavaScript reference implementation for GraphQL, a query language for APIs created by Facebook.
@@ -14,6 +14,18 @@ import type { Source } from '../language/source';
14
14
  export interface GraphQLErrorExtensions {
15
15
  [attributeName: string]: unknown;
16
16
  }
17
+ /**
18
+ * Custom formatted extensions
19
+ *
20
+ * @remarks
21
+ * Use a unique identifier name for your extension, for example the name of
22
+ * your library or project. Do not use a shortened identifier as this increases
23
+ * the risk of conflicts. We recommend you add at most one extension field,
24
+ * an object which can contain all the values you need.
25
+ */
26
+ export interface GraphQLFormattedErrorExtensions {
27
+ [attributeName: string]: unknown;
28
+ }
17
29
  export interface GraphQLErrorOptions {
18
30
  nodes?: ReadonlyArray<ASTNode> | ASTNode | null;
19
31
  source?: Maybe<Source>;
@@ -122,9 +134,7 @@ export interface GraphQLFormattedError {
122
134
  * Reserved for implementors to extend the protocol however they see fit,
123
135
  * and hence there are no additional restrictions on its contents.
124
136
  */
125
- readonly extensions?: {
126
- [key: string]: unknown;
127
- };
137
+ readonly extensions?: GraphQLFormattedErrorExtensions;
128
138
  }
129
139
  /**
130
140
  * Prints a GraphQLError to a string, representing useful location information
package/error/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export type {
3
3
  GraphQLErrorOptions,
4
4
  GraphQLFormattedError,
5
5
  GraphQLErrorExtensions,
6
+ GraphQLFormattedErrorExtensions,
6
7
  } from './GraphQLError';
7
8
  export { syntaxError } from './syntaxError';
8
9
  export { locatedError } from './locatedError';
package/index.d.ts CHANGED
@@ -352,6 +352,7 @@ export type {
352
352
  GraphQLErrorOptions,
353
353
  GraphQLFormattedError,
354
354
  GraphQLErrorExtensions,
355
+ GraphQLFormattedErrorExtensions,
355
356
  } from './error/index';
356
357
  export {
357
358
  getIntrospectionQuery,
package/language/ast.d.ts CHANGED
@@ -162,6 +162,7 @@ export interface DocumentNode {
162
162
  readonly kind: Kind.DOCUMENT;
163
163
  readonly loc?: Location;
164
164
  readonly definitions: ReadonlyArray<DefinitionNode>;
165
+ readonly tokenCount?: number | undefined;
165
166
  }
166
167
  export declare type DefinitionNode =
167
168
  | ExecutableDefinitionNode
@@ -149,6 +149,7 @@ export declare class Parser {
149
149
  protected _lexer: Lexer;
150
150
  protected _tokenCounter: number;
151
151
  constructor(source: string | Source, options?: ParseOptions);
152
+ get tokenCount(): number;
152
153
  /**
153
154
  * Converts a name lex token into a name parse node.
154
155
  */
@@ -29,7 +29,12 @@ var _tokenKind = require('./tokenKind.js');
29
29
  */
30
30
  function parse(source, options) {
31
31
  const parser = new Parser(source, options);
32
- return parser.parseDocument();
32
+ const document = parser.parseDocument();
33
+ Object.defineProperty(document, 'tokenCount', {
34
+ enumerable: false,
35
+ value: parser.tokenCount,
36
+ });
37
+ return document;
33
38
  }
34
39
  /**
35
40
  * Given a string containing a GraphQL value (ex. `[42]`), parse the AST for
@@ -100,6 +105,10 @@ class Parser {
100
105
  this._options = options;
101
106
  this._tokenCounter = 0;
102
107
  }
108
+
109
+ get tokenCount() {
110
+ return this._tokenCounter;
111
+ }
103
112
  /**
104
113
  * Converts a name lex token into a name parse node.
105
114
  */
@@ -1535,10 +1544,10 @@ class Parser {
1535
1544
 
1536
1545
  const token = this._lexer.advance();
1537
1546
 
1538
- if (maxTokens !== undefined && token.kind !== _tokenKind.TokenKind.EOF) {
1547
+ if (token.kind !== _tokenKind.TokenKind.EOF) {
1539
1548
  ++this._tokenCounter;
1540
1549
 
1541
- if (this._tokenCounter > maxTokens) {
1550
+ if (maxTokens !== undefined && this._tokenCounter > maxTokens) {
1542
1551
  throw (0, _syntaxError.syntaxError)(
1543
1552
  this._lexer.source,
1544
1553
  token.start,
@@ -15,7 +15,12 @@ import { TokenKind } from './tokenKind.mjs';
15
15
  */
16
16
  export function parse(source, options) {
17
17
  const parser = new Parser(source, options);
18
- return parser.parseDocument();
18
+ const document = parser.parseDocument();
19
+ Object.defineProperty(document, 'tokenCount', {
20
+ enumerable: false,
21
+ value: parser.tokenCount,
22
+ });
23
+ return document;
19
24
  }
20
25
  /**
21
26
  * Given a string containing a GraphQL value (ex. `[42]`), parse the AST for
@@ -84,6 +89,10 @@ export class Parser {
84
89
  this._options = options;
85
90
  this._tokenCounter = 0;
86
91
  }
92
+
93
+ get tokenCount() {
94
+ return this._tokenCounter;
95
+ }
87
96
  /**
88
97
  * Converts a name lex token into a name parse node.
89
98
  */
@@ -1494,10 +1503,10 @@ export class Parser {
1494
1503
 
1495
1504
  const token = this._lexer.advance();
1496
1505
 
1497
- if (maxTokens !== undefined && token.kind !== TokenKind.EOF) {
1506
+ if (token.kind !== TokenKind.EOF) {
1498
1507
  ++this._tokenCounter;
1499
1508
 
1500
- if (this._tokenCounter > maxTokens) {
1509
+ if (maxTokens !== undefined && this._tokenCounter > maxTokens) {
1501
1510
  throw syntaxError(
1502
1511
  this._lexer.source,
1503
1512
  token.start,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphql",
3
- "version": "16.9.0",
3
+ "version": "16.10.0",
4
4
  "description": "A Query Language and Runtime which can target any service.",
5
5
  "license": "MIT",
6
6
  "main": "index",
package/type/validate.js CHANGED
@@ -175,8 +175,14 @@ function validateDirectives(context) {
175
175
  continue;
176
176
  } // Ensure they are named correctly.
177
177
 
178
- validateName(context, directive); // TODO: Ensure proper locations.
179
- // Ensure the arguments are valid.
178
+ validateName(context, directive);
179
+
180
+ if (directive.locations.length === 0) {
181
+ context.reportError(
182
+ `Directive @${directive.name} must include 1 or more locations.`,
183
+ directive.astNode,
184
+ );
185
+ } // Ensure the arguments are valid.
180
186
 
181
187
  for (const arg of directive.args) {
182
188
  // Ensure they are named correctly.
package/type/validate.mjs CHANGED
@@ -173,8 +173,14 @@ function validateDirectives(context) {
173
173
  continue;
174
174
  } // Ensure they are named correctly.
175
175
 
176
- validateName(context, directive); // TODO: Ensure proper locations.
177
- // Ensure the arguments are valid.
176
+ validateName(context, directive);
177
+
178
+ if (directive.locations.length === 0) {
179
+ context.reportError(
180
+ `Directive @${directive.name} must include 1 or more locations.`,
181
+ directive.astNode,
182
+ );
183
+ } // Ensure the arguments are valid.
178
184
 
179
185
  for (const arg of directive.args) {
180
186
  // Ensure they are named correctly.
@@ -39,9 +39,9 @@ function getIntrospectionQuery(options) {
39
39
  query IntrospectionQuery {
40
40
  __schema {
41
41
  ${schemaDescription}
42
- queryType { name }
43
- mutationType { name }
44
- subscriptionType { name }
42
+ queryType { name kind }
43
+ mutationType { name kind }
44
+ subscriptionType { name kind }
45
45
  types {
46
46
  ...FullType
47
47
  }
@@ -32,9 +32,9 @@ export function getIntrospectionQuery(options) {
32
32
  query IntrospectionQuery {
33
33
  __schema {
34
34
  ${schemaDescription}
35
- queryType { name }
36
- mutationType { name }
37
- subscriptionType { name }
35
+ queryType { name kind }
36
+ mutationType { name kind }
37
+ subscriptionType { name kind }
38
38
  types {
39
39
  ...FullType
40
40
  }
@@ -43,9 +43,10 @@ function reasonMessage(reason) {
43
43
  */
44
44
 
45
45
  function OverlappingFieldsCanBeMergedRule(context) {
46
- // A memoization for when two fragments are compared "between" each other for
47
- // conflicts. Two fragments may be compared many times, so memoizing this can
48
- // dramatically improve the performance of this validator.
46
+ // A memoization for when fields and a fragment or two fragments are compared
47
+ // "between" each other for conflicts. Comparisons made be made many times,
48
+ // so memoizing this can dramatically improve the performance of this validator.
49
+ const comparedFieldsAndFragmentPairs = new OrderedPairSet();
49
50
  const comparedFragmentPairs = new PairSet(); // A cache for the "field map" and list of fragment names found in any given
50
51
  // selection set. Selection sets may be asked for this information multiple
51
52
  // times, so this improves the performance of this validator.
@@ -56,6 +57,7 @@ function OverlappingFieldsCanBeMergedRule(context) {
56
57
  const conflicts = findConflictsWithinSelectionSet(
57
58
  context,
58
59
  cachedFieldsAndFragmentNames,
60
+ comparedFieldsAndFragmentPairs,
59
61
  comparedFragmentPairs,
60
62
  context.getParentType(),
61
63
  selectionSet,
@@ -136,6 +138,7 @@ function OverlappingFieldsCanBeMergedRule(context) {
136
138
  function findConflictsWithinSelectionSet(
137
139
  context,
138
140
  cachedFieldsAndFragmentNames,
141
+ comparedFieldsAndFragmentPairs,
139
142
  comparedFragmentPairs,
140
143
  parentType,
141
144
  selectionSet,
@@ -153,6 +156,7 @@ function findConflictsWithinSelectionSet(
153
156
  context,
154
157
  conflicts,
155
158
  cachedFieldsAndFragmentNames,
159
+ comparedFieldsAndFragmentPairs,
156
160
  comparedFragmentPairs,
157
161
  fieldMap,
158
162
  );
@@ -165,6 +169,7 @@ function findConflictsWithinSelectionSet(
165
169
  context,
166
170
  conflicts,
167
171
  cachedFieldsAndFragmentNames,
172
+ comparedFieldsAndFragmentPairs,
168
173
  comparedFragmentPairs,
169
174
  false,
170
175
  fieldMap,
@@ -179,6 +184,7 @@ function findConflictsWithinSelectionSet(
179
184
  context,
180
185
  conflicts,
181
186
  cachedFieldsAndFragmentNames,
187
+ comparedFieldsAndFragmentPairs,
182
188
  comparedFragmentPairs,
183
189
  false,
184
190
  fragmentNames[i],
@@ -196,11 +202,29 @@ function collectConflictsBetweenFieldsAndFragment(
196
202
  context,
197
203
  conflicts,
198
204
  cachedFieldsAndFragmentNames,
205
+ comparedFieldsAndFragmentPairs,
199
206
  comparedFragmentPairs,
200
207
  areMutuallyExclusive,
201
208
  fieldMap,
202
209
  fragmentName,
203
210
  ) {
211
+ // Memoize so the fields and fragments are not compared for conflicts more
212
+ // than once.
213
+ if (
214
+ comparedFieldsAndFragmentPairs.has(
215
+ fieldMap,
216
+ fragmentName,
217
+ areMutuallyExclusive,
218
+ )
219
+ ) {
220
+ return;
221
+ }
222
+
223
+ comparedFieldsAndFragmentPairs.add(
224
+ fieldMap,
225
+ fragmentName,
226
+ areMutuallyExclusive,
227
+ );
204
228
  const fragment = context.getFragment(fragmentName);
205
229
 
206
230
  if (!fragment) {
@@ -223,6 +247,7 @@ function collectConflictsBetweenFieldsAndFragment(
223
247
  context,
224
248
  conflicts,
225
249
  cachedFieldsAndFragmentNames,
250
+ comparedFieldsAndFragmentPairs,
226
251
  comparedFragmentPairs,
227
252
  areMutuallyExclusive,
228
253
  fieldMap,
@@ -231,26 +256,11 @@ function collectConflictsBetweenFieldsAndFragment(
231
256
  // and any fragment names found in the given fragment.
232
257
 
233
258
  for (const referencedFragmentName of referencedFragmentNames) {
234
- // Memoize so two fragments are not compared for conflicts more than once.
235
- if (
236
- comparedFragmentPairs.has(
237
- referencedFragmentName,
238
- fragmentName,
239
- areMutuallyExclusive,
240
- )
241
- ) {
242
- continue;
243
- }
244
-
245
- comparedFragmentPairs.add(
246
- referencedFragmentName,
247
- fragmentName,
248
- areMutuallyExclusive,
249
- );
250
259
  collectConflictsBetweenFieldsAndFragment(
251
260
  context,
252
261
  conflicts,
253
262
  cachedFieldsAndFragmentNames,
263
+ comparedFieldsAndFragmentPairs,
254
264
  comparedFragmentPairs,
255
265
  areMutuallyExclusive,
256
266
  fieldMap,
@@ -264,6 +274,7 @@ function collectConflictsBetweenFragments(
264
274
  context,
265
275
  conflicts,
266
276
  cachedFieldsAndFragmentNames,
277
+ comparedFieldsAndFragmentPairs,
267
278
  comparedFragmentPairs,
268
279
  areMutuallyExclusive,
269
280
  fragmentName1,
@@ -310,6 +321,7 @@ function collectConflictsBetweenFragments(
310
321
  context,
311
322
  conflicts,
312
323
  cachedFieldsAndFragmentNames,
324
+ comparedFieldsAndFragmentPairs,
313
325
  comparedFragmentPairs,
314
326
  areMutuallyExclusive,
315
327
  fieldMap1,
@@ -322,6 +334,7 @@ function collectConflictsBetweenFragments(
322
334
  context,
323
335
  conflicts,
324
336
  cachedFieldsAndFragmentNames,
337
+ comparedFieldsAndFragmentPairs,
325
338
  comparedFragmentPairs,
326
339
  areMutuallyExclusive,
327
340
  fragmentName1,
@@ -335,6 +348,7 @@ function collectConflictsBetweenFragments(
335
348
  context,
336
349
  conflicts,
337
350
  cachedFieldsAndFragmentNames,
351
+ comparedFieldsAndFragmentPairs,
338
352
  comparedFragmentPairs,
339
353
  areMutuallyExclusive,
340
354
  referencedFragmentName1,
@@ -348,6 +362,7 @@ function collectConflictsBetweenFragments(
348
362
  function findConflictsBetweenSubSelectionSets(
349
363
  context,
350
364
  cachedFieldsAndFragmentNames,
365
+ comparedFieldsAndFragmentPairs,
351
366
  comparedFragmentPairs,
352
367
  areMutuallyExclusive,
353
368
  parentType1,
@@ -373,6 +388,7 @@ function findConflictsBetweenSubSelectionSets(
373
388
  context,
374
389
  conflicts,
375
390
  cachedFieldsAndFragmentNames,
391
+ comparedFieldsAndFragmentPairs,
376
392
  comparedFragmentPairs,
377
393
  areMutuallyExclusive,
378
394
  fieldMap1,
@@ -385,6 +401,7 @@ function findConflictsBetweenSubSelectionSets(
385
401
  context,
386
402
  conflicts,
387
403
  cachedFieldsAndFragmentNames,
404
+ comparedFieldsAndFragmentPairs,
388
405
  comparedFragmentPairs,
389
406
  areMutuallyExclusive,
390
407
  fieldMap1,
@@ -398,6 +415,7 @@ function findConflictsBetweenSubSelectionSets(
398
415
  context,
399
416
  conflicts,
400
417
  cachedFieldsAndFragmentNames,
418
+ comparedFieldsAndFragmentPairs,
401
419
  comparedFragmentPairs,
402
420
  areMutuallyExclusive,
403
421
  fieldMap2,
@@ -413,6 +431,7 @@ function findConflictsBetweenSubSelectionSets(
413
431
  context,
414
432
  conflicts,
415
433
  cachedFieldsAndFragmentNames,
434
+ comparedFieldsAndFragmentPairs,
416
435
  comparedFragmentPairs,
417
436
  areMutuallyExclusive,
418
437
  fragmentName1,
@@ -428,6 +447,7 @@ function collectConflictsWithin(
428
447
  context,
429
448
  conflicts,
430
449
  cachedFieldsAndFragmentNames,
450
+ comparedFieldsAndFragmentPairs,
431
451
  comparedFragmentPairs,
432
452
  fieldMap,
433
453
  ) {
@@ -445,6 +465,7 @@ function collectConflictsWithin(
445
465
  const conflict = findConflict(
446
466
  context,
447
467
  cachedFieldsAndFragmentNames,
468
+ comparedFieldsAndFragmentPairs,
448
469
  comparedFragmentPairs,
449
470
  false, // within one collection is never mutually exclusive
450
471
  responseName,
@@ -469,6 +490,7 @@ function collectConflictsBetween(
469
490
  context,
470
491
  conflicts,
471
492
  cachedFieldsAndFragmentNames,
493
+ comparedFieldsAndFragmentPairs,
472
494
  comparedFragmentPairs,
473
495
  parentFieldsAreMutuallyExclusive,
474
496
  fieldMap1,
@@ -488,6 +510,7 @@ function collectConflictsBetween(
488
510
  const conflict = findConflict(
489
511
  context,
490
512
  cachedFieldsAndFragmentNames,
513
+ comparedFieldsAndFragmentPairs,
491
514
  comparedFragmentPairs,
492
515
  parentFieldsAreMutuallyExclusive,
493
516
  responseName,
@@ -508,6 +531,7 @@ function collectConflictsBetween(
508
531
  function findConflict(
509
532
  context,
510
533
  cachedFieldsAndFragmentNames,
534
+ comparedFieldsAndFragmentPairs,
511
535
  comparedFragmentPairs,
512
536
  parentFieldsAreMutuallyExclusive,
513
537
  responseName,
@@ -577,6 +601,7 @@ function findConflict(
577
601
  const conflicts = findConflictsBetweenSubSelectionSets(
578
602
  context,
579
603
  cachedFieldsAndFragmentNames,
604
+ comparedFieldsAndFragmentPairs,
580
605
  comparedFragmentPairs,
581
606
  areMutuallyExclusive,
582
607
  (0, _definition.getNamedType)(type1),
@@ -779,42 +804,65 @@ function subfieldConflicts(conflicts, responseName, node1, node2) {
779
804
  }
780
805
  }
781
806
  /**
782
- * A way to keep track of pairs of things when the ordering of the pair does not matter.
807
+ * A way to keep track of pairs of things where the ordering of the pair
808
+ * matters.
809
+ *
810
+ * Provides a third argument for has/set to allow flagging the pair as
811
+ * weakly or strongly present within the collection.
783
812
  */
784
813
 
785
- class PairSet {
814
+ class OrderedPairSet {
786
815
  constructor() {
787
816
  this._data = new Map();
788
817
  }
789
818
 
790
- has(a, b, areMutuallyExclusive) {
819
+ has(a, b, weaklyPresent) {
791
820
  var _this$_data$get;
792
821
 
793
- const [key1, key2] = a < b ? [a, b] : [b, a];
794
822
  const result =
795
- (_this$_data$get = this._data.get(key1)) === null ||
823
+ (_this$_data$get = this._data.get(a)) === null ||
796
824
  _this$_data$get === void 0
797
825
  ? void 0
798
- : _this$_data$get.get(key2);
826
+ : _this$_data$get.get(b);
799
827
 
800
828
  if (result === undefined) {
801
829
  return false;
802
- } // areMutuallyExclusive being false is a superset of being true, hence if
803
- // we want to know if this PairSet "has" these two with no exclusivity,
804
- // we have to ensure it was added as such.
830
+ }
805
831
 
806
- return areMutuallyExclusive ? true : areMutuallyExclusive === result;
832
+ return weaklyPresent ? true : weaklyPresent === result;
807
833
  }
808
834
 
809
- add(a, b, areMutuallyExclusive) {
810
- const [key1, key2] = a < b ? [a, b] : [b, a];
811
-
812
- const map = this._data.get(key1);
835
+ add(a, b, weaklyPresent) {
836
+ const map = this._data.get(a);
813
837
 
814
838
  if (map === undefined) {
815
- this._data.set(key1, new Map([[key2, areMutuallyExclusive]]));
839
+ this._data.set(a, new Map([[b, weaklyPresent]]));
840
+ } else {
841
+ map.set(b, weaklyPresent);
842
+ }
843
+ }
844
+ }
845
+ /**
846
+ * A way to keep track of pairs of similar things when the ordering of the pair
847
+ * does not matter.
848
+ */
849
+
850
+ class PairSet {
851
+ constructor() {
852
+ this._orderedPairSet = new OrderedPairSet();
853
+ }
854
+
855
+ has(a, b, weaklyPresent) {
856
+ return a < b
857
+ ? this._orderedPairSet.has(a, b, weaklyPresent)
858
+ : this._orderedPairSet.has(b, a, weaklyPresent);
859
+ }
860
+
861
+ add(a, b, weaklyPresent) {
862
+ if (a < b) {
863
+ this._orderedPairSet.add(a, b, weaklyPresent);
816
864
  } else {
817
- map.set(key2, areMutuallyExclusive);
865
+ this._orderedPairSet.add(b, a, weaklyPresent);
818
866
  }
819
867
  }
820
868
  }
@@ -37,9 +37,10 @@ function reasonMessage(reason) {
37
37
  */
38
38
 
39
39
  export function OverlappingFieldsCanBeMergedRule(context) {
40
- // A memoization for when two fragments are compared "between" each other for
41
- // conflicts. Two fragments may be compared many times, so memoizing this can
42
- // dramatically improve the performance of this validator.
40
+ // A memoization for when fields and a fragment or two fragments are compared
41
+ // "between" each other for conflicts. Comparisons made be made many times,
42
+ // so memoizing this can dramatically improve the performance of this validator.
43
+ const comparedFieldsAndFragmentPairs = new OrderedPairSet();
43
44
  const comparedFragmentPairs = new PairSet(); // A cache for the "field map" and list of fragment names found in any given
44
45
  // selection set. Selection sets may be asked for this information multiple
45
46
  // times, so this improves the performance of this validator.
@@ -50,6 +51,7 @@ export function OverlappingFieldsCanBeMergedRule(context) {
50
51
  const conflicts = findConflictsWithinSelectionSet(
51
52
  context,
52
53
  cachedFieldsAndFragmentNames,
54
+ comparedFieldsAndFragmentPairs,
53
55
  comparedFragmentPairs,
54
56
  context.getParentType(),
55
57
  selectionSet,
@@ -130,6 +132,7 @@ export function OverlappingFieldsCanBeMergedRule(context) {
130
132
  function findConflictsWithinSelectionSet(
131
133
  context,
132
134
  cachedFieldsAndFragmentNames,
135
+ comparedFieldsAndFragmentPairs,
133
136
  comparedFragmentPairs,
134
137
  parentType,
135
138
  selectionSet,
@@ -147,6 +150,7 @@ function findConflictsWithinSelectionSet(
147
150
  context,
148
151
  conflicts,
149
152
  cachedFieldsAndFragmentNames,
153
+ comparedFieldsAndFragmentPairs,
150
154
  comparedFragmentPairs,
151
155
  fieldMap,
152
156
  );
@@ -159,6 +163,7 @@ function findConflictsWithinSelectionSet(
159
163
  context,
160
164
  conflicts,
161
165
  cachedFieldsAndFragmentNames,
166
+ comparedFieldsAndFragmentPairs,
162
167
  comparedFragmentPairs,
163
168
  false,
164
169
  fieldMap,
@@ -173,6 +178,7 @@ function findConflictsWithinSelectionSet(
173
178
  context,
174
179
  conflicts,
175
180
  cachedFieldsAndFragmentNames,
181
+ comparedFieldsAndFragmentPairs,
176
182
  comparedFragmentPairs,
177
183
  false,
178
184
  fragmentNames[i],
@@ -190,11 +196,29 @@ function collectConflictsBetweenFieldsAndFragment(
190
196
  context,
191
197
  conflicts,
192
198
  cachedFieldsAndFragmentNames,
199
+ comparedFieldsAndFragmentPairs,
193
200
  comparedFragmentPairs,
194
201
  areMutuallyExclusive,
195
202
  fieldMap,
196
203
  fragmentName,
197
204
  ) {
205
+ // Memoize so the fields and fragments are not compared for conflicts more
206
+ // than once.
207
+ if (
208
+ comparedFieldsAndFragmentPairs.has(
209
+ fieldMap,
210
+ fragmentName,
211
+ areMutuallyExclusive,
212
+ )
213
+ ) {
214
+ return;
215
+ }
216
+
217
+ comparedFieldsAndFragmentPairs.add(
218
+ fieldMap,
219
+ fragmentName,
220
+ areMutuallyExclusive,
221
+ );
198
222
  const fragment = context.getFragment(fragmentName);
199
223
 
200
224
  if (!fragment) {
@@ -217,6 +241,7 @@ function collectConflictsBetweenFieldsAndFragment(
217
241
  context,
218
242
  conflicts,
219
243
  cachedFieldsAndFragmentNames,
244
+ comparedFieldsAndFragmentPairs,
220
245
  comparedFragmentPairs,
221
246
  areMutuallyExclusive,
222
247
  fieldMap,
@@ -225,26 +250,11 @@ function collectConflictsBetweenFieldsAndFragment(
225
250
  // and any fragment names found in the given fragment.
226
251
 
227
252
  for (const referencedFragmentName of referencedFragmentNames) {
228
- // Memoize so two fragments are not compared for conflicts more than once.
229
- if (
230
- comparedFragmentPairs.has(
231
- referencedFragmentName,
232
- fragmentName,
233
- areMutuallyExclusive,
234
- )
235
- ) {
236
- continue;
237
- }
238
-
239
- comparedFragmentPairs.add(
240
- referencedFragmentName,
241
- fragmentName,
242
- areMutuallyExclusive,
243
- );
244
253
  collectConflictsBetweenFieldsAndFragment(
245
254
  context,
246
255
  conflicts,
247
256
  cachedFieldsAndFragmentNames,
257
+ comparedFieldsAndFragmentPairs,
248
258
  comparedFragmentPairs,
249
259
  areMutuallyExclusive,
250
260
  fieldMap,
@@ -258,6 +268,7 @@ function collectConflictsBetweenFragments(
258
268
  context,
259
269
  conflicts,
260
270
  cachedFieldsAndFragmentNames,
271
+ comparedFieldsAndFragmentPairs,
261
272
  comparedFragmentPairs,
262
273
  areMutuallyExclusive,
263
274
  fragmentName1,
@@ -304,6 +315,7 @@ function collectConflictsBetweenFragments(
304
315
  context,
305
316
  conflicts,
306
317
  cachedFieldsAndFragmentNames,
318
+ comparedFieldsAndFragmentPairs,
307
319
  comparedFragmentPairs,
308
320
  areMutuallyExclusive,
309
321
  fieldMap1,
@@ -316,6 +328,7 @@ function collectConflictsBetweenFragments(
316
328
  context,
317
329
  conflicts,
318
330
  cachedFieldsAndFragmentNames,
331
+ comparedFieldsAndFragmentPairs,
319
332
  comparedFragmentPairs,
320
333
  areMutuallyExclusive,
321
334
  fragmentName1,
@@ -329,6 +342,7 @@ function collectConflictsBetweenFragments(
329
342
  context,
330
343
  conflicts,
331
344
  cachedFieldsAndFragmentNames,
345
+ comparedFieldsAndFragmentPairs,
332
346
  comparedFragmentPairs,
333
347
  areMutuallyExclusive,
334
348
  referencedFragmentName1,
@@ -342,6 +356,7 @@ function collectConflictsBetweenFragments(
342
356
  function findConflictsBetweenSubSelectionSets(
343
357
  context,
344
358
  cachedFieldsAndFragmentNames,
359
+ comparedFieldsAndFragmentPairs,
345
360
  comparedFragmentPairs,
346
361
  areMutuallyExclusive,
347
362
  parentType1,
@@ -367,6 +382,7 @@ function findConflictsBetweenSubSelectionSets(
367
382
  context,
368
383
  conflicts,
369
384
  cachedFieldsAndFragmentNames,
385
+ comparedFieldsAndFragmentPairs,
370
386
  comparedFragmentPairs,
371
387
  areMutuallyExclusive,
372
388
  fieldMap1,
@@ -379,6 +395,7 @@ function findConflictsBetweenSubSelectionSets(
379
395
  context,
380
396
  conflicts,
381
397
  cachedFieldsAndFragmentNames,
398
+ comparedFieldsAndFragmentPairs,
382
399
  comparedFragmentPairs,
383
400
  areMutuallyExclusive,
384
401
  fieldMap1,
@@ -392,6 +409,7 @@ function findConflictsBetweenSubSelectionSets(
392
409
  context,
393
410
  conflicts,
394
411
  cachedFieldsAndFragmentNames,
412
+ comparedFieldsAndFragmentPairs,
395
413
  comparedFragmentPairs,
396
414
  areMutuallyExclusive,
397
415
  fieldMap2,
@@ -407,6 +425,7 @@ function findConflictsBetweenSubSelectionSets(
407
425
  context,
408
426
  conflicts,
409
427
  cachedFieldsAndFragmentNames,
428
+ comparedFieldsAndFragmentPairs,
410
429
  comparedFragmentPairs,
411
430
  areMutuallyExclusive,
412
431
  fragmentName1,
@@ -422,6 +441,7 @@ function collectConflictsWithin(
422
441
  context,
423
442
  conflicts,
424
443
  cachedFieldsAndFragmentNames,
444
+ comparedFieldsAndFragmentPairs,
425
445
  comparedFragmentPairs,
426
446
  fieldMap,
427
447
  ) {
@@ -439,6 +459,7 @@ function collectConflictsWithin(
439
459
  const conflict = findConflict(
440
460
  context,
441
461
  cachedFieldsAndFragmentNames,
462
+ comparedFieldsAndFragmentPairs,
442
463
  comparedFragmentPairs,
443
464
  false, // within one collection is never mutually exclusive
444
465
  responseName,
@@ -463,6 +484,7 @@ function collectConflictsBetween(
463
484
  context,
464
485
  conflicts,
465
486
  cachedFieldsAndFragmentNames,
487
+ comparedFieldsAndFragmentPairs,
466
488
  comparedFragmentPairs,
467
489
  parentFieldsAreMutuallyExclusive,
468
490
  fieldMap1,
@@ -482,6 +504,7 @@ function collectConflictsBetween(
482
504
  const conflict = findConflict(
483
505
  context,
484
506
  cachedFieldsAndFragmentNames,
507
+ comparedFieldsAndFragmentPairs,
485
508
  comparedFragmentPairs,
486
509
  parentFieldsAreMutuallyExclusive,
487
510
  responseName,
@@ -502,6 +525,7 @@ function collectConflictsBetween(
502
525
  function findConflict(
503
526
  context,
504
527
  cachedFieldsAndFragmentNames,
528
+ comparedFieldsAndFragmentPairs,
505
529
  comparedFragmentPairs,
506
530
  parentFieldsAreMutuallyExclusive,
507
531
  responseName,
@@ -571,6 +595,7 @@ function findConflict(
571
595
  const conflicts = findConflictsBetweenSubSelectionSets(
572
596
  context,
573
597
  cachedFieldsAndFragmentNames,
598
+ comparedFieldsAndFragmentPairs,
574
599
  comparedFragmentPairs,
575
600
  areMutuallyExclusive,
576
601
  getNamedType(type1),
@@ -764,42 +789,65 @@ function subfieldConflicts(conflicts, responseName, node1, node2) {
764
789
  }
765
790
  }
766
791
  /**
767
- * A way to keep track of pairs of things when the ordering of the pair does not matter.
792
+ * A way to keep track of pairs of things where the ordering of the pair
793
+ * matters.
794
+ *
795
+ * Provides a third argument for has/set to allow flagging the pair as
796
+ * weakly or strongly present within the collection.
768
797
  */
769
798
 
770
- class PairSet {
799
+ class OrderedPairSet {
771
800
  constructor() {
772
801
  this._data = new Map();
773
802
  }
774
803
 
775
- has(a, b, areMutuallyExclusive) {
804
+ has(a, b, weaklyPresent) {
776
805
  var _this$_data$get;
777
806
 
778
- const [key1, key2] = a < b ? [a, b] : [b, a];
779
807
  const result =
780
- (_this$_data$get = this._data.get(key1)) === null ||
808
+ (_this$_data$get = this._data.get(a)) === null ||
781
809
  _this$_data$get === void 0
782
810
  ? void 0
783
- : _this$_data$get.get(key2);
811
+ : _this$_data$get.get(b);
784
812
 
785
813
  if (result === undefined) {
786
814
  return false;
787
- } // areMutuallyExclusive being false is a superset of being true, hence if
788
- // we want to know if this PairSet "has" these two with no exclusivity,
789
- // we have to ensure it was added as such.
815
+ }
790
816
 
791
- return areMutuallyExclusive ? true : areMutuallyExclusive === result;
817
+ return weaklyPresent ? true : weaklyPresent === result;
792
818
  }
793
819
 
794
- add(a, b, areMutuallyExclusive) {
795
- const [key1, key2] = a < b ? [a, b] : [b, a];
796
-
797
- const map = this._data.get(key1);
820
+ add(a, b, weaklyPresent) {
821
+ const map = this._data.get(a);
798
822
 
799
823
  if (map === undefined) {
800
- this._data.set(key1, new Map([[key2, areMutuallyExclusive]]));
824
+ this._data.set(a, new Map([[b, weaklyPresent]]));
825
+ } else {
826
+ map.set(b, weaklyPresent);
827
+ }
828
+ }
829
+ }
830
+ /**
831
+ * A way to keep track of pairs of similar things when the ordering of the pair
832
+ * does not matter.
833
+ */
834
+
835
+ class PairSet {
836
+ constructor() {
837
+ this._orderedPairSet = new OrderedPairSet();
838
+ }
839
+
840
+ has(a, b, weaklyPresent) {
841
+ return a < b
842
+ ? this._orderedPairSet.has(a, b, weaklyPresent)
843
+ : this._orderedPairSet.has(b, a, weaklyPresent);
844
+ }
845
+
846
+ add(a, b, weaklyPresent) {
847
+ if (a < b) {
848
+ this._orderedPairSet.add(a, b, weaklyPresent);
801
849
  } else {
802
- map.set(key2, areMutuallyExclusive);
850
+ this._orderedPairSet.add(b, a, weaklyPresent);
803
851
  }
804
852
  }
805
853
  }
@@ -48,6 +48,17 @@ function ScalarLeafsRule(context) {
48
48
  },
49
49
  ),
50
50
  );
51
+ } else if (selectionSet.selections.length === 0) {
52
+ const fieldName = node.name.value;
53
+ const typeStr = (0, _inspect.inspect)(type);
54
+ context.reportError(
55
+ new _GraphQLError.GraphQLError(
56
+ `Field "${fieldName}" of type "${typeStr}" must have at least one field selected.`,
57
+ {
58
+ nodes: node,
59
+ },
60
+ ),
61
+ );
51
62
  }
52
63
  }
53
64
  },
@@ -39,6 +39,17 @@ export function ScalarLeafsRule(context) {
39
39
  },
40
40
  ),
41
41
  );
42
+ } else if (selectionSet.selections.length === 0) {
43
+ const fieldName = node.name.value;
44
+ const typeStr = inspect(type);
45
+ context.reportError(
46
+ new GraphQLError(
47
+ `Field "${fieldName}" of type "${typeStr}" must have at least one field selected.`,
48
+ {
49
+ nodes: node,
50
+ },
51
+ ),
52
+ );
42
53
  }
43
54
  }
44
55
  },
package/version.js CHANGED
@@ -10,7 +10,7 @@ exports.versionInfo = exports.version = void 0;
10
10
  /**
11
11
  * A string containing the version of the GraphQL.js library
12
12
  */
13
- const version = '16.9.0';
13
+ const version = '16.10.0';
14
14
  /**
15
15
  * An object containing the components of the GraphQL.js version string
16
16
  */
@@ -18,7 +18,7 @@ const version = '16.9.0';
18
18
  exports.version = version;
19
19
  const versionInfo = Object.freeze({
20
20
  major: 16,
21
- minor: 9,
21
+ minor: 10,
22
22
  patch: 0,
23
23
  preReleaseTag: null,
24
24
  });
package/version.mjs CHANGED
@@ -4,14 +4,14 @@
4
4
  /**
5
5
  * A string containing the version of the GraphQL.js library
6
6
  */
7
- export const version = '16.9.0';
7
+ export const version = '16.10.0';
8
8
  /**
9
9
  * An object containing the components of the GraphQL.js version string
10
10
  */
11
11
 
12
12
  export const versionInfo = Object.freeze({
13
13
  major: 16,
14
- minor: 9,
14
+ minor: 10,
15
15
  patch: 0,
16
16
  preReleaseTag: null,
17
17
  });