Skip to main content

__fulfilled meta field

At a glance

Timeline


Proposal: Add a __fulfilled(label: String): Boolean! meta-field

This is currently a Stage 0: Strawman proposal, but I'm including a PR with edits and a graphql-js implementation such that this can get knocked down or advance to Stage 1 quickly. @mjmahone is currently championing this proposal.

Implemented in graphql-js PR 3216.

Details: Add the below after 4.1: Type Introspection

GraphQL supports the ability to introspect into whether any given selection was included in a response. When a selection is included, all of that selection's fields will be explicitly set, except those not executed due to a directive such as @include or @skip. Selection introspection is accomplished via the meta-field __fulfilled(label: String): Boolean! on any Object, Interface, or Union. It always returns the value true: __fulfilled will be set if and only if the selection containing it is included in the response.

For example, given the operation:

query Q($foo: Boolean!) {
... @include(if: $foo) {
included: __fulfilled(label: "user.included")
}
... @skip(if: $foo) {
skipped: __fulfilled(label: "user.skipped")
}
}

The response should be either:

{
"included": true
}

or

{
"skipped": true
}

This meta-field is often used to clarify response interaction: as an example, the __fulfilled field can be used as a way to tell the consumer whether a specific fragment was included in the response. The optional label argument may be used to differentiate between __fulfilled selections.

As a meta-field, __fulfilled is implicit and does not appear in the fields list in any defined type.

Note: __fulfilled may not be included as a root field in a subscription operation.

End Spec Addition: Discussion continues below

Why?

I've discussed in the past about how Facebook is moving from product developers interacting with Response-shaped models that use inheritance such that any field in a selection set and all of its children are available. Instead, we try to use composition such that there is one model a product can interact with per selection set, and models need to explicitly convert (via a special conversion method) from parent to child selection sets.

For example, in combination with the @defer specification, when we have GraphQL that looks like:

fragment UserWithName on User {
name
...UserProfilePic @defer
}

fragment UserProfilePic on User {
name
profilePicture {
uri
}
}

An example of well-typed fragment models for the user to interact with, in pseudo-typescript, might look like this:

type UserWithName = {
name: string,
asUserProfilePic(): UserProfilePic | null,
};

type UserProfilePic = {
name: string,
profilePicture: {
uri: string,
},
};

In this case, asUserProfilePic() should return null if UserProfilePic has not yet been fulfilled in a response payload. Unfortunately, to determine that, we'd need to reach outside our local fragment, and check the response's label. Even that seems a bit buggy: what if UserWithName is included in multiple locations in the operation's tree?

The cleanest solution I've thought through is having a well-defined boolean field inserted, at the location we care about, that will be true if and only if the selection set we care about is included in the response. Note, this solution applies not just to @defer, but also to existing directives like @include and @skip, and could even enable simpler handling of type resolution. All a client needs to do to check whether a selection set was included, is to add something like Foo__fulfilled: __fulfilled(label: "Foo"), and see if the response contains Foo_fulfilled: true.

Client Specific Considerations

On most of our clients today (including Relay), we parse data from the server into a Graph format: we merge every instance of a single unique record (usually as determined by a primary key's value like id), taking all fields requested across an entire operation (or many operations), and merge them into a locally consistent graph.

However, product developers interact with this local graph as though it is shaped like the GraphQL selection sets described by their operation and fragment definitions. Meaning even though the server response might look like:

{
"actor": {
"__typename": "OpenSourceContributor",
"name": "Matt",
"org": "GraphQL"
}
}

there may be a variety of fragments, with drastically different shapes, that describe how specific components use that underlying response:

fragment A on Viewer {
actor {
__typename
name
...B @include(if: $use_b)
... on Business @include(if: $use_business) {
org
}
}
}

fragment B on User {
org
}

Just by looking at the structure of the response, I don't know whether my component using A should have access to actor.org: if both User and Business are interfaces, how can we tell whether this is the User's org or the Business' org? Or both?

Today, in order to determine whether org is from fragment B or ... on Business, we have clients that do one of:

  • Include a known type hierarchy in the client, and
    • Check the boolean values for $use_b and $use_business sent with the original request
    • Check whether the concrete type OpenSourceContributor is a User or Business
    • NOTE: this is prone to dropping fields, as future concrete types are not known to implement any interfaces by the client.
  • Use the __typename-aliasing hack, and re-route aliased typenames to a client-only field for our local Graph.

What to call this boolean meta-field?

I am open to bikeshedding here. Some alternatives:

  • __exists: the field is for figuring out whether a selection set exists, so this feels natural
  • __id: it's almost a "selection set identity" function. This is probably not a good name, as it will clash with lots of other implementations where __id means "identifier" rather than the "identity function id(x: X): X".
  • isFulfilled: this makes it clear we're using the field to answer a question. The downside is it's a camelCase'd meta-field, where many implementations use a snake_case style guide. I would greatly prefer a name that works well under either camelCase or snake_case paradigms

I am open to other suggestions!

Alternative 1: Just use __typename

Using __typename is an option. However, because __typename is of type String!, it's a complicating option: the response is bloated with unnecessary information, and any infra that relies on this field needs to translate the response as a string to a boolean value, based on whether the key exists rather than the actual __typename value.

Concretely: it's much less error-prone to generate models that, under the hood, do boolean checking on boolean-returned values, rather than requiring either a field-existence check, or a string-exists-and-is-not-empty check (depending on client language).

Furthermore, if we are using a normalized, canonical store for our response data, as Relay and Apollo and other clients do, we need a special fulfilled field handler just to make sure we don't accidentally write is_Foo_fulfilled to the canonical __typename, and ask whether __typename exists. Note, this is usually how we attempt to solve the problem today, and it has lead to actual bugs, hence this PR.

With the __fulfilled(label:) argument, the canonical version of the field can be used to differentiate unique selection sets, allowing the asUserProfilePic implementation from the Why? section to become:

  asUserProfilePic(): UserProfilePic | null {
if (_normalizedStoreRecord.getBoolean('__fulfilled(label:"UserProfilePic")')) {
return new UserProfilePic(_normalizedStoreRecord);
}
return null;
}

Alternative 2: Don't use a meta-field, if you want this ability explicitly add the field to each type in your schema

We could do this, but then we can't use this field on Unions. Also, if a field exists on every type, it should probably be a meta-field.

Other Alternatives

This is a straw man, so I'd be very interested in how other people solve this problem, and whether the above proposal would make their implementations better or be completely useless or harmful.