Skip to content
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

Spec edits for incremental delivery, Section 3 only #1132

Open
wants to merge 15 commits into
base: incremental-integration
Choose a base branch
from
Open
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
30 changes: 30 additions & 0 deletions .github/algorithm-format-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { readFile, readdir } from "node:fs/promises";

const SPEC_DIR = new URL("../spec", import.meta.url).pathname;

/** @see {@link https://spec-md.com/#sec-Value-Literals} */
const valueLiteralsRegexp = /\{((?:[^{}]|(?:\{[^{}]*\}))+)\}/g;

process.exitCode = 0;
const filenames = await readdir(SPEC_DIR);
for (const filename of filenames) {
Expand Down Expand Up @@ -72,6 +75,33 @@ for (const filename of filenames) {
console.log();
process.exitCode = 1;
}

const stepWithoutValueLiterals = step.replace(
valueLiteralsRegexp,
""
);
if (stepWithoutValueLiterals.match(/\b[A-Z][A-Za-z0-9]+\(/)) {
console.log(
`Bad formatting of '${algorithmName}' step (algorithm call should be wrapped in braces: \`{MyAlgorithm(a, b, c)}\`) in '${filename}':`
);
console.dir(step);
console.log();
process.exitCode = 1;
}

const valueLiterals = step.matchAll(valueLiteralsRegexp, "");
for (const lit of valueLiterals) {
const inner = lit[1];
if (inner.includes("{")) {
console.log(
`Bad formatting of '${algorithmName}' step (algorithm call should not contain braces: \`${lit}\`) in '${filename}':`
);
console.dir(step);
console.log();
process.exitCode = 1;
}
}

const trimmedInnerLine = step.replace(/\s+/g, " ");
if (
trimmedInnerLine.match(
Expand Down
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ contributions.

Contributions that do not change the interpretation of the spec but instead
improve legibility, fix editorial errors, clear up ambiguity and improve
examples are encouraged and are often merged by a spec editor with little
process.
examples are encouraged. These "editorial changes" will normally be given the
["✏ Editorial" label](https://github.com/graphql/graphql-spec/issues?q=sort%3Aupdated-desc+is%3Aopen+label%3A%22%E2%9C%8F%EF%B8%8F+Editorial%22)
and are often merged by a spec editor with little process.

However, contributions that _do_ meaningfully change the interpretation of the
spec must follow an RFC (Request For Comments) process led by a _champion_
Expand Down
22 changes: 22 additions & 0 deletions STYLE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,25 @@ MyAlgorithm(argOne, argTwo):
- Let {something} be {true}.
- Return {something}.
```

## Definitions

For important terms, use
[Spec Markdown definition paragraphs](https://spec-md.com/#sec-Definition-Paragraph).

Definition paragraphs start with `::` and add the matching italicized term to
the [specification index](https://spec.graphql.org/draft/#index), making it easy
to reference them.

## Tone of voice

The GraphQL specification is a reference document and should use neutral and
descriptive tone of voice.

**Favor the present tense**

The present tense is usually clearer and shorter:

✅ Present: The client then sends a request to the server.

❌ Future: The client will then send a request to the server.
2 changes: 1 addition & 1 deletion spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,7 @@ Type : Name

- Let {name} be the string value of {Name}.
- Let {type} be the type defined in the Schema named {name}.
- {type} must not be {null}.
- {type} must exist.
- Return {type}.

Type : [ Type ]
Expand Down
131 changes: 127 additions & 4 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ IsInputType(type):

- If {type} is a List type or Non-Null type:
- Let {unwrappedType} be the unwrapped type of {type}.
- Return IsInputType({unwrappedType}).
- Return {IsInputType(unwrappedType)}.
- If {type} is a Scalar, Enum, or Input Object type:
- Return {true}.
- Return {false}.
Expand All @@ -360,7 +360,7 @@ IsOutputType(type):

- If {type} is a List type or Non-Null type:
- Let {unwrappedType} be the unwrapped type of {type}.
- Return IsOutputType({unwrappedType}).
- Return {IsOutputType(unwrappedType)}.
- If {type} is a Scalar, Object, Interface, Union, or Enum type:
- Return {true}.
- Return {false}.
Expand Down Expand Up @@ -794,8 +794,8 @@ And will yield the subset of each object type queried:
When querying an Object, the resulting mapping of fields are conceptually
ordered in the same order in which they were encountered during execution,
excluding fragments for which the type does not apply and fields or fragments
that are skipped via `@skip` or `@include` directives. This ordering is
correctly produced when using the {CollectFields()} algorithm.
that are skipped via `@skip` or `@include` directives or postponed via `@defer`.
This ordering is correctly produced when using the {CollectFields()} algorithm.

Response serialization formats capable of representing ordered maps should
maintain this ordering. Serialization formats which can only represent unordered
Expand Down Expand Up @@ -1948,6 +1948,15 @@ GraphQL implementations that support the type system definition language must
provide the `@deprecated` directive if representing deprecated portions of the
schema.

GraphQL implementations may provide the `@defer` and/or `@stream` directives. If
either or both of these directives are provided, they must conform to the
requirements defined in this specification.

Note: The [Directives Are Defined](#sec-Directives-Are-Defined) validation rule
ensures that GraphQL operations can only include directives available on the
schema; thus operations including `@defer` or `@stream` directives can only be
executed by a GraphQL service that supports them.

GraphQL implementations that support the type system definition language should
provide the `@specifiedBy` directive if representing custom scalar definitions.

Expand Down Expand Up @@ -2164,3 +2173,117 @@ to the relevant IETF specification.
```graphql example
scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")
```

### @defer

```graphql
directive @defer(
if: Boolean! = true
label: String
) on FRAGMENT_SPREAD | INLINE_FRAGMENT
```

The `@defer` directive may be provided on a fragment spread or inline fragment
to indicate that execution of the related selection set should be deferred. When
a request includes the `@defer` directive, it may return an _incremental stream_
consisting of an _initial response payload_ containing all non-deferred data,
followed by one or more _subsequent response payload_ including the deferred
data.

The `@include` and `@skip` directives take precedence over `@defer`.

```graphql example
query myQuery($shouldDefer: Boolean! = true) {
user {
name
...someFragment @defer(label: "someLabel", if: $shouldDefer)
}
}
fragment someFragment on User {
id
profile_picture {
uri
}
}
```

#### @defer Arguments

- `if: Boolean! = true` - When `true`, fragment _should_ be deferred (see
related note below). When `false`, fragment must not be deferred. Defaults to
`true` when omitted.
- `label: String` - An optional string literal (variables are disallowed) used
by GraphQL clients to identify data in the _incremental stream_ and associate
it with the corresponding defer directive. If provided, the GraphQL service
must include this label in the corresponding pending object within the
_incremental stream_. The `label` argument must be unique across all `@defer`
and `@stream` directives in the document.

### @stream

```graphql
directive @stream(
initialCount: Int! = 0
if: Boolean! = true
label: String
) on FIELD
```

The `@stream` directive may be provided for a field whose type incorporates a
`List` type modifier. The directive enables returning a partial list initially,
followed by additional items in subsequent payloads. If the field type
incorporates multiple `List` type modifiers, only the outermost list is
streamed.

Note: The mechanism through which items are streamed is implementation-defined
and may use technologies such as asynchronous iterators.

The `@include` and `@skip` directives take precedence over `@stream`.

```graphql example
query myQuery($shouldStream: Boolean! = true) {
user {
friends(first: 10)
@stream(label: "friendsStream", initialCount: 5, if: $shouldStream) {
name
}
}
}
```

#### @stream Arguments

- `initialCount: Int! = 0` - The number of list items to include initially when
completing the parent selection set. If omitted, defaults to `0`. A field
error will be raised if the value of this argument is less than `0`. When the
size of the list is greater than or equal to the value of `initialCount`, the
GraphQL service _must_ initially include at least as many list items as the
value of `initialCount` (see related note below).
- `if: Boolean! = true` - When `true`, field _should_ be streamed (see related
note below). When `false`, the field must behave as if the `@stream` directive
is not present—it must not be streamed and all of the list items must be
included. Defaults to `true` when omitted.
- `label: String` - An optional string literal (variables are disallowed) used
by GraphQL clients to identify data in the _incremental stream_ and associate
it with the corresponding stream directive. If provided, the GraphQL service
must include this label in the corresponding pending object within the
_incremental stream_. The `label` argument must be unique across all `@defer`
and `@stream` directives in the document.

Note: The
[Defer And Stream Directive Labels Are Unique](#sec-Defer-And-Stream-Directive-Labels-Are-Unique)
validation rule ensures uniqueness of the values passed to `label` on both the
`@defer` and `@stream` directives. Variables are disallowed in the `label`
because their values may not be known during validation.

Note: The ability to defer and/or stream data can have a potentially significant
impact on application performance. Developers generally need clear, predictable
control over their application's performance. It is highly recommended that
GraphQL services honor the `@defer` and `@stream` directives on each execution.
However, the specification allows advanced use cases where the service can
determine that it is more performant to not defer and/or stream. Therefore,
GraphQL clients _must_ be able to process a _response_ that ignores individual
`@defer` and/or `@stream` directives. This also applies to the `initialCount`
argument on the `@stream` directive. Clients must be able to process a streamed
field result that contains more initial list items than what was specified in
the `initialCount` argument.
5 changes: 3 additions & 2 deletions spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ FieldsInSetCanMerge(set):

- Let {fieldsForName} be the set of selections with a given response name in
{set} including visiting fragments and inline fragments.
- Given each pair of members {fieldA} and {fieldB} in {fieldsForName}:
- Given each pair of distinct members {fieldA} and {fieldB} in {fieldsForName}:
- {SameResponseShape(fieldA, fieldB)} must be true.
- If the parent types of {fieldA} and {fieldB} are equal or if either is not
an Object Type:
Expand Down Expand Up @@ -452,7 +452,8 @@ SameResponseShape(fieldA, fieldB):
selection set of {fieldB}.
- Let {fieldsForName} be the set of selections with a given response name in
{mergedSet} including visiting fragments and inline fragments.
- Given each pair of members {subfieldA} and {subfieldB} in {fieldsForName}:
- Given each pair of distinct members {subfieldA} and {subfieldB} in
{fieldsForName}:
- If {SameResponseShape(subfieldA, subfieldB)} is {false}, return {false}.
- Return {true}.

Expand Down
21 changes: 10 additions & 11 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,11 @@ ExecuteMutation(mutation, schema, variableValues, initialValue):
### Subscription

If the operation is a subscription, the result is an _event stream_ called the
"Response Stream" where each event in the event stream is the result of
executing the operation for each new event on an underlying "Source Stream".
_response stream_ where each event in the event stream is the result of
executing the operation for each new event on an underlying _source stream_.

Executing a subscription operation creates a persistent function on the service
that maps an underlying Source Stream to a returned Response Stream.
that maps an underlying _source stream_ to a returned _response stream_.

Subscribe(subscription, schema, variableValues, initialValue):

Expand Down Expand Up @@ -257,9 +257,9 @@ service details should be chosen by the implementing service.

#### Source Stream

A Source Stream is an _event stream_ representing a sequence of root values,
each of which will trigger a GraphQL execution. Like field value resolution, the
logic to create a Source Stream is application-specific.
:: A _source stream_ is an _event stream_ representing a sequence of root
values, each of which will trigger a GraphQL execution. Like field value
resolution, the logic to create a _source stream_ is application-specific.

CreateSourceEventStream(subscription, schema, variableValues, initialValue):

Expand Down Expand Up @@ -294,7 +294,7 @@ operation type.

#### Response Stream

Each event from the underlying Source Stream triggers execution of the
Each event from the underlying _source stream_ triggers execution of the
subscription _selection set_ using that event's value as the {initialValue}.

MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues):
Expand Down Expand Up @@ -339,7 +339,7 @@ Note: The {ExecuteSubscriptionEvent()} algorithm is intentionally similar to

#### Unsubscribe

Unsubscribe cancels the Response Stream when a client no longer wishes to
Unsubscribe cancels the _response stream_ when a client no longer wishes to
receive payloads for a subscription. This in turn also cancels the Source
Stream, which is a good opportunity to clean up any other resources used by the
subscription.
Expand All @@ -355,8 +355,7 @@ type need to be known, as well as whether it must be executed serially, or may
be executed in parallel.

First, the selection set is turned into a grouped field set; then, each
represented field in the grouped field set produces an entry into a response
map.
represented field in the grouped field set produces an entry into a result map.

ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues):

Expand Down Expand Up @@ -596,7 +595,7 @@ directives may be applied in either order since they apply commutatively.
## Executing Fields

Each field requested in the grouped field set that is defined on the selected
objectType will result in an entry in the response map. Field execution first
objectType will result in an entry in the result map. Field execution first
coerces any provided argument values, then resolves a value for the field, and
finally completes that value either by recursively executing another selection
set or coercing a scalar value.
Expand Down
12 changes: 11 additions & 1 deletion spec/Section 7 -- Response.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ the case that any _field error_ was raised on a field and was replaced with

## Response Format

A response to a GraphQL request must be a map.
A GraphQL request returns either a _response_ or a _response stream_.

### Response

:: A GraphQL request returns a _response_ when the GraphQL operation is a query
or mutation. A _response_ must be a map.

If the request raised any errors, the response map must contain an entry with
key `errors`. The value of this entry is described in the "Errors" section. If
Expand All @@ -35,6 +40,11 @@ Note: When `errors` is present in the response, it may be helpful for it to
appear first when serialized to make it more clear when errors are present in a
response during debugging.

### Response Stream

:: A GraphQL request returns a _response stream_ when the GraphQL operation is a
subscription. A _response stream_ must be a stream of _response_.

### Data

The `data` entry in the response will be the result of the execution of the
Expand Down