graphql-buddy 0.1.0 → 0.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphql-buddy",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Hunter John Larco",
@@ -79,6 +79,12 @@ describe(`validate`, () => {
79
79
  ).resolves.toStrictEqual(expect.any(String));
80
80
  });
81
81
 
82
+ it(`throws for operations when the schema has no matching root type.`, async () => {
83
+ await expect(
84
+ validate.validate(documentsFixture(`missing_root_type`)),
85
+ ).rejects.toThrowError(`Schema does not define a query root type.`);
86
+ });
87
+
82
88
  it(`still validates field selections inside fragment-only files.`, async () => {
83
89
  await expect(
84
90
  validate.validate(documentsFixture(`invalid_fragment`)),
@@ -22,24 +22,76 @@ export const validateDocuments = (options: ValidateDocumentsOptions): void => {
22
22
  continue;
23
23
  }
24
24
 
25
- // Fragment-only documents are fragment "libraries" that operations pull in
26
- // via `#import`. Validated in isolation they would trip
27
- // `NoUnusedFragmentsRule`, so we drop that single rule for them. Every
28
- // other rule, most importantly that each referenced field, argument, and
29
- // type actually exists, are still enforced.
30
- const definesOperation = source.document.definitions.some(
31
- (definition) => definition.kind === graphql.Kind.OPERATION_DEFINITION,
32
- );
33
-
34
- const errors = graphql.validate(
35
- schema,
36
- source.document,
37
- graphql.specifiedRules.filter(
38
- (rule) => definesOperation || rule !== graphql.NoUnusedFragmentsRule,
39
- ),
40
- );
25
+ const errors = validateDocument(schema, source.document);
41
26
  if (errors.length > 0) {
42
27
  throw errors[0];
43
28
  }
44
29
  }
45
30
  };
31
+
32
+ /**
33
+ * Validates a single document against the schema.
34
+ *
35
+ * This deliberately does not use `graphql.validate`. That function begins by
36
+ * asserting the *schema* is valid, which among other things requires a query
37
+ * root type. Fragment libraries are frequently validated against only the
38
+ * type definitions they select from.
39
+ *
40
+ * @param schema - The parsed graphql schema.
41
+ * @param document - The document to validate.
42
+ *
43
+ * @returns Any errors found in the document.
44
+ */
45
+ const validateDocument = (
46
+ schema: graphql.GraphQLSchema,
47
+ document: graphql.DocumentNode,
48
+ ): Array<graphql.GraphQLError> => {
49
+ const errors: Array<graphql.GraphQLError> = [];
50
+
51
+ const operations = document.definitions.filter(
52
+ (definition): definition is graphql.OperationDefinitionNode =>
53
+ definition.kind === graphql.Kind.OPERATION_DEFINITION,
54
+ );
55
+
56
+ // Ensure each operation matches a root GraphQL type. This is not natively
57
+ // checked by the `ValidationContext` because the validation context only
58
+ // inspects operations whose type information is available.
59
+ for (const operation of operations) {
60
+ if (schema.getRootType(operation.operation) == null) {
61
+ errors.push(
62
+ new graphql.GraphQLError(
63
+ `Schema does not define a ${operation.operation} root type.`,
64
+ { nodes: operation },
65
+ ),
66
+ );
67
+ }
68
+ }
69
+
70
+ // Fragment-only documents are fragment "libraries" that operations pull in
71
+ // via `#import`. Validated in isolation they would trip
72
+ // `NoUnusedFragmentsRule`, so we drop that single rule for them. Every
73
+ // other rule, most importantly that each referenced field, argument, and
74
+ // type actually exists, are still enforced.
75
+ const rules = graphql.specifiedRules.filter(
76
+ (rule) => operations.length > 0 || rule !== graphql.NoUnusedFragmentsRule,
77
+ );
78
+
79
+ const typeInfo = new graphql.TypeInfo(schema);
80
+ const context = new graphql.ValidationContext(
81
+ schema,
82
+ document,
83
+ typeInfo,
84
+ (error) => {
85
+ errors.push(error);
86
+ },
87
+ );
88
+ graphql.visit(
89
+ document,
90
+ graphql.visitWithTypeInfo(
91
+ typeInfo,
92
+ graphql.visitInParallel(rules.map((rule) => rule(context))),
93
+ ),
94
+ );
95
+
96
+ return errors;
97
+ };
@@ -0,0 +1,7 @@
1
+ type Foo {
2
+ id: ID!
3
+ name: String
4
+ dateCreated: DateTime!
5
+ }
6
+
7
+ scalar DateTime
@@ -0,0 +1,5 @@
1
+ query AllFoo {
2
+ allFoo {
3
+ id
4
+ }
5
+ }
@@ -0,0 +1,7 @@
1
+ type Foo {
2
+ id: ID!
3
+ name: String
4
+ dateCreated: DateTime!
5
+ }
6
+
7
+ scalar DateTime
@@ -1,20 +0,0 @@
1
- type Query {
2
- allFoo: [Foo!]!
3
- foo(id: ID!): Foo
4
- }
5
-
6
- type Foo {
7
- id: ID!
8
- name: String
9
- bar: Bar
10
- }
11
-
12
- type Bar {
13
- baz: Baz
14
- }
15
-
16
- type Baz {
17
- dateCreated: DateTime!
18
- }
19
-
20
- scalar DateTime