@suss/contract-graphql 0.19.0 → 0.21.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 +92 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @suss/contract-graphql
|
|
2
|
+
|
|
3
|
+
Generate suss `BehavioralSummary[]` from GraphQL files a repository already commits. Two readers ship in this package, one for each side of a GraphQL boundary:
|
|
4
|
+
|
|
5
|
+
| Reader | Input | Summaries |
|
|
6
|
+
|--------|-------|-----------|
|
|
7
|
+
| `suss contract --from graphql` | a plain SDL schema file | one `resolver`-kind summary per root field (the provider side) |
|
|
8
|
+
| `suss contract --from graphql-documents` | `.graphql` / `.gql` operation documents | one `client`-kind summary per operation (the consumer side) |
|
|
9
|
+
|
|
10
|
+
Either side pairs against the other, and against summaries extracted from code by the Apollo and NestJS packs. Nothing has to be traced through TypeScript first.
|
|
11
|
+
|
|
12
|
+
## Reading a schema
|
|
13
|
+
|
|
14
|
+
`graphqlSdlFileToSummaries` parses the SDL and walks every field on `Query`, `Mutation`, and `Subscription`, including fields added through `extend type Query { ... }`. Each field becomes one summary:
|
|
15
|
+
|
|
16
|
+
- `identity.name` is `Type.field`, with a `graphql-resolver` boundary binding that records `transport: "http-graphql"`, `recognition: "graphql"`, and the type and field names. Both defaults are overridable, which is what you want when the same SDL backs several deployments and findings should tell them apart.
|
|
17
|
+
- Inputs come from the field's arguments, each with `role: "args"`.
|
|
18
|
+
- Two transitions: a default success returning the field's declared shape, and a `GraphQLError` throw for the `errors[]` path.
|
|
19
|
+
- `metadata.graphql.declaredContract` records the return type, the arguments with a `required` flag per argument, and the framework tag. Its provenance is `"derived"`, because the contract and the transitions come from the same field declaration and comparing one against the other would say nothing.
|
|
20
|
+
|
|
21
|
+
One extra `library`-kind summary contains the SDL text itself, under `metadata.graphql.schemaSdl`. Type definitions belong to the schema rather than to any one field, so every resolver points at that document through a source-document label, and the checker follows the label to resolve a consumer's nested selections against a resolver's return type. That summary binds to no boundary, so pairing records it as taking no part.
|
|
22
|
+
|
|
23
|
+
Named types convert to `TypeShape` as follows: `String` and `ID` become `text`, `Int` and `Float` become `number`, `Boolean` becomes `boolean`, and any other named type becomes a `ref` with that name. A list becomes an `array`, and a non-null wrapper is dropped because `TypeShape` treats nullability as the absence of null in a union.
|
|
24
|
+
|
|
25
|
+
## Reading operation documents
|
|
26
|
+
|
|
27
|
+
`graphqlDocumentsPathToSummaries` accepts a single file or a directory walked recursively for `.graphql` and `.gql` files (`node_modules` is skipped). Each query, mutation, and subscription definition becomes one summary:
|
|
28
|
+
|
|
29
|
+
- A `graphql-operation` boundary binding with the operation type and, when the operation is named, its name. The default recognition tag is `"graphql-documents"`, so findings distinguish these from operations traced at a call site.
|
|
30
|
+
- Inputs from the operation's variable definitions, each with `role: "variable"`, the same role the TypeScript adapter stamps on `$variables` it recovers from a tagged template literal.
|
|
31
|
+
- The full document text at `metadata.graphql.document`, the same place the TypeScript adapter puts documents it finds in code, so the checker's GraphQL pairing pass reads both alike.
|
|
32
|
+
- A response shape approximated from the selection set. Field names become record properties and leaves become `unknown`, since a document does not declare field types. A field selected twice, once directly and once through a fragment, merges into one property the way a server would merge it.
|
|
33
|
+
|
|
34
|
+
Fragment spreads are resolved against every fragment definition in the read set and inlined into the stored document, so the pairing pass sees the selected fields directly. A spread the reader cannot expand stays in the document as written and becomes a gap on the summary rather than an error: the fragment may be missing from the read set, part of a cycle, or defined in more than one file. In the ambiguous case the first definition in read order wins and the gap says which files competed.
|
|
35
|
+
|
|
36
|
+
An anonymous operation is named after its path relative to the directory you passed, plus its operation type, so the name is the same on every machine that checks the repo out. Repeated names get a `#2` suffix, because transition ids are built from the name and a repeated id would make two operations look like one.
|
|
37
|
+
|
|
38
|
+
## What neither reader does
|
|
39
|
+
|
|
40
|
+
- **Non-root resolvers.** A schema reader summary is emitted for `Query`, `Mutation`, and `Subscription` fields. A field resolver on `Order.customer` is not a boundary summary today.
|
|
41
|
+
- **Directives, unions and interface resolution, and custom scalars** beyond the `ref` fallback are not modeled.
|
|
42
|
+
- **Schema validation.** An SDL file that fails to parse comes back as an empty array rather than an error, and the documents reader skips a file it cannot parse so one bad file does not lose the rest.
|
|
43
|
+
- **Cross-checking documents against a schema.** The documents reader never resolves a selected field's type; that pairing happens later in the checker, against the schema document summary.
|
|
44
|
+
|
|
45
|
+
## Worked example
|
|
46
|
+
|
|
47
|
+
```graphql
|
|
48
|
+
# schema.graphql
|
|
49
|
+
type User { id: ID!, email: String! }
|
|
50
|
+
type Query { user(id: ID!): User }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```graphql
|
|
54
|
+
# src/queries/user.graphql
|
|
55
|
+
query GetUser($id: ID!) {
|
|
56
|
+
user(id: $id) { id email }
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
suss contract --from graphql schema.graphql -o summaries/schema.json
|
|
62
|
+
suss contract --from graphql-documents src/queries -o summaries/operations.json
|
|
63
|
+
suss check summaries/schema.json summaries/operations.json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or programmatically:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import {
|
|
70
|
+
graphqlSdlFileToSummaries,
|
|
71
|
+
graphqlDocumentsPathToSummaries,
|
|
72
|
+
} from "@suss/contract-graphql";
|
|
73
|
+
|
|
74
|
+
const provider = graphqlSdlFileToSummaries("schema.graphql");
|
|
75
|
+
const consumer = graphqlDocumentsPathToSummaries("src/queries");
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Where it fits in suss
|
|
79
|
+
|
|
80
|
+
Depends only on `@suss/behavioral-ir` (for the IR types it produces) and `graphql` (for parsing and printing). It does not extract from source code and is independent of the language adapters. `@suss/contract-appsync` reuses this package's SDL loader and type conversion for AppSync schemas declared in CloudFormation.
|
|
81
|
+
|
|
82
|
+
## Coverage
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
Licensed under Apache 2.0. See [LICENSE](../../../LICENSE).
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
For the format the summaries conform to, see [`docs/behavioral-summary-format.md`](../../../docs/behavioral-summary-format.md). For how contract sources fit together, see [`docs/contract-sources.md`](../../../docs/contract-sources.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/contract-graphql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Generate behavioral summaries from GraphQL sources: a plain SDL file (root fields become resolver-kind summaries) or committed .graphql operation documents (each operation becomes a client-kind summary).",
|
|
6
6
|
"keywords": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@suss/behavioral-ir": "0.
|
|
48
|
+
"@suss/behavioral-ir": "0.21.0",
|
|
49
49
|
"graphql": "^16.8.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|