Skip to content

Add Transitional Non-Null appendix (@noPropagate directive) #1165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: error-behavior2
Choose a base branch
from

Conversation

benjie
Copy link
Member

@benjie benjie commented Apr 30, 2025

This is essentially solution 8 to the Semantic Nullability RFC:

  • Enables semantic nullability to be reflected in schemas without breaking legacy behavior.
  • Facilitates incremental adoption of modern error handling without requiring disruptive changes.
  • Requires minimal spec impact and is fully optional for implementations.
  • Reflects transitional nature of this change in behavior

I've based it on:

since, like all solutions to the semantic nullability problem1, it is designed to enable clients with error propagation disabled to leverage the true nullability of the underlying data without breaking legacy clients. The approach could be rebuilt atop an alternative method of toggling error propagation, for example a directive-based approach.


This PR introduces an appendix to the GraphQL specification defining an optional solution to the semantic nullability problem using the following key mechanisms:

  • @noPropagate2 directive — allows schema authors to annotate Non-Null return types as transitional, suppressing propagation but preserving runtime error generation.
  • Transitional Non-Null semantics — errors at these positions behave like nullable fields in terms of (no!) propagation but like non-nullable fields in value completion (error on null).
  • New __Field.noPropagateLevels: [Int!] field — exposes transitional status to modern clients.
  • Transitional non-null hidden from legacy clients — tooling using the legacy PROPAGATE error behavior will get results from __Field.type that unwrap transitional non-null types.3

This solution attempts to address all of the feedback on previous solutions to this problem, whilst being explicitly transitional. It:

  • Is optional: explicitly only for schemas supporting legacy clients
  • Requires no changes to the main spec text
  • Introduces no new syntax
  • True to its name: an error here will not propagate (@noPropagate), regardless of whether error propagation is enabled or disabled.
  • Maintains introspection results for existing (deployed) clients and tooling
  • Maintains error boundaries for existing (deployed) clients
  • Allows all new schemas and new fields to use ! (non-null) directly for semantically non-null positions
  • Allows existing fields to use ! (non-null) for error handling clients without breaking legacy clients by adding the @noPropagate directive4
  • Can be adopted gradually, field-by-field, or en masse by applying @noPropagate to all nullable positions.
  • Can be removed from each field the moment no legacy clients query it

Footnotes

  1. Except solution 5

  2. This is essentially the same as the @semanticNonNull directive, but more strictly defined and reflected through introspection.

  3. This may be controversial, but I truly think it's the right decision. All new tooling (and all new clients!) should use onError: ABORT or onError: NO_PROPAGATE, and thus will see the true introspection. Existing tooling doesn't know about onError and so should not see these "transitional" non-null types.

  4. And if you forget to add it, adding it later is only a potentially breaking change for any new versions of legacy clients deployed since the change; error-handling clients (NO_PROPAGATE or ABORT) are unimpacted.

## The @noPropagate Directive

```graphql
directive @noPropagate(levels: [Int!]! = [0]) on FIELD_DEFINITION
Copy link
Contributor

@fotoetienne fotoetienne Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some alternative names:

  • @semantic - This field type is semantically non-null. It will never have a null value except in case of error, but we since errors happen it may have a null value
  • @soft - This is a "soft" non-null in that it is not enforced by propagation

Copy link
Contributor

@martinbonnin martinbonnin Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • @transitionalErrorBoundary
  • @transitionallyNullable
  • @localErrors

??

@benjie benjie added the 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) label May 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants