Skip to content

Commit 8db7763

Browse files
authored
RFC: operation expressions (#823)
* RFC: operation expressions * Add more examples and grammar summary * Fix headers, add number syntax * Format (some of) RFC with prettier * Allow aliasing fragments within a SelectionPath * Clarify grammar introduction * Use > rather than . for name paths
1 parent 7f56b46 commit 8db7763

File tree

1 file changed

+391
-0
lines changed

1 file changed

+391
-0
lines changed

rfcs/OperationExpressions.md

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
# RFC: Operation Expressions
2+
3+
(WORKING TITLE!)
4+
5+
**Proposed by:** [Benjie Gillam](https://twitter.com/benjie) - Graphile
6+
7+
In the [Schema Coordinates RFC](./SchemaCoordinates.md) Mark introduced the
8+
concept of "schema coordinates" which give a standard human- and
9+
machine-readable way to unambiguously refer to entities within a GraphQL schema:
10+
types, fields, field arguments, enum values, directives and directive arguments.
11+
The scope of that RFC is deliberately very tight, and it serves that goal well,
12+
providing a one-to-one mapping between the schema coordinates and the schema
13+
entities.
14+
15+
This RFC is to gather feedback on expansions of the Schema Coordinate syntax
16+
that could be used for different purposes whilst maintaining familiarity.
17+
18+
## Aim
19+
20+
The aim of this RFC is to give the GraphQL community a standard syntax that
21+
people, tools and documentation can use to concisely and consistently reference
22+
GraphQL operation concepts such as paths that is more fluid, expressive, and
23+
contains more context than the Schema Coordinates RFC that this RFC builds on
24+
top of.
25+
26+
This is not intended to be a replacement of the Schema Coordinates RFC, but an
27+
extension to it for a number of additional use-cases.
28+
29+
## Use cases
30+
31+
#### Referencing a position within a GraphQL Operation Document
32+
33+
Imagine you have the following GraphQL query:
34+
35+
```graphql
36+
{
37+
businesses: searchBusinesses(name: "Automotive") {
38+
id
39+
name
40+
owner: personByOwnerId {
41+
id
42+
name
43+
email # <<< HERE
44+
}
45+
}
46+
}
47+
```
48+
49+
You might reference the marked (`<<< HERE`) field with an expression such as:
50+
51+
- `Person.email` - this is the "schema coordinate" which uniquely identifies the
52+
field, but lacks context on how we retrieved it
53+
- `>businesses>owner>email` - given the GraphQL query document, this is
54+
sufficient to uniquely identify this specific reference (caveat: duplicate
55+
fields would all be referenced with the same expression)
56+
- `>businesses:searchBusinesses>owner:personByOwnerId>email` - this contains
57+
more context than the above, indicating not just the aliases but the actual
58+
field names too; with this access to the operation document is not required to
59+
determine what was requested
60+
- `>businesses:searchBusinesses(name:)>owner:personByOwnerId>email` - this
61+
contains even more context (the argument names that were used)
62+
63+
These are all valid operation expressions, but they each convey different levels
64+
of context.
65+
66+
### Generating a GraphQL Operation Document quickly (Emmet-style)
67+
68+
> Emmet is a plugin for many popular text editors which greatly improves HTML &
69+
> CSS workflow:
70+
71+
Emmet is a popular syntax for quickly generating HTML/CSS. It's easy to imagine
72+
how a operation expression syntax could be combined with a GraphQL schema
73+
definition to quickly generate GraphQL queries, mutations and subscriptions with
74+
a concise syntax. For example the expression:
75+
76+
`>businesses:searchBusinesses(name:)>owner:personByOwnerId>email`
77+
78+
might expand to:
79+
80+
```graphql
81+
query($name: String!) {
82+
businesses: searchBusinesses(name: $name) {
83+
owner: personByOwnerId {
84+
email
85+
}
86+
}
87+
}
88+
```
89+
90+
`MyFragment:User.businesses>owner>email`
91+
92+
might expand to:
93+
94+
```graphql
95+
fragment MyFragment on User {
96+
businesses {
97+
owner {
98+
email
99+
}
100+
}
101+
}
102+
```
103+
104+
### Documentation Permalinks
105+
106+
When navigating the GraphiQL documentation, GraphiQL maintains a stack of the
107+
path you arrived to the current documentation page through. It could be valuable
108+
to store this into the query string such that you could share a "documentation
109+
stack" with someone else (or bookmark it). For example if you browsed through
110+
the documentation via:
111+
112+
- `User` type
113+
- `User.friends` field (returns a `User`)
114+
- `User.latestMedia` field (returns a `Media` union)
115+
- `Post` type in Media union
116+
- `title` field
117+
118+
you might use a query string such as:
119+
120+
```
121+
?docs=User.friends>latestMedia>Post.title
122+
```
123+
124+
### Linking from a field description to an operation path
125+
126+
If, for example, you were to deprecate a root-level field in your schema, you
127+
might want to indicate where the user can retrieve the equivalent data now. You
128+
could do this by including an operation expression as part of the deprecation
129+
reason:
130+
131+
> The `Query.branchesFromFork` field is being removed; please use the following
132+
> path instead: `Query>repositories>forks>branches`
133+
134+
### Indicating how to access a particular field
135+
136+
When reading the documentation of a type in GraphiQL it currently does not
137+
indicate how to reach a particular field. Though there are often infinitely many
138+
paths to reach a field, often the shortest are the most valuable, so GraphiQL
139+
could indicate a few of the shorter paths using operation expression syntax:
140+
141+
> `User.firstName` can be accessed through paths such as:
142+
>
143+
> - `>me>firstName`
144+
> - `>articles>author>firstName`
145+
> - `>searchMedia>Book.author>firstName`
146+
> - `mutation>createUser>user>firstName`
147+
148+
### Analytics
149+
150+
When analysing how a GraphQL schema is used, it may be useful to track
151+
statistics for each type, field, argument using Schema Coordinates; but it may
152+
also be interesting to track through what paths users are finding said fields.
153+
You could use operation expression syntax to track this:
154+
155+
```
156+
counters['Query.cities>libraries>findBook(isbn:)']++
157+
```
158+
159+
## Syntax
160+
161+
Syntax is in flux; but here's some thoughts:
162+
163+
#### Pathing
164+
165+
Following a path from one field to the next could use the `>` character; this is
166+
already used in Apollo's GraphQL documentation browser and is intuitive for
167+
navigation. This leaves `.` available and non-ambiguous for referring to fields
168+
on a type, which is useful when disambiguating references on a union type, for
169+
instance:
170+
171+
```
172+
>me>media>Film.duration
173+
```
174+
175+
might model:
176+
177+
```graphql
178+
{
179+
me {
180+
media {
181+
... on Film {
182+
duration
183+
}
184+
}
185+
}
186+
}
187+
```
188+
189+
#### Operations
190+
191+
The expression `>me>name` would expand to `{ me { name } }`.
192+
193+
If you want to create a mutation or subscription operation, you can prefix the
194+
path with the operation type (you can do this for queries too, but just like in
195+
operation documents, the query keyword is optional):
196+
197+
- `mutation>createUser>user>name` expands to
198+
`mutation ($input: CreateUserInput!) { createUser(input: $input) { user { name } } }`
199+
- `subscription>currentUserUpdated>name` expands to
200+
`subscription { currentUserUpdated { name } }`
201+
- `query>me>name` expands to `query { me { name } }`
202+
203+
You may name operations by prefixing with an operation name followed by a colon;
204+
for example:
205+
206+
- `MyQuery:>me>name` and `MyQuery:query>me>name` expand to
207+
`query MyQuery { me { name } }`.
208+
- `MyMutation:mutation>createUser>name` expands to
209+
`mutation MyMutation { createUser { name } }`.
210+
- `MySubscription:subscription>userCreated>name` expands to
211+
`subscription MySubscription { userCreated { name } }`.
212+
213+
#### Fragments
214+
215+
Fragments start with a type name followed by a period: `User.friends>name`
216+
expands to `... on User { friends { name } }`.
217+
218+
You can name fragments by prefixing with a fragment name and a colon:
219+
`FriendNames:User.friends>name` expands to
220+
`fragment FriendNames on User { friends { name } }`.
221+
222+
Other examples:
223+
224+
- `MyFragment:Node.User.fullName:name` expands to
225+
`fragment MyFragment on Node { ... on User { fullName: name } }`
226+
227+
- `MyQuery:>allEntities>edges>node>MyNodeFragment:Node.MyUserFragment:User.fullName:name`
228+
expands to
229+
230+
```graphql
231+
query MyQuery {
232+
allEntities {
233+
edges {
234+
node {
235+
...MyNodeFragment
236+
}
237+
}
238+
}
239+
}
240+
241+
fragment MyNodeFragment on Node {
242+
...MyUserFragment
243+
}
244+
245+
fragment MyUserFragment on User {
246+
fullName: name
247+
}
248+
```
249+
250+
#### Arguments
251+
252+
Arguments use the same syntax as Schema Coordinates; namely parenthesis and a
253+
colon: `>searchBusinesses(name:)>city`.
254+
255+
We also allow you to reference input objects used in arguments, for example:
256+
257+
`>searchBusinesses(where>size>greaterThan:)>city`
258+
259+
expands to something like:
260+
261+
```graphql
262+
query($whereSizeGreaterThan: Int) {
263+
searchBusinesses(where: { size: { greaterThan: $whereSizeGreaterThan } }) {
264+
city
265+
}
266+
}
267+
```
268+
269+
Further we allow for multiple arguments to be specified, joined with commas:
270+
271+
`>searchBusinesses(where>size>greaterThan:,where>size>lessThan:,where>city>equalTo:)>name`
272+
273+
expands to something like:
274+
275+
```graphql
276+
query(
277+
$whereSizeGreaterThan: Int
278+
$whereSizeLessThan: Int
279+
$whereCityEqualTo: String
280+
) {
281+
searchBusinesses(
282+
where: {
283+
size: { greaterThan: $whereSizeGreaterThan, lessThan: $whereSizeLessThan }
284+
city: { equalTo: $whereCityEqualTo }
285+
}
286+
) {
287+
name
288+
}
289+
}
290+
```
291+
292+
> NOTE: the following number syntax probably needs more thought. Added only for
293+
> completeness.
294+
295+
We also allow `[number]` syntax to refer to a numbered entry in a list, or `[]`
296+
to refer to the next entry; e.g.:
297+
298+
`>findUsers(byIds[]:,byIds[],byIds[],byIds[5])>name`
299+
300+
expands to something like:
301+
302+
```graphql
303+
query($byIds0: ID, $byIds1: ID, $byIds2: ID, $byIds5: ID) {
304+
findUsers(byIds: [$byIds0, $byIds1, $byIds2, null, null, $byIds5]) {
305+
name
306+
}
307+
}
308+
```
309+
310+
## Grammar
311+
312+
The Lexical Tokens below plus `OperationType` and `Alias` are defined as in the
313+
GraphQL spec. Note there are no ignored characters: **whitespace is not
314+
ignored**.
315+
316+
### Lexical Tokens
317+
318+
Name ::
319+
- NameStart NameContinue* [lookahead != NameContinue]
320+
321+
NameStart ::
322+
- Letter
323+
- `_`
324+
325+
NameContinue ::
326+
- Letter
327+
- Digit
328+
- `_`
329+
330+
Letter :: one of
331+
`A` `B` `C` `D` `E` `F` `G` `H` `I` `J` `K` `L` `M`
332+
`N` `O` `P` `Q` `R` `S` `T` `U` `V` `W` `X` `Y` `Z`
333+
`a` `b` `c` `d` `e` `f` `g` `h` `i` `j` `k` `l` `m`
334+
`n` `o` `p` `q` `r` `s` `t` `u` `v` `w` `x` `y` `z`
335+
336+
Digit :: one of
337+
`0` `1` `2` `3` `4` `5` `6` `7` `8` `9`
338+
339+
IntValue :: IntegerPart [lookahead != {Digit, `.`, NameStart}]
340+
341+
IntegerPart ::
342+
- NegativeSign? 0
343+
- NegativeSign? NonZeroDigit Digit*
344+
345+
NegativeSign :: -
346+
347+
NonZeroDigit :: Digit but not `0`
348+
349+
Comma :: ,
350+
351+
### Expression Syntax
352+
353+
Expression :
354+
- FragmentExpression
355+
- OperationExpression
356+
357+
OperationExpression : Alias? OperationType? > SelectionPath
358+
359+
FragmentExpression : Alias? Name . SelectionPath
360+
361+
Alias : Name :
362+
363+
OperationType : one of `query` `mutation` `subscription`
364+
365+
SelectionPath :
366+
- Alias? Name . Alias? Name ( Arguments ) > SelectionPath
367+
- Alias? Name . Alias? Name ( Arguments )
368+
- Alias? Name . Alias? Name > SelectionPath
369+
- Alias? Name . Alias? Name
370+
- Alias? Name ( Arguments ) > SelectionPath
371+
- Alias? Name ( Arguments )
372+
- Alias? Name > SelectionPath
373+
- Alias? Name
374+
375+
Arguments :
376+
- Argument Comma Arguments
377+
- Argument
378+
379+
Argument : NamePath :
380+
381+
NamePath :
382+
- Name Indexes? > NamePath
383+
- Name Indexes?
384+
385+
Indexes :
386+
- Index Indexes
387+
- Index
388+
389+
Index :
390+
- [ IntValue ]
391+
- [ ]

0 commit comments

Comments
 (0)