lighthouse-graphql 0.3.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 +182 -1
- data/README.md +18 -7
- data/docs/best-practices.md +16 -6
- data/docs/configuration.md +33 -8
- data/docs/directives.md +318 -10
- data/docs/events.md +3 -1
- data/docs/federation.md +3 -2
- data/docs/generators.md +82 -0
- data/docs/getting-started.md +66 -32
- data/docs/motivation.md +3 -2
- data/docs/relationships.md +42 -0
- data/docs/relay.md +137 -0
- data/docs/resolvers.md +338 -25
- data/docs/security.md +227 -0
- data/docs/subscriptions.md +60 -37
- data/docs/types.md +219 -0
- data/docs/uploads.md +80 -0
- data/lib/generators/lighthouse/base_classes.rb +70 -0
- data/lib/generators/lighthouse/class_generator.rb +19 -8
- data/lib/generators/lighthouse/install/install_generator.rb +96 -20
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +19 -10
- data/lib/generators/lighthouse/install/templates/schema.rb.tt +21 -2
- data/lib/generators/lighthouse/mutation_generator.rb +5 -4
- data/lib/generators/lighthouse/query_generator.rb +5 -4
- data/lib/generators/lighthouse/subscription_generator.rb +21 -16
- data/lib/lighthouse/async_mutation_job.rb +17 -0
- data/lib/lighthouse/complexity.rb +175 -0
- data/lib/lighthouse/complexity_analyzer.rb +56 -0
- data/lib/lighthouse/config.rb +16 -4
- 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 +16 -5
- data/lib/lighthouse/directive_resolvers/convert_empty_strings_resolver.rb +1 -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 +14 -5
- 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 -20
- data/lib/lighthouse/directives/visibility_directive.rb +71 -0
- data/lib/lighthouse/enum_directive.rb +41 -0
- data/lib/lighthouse/events/execution_trace.rb +2 -1
- data/lib/lighthouse/events/lifecycle.rb +3 -1
- data/lib/lighthouse/field_resolver_stack.rb +59 -11
- 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 +36 -0
- data/lib/lighthouse/relation_counts.rb +29 -0
- data/lib/lighthouse/resolve_info.rb +80 -0
- data/lib/lighthouse/resolver_bases.rb +68 -0
- 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_factory.rb +0 -1
- data/lib/lighthouse/schema_generator.rb +21 -12
- data/lib/lighthouse/schema_implementation.rb +4 -8
- data/lib/lighthouse/sdl_loader.rb +0 -2
- data/lib/lighthouse/support/environment.rb +13 -0
- data/lib/lighthouse/support/model_resolver.rb +8 -6
- 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/graphql.rbs +29 -0
- data/sig/lighthouse.rbs +18 -0
- metadata +43 -1
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
|
|
|
@@ -75,16 +67,48 @@ argument (the `@eq` makes `id` a filter). Lighthouse infers the model from the
|
|
|
75
67
|
field's return type name (`User`), or you can be explicit with
|
|
76
68
|
`@all(model: "User")`.
|
|
77
69
|
|
|
78
|
-
## 2. Provide your base
|
|
70
|
+
## 2. Provide your base classes
|
|
79
71
|
|
|
80
|
-
Lighthouse builds on graphql-ruby,
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
Lighthouse builds on graphql-ruby, and follows its convention: each kind of class
|
|
73
|
+
inherits from a base class in its own namespace, where what they all share goes
|
|
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:`):
|
|
78
|
+
|
|
79
|
+
```shell
|
|
80
|
+
bin/rails generate graphql:install --no-relay --skip-graphiql
|
|
81
|
+
```
|
|
83
82
|
|
|
84
83
|
```ruby
|
|
85
|
-
# app/graphql/
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
# app/graphql/mutations/base_mutation.rb — Mutation fields (Mutations::PublishPost)
|
|
85
|
+
module Mutations
|
|
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
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# app/graphql/subscriptions/base_subscription.rb — Subscription fields
|
|
94
|
+
module Subscriptions
|
|
95
|
+
class BaseSubscription < GraphQL::Schema::Subscription
|
|
96
|
+
include Lighthouse::GraphQL::Subscription
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
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.
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
# app/graphql/types/base_object.rb
|
|
107
|
+
module Types
|
|
108
|
+
class BaseObject < GraphQL::Schema::Object
|
|
109
|
+
field_class Types::BaseField
|
|
110
|
+
# include ApolloFederation::Object # if you use federation
|
|
111
|
+
end
|
|
88
112
|
end
|
|
89
113
|
```
|
|
90
114
|
|
|
@@ -98,13 +122,22 @@ AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
|
98
122
|
sdl_folder: Rails.root.join('graphql').to_s,
|
|
99
123
|
options: {
|
|
100
124
|
base_types: {
|
|
101
|
-
|
|
125
|
+
resolver: Resolvers::BaseResolver,
|
|
126
|
+
mutation: Mutations::BaseMutation,
|
|
127
|
+
subscription: Subscriptions::BaseSubscription,
|
|
128
|
+
object: Types::BaseObject,
|
|
102
129
|
# interface:, union:, enum:, input_object:, scalar: ...
|
|
103
130
|
}
|
|
104
131
|
}
|
|
105
132
|
)
|
|
106
133
|
```
|
|
107
134
|
|
|
135
|
+
`object:` and the other SDL kinds go to graphql-ruby, which builds the SDL's types
|
|
136
|
+
from them. `resolver:`, `mutation:` and `subscription:` are Lighthouse's: every
|
|
137
|
+
class a `Query`, `Mutation` or `Subscription` field resolves to must inherit from
|
|
138
|
+
its kind's base, and a `Subscription` field with no class of its own resolves with
|
|
139
|
+
the subscription base. See [resolvers.md](resolvers.md#base-classes).
|
|
140
|
+
|
|
108
141
|
`get_schema` returns a `GraphQL::Schema` subclass you can use directly, or
|
|
109
142
|
subclass further (e.g. to add federation, tracing, a Dataloader):
|
|
110
143
|
|
|
@@ -247,18 +280,19 @@ type User {
|
|
|
247
280
|
}
|
|
248
281
|
```
|
|
249
282
|
|
|
250
|
-
## Custom
|
|
283
|
+
## Custom resolvers and mutations
|
|
251
284
|
|
|
252
285
|
When a field needs Ruby, write a graphql-ruby resolver or mutation class named
|
|
253
|
-
after it — `Query.latestPosts` resolves to `
|
|
254
|
-
(`app/graphql/
|
|
255
|
-
`Mutations::PublishPost
|
|
256
|
-
scaffolds one. See
|
|
286
|
+
after it — `Query.latestPosts` resolves to `Resolvers::LatestPosts`
|
|
287
|
+
(`app/graphql/resolvers/latest_posts.rb`, a `Resolvers::BaseResolver`),
|
|
288
|
+
`Mutation.publishPost` to `Mutations::PublishPost` (a `Mutations::BaseMutation`).
|
|
289
|
+
`bin/rails generate lighthouse:query LatestPosts` scaffolds one. See
|
|
290
|
+
[resolvers.md](resolvers.md).
|
|
257
291
|
|
|
258
292
|
## Next steps
|
|
259
293
|
|
|
260
294
|
- [Directives reference](directives.md) — the full vocabulary.
|
|
261
|
-
- [
|
|
295
|
+
- [Resolvers, mutations & builders](resolvers.md), [Mutations](mutations.md),
|
|
262
296
|
[Validation](validation.md), [Caching](caching.md).
|
|
263
297
|
- [Relationships](relationships.md) — wiring associations.
|
|
264
298
|
- [Filtering & ordering](filtering-and-ordering.md) — query constraints.
|
data/docs/motivation.md
CHANGED
|
@@ -151,8 +151,9 @@ Schema-first doesn't mean schema-only. When a field does something no directive
|
|
|
151
151
|
describes, write Ruby, using graphql-ruby's own classes:
|
|
152
152
|
|
|
153
153
|
- A `Query` or `Mutation` field without a resolving directive resolves by convention to
|
|
154
|
-
`
|
|
155
|
-
`
|
|
154
|
+
`Resolvers::FieldName` (a `Resolvers::BaseResolver`) or `Mutations::FieldName` (a
|
|
155
|
+
`Mutations::BaseMutation`), under the base classes graphql-ruby's convention gives
|
|
156
|
+
each kind. See [Resolvers, Mutations and Builders](resolvers.md).
|
|
156
157
|
- `@builder` classes adjust a query; `@validator` contracts check arguments.
|
|
157
158
|
- Your own directives plug in through the registry, with the same contracts as the
|
|
158
159
|
built-in ones. See [Custom Directives](custom-directives.md).
|
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
|
+
```
|