-
Notifications
You must be signed in to change notification settings - Fork 6
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
Introduce Lookup Directive. #30
base: main
Are you sure you want to change the base?
Changes from all commits
14badaf
f54f24a
dc499eb
5c9d4a1
9cef444
8771136
2798146
a75d22a
5766888
a5d8a5e
025718a
cbd4a36
e51f92a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,135 +2,177 @@ | |||||
|
||||||
## Directives | ||||||
|
||||||
### @entityResolver | ||||||
### @lookup | ||||||
|
||||||
```graphql | ||||||
directive @entityResolver on FIELD_DEFINITION | ||||||
directive @lookup(map: SelectionPath!) on FIELD_DEFINITION | ||||||
``` | ||||||
|
||||||
Entity resolvers are fields on the query root type of a subgraph that can | ||||||
resolve an entity by a stable key. The stable key is defined by the arguments of | ||||||
the field. | ||||||
The `@lookup` directive is used within a _source schema_ to specify object | ||||||
fields that can be used by the _distributed GraphQL executor_ to resolve an | ||||||
entity by a stable key. | ||||||
|
||||||
The stable key is defined by the arguments of the field. Only fields that are | ||||||
annotated with the `@lookup` directive will be recognized as lookup field. | ||||||
|
||||||
Source schemas can provide multiple lookup fields for the same entity with | ||||||
different keys. | ||||||
|
||||||
In this example the source schema specifies that the `Person` entity can be | ||||||
resolved with the `personById` field or the `personByName` field on the `Query` | ||||||
type. Both fields can resolve the same entity but do so with different keys. | ||||||
|
||||||
```graphql example | ||||||
extend type Query { | ||||||
version: Int # NOT an entity resolver. | ||||||
personById(id: ID!): Person @entityResolver | ||||||
type Query { | ||||||
version: Int # NOT a lookup field. | ||||||
personById(id: ID!): Person @lookup(map: "{ id: id }") | ||||||
personByName(name: String!): Person @lookup(map: "{ name: name }") | ||||||
} | ||||||
|
||||||
extend type Person { | ||||||
id: ID! # matches the argument of personById | ||||||
type Person @key(fields "id") @key(fields "name") { | ||||||
id: ID! | ||||||
name: String! | ||||||
} | ||||||
``` | ||||||
|
||||||
The arguments of an entity resolver field must match fields of the returning | ||||||
type. | ||||||
The selection syntax provided as a value to the `map` argument of the `@lookup` | ||||||
directive must correspond to the all arguments of a lookup field. Further it | ||||||
must correspond to fields specified by a `@key` directive annotated on the | ||||||
return type of the lookup field. | ||||||
|
||||||
```graphql example | ||||||
extend type Query { | ||||||
node(id: ID!): Node @entityResolver | ||||||
type Query { | ||||||
node(id: ID!): Node @lookup(map: "{ id: id }") | ||||||
} | ||||||
|
||||||
interface Node { | ||||||
interface Node @key(fields "id") { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
id: ID! | ||||||
} | ||||||
``` | ||||||
|
||||||
When an entity resolver returns an interface all implementing types are inferred | ||||||
as entities. | ||||||
Lookup fields may return object, interface or union types. In case a lookup | ||||||
field returns an interface or union type all possible object types are | ||||||
michaelstaib marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
considered entities and must have keys that correspond with the lookup map. | ||||||
|
||||||
```graphql example | ||||||
extend type Query { | ||||||
entityById(id: ID!, categoryId: Int): Entity @entityResolver | ||||||
type Query { | ||||||
entityById(id: ID!, categoryId: Int): Entity @lookup(map: "{ id: id, categoryId: categoryId }") | ||||||
} | ||||||
|
||||||
union Entity = Cat | Dog | ||||||
|
||||||
extend type Dog { | ||||||
type Dog @key(fields "id categoryId") { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
id: ID! | ||||||
categoryId: Int | ||||||
} | ||||||
|
||||||
extend type Cat { | ||||||
type Cat @key(fields "id categoryId") { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
id: ID! | ||||||
categoryId: Int | ||||||
} | ||||||
``` | ||||||
|
||||||
### @is | ||||||
The following example shows an invalid lookup field as the `Cat` type does not | ||||||
declare a key that corresponds with the lookup fields argument signature. | ||||||
|
||||||
```graphql | ||||||
directive @is( | ||||||
field: FieldSelection | ||||||
coordinate: Schemacoordinate | ||||||
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ||||||
``` | ||||||
```graphql counter-example | ||||||
type Query { | ||||||
entityById(id: ID!, categoryId: Int): Entity @lookup(map: "{ id: id, categoryId: categoryId }") | ||||||
} | ||||||
|
||||||
The `@is` directive is utilized to establish semantic equivalence between | ||||||
disparate type system members across distinct subgraphs, which the schema | ||||||
composition uses to connect types. | ||||||
union Entity = Cat | Dog | ||||||
|
||||||
In the following example, the directive specifies that the `id` argument on the | ||||||
field `Query.personById` and the field `Person.id` on the return type of the | ||||||
field are semantically the same. This information is used to infer an entity | ||||||
resolver for `Person` from the field `Query.personById`. | ||||||
type Dog @key(fields "id categoryId") { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
id: ID! | ||||||
categoryId: Int | ||||||
} | ||||||
|
||||||
```graphql example | ||||||
extend type Query { | ||||||
personById(id: ID! @is(field: "id")): Person @entityResolver | ||||||
type Cat @key(fields "id") { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
id: ID! | ||||||
} | ||||||
``` | ||||||
|
||||||
The `@is` directive also allows to refer to nested fields relative to `Person`. | ||||||
If the lookup returns an interface in particular, the interface must also be | ||||||
annotated with a `@key` directive. | ||||||
|
||||||
```graphql example | ||||||
extend type Query { | ||||||
personByAddressId(id: ID! @is(field: "address { id }")): Person | ||||||
interface Node @key(fields "id") { | ||||||
id: ID! | ||||||
} | ||||||
``` | ||||||
|
||||||
The `@is` directive not limited to a single argument. | ||||||
Lookup fields must be accessible from the Query type. If not directly on the | ||||||
Query type, they must be accessible via fields that do not require arguments, | ||||||
starting from the Query root type. | ||||||
|
||||||
```graphql example | ||||||
extend type Query { | ||||||
personByAddressId( | ||||||
id: ID! @is(field: "address { id }") | ||||||
kind: PersonKind @is(field: "kind") | ||||||
): Person | ||||||
type Query { | ||||||
lookups: Lookups! | ||||||
} | ||||||
|
||||||
type Lookups { | ||||||
personById(id: ID!): Person @lookup(map: "{ id: id }") | ||||||
} | ||||||
|
||||||
type Person @key(fields "id") { | ||||||
id: ID! | ||||||
} | ||||||
``` | ||||||
|
||||||
The directive can also establish semantic equivalence between two output fields. | ||||||
In this example, the field `productSKU` is semantically equivalent to the field | ||||||
`Product.sku`, allowing the schema composition to infer the connection of the | ||||||
`Product` with the `Review` type. | ||||||
Lookup fields can also use the `@oneOf` directive to specify a lookup field that | ||||||
can resolve multiple keys. | ||||||
|
||||||
```graphql example | ||||||
extend type Review { | ||||||
productSKU: ID! @is(coordinate: "Product.sku") @internal | ||||||
product: Product @resolve | ||||||
type Query { | ||||||
person(finder: PersonFinderInput!): Person @lookup(map: "{ name: name } | { id: id }") | ||||||
} | ||||||
|
||||||
type Person @key(fields "id") @key(fields "name") { | ||||||
id: ID! | ||||||
name: String! | ||||||
} | ||||||
|
||||||
input PersonFinderInput @oneOf { | ||||||
id: ID | ||||||
name: String | ||||||
} | ||||||
``` | ||||||
|
||||||
The `@is` directive can use either the `field` or `coordinate` argument. If both | ||||||
are specified, the schema composition must fail. | ||||||
**Arguments:** | ||||||
|
||||||
```graphql counter-example | ||||||
extend type Review { | ||||||
productSKU: ID! | ||||||
@is(coordinate: "Product.sku", field: "product { sku }") | ||||||
@internal | ||||||
product: Product @resolve | ||||||
- `map`: Represents a selection path that describes how keys are mapped to | ||||||
arguments of a lookup field. | ||||||
|
||||||
### @patch | ||||||
|
||||||
```graphql | ||||||
directive @patch(map: SelectionPath!) on FIELD_DEFINITION | ||||||
``` | ||||||
|
||||||
The `@patch` directive is used within a _source schema_ to specify object fields | ||||||
that can be used by the _distributed GraphQL executor_ to resolve additional | ||||||
data for an entity rather than fetching the entity itself. A patch resolver | ||||||
result does noth mean that the actual entity exists. | ||||||
|
||||||
```graphql example | ||||||
type Query { | ||||||
personById(id: ID!): Person @patch(map: "{ id: id }") | ||||||
personByName(name: String!): Person @patch(map: "{ name: name }") | ||||||
} | ||||||
|
||||||
type Person @key(fields "id") @key(fields "name") { | ||||||
id: ID! | ||||||
name: String! | ||||||
} | ||||||
``` | ||||||
|
||||||
Patch resolve as oposed to lookup fields will be omitted from the _composite | ||||||
schema_ but will be referenced within the _composite execution schema_. | ||||||
|
||||||
**Arguments:** | ||||||
|
||||||
- `field`: Represents a GraphQL field selection syntax that refers to field | ||||||
relative to the current type; or, when used on arguments it refers to a field | ||||||
relative to the return type. | ||||||
- `coordinate`: Represents a schema coordinate that refers to a type system | ||||||
member. | ||||||
- `map`: Represents a selection path that describes how keys are mapped to | ||||||
arguments of a lookup field. | ||||||
|
||||||
### @shareable | ||||||
|
||||||
|
@@ -156,8 +198,8 @@ Note: Key fields are always considered sharable. | |||||
|
||||||
```graphql | ||||||
directive @require( | ||||||
field: FieldSelection! | ||||||
) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ||||||
map: SelectionPath! | ||||||
) on FIELD_DEFINITION | ||||||
``` | ||||||
|
||||||
The `@require` directive is used to express data requirements with other | ||||||
|
@@ -170,9 +212,9 @@ type Product { | |||||
id: ID! | ||||||
delivery( | ||||||
zip: String! | ||||||
size: Int! @require(field: "dimension { size }") | ||||||
weight: Int! @require(field: "dimension { weight }") | ||||||
): DeliveryEstimates | ||||||
size: Int! | ||||||
weight: Int! | ||||||
): DeliveryEstimates @require(field: "{ size: dimension.size weight: dimension.weight }") | ||||||
} | ||||||
``` | ||||||
|
||||||
|
@@ -185,8 +227,8 @@ type Product { | |||||
id: ID! | ||||||
delivery( | ||||||
zip: String! | ||||||
dimension: ProductDimensionInput! @require(field: "dimension")) | ||||||
): DeliveryEstimates | ||||||
dimension: ProductDimensionInput!) | ||||||
): DeliveryEstimates @require(field: "{ dimension: { size: dimension.size weight: dimension.weight } }") | ||||||
} | ||||||
``` | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.