lighthouse-graphql 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +139 -1
- data/README.md +8 -5
- data/docs/configuration.md +26 -2
- data/docs/directives.md +287 -4
- data/docs/generators.md +82 -0
- data/docs/getting-started.md +26 -30
- data/docs/relationships.md +42 -0
- data/docs/relay.md +137 -0
- data/docs/resolvers.md +261 -22
- data/docs/security.md +17 -0
- data/docs/types.md +219 -0
- data/docs/uploads.md +80 -0
- data/lib/generators/lighthouse/base_classes.rb +32 -49
- data/lib/generators/lighthouse/install/install_generator.rb +83 -24
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +14 -6
- data/lib/generators/lighthouse/install/templates/schema.rb.tt +10 -3
- data/lib/lighthouse/async_mutation_job.rb +17 -0
- data/lib/lighthouse/complexity.rb +5 -4
- data/lib/lighthouse/config.rb +13 -2
- data/lib/lighthouse/controller.rb +27 -9
- data/lib/lighthouse/dataloader_sources/preload_loader.rb +16 -0
- data/lib/lighthouse/dataloader_sources/relation_counts_loader.rb +46 -0
- data/lib/lighthouse/default_resolve_dispatcher.rb +26 -0
- data/lib/lighthouse/directive_resolvers/all_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/argument_binding.rb +61 -0
- data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +220 -0
- data/lib/lighthouse/directive_resolvers/association_resolvers.rb +2 -2
- data/lib/lighthouse/directive_resolvers/async_resolver.rb +90 -0
- data/lib/lighthouse/directive_resolvers/base.rb +63 -14
- data/lib/lighthouse/directive_resolvers/builder_applier.rb +89 -19
- data/lib/lighthouse/directive_resolvers/can_resolvers.rb +1 -1
- data/lib/lighthouse/directive_resolvers/convention_resolver.rb +2 -1
- data/lib/lighthouse/directive_resolvers/count_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/field_resolver.rb +35 -7
- data/lib/lighthouse/directive_resolvers/first_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +57 -0
- data/lib/lighthouse/directive_resolvers/lazy_load_resolver.rb +61 -0
- data/lib/lighthouse/directive_resolvers/limit_resolver.rb +59 -0
- data/lib/lighthouse/directive_resolvers/method_resolver.rb +38 -3
- data/lib/lighthouse/directive_resolvers/model_mutation_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/namespaced_resolver.rb +31 -0
- data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +1 -1
- data/lib/lighthouse/directive_resolvers/resolution.rb +19 -4
- data/lib/lighthouse/directive_resolvers/subscription_resolver.rb +2 -1
- data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +77 -0
- data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +81 -0
- data/lib/lighthouse/directive_resolvers/with_count_resolver.rb +43 -0
- data/lib/lighthouse/directive_resolvers/with_resolver.rb +42 -0
- data/lib/lighthouse/directives/arguments/more_filters.rb +215 -0
- data/lib/lighthouse/directives/definitions.rb +52 -0
- data/lib/lighthouse/directives/model_directive.rb +131 -0
- data/lib/lighthouse/directives/node_directive.rb +134 -0
- data/lib/lighthouse/directives/pagination_manipulator.rb +1 -1
- data/lib/lighthouse/directives/registry.rb +21 -21
- data/lib/lighthouse/directives/visibility_directive.rb +71 -0
- data/lib/lighthouse/enum_directive.rb +41 -0
- data/lib/lighthouse/field_resolver_stack.rb +53 -6
- data/lib/lighthouse/generated_resolvers.rb +13 -8
- data/lib/lighthouse/global_id.rb +31 -0
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/multipart_request.rb +43 -0
- data/lib/lighthouse/node_registry.rb +68 -0
- data/lib/lighthouse/pagination.rb +3 -0
- data/lib/lighthouse/rb_lighthouse.rb +33 -0
- data/lib/lighthouse/relation_counts.rb +29 -0
- data/lib/lighthouse/resolve_info.rb +80 -0
- data/lib/lighthouse/resolver_bases.rb +9 -2
- data/lib/lighthouse/resolver_dispatch.rb +36 -11
- data/lib/lighthouse/scalar_coercion.rb +72 -0
- data/lib/lighthouse/schema_cache.rb +2 -1
- data/lib/lighthouse/schema_generator.rb +11 -10
- data/lib/lighthouse/schema_implementation.rb +4 -8
- data/lib/lighthouse/support/environment.rb +13 -0
- data/lib/lighthouse/support/model_resolver.rb +7 -5
- data/lib/lighthouse/support/preload.rb +49 -0
- data/lib/lighthouse/type.rb +103 -0
- data/lib/lighthouse/type_resolution.rb +150 -0
- data/lib/lighthouse/upload.rb +26 -0
- data/lib/lighthouse/upload_store.rb +32 -0
- data/sig/lighthouse/directives.rbs +3 -2
- data/sig/lighthouse.rbs +18 -0
- metadata +38 -1
data/docs/generators.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Generators
|
|
2
|
+
|
|
3
|
+
Lighthouse builds on graphql-ruby, and so do its generators: it writes only what
|
|
4
|
+
graphql-ruby has no generator for, or what an SDL-first schema needs differently.
|
|
5
|
+
Every graphql-ruby app's base classes (`Types::BaseObject`, `Resolvers::BaseResolver`,
|
|
6
|
+
`Mutations::BaseMutation`, ...) come from graphql-ruby's own `graphql:install`.
|
|
7
|
+
|
|
8
|
+
## `lighthouse:install`
|
|
9
|
+
|
|
10
|
+
```shell
|
|
11
|
+
bin/rails generate lighthouse:install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
It runs graphql-ruby's install first
|
|
15
|
+
(`graphql:install --no-relay --skip-graphiql --skip-query-logs`), which writes:
|
|
16
|
+
|
|
17
|
+
- `app/graphql/types/base_*.rb`: `BaseObject`, `BaseField`, `BaseArgument`,
|
|
18
|
+
`BaseInterface`, `BaseUnion`, `BaseScalar`, `BaseEnum`, `BaseInputObject`;
|
|
19
|
+
- `app/graphql/resolvers/base_resolver.rb` and `app/graphql/mutations/base_mutation.rb`;
|
|
20
|
+
- its schema class, `Types::QueryType`, `Types::MutationType` and controller, and the
|
|
21
|
+
`POST /graphql` route.
|
|
22
|
+
|
|
23
|
+
Then Lighthouse puts its own on top of what graphql-ruby wrote in that run:
|
|
24
|
+
|
|
25
|
+
| File | What Lighthouse does |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `app/graphql/app_schema.rb` | Replaced by the schema built from the SDL, naming every base class in `base_types` |
|
|
28
|
+
| `app/controllers/graphql_controller.rb` | Replaced by one including `Lighthouse::GraphQL::Controller` |
|
|
29
|
+
| `app/graphql/types/query_type.rb`, `mutation_type.rb` | Removed: the SDL declares the root types |
|
|
30
|
+
| `app/graphql/mutations/base_mutation.rb` | Made a `GraphQL::Schema::Mutation`: the SDL declares a mutation's arguments, where graphql-ruby's `RelayClassicMutation` takes a single `input:` |
|
|
31
|
+
| `app/graphql/subscriptions/base_subscription.rb` | Written: graphql-ruby has no subscription base. It includes `Lighthouse::GraphQL::Subscription` |
|
|
32
|
+
| `config/initializers/lighthouse.rb` | Written: every [configuration](configuration.md) option, commented out |
|
|
33
|
+
| `graphql/schema.graphql` | A starter schema, unless the folder holds `.graphql` files already |
|
|
34
|
+
| `graphql.config.yml` | IDE configuration, and `.gitignore` entries for the [IDE helper](getting-started.md#ide-support) files |
|
|
35
|
+
|
|
36
|
+
| Option | Effect |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `--schema=ApiSchema` | Name of the schema constant (default `AppSchema`) |
|
|
39
|
+
| `--sdl-folder=app/schema` | Where the `.graphql` files live (default `graphql`); sets `config.sdl_folder` |
|
|
40
|
+
| `--skip-controller` | No controller or route (graphql-ruby's are removed too) — for apps that serve GraphQL themselves |
|
|
41
|
+
| `--skip-ide` | No `graphql.config.yml` or `.gitignore` entries |
|
|
42
|
+
| `--force` | Replace a schema class or controller the app had before |
|
|
43
|
+
|
|
44
|
+
Rerunning it is safe: a file the app has is kept, and the route and `.gitignore`
|
|
45
|
+
entries are added once. In an app that already had a graphql-ruby schema class or
|
|
46
|
+
controller, those are kept (Lighthouse replaces only what graphql-ruby wrote in the
|
|
47
|
+
same run), and graphql-ruby's install points that schema at the root types it
|
|
48
|
+
wrote: have the controller call `AppSchema.execute(...)` to serve Lighthouse's
|
|
49
|
+
schema, or pass `--force`.
|
|
50
|
+
|
|
51
|
+
## Lighthouse's generators
|
|
52
|
+
|
|
53
|
+
For the classes graphql-ruby has no generator for, or none that fits an SDL-first
|
|
54
|
+
schema. Each writes into the first namespace configured for its kind (or
|
|
55
|
+
`--namespace=…`); namespaced names nest modules (`Blog::Publish`).
|
|
56
|
+
|
|
57
|
+
| Generator | Writes |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `lighthouse:query LatestPosts` | `Resolvers::LatestPosts < BaseResolver`, which `Query.latestPosts` resolves to by convention |
|
|
60
|
+
| `lighthouse:mutation PublishPost` | `Mutations::PublishPost < BaseMutation`, for `Mutation.publishPost` |
|
|
61
|
+
| `lighthouse:subscription PostUpdated` | `Subscriptions::PostUpdated < BaseSubscription`, with its hooks (and the base, when missing) |
|
|
62
|
+
| `lighthouse:builder Published` | `Builders::Published`, for `@builder(class: "Published")` |
|
|
63
|
+
| `lighthouse:validator CreatePostInput` | `Validators::CreatePostInputValidator`, a `Dry::Validation::Contract` |
|
|
64
|
+
|
|
65
|
+
The class's arguments and return type are the SDL's, so it declares neither. When
|
|
66
|
+
the base class is missing (an app that never ran an install, or a namespace of its
|
|
67
|
+
own such as `Api::Resolvers`), the generator says so: `lighthouse:install` writes it
|
|
68
|
+
through graphql-ruby, or write `Api::Resolvers::BaseResolver` yourself.
|
|
69
|
+
|
|
70
|
+
## graphql-ruby's generators in a Lighthouse app
|
|
71
|
+
|
|
72
|
+
| Generator | In a Lighthouse app |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `graphql:install` | Run by `lighthouse:install`. On its own, it writes a code-first schema, root types and a `RelayClassicMutation` base: run `lighthouse:install` instead |
|
|
75
|
+
| `graphql:object Post` | **Use it** for a [type class](resolvers.md#type-classes): add `include Lighthouse::GraphQL::Type`, then methods. The `field` lines it writes (from the model's columns, when there is one) are ignored, as the SDL declares the fields: delete them |
|
|
76
|
+
| `graphql:scalar DateTime` | **Use it** for a [scalar](types.md#scalars): add `include Lighthouse::GraphQL::Type`; its `coerce_input` / `coerce_result` are the ones Lighthouse calls |
|
|
77
|
+
| `graphql:union SearchResult`, `graphql:interface Authored` | **Use them** when the [default resolution](types.md#the-default) is not enough: add `include Lighthouse::GraphQL::Type` and a `resolve_type`. Its `possible_types` and `field` lines are ignored |
|
|
78
|
+
| `graphql:enum`, `graphql:input` | Not needed: the SDL declares enums and inputs ([`@enum`](directives.md#enum) gives a value an internal one) |
|
|
79
|
+
| `graphql:mutation`, `graphql:mutation_create` / `_update` / `_delete` | **Not ready**: they add a field to `Types::MutationType`, which a Lighthouse app has none of (they write it again), and write a `RelayClassicMutation` taking `input:`. Use `lighthouse:mutation`, or [`@create`, `@update`, `@delete`](mutations.md) |
|
|
80
|
+
| `graphql:relay` | Not needed: [`@node`, `@globalId`](relay.md) and `@paginate(type: CONNECTION)` |
|
|
81
|
+
| `graphql:loader` | A GraphQL::Batch loader, which your own resolver classes may use; Lighthouse's directives batch through graphql-ruby's Dataloader instead |
|
|
82
|
+
| `graphql:detailed_trace` | **Not ready**: it edits a `class AppSchema < GraphQL::Schema`, which Lighthouse's `AppSchema = get_schema(...)` is not. Add its migration and `AppSchema.use(GraphQL::Tracing::DetailedTrace)` by hand |
|
data/docs/getting-started.md
CHANGED
|
@@ -24,32 +24,24 @@ a hard dependency of Lighthouse so non-federated apps stay lean.
|
|
|
24
24
|
bin/rails generate lighthouse:install
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
does steps 1–5 below for you:
|
|
27
|
+
does steps 1–5 below for you. It runs graphql-ruby's `graphql:install` first, for
|
|
28
|
+
the base classes every graphql-ruby app has (`Types::Base*`, `Resolvers::BaseResolver`,
|
|
29
|
+
`Mutations::BaseMutation`), then puts Lighthouse's own on top:
|
|
28
30
|
|
|
29
31
|
| File | What it is |
|
|
30
32
|
| --- | --- |
|
|
31
33
|
| `config/initializers/lighthouse.rb` | Every [configuration](configuration.md) option, commented out at its default |
|
|
32
34
|
| `graphql/schema.graphql` | A starter schema (`user` via `@find`, `users` via `@paginate`) that assumes a `User` model — skipped when the folder already holds `.graphql` files |
|
|
33
|
-
| `app/graphql/app_schema.rb` | The executable schema, with `GraphQL::Dataloader` enabled |
|
|
35
|
+
| `app/graphql/app_schema.rb` | The executable schema, built from the SDL, with `GraphQL::Dataloader` enabled and the base classes in `base_types` |
|
|
36
|
+
| `app/graphql/{types,resolvers,mutations,subscriptions}/base_*.rb` | graphql-ruby's base classes, and Lighthouse's `Subscriptions::BaseSubscription` |
|
|
34
37
|
| `app/controllers/graphql_controller.rb` | The endpoint, plus a `post '/graphql'` route |
|
|
35
38
|
| `graphql.config.yml` | IDE configuration — see [IDE support](#ide-support) |
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| `--skip-ide` | No `graphql.config.yml` or `.gitignore` entries |
|
|
43
|
-
|
|
44
|
-
Rerunning it is safe: the route and `.gitignore` entries are only added once, and
|
|
45
|
-
an existing schema class or controller is kept (pass `--force` to replace it).
|
|
46
|
-
|
|
47
|
-
It coexists with graphql-ruby's own tooling. The generators live under
|
|
48
|
-
`lighthouse:` and the rake task is `lighthouse:ide_helper`, next to graphql-ruby's
|
|
49
|
-
`graphql:*` generators and `graphql:schema:*` tasks. `graphql:install` writes the
|
|
50
|
-
same controller and route; whichever runs second finds the route already in place,
|
|
51
|
-
and Lighthouse keeps a controller graphql-ruby generated. In that case, have the
|
|
52
|
-
controller call `AppSchema.execute(...)` to serve the Lighthouse schema.
|
|
40
|
+
`--schema=ApiSchema`, `--sdl-folder=app/schema`, `--skip-controller` and
|
|
41
|
+
`--skip-ide` change what it writes. Rerunning it is safe, and a schema class or
|
|
42
|
+
controller the app had before is kept (pass `--force` to replace it).
|
|
43
|
+
[generators.md](generators.md) has the details, every generator Lighthouse has, and
|
|
44
|
+
which of graphql-ruby's `graphql:*` generators fit a Lighthouse app.
|
|
53
45
|
|
|
54
46
|
## 1. Write your schema (SDL)
|
|
55
47
|
|
|
@@ -79,19 +71,22 @@ field's return type name (`User`), or you can be explicit with
|
|
|
79
71
|
|
|
80
72
|
Lighthouse builds on graphql-ruby, and follows its convention: each kind of class
|
|
81
73
|
inherits from a base class in its own namespace, where what they all share goes
|
|
82
|
-
once. `
|
|
83
|
-
|
|
74
|
+
once. graphql-ruby's `graphql:install` writes them, and `lighthouse:install` runs it;
|
|
75
|
+
by hand, run it yourself, then add the subscription base, which graphql-ruby has
|
|
76
|
+
none of, and make the mutation base a `GraphQL::Schema::Mutation` (the SDL declares a
|
|
77
|
+
mutation's arguments, where graphql-ruby's `RelayClassicMutation` takes one `input:`):
|
|
84
78
|
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class BaseResolver < GraphQL::Schema::Resolver
|
|
89
|
-
end
|
|
90
|
-
end
|
|
79
|
+
```shell
|
|
80
|
+
bin/rails generate graphql:install --no-relay --skip-graphiql
|
|
81
|
+
```
|
|
91
82
|
|
|
83
|
+
```ruby
|
|
92
84
|
# app/graphql/mutations/base_mutation.rb — Mutation fields (Mutations::PublishPost)
|
|
93
85
|
module Mutations
|
|
94
|
-
class BaseMutation < GraphQL::Schema::Mutation
|
|
86
|
+
class BaseMutation < GraphQL::Schema::Mutation # graphql-ruby writes RelayClassicMutation
|
|
87
|
+
argument_class Types::BaseArgument
|
|
88
|
+
field_class Types::BaseField
|
|
89
|
+
object_class Types::BaseObject
|
|
95
90
|
end
|
|
96
91
|
end
|
|
97
92
|
|
|
@@ -103,14 +98,15 @@ module Subscriptions
|
|
|
103
98
|
end
|
|
104
99
|
```
|
|
105
100
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
behavior.
|
|
101
|
+
Delete the `Types::QueryType` and `Types::MutationType` it writes: the SDL declares
|
|
102
|
+
the root types. The `Types::Base*` classes are the SDL's types' bases: the place to
|
|
103
|
+
mix in Apollo Federation or custom field and argument behavior.
|
|
109
104
|
|
|
110
105
|
```ruby
|
|
111
106
|
# app/graphql/types/base_object.rb
|
|
112
107
|
module Types
|
|
113
108
|
class BaseObject < GraphQL::Schema::Object
|
|
109
|
+
field_class Types::BaseField
|
|
114
110
|
# include ApolloFederation::Object # if you use federation
|
|
115
111
|
end
|
|
116
112
|
end
|
data/docs/relationships.md
CHANGED
|
@@ -31,6 +31,10 @@ The `morph*` names are PHP Lighthouse's. ActiveRecord resolves polymorphic
|
|
|
31
31
|
associations the same way as any other, so they behave exactly like their
|
|
32
32
|
non-polymorphic counterparts. Use whichever reads better.
|
|
33
33
|
|
|
34
|
+
`@hasMany` and `@hasOne` resolve `has_many` / `has_one :through` associations as
|
|
35
|
+
they are. PHP Lighthouse's `@hasManyThrough` and `@hasOneThrough` work too, as
|
|
36
|
+
aliases, so an SDL written for it builds unchanged.
|
|
37
|
+
|
|
34
38
|
## Avoiding the N+1 problem
|
|
35
39
|
|
|
36
40
|
The relationship directives load through graphql-ruby's **Dataloader** and
|
|
@@ -50,6 +54,44 @@ the Dataloader, the directives still work, but they load one parent at a time.
|
|
|
50
54
|
A field with `scopes:` or a filter argument the client supplied runs its own query
|
|
51
55
|
per parent, because each parent's query differs.
|
|
52
56
|
|
|
57
|
+
## Eager loading for other fields
|
|
58
|
+
|
|
59
|
+
The relationship directives load their own association. Three directives load for
|
|
60
|
+
the fields that only read one: a method, a type class method, a field read off
|
|
61
|
+
the model. Each one loads across every parent in the response at once, through the
|
|
62
|
+
Dataloader:
|
|
63
|
+
|
|
64
|
+
- `@with(relation: "tasks")` loads a relation of the parent before the field
|
|
65
|
+
resolves. `user.tasks` is then in memory for the method that reads it.
|
|
66
|
+
- `@withCount(relation: "posts")` counts a relation for every parent in one
|
|
67
|
+
`GROUP BY` query, and keeps each count on its parent as `<relation>_count`, as
|
|
68
|
+
Laravel's `withCount` does. It is not the field's value: the field's resolver,
|
|
69
|
+
or the default reader of a `postsCount` field, reads `user.posts_count`.
|
|
70
|
+
- `@lazyLoad(relations: ["tags"])` loads relations of the records the field
|
|
71
|
+
returns: a list, a relation, a `@paginate` page. On a `@hasMany`, the records of
|
|
72
|
+
every parent load their relations together.
|
|
73
|
+
|
|
74
|
+
```graphql
|
|
75
|
+
type User {
|
|
76
|
+
latestPostTitle: String @with(relation: "posts") @method
|
|
77
|
+
usage: String! @withCount(relation: "posts", scopes: ["published"]) @method
|
|
78
|
+
posts: [Post!]! @hasMany @lazyLoad(relations: ["tags"])
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
class User < ApplicationRecord
|
|
84
|
+
def latest_post_title = posts.max_by(&:created_at)&.title
|
|
85
|
+
def usage = "#{posts_count} published posts"
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Relations may be nested with dots (`"posts.comments"`), and `scopes` narrow what is
|
|
90
|
+
loaded or counted, as model scopes. A count loads in one query when the database has
|
|
91
|
+
window functions and the relation links its parent through one column, as a
|
|
92
|
+
[paginated relation](#paginating-many-parents-at-once) does; otherwise once per
|
|
93
|
+
parent.
|
|
94
|
+
|
|
53
95
|
## Lists, and paginating them
|
|
54
96
|
|
|
55
97
|
Collection directives return a **plain list**, as in PHP Lighthouse:
|
data/docs/relay.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Relay: Global Object Identification
|
|
2
|
+
|
|
3
|
+
Relay clients refetch any object by one opaque id, through a root `node` field.
|
|
4
|
+
Lighthouse gives a schema that, as [PHP Lighthouse](https://lighthouse-php.com/master/digging-deeper/relay.html)
|
|
5
|
+
does, with two directives: `@globalId` encodes and decodes the ids, and `@node`
|
|
6
|
+
makes a type findable by them. For Relay's cursor pagination, see
|
|
7
|
+
[`@paginate(type: CONNECTION)`](directives.md#paginate).
|
|
8
|
+
|
|
9
|
+
## Global ids
|
|
10
|
+
|
|
11
|
+
`@globalId` on a field encodes its value with the type's name:
|
|
12
|
+
|
|
13
|
+
```graphql
|
|
14
|
+
type User {
|
|
15
|
+
id: ID! @globalId
|
|
16
|
+
name: String
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`{ user(id: 1) { id } }` answers `"VXNlcjox"`, the base64 of `User:1`.
|
|
21
|
+
|
|
22
|
+
On an argument, or an input field, `@globalId` decodes what the client sent before
|
|
23
|
+
the field resolves, so every resolver (a directive, `@field`, a `Resolvers::` class)
|
|
24
|
+
gets the decoded value. `decode:` says which part:
|
|
25
|
+
|
|
26
|
+
| `decode:` | The resolver gets |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `ARRAY` (default) | `["User", "1"]` |
|
|
29
|
+
| `TYPE` | `"User"` |
|
|
30
|
+
| `ID` | `"1"` |
|
|
31
|
+
|
|
32
|
+
```graphql
|
|
33
|
+
type Query {
|
|
34
|
+
user(id: ID! @globalId(decode: ID) @eq): User @find
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Mutation {
|
|
38
|
+
deleteNode(id: ID! @globalId): Node
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Lists of ids decode item by item. A value that is not a global id makes the field
|
|
43
|
+
fail with `"…" is not a valid global id.`
|
|
44
|
+
|
|
45
|
+
## Nodes
|
|
46
|
+
|
|
47
|
+
`@node` registers a type for refetching:
|
|
48
|
+
|
|
49
|
+
```graphql
|
|
50
|
+
type User @node {
|
|
51
|
+
id: ID! @globalId
|
|
52
|
+
name: String
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
and adds what Relay expects, leaving alone whatever the SDL declares itself:
|
|
57
|
+
|
|
58
|
+
```graphql
|
|
59
|
+
"Any object implementing this type can be found by ID through `Query.node`."
|
|
60
|
+
interface Node {
|
|
61
|
+
"Global identifier that can be used to resolve any Node implementation."
|
|
62
|
+
id: ID!
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type User implements Node @node { ... }
|
|
66
|
+
|
|
67
|
+
type Query {
|
|
68
|
+
"Fetches an object given its globally unique `ID`."
|
|
69
|
+
node(id: ID! @globalId): Node
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```graphql
|
|
74
|
+
{
|
|
75
|
+
node(id: "VXNlcjox") {
|
|
76
|
+
id
|
|
77
|
+
... on User { name }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The type keeps its own `id: ID! @globalId`, which encodes the ids `node` decodes.
|
|
83
|
+
`node` answers `null` for an id with no object, and an error for the id of a type
|
|
84
|
+
without `@node`.
|
|
85
|
+
|
|
86
|
+
### Where the object comes from
|
|
87
|
+
|
|
88
|
+
1. `@node(resolver: "Countries@by_code")` names a class, and a method after the `@`
|
|
89
|
+
(`call` without one). It gets the decoded id and the context, and returns any
|
|
90
|
+
object, a Hash included:
|
|
91
|
+
|
|
92
|
+
```graphql
|
|
93
|
+
type Country @node(resolver: "Countries@by_code") {
|
|
94
|
+
id: ID! @globalId
|
|
95
|
+
name: String!
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class Resolvers::Countries
|
|
101
|
+
COUNTRIES = { 'DE' => { id: 'DE', name: 'Germany' } }.freeze
|
|
102
|
+
|
|
103
|
+
def self.by_code(id, _context) = COUNTRIES[id]
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The class is looked up as `@field` looks one up on a type: in the resolvers
|
|
108
|
+
namespace, then in [`resolver_namespaces`](configuration.md#resolver_namespaces).
|
|
109
|
+
2. `@node(model: "Account")` names the model.
|
|
110
|
+
3. Otherwise, the type's [`@model`](directives.md#model), or the model named
|
|
111
|
+
after the type.
|
|
112
|
+
|
|
113
|
+
A model's record is found by its primary key, through graphql-ruby's Dataloader, so
|
|
114
|
+
several `node` fields in one query load together.
|
|
115
|
+
|
|
116
|
+
To give `Query.node` your own description or middleware, declare it, with the
|
|
117
|
+
registry Lighthouse uses:
|
|
118
|
+
|
|
119
|
+
```graphql
|
|
120
|
+
type Query {
|
|
121
|
+
"This description is up to you."
|
|
122
|
+
node(id: ID! @globalId): Node
|
|
123
|
+
@field(resolver: "Lighthouse::GraphQL::NodeRegistry@resolve")
|
|
124
|
+
@guard
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Your own encoding
|
|
129
|
+
|
|
130
|
+
[`global_id`](configuration.md#global_id) takes any object with
|
|
131
|
+
`encode(type, id)` and `decode(global_id)`, which returns `[type, id]`:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
Lighthouse.configure do |config|
|
|
135
|
+
config.global_id = MyGlobalIds # e.g. signed, or a UUID lookup
|
|
136
|
+
end
|
|
137
|
+
```
|