Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# master

- Add support for `@exhaustive` - a directive to trigger exhaustiveness checks for unions at the GraphQL operation level.

# 3.3.0

- Add support for top level `@catch` on fragments on unions.
Expand Down
2 changes: 1 addition & 1 deletion packages/relay
Submodule relay updated 30 files
+5 −0 compiler/crates/relay-compiler/relay-compiler-config-schema.json
+2 −0 compiler/crates/relay-compiler/src/build_project/validate.rs
+6 −0 compiler/crates/relay-compiler/src/config.rs
+6 −0 compiler/crates/relay-config/src/project_config.rs
+7 −0 compiler/crates/relay-schema/src/relay-extensions.graphql
+21 −0 compiler/crates/relay-transforms/src/errors.rs
+2 −0 compiler/crates/relay-transforms/src/validations.rs
+309 −0 compiler/crates/relay-transforms/src/validations/validate_exhaustive_directive.rs
+22 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive.rs
+28 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/all-members-valid.expected
+14 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/all-members-valid.graphql
+20 −0 ...crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/auto-mutation-missing.invalid.expected
+11 −0 .../crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/auto-mutation-missing.invalid.graphql
+23 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/auto-mutation-valid.expected
+11 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/auto-mutation-valid.graphql
+13 −0 ...rates/relay-transforms/tests/validate_exhaustive_directive/fixtures/directive-on-non-union.invalid.expected
+4 −0 ...crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/directive-on-non-union.invalid.graphql
+24 −0 ...er/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/fragment-all-members-valid.expected
+12 −0 ...ler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/fragment-all-members-valid.graphql
+14 −0 ...ates/relay-transforms/tests/validate_exhaustive_directive/fixtures/fragment-missing-member.invalid.expected
+5 −0 ...rates/relay-transforms/tests/validate_exhaustive_directive/fixtures/fragment-missing-member.invalid.graphql
+18 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/ignore-member-valid.expected
+6 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/ignore-member-valid.graphql
+16 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/missing-member.invalid.expected
+7 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive/fixtures/missing-member.invalid.graphql
+15 −0 ...tes/relay-transforms/tests/validate_exhaustive_directive/fixtures/multiple-missing-members.invalid.expected
+6 −0 ...ates/relay-transforms/tests/validate_exhaustive_directive/fixtures/multiple-missing-members.invalid.graphql
+171 −0 compiler/crates/relay-transforms/tests/validate_exhaustive_directive_test.rs
+10 −0 compiler/test-project-res/src/Test_unionFragment.res
+244 −0 compiler/test-project-res/src/__generated__/TestUnionFragmentExhaustiveQuery_graphql.res
3 changes: 3 additions & 0 deletions rescript-relay-documentation/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ module.exports = {
schema: "./schema.graphql", // Path to the schema.graphql you've exported from your API. Don't know what this is? It's a saved introspection of what your schema looks like. You can run `npx get-graphql-schema http://path/to/my/graphql/server > schema.graphql` in your root to generate it
artifactDirectory: "./src/__generated__", // The directory where all generated files will be emitted

// Enable this if you want mutations that return unions to always force selecting all union members. Read more in the docs on unions.
autoExhaustiveMutations: true,

// You can add type definitions for custom scalars here.
// Whenever a custom scalar is encountered, the type emitted will correspond to the definition defined here. You can then deal with the type as needed when accessing the data.
customScalarTypes: {
Expand Down
74 changes: 74 additions & 0 deletions rescript-relay-documentation/docs/unions.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,83 @@ Let's break down what's going on here:
2. We fetch our query data, and we switch on `roomOwner` to make sure it's actually there in the data.
3. We then switch again, but this time on the variant representing the union. This polymorphic variant will have each possible type of the union, and the fields selected on that type. It also adds `UnselectedUnionMember(string)` to every union, which will force you to handle _what happens if there's another member added to the union before you have time to update your app_. The `string` payload is the `__typename` of the retrieved member that wasn't selected. This is pretty neat way to ensure you gracefully handle your schema evolving.

## Exhaustiveness Checking with `@exhaustive`

RescriptRelay provides an `@exhaustive` directive that helps ensure you've selected all available union members in your GraphQL queries. This directive can be applied to fields or fragment definitions and will trigger exhaustiveness checks at compile time.
This is useful when you there are unions in your schema where you'll want to be alerted at compile time that the server added new possible return values. This makes unions work just like enums in this regard.

### Usage

The `@exhaustive` directive is defined as:

```graphql
@exhaustive(ignore: [String!], disabled: Boolean) on FIELD | FRAGMENT_DEFINITION
```

Here's how to use it with our previous example:

```rescript
/* RoomOwner.res */
module Query = %relay(
`
query RoomOwnerQuery($roomId: ID!) {
roomOwner(roomId: $roomId) @exhaustive {
__typename

... on User {
firstName
lastName
}

... on Group {
name
}
}
}
`
)
```

When you use `@exhaustive`, RescriptRelay will check that you've included fragments for all possible union members. If you're missing any, you'll get a compile-time error telling you which union members you haven't selected.

### Parameters

- **`ignore: [String!]`** - An array of union member type names to ignore during exhaustiveness checking. Use this when you intentionally don't want to handle certain union members.

```rescript
/* Ignore the Group type in exhaustiveness checking */
roomOwner(roomId: $roomId) @exhaustive(ignore: ["Group"]) {
__typename

... on User {
firstName
lastName
}
}
```

- **`disabled: Boolean`** - Set to `true` to completely disable exhaustiveness checking for this field while keeping the directive for documentation purposes.

```rescript
/* Temporarily disable exhaustiveness checking */
roomOwner(roomId: $roomId) @exhaustive(disabled: true) {
__typename

... on User {
firstName
lastName
}
}
```

### Auto-enabling for Mutations

You can also configure RescriptRelay to automatically apply exhaustiveness checking to mutation fields that return unions. Add `autoExhaustiveMutations: true` to your Relay configuration to enable this behavior for any top-level mutation field that returns a union type.

## Wrapping up

And that's that! Keep the following in mind about unions and everything will be fine:

- Remember to select `__typename`
- Remember to handle `UnselectedUnionMember`
- Consider using `@exhaustive` to ensure you handle all union members
Loading