lighthouse-graphql 0.5.0 → 0.6.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 +118 -1
- data/README.md +24 -16
- data/docs/caching.md +4 -3
- data/docs/configuration.md +1 -21
- data/docs/directives.md +14 -7
- data/docs/generators.md +41 -39
- data/docs/getting-started.md +88 -51
- data/docs/performance.md +2 -2
- data/docs/relationships.md +5 -4
- data/docs/relay.md +63 -0
- data/docs/resolvers.md +90 -37
- data/docs/security.md +64 -16
- data/docs/subscriptions.md +16 -9
- data/docs/types.md +16 -17
- data/docs/uploads.md +41 -14
- data/lib/generators/lighthouse/base_classes.rb +9 -14
- data/lib/generators/lighthouse/install/graphql_ruby_install.rb +113 -0
- data/lib/generators/lighthouse/install/install_generator.rb +112 -91
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +3 -9
- data/lib/lighthouse/caching.rb +9 -3
- data/lib/lighthouse/config.rb +6 -23
- data/lib/lighthouse/controller.rb +28 -5
- data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +18 -18
- data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +1 -1
- data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +3 -1
- data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +85 -26
- data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +12 -6
- data/lib/lighthouse/directives/arguments/more_filters.rb +1 -1
- data/lib/lighthouse/field_resolver_stack.rb +9 -6
- data/lib/lighthouse/global_id.rb +29 -3
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/graphql_ruby_patches.rb +45 -0
- data/lib/lighthouse/rb_lighthouse.rb +1 -0
- data/lib/lighthouse/resolver_bases.rb +18 -4
- data/lib/lighthouse/resolver_dispatch.rb +16 -1
- data/lib/lighthouse/root_types.rb +117 -0
- data/lib/lighthouse/schema_generator.rb +4 -3
- data/lib/lighthouse/upload.rb +4 -2
- data/lib/lighthouse/upload_store.rb +39 -14
- data/sig/lighthouse.rbs +0 -10
- metadata +3 -4
- data/lib/generators/lighthouse/install/templates/schema.rb.tt +0 -29
- data/lib/generators/lighthouse/mutation_generator.rb +0 -31
- data/lib/generators/lighthouse/query_generator.rb +0 -30
data/docs/getting-started.md
CHANGED
|
@@ -24,22 +24,25 @@ 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. It
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
does steps 1–5 below for you. It builds on graphql-ruby's own install,
|
|
28
|
+
`rails g graphql:install`, with whichever of its options the app chose (`--relay`,
|
|
29
|
+
`--batch`, `--api`, ...). When the app has no graphql-ruby schema class yet, it runs
|
|
30
|
+
`graphql:install` first, with graphql-ruby's defaults. Then it adapts what
|
|
31
|
+
graphql-ruby wrote, and adds Lighthouse's own:
|
|
30
32
|
|
|
31
33
|
| File | What it is |
|
|
32
34
|
| --- | --- |
|
|
35
|
+
| `app/graphql/app_schema.rb` | graphql-ruby's schema class, now built by Lighthouse from the SDL on every class graphql-ruby wrote (`base_types`). The rest of the class is graphql-ruby's |
|
|
36
|
+
| `app/graphql/types/query_type.rb`, `mutation_type.rb` | graphql-ruby's root types, which the SDL's `Query` and `Mutation` are built on. Their example `test_field` goes; Relay's `node` / `nodes` stay |
|
|
37
|
+
| `app/graphql/types/base_*.rb`, `resolvers/base_resolver.rb`, `mutations/base_mutation.rb` | graphql-ruby's base classes. `BaseObject`, `BaseInterface`, `BaseUnion` and `BaseScalar` include `Lighthouse::GraphQL::Type` |
|
|
38
|
+
| `app/controllers/graphql_controller.rb` | graphql-ruby's controller, replaced by one including `Lighthouse::GraphQL::Controller` |
|
|
33
39
|
| `config/initializers/lighthouse.rb` | Every [configuration](configuration.md) option, commented out at its default |
|
|
34
40
|
| `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 |
|
|
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` |
|
|
37
|
-
| `app/controllers/graphql_controller.rb` | The endpoint, plus a `post '/graphql'` route |
|
|
38
41
|
| `graphql.config.yml` | IDE configuration — see [IDE support](#ide-support) |
|
|
39
42
|
|
|
40
|
-
`--schema=ApiSchema`, `--sdl-folder=app/schema`, `--
|
|
41
|
-
`--skip-ide` change what it
|
|
42
|
-
controller the app
|
|
43
|
+
`--schema=ApiSchema`, `--sdl-folder=app/schema`, `--subscriptions`,
|
|
44
|
+
`--skip-controller` and `--skip-ide` change what it does. Rerunning it changes
|
|
45
|
+
nothing, and a controller the app changed is kept.
|
|
43
46
|
[generators.md](generators.md) has the details, every generator Lighthouse has, and
|
|
44
47
|
which of graphql-ruby's `graphql:*` generators fit a Lighthouse app.
|
|
45
48
|
|
|
@@ -71,25 +74,34 @@ field's return type name (`User`), or you can be explicit with
|
|
|
71
74
|
|
|
72
75
|
Lighthouse builds on graphql-ruby, and follows its convention: each kind of class
|
|
73
76
|
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`
|
|
75
|
-
|
|
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:`):
|
|
77
|
+
once. graphql-ruby's `graphql:install` writes them, and `lighthouse:install` builds
|
|
78
|
+
on them. By hand, run graphql-ruby's install with its defaults:
|
|
78
79
|
|
|
79
80
|
```shell
|
|
80
|
-
bin/rails generate graphql:install
|
|
81
|
+
bin/rails generate graphql:install
|
|
81
82
|
```
|
|
82
83
|
|
|
84
|
+
Then include `Lighthouse::GraphQL::Type` in the base object, interface, union and
|
|
85
|
+
scalar, so every type class that inherits from them lends its methods to the SDL
|
|
86
|
+
type of its name (see [type classes](resolvers.md#type-classes)):
|
|
87
|
+
|
|
83
88
|
```ruby
|
|
84
|
-
# app/graphql/
|
|
85
|
-
module
|
|
86
|
-
class
|
|
87
|
-
|
|
89
|
+
# app/graphql/types/base_object.rb
|
|
90
|
+
module Types
|
|
91
|
+
class BaseObject < GraphQL::Schema::Object
|
|
92
|
+
include Lighthouse::GraphQL::Type
|
|
93
|
+
edge_type_class(Types::BaseEdge)
|
|
94
|
+
connection_type_class(Types::BaseConnection)
|
|
88
95
|
field_class Types::BaseField
|
|
89
|
-
|
|
96
|
+
# include ApolloFederation::Object # if you use federation
|
|
90
97
|
end
|
|
91
98
|
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Add the subscription base, which graphql-ruby has none of, if the schema has a
|
|
102
|
+
`Subscription` type (`lighthouse:install --subscriptions` writes it):
|
|
92
103
|
|
|
104
|
+
```ruby
|
|
93
105
|
# app/graphql/subscriptions/base_subscription.rb — Subscription fields
|
|
94
106
|
module Subscriptions
|
|
95
107
|
class BaseSubscription < GraphQL::Schema::Subscription
|
|
@@ -98,62 +110,85 @@ module Subscriptions
|
|
|
98
110
|
end
|
|
99
111
|
```
|
|
100
112
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
112
|
-
end
|
|
113
|
-
```
|
|
113
|
+
graphql-ruby's `Mutations::BaseMutation` stays a `RelayClassicMutation`. A
|
|
114
|
+
`Mutation` field that declares its arguments one by one calls it as a plain
|
|
115
|
+
mutation, with those arguments as keywords; one with a single `input:` argument
|
|
116
|
+
goes through `RelayClassicMutation`'s own `input:` handling.
|
|
114
117
|
|
|
115
118
|
## 3. Build the schema
|
|
116
119
|
|
|
120
|
+
graphql-ruby's schema class becomes Lighthouse's: it inherits from the schema
|
|
121
|
+
`get_schema` builds from the SDL, instead of `GraphQL::Schema`.
|
|
122
|
+
|
|
117
123
|
```ruby
|
|
118
124
|
# app/graphql/app_schema.rb
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
122
|
-
sdl_folder: Rails.root.join('graphql').to_s,
|
|
125
|
+
class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
123
126
|
options: {
|
|
124
127
|
base_types: {
|
|
128
|
+
query_type: Types::QueryType,
|
|
129
|
+
mutation_type: Types::MutationType,
|
|
130
|
+
node: Types::NodeType,
|
|
125
131
|
resolver: Resolvers::BaseResolver,
|
|
126
132
|
mutation: Mutations::BaseMutation,
|
|
127
133
|
subscription: Subscriptions::BaseSubscription,
|
|
128
134
|
object: Types::BaseObject,
|
|
129
|
-
|
|
135
|
+
interface: Types::BaseInterface,
|
|
136
|
+
union: Types::BaseUnion,
|
|
137
|
+
scalar: Types::BaseScalar,
|
|
138
|
+
enum: Types::BaseEnum,
|
|
139
|
+
input_object: Types::BaseInputObject
|
|
130
140
|
}
|
|
131
141
|
}
|
|
132
142
|
)
|
|
143
|
+
# Lighthouse batches relationship loads through graphql-ruby's Dataloader.
|
|
144
|
+
use GraphQL::Dataloader
|
|
145
|
+
|
|
146
|
+
max_depth(15)
|
|
147
|
+
max_query_string_tokens(5000)
|
|
148
|
+
validate_max_errors(100)
|
|
149
|
+
|
|
150
|
+
# Relay-style Object Identification, as graphql-ruby's install writes it:
|
|
151
|
+
def self.id_from_object(object, type_definition, query_ctx)
|
|
152
|
+
object.to_gid_param
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def self.object_from_id(global_id, query_ctx)
|
|
156
|
+
GlobalID.find(global_id)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
133
159
|
```
|
|
134
160
|
|
|
161
|
+
The SDL is read from `config.sdl_folder` (`get_schema(sdl_folder: ...)` overrides
|
|
162
|
+
it). The class body is graphql-ruby's, so plugins and settings go there as in any
|
|
163
|
+
graphql-ruby schema (`use ...`, `max_complexity(...)`), but for `query(...)` and
|
|
164
|
+
`mutation(...)`, which the SDL's root types replace, and `resolve_type`, which
|
|
165
|
+
Lighthouse answers (see [types.md](types.md)).
|
|
166
|
+
|
|
135
167
|
`object:` and the other SDL kinds go to graphql-ruby, which builds the SDL's types
|
|
136
|
-
from them. `
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
168
|
+
from them. `query_type:` and `mutation_type:` are the classes the SDL's `Query` and
|
|
169
|
+
`Mutation` are built on, so their own `field`s (Relay's `node` / `nodes`) are the
|
|
170
|
+
schema's too; `node:` is the SDL's `Node` interface (see [relay.md](relay.md)).
|
|
171
|
+
`resolver:`, `mutation:` and `subscription:` are Lighthouse's: every class a
|
|
172
|
+
`Query`, `Mutation` or `Subscription` field resolves to must inherit from its kind's
|
|
173
|
+
base, and a `Subscription` field with no class of its own resolves with the
|
|
174
|
+
subscription base. See [resolvers.md](resolvers.md#base-classes).
|
|
140
175
|
|
|
141
|
-
|
|
142
|
-
subclass further (e.g. to add federation, tracing, a Dataloader):
|
|
176
|
+
A constant works as well, with the settings passed as options or set on it:
|
|
143
177
|
|
|
144
178
|
```ruby
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
179
|
+
AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
180
|
+
options: { base_types: { object: Types::BaseObject }, using: { GraphQL::Dataloader => {} } }
|
|
181
|
+
)
|
|
148
182
|
```
|
|
149
183
|
|
|
150
|
-
> Lighthouse uses graphql-ruby's `Dataloader` to batch relationship loads.
|
|
151
|
-
> `use GraphQL::Dataloader`
|
|
184
|
+
> Lighthouse uses graphql-ruby's `Dataloader` to batch relationship loads. Keep
|
|
185
|
+
> `use GraphQL::Dataloader` in your schema to get N+1-free relationships.
|
|
152
186
|
|
|
153
187
|
## 4. Serve it
|
|
154
188
|
|
|
155
|
-
`rails g lighthouse:install`
|
|
156
|
-
`Lighthouse::GraphQL::Controller`, a concern that
|
|
189
|
+
`rails g lighthouse:install` replaces graphql-ruby's controller (when the app has
|
|
190
|
+
not changed it) with one including `Lighthouse::GraphQL::Controller`, a concern that
|
|
191
|
+
serves the schema:
|
|
157
192
|
|
|
158
193
|
```ruby
|
|
159
194
|
class GraphqlController < ApplicationController
|
|
@@ -286,7 +321,9 @@ When a field needs Ruby, write a graphql-ruby resolver or mutation class named
|
|
|
286
321
|
after it — `Query.latestPosts` resolves to `Resolvers::LatestPosts`
|
|
287
322
|
(`app/graphql/resolvers/latest_posts.rb`, a `Resolvers::BaseResolver`),
|
|
288
323
|
`Mutation.publishPost` to `Mutations::PublishPost` (a `Mutations::BaseMutation`).
|
|
289
|
-
|
|
324
|
+
A method of the root type class comes first, as in graphql-ruby: `Query.latestPosts`
|
|
325
|
+
calls `Types::QueryType#latest_posts` when it defines one. `bin/rails generate
|
|
326
|
+
graphql:mutation PublishPost` writes a code-first mutation. See
|
|
290
327
|
[resolvers.md](resolvers.md).
|
|
291
328
|
|
|
292
329
|
## Next steps
|
data/docs/performance.md
CHANGED
|
@@ -61,8 +61,8 @@ Lighthouse.configure { |config| config.watch_sdl = Rails.env.development? }
|
|
|
61
61
|
Rails' reloader watches Ruby files, not `.graphql` ones, so without this an SDL
|
|
62
62
|
edit only shows up after restarting the server. With it, the SDL folder joins
|
|
63
63
|
`config.watchable_dirs`, and an edit reloads the app, and with it the schema, on
|
|
64
|
-
the next request, like a Ruby change. This relies on the schema
|
|
65
|
-
in an autoloaded file, as
|
|
64
|
+
the next request, like a Ruby change. This relies on the schema class living
|
|
65
|
+
in an autoloaded file, as `app/graphql/app_schema.rb` does.
|
|
66
66
|
|
|
67
67
|
## Persisted queries
|
|
68
68
|
|
data/docs/relationships.md
CHANGED
|
@@ -39,12 +39,13 @@ aliases, so an SDL written for it builds unchanged.
|
|
|
39
39
|
|
|
40
40
|
The relationship directives load through graphql-ruby's **Dataloader** and
|
|
41
41
|
ActiveRecord's preloader, one query per association per batch. Enable the
|
|
42
|
-
Dataloader on your schema (
|
|
42
|
+
Dataloader on your schema (graphql-ruby's install does, and with `--batch`
|
|
43
|
+
`lighthouse:install` adds it beside GraphQL::Batch):
|
|
43
44
|
|
|
44
45
|
```ruby
|
|
45
|
-
AppSchema
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(options: { base_types: { object: Types::BaseObject } })
|
|
47
|
+
use GraphQL::Dataloader
|
|
48
|
+
end
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
A query for 100 posts and their authors then issues two queries, not 101. A
|
data/docs/relay.md
CHANGED
|
@@ -6,6 +6,11 @@ does, with two directives: `@globalId` encodes and decodes the ids, and `@node`
|
|
|
6
6
|
makes a type findable by them. For Relay's cursor pagination, see
|
|
7
7
|
[`@paginate(type: CONNECTION)`](directives.md#paginate).
|
|
8
8
|
|
|
9
|
+
An app whose schema has graphql-ruby's Relay, as `rails g graphql:install` writes it
|
|
10
|
+
by default, uses the same directives on graphql-ruby's ids and `Node`: see
|
|
11
|
+
[Relay through graphql-ruby](#relay-through-graphql-ruby). The sections before it
|
|
12
|
+
describe a schema without it.
|
|
13
|
+
|
|
9
14
|
## Global ids
|
|
10
15
|
|
|
11
16
|
`@globalId` on a field encodes its value with the type's name:
|
|
@@ -125,6 +130,64 @@ type Query {
|
|
|
125
130
|
}
|
|
126
131
|
```
|
|
127
132
|
|
|
133
|
+
## Relay through graphql-ruby
|
|
134
|
+
|
|
135
|
+
graphql-ruby's install (`--relay`, its default) writes Relay's side in Ruby:
|
|
136
|
+
`Types::NodeType`, the `node` and `nodes` fields of `Types::QueryType`, and the
|
|
137
|
+
schema's `id_from_object` / `object_from_id`, on Rails' GlobalID:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
141
|
+
options: { base_types: { query_type: Types::QueryType, node: Types::NodeType, object: Types::BaseObject } }
|
|
142
|
+
)
|
|
143
|
+
def self.id_from_object(object, type_definition, query_ctx)
|
|
144
|
+
object.to_gid_param
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.object_from_id(global_id, query_ctx)
|
|
148
|
+
GlobalID.find(global_id)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`lighthouse:install` names those classes in `base_types`, and Lighthouse's
|
|
154
|
+
directives then work on them. The schema has one kind of global id and one `Node`:
|
|
155
|
+
|
|
156
|
+
- `@globalId` on a field that resolves an object's own id encodes it with
|
|
157
|
+
`id_from_object`: `Tag.id` answers `tag.to_gid_param`, as graphql-ruby's `Node.id`
|
|
158
|
+
does. Any other value is encoded as before.
|
|
159
|
+
- `@globalId` on an argument decodes with `object_from_id`, which loads the object.
|
|
160
|
+
`ID` gives its id, and `ARRAY` / `TYPE` its class name (`["Tag", 1]`, `"Tag"`).
|
|
161
|
+
- `node:` is the SDL's `Node` interface: `interface Node`, which `@node` adds, and
|
|
162
|
+
`implements Node` refer to `Types::NodeType`. A `@node` type implements it.
|
|
163
|
+
- `Query.node` and `nodes` are graphql-ruby's code-first fields of
|
|
164
|
+
`Types::QueryType`, which find objects with `object_from_id`. The `node` field
|
|
165
|
+
`@node` would add to `Query` gives way to them.
|
|
166
|
+
|
|
167
|
+
```graphql
|
|
168
|
+
type Tag @node {
|
|
169
|
+
id: ID! @globalId
|
|
170
|
+
name: String!
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
type Query {
|
|
174
|
+
tag(id: ID! @globalId(decode: ID) @eq): Tag @find
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```graphql
|
|
179
|
+
{
|
|
180
|
+
node(id: "Z2lkOi8vYXBwL1RhZy8x") { id ... on Tag { name } }
|
|
181
|
+
tag(id: "Z2lkOi8vYXBwL1RhZy8x") { name }
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`Tag.id` and both arguments carry `Tag` 1's GlobalID (`gid://app/Tag/1`, in base64).
|
|
186
|
+
`Query.node` finds every object with `object_from_id`, so `@node(resolver:)` and
|
|
187
|
+
`@node(model:)` play no part in it, and [`global_id`](configuration.md#global_id)
|
|
188
|
+
encodes only what `id_from_object` does not. A schema without `id_from_object` /
|
|
189
|
+
`object_from_id` works as the sections above describe.
|
|
190
|
+
|
|
128
191
|
## Your own encoding
|
|
129
192
|
|
|
130
193
|
[`global_id`](configuration.md#global_id) takes any object with
|
data/docs/resolvers.md
CHANGED
|
@@ -8,9 +8,9 @@ interchangeable. Each kind has its own contract and its own folder:
|
|
|
8
8
|
| Kind | Folder / namespace | Contract | Used by |
|
|
9
9
|
| --- | --- | --- | --- |
|
|
10
10
|
| Resolver | `app/graphql/resolvers` · `Resolvers::` | `Resolvers::BaseResolver` (a `GraphQL::Schema::Resolver`) | `Query` fields |
|
|
11
|
-
| Mutation | `app/graphql/mutations` · `Mutations::` | `Mutations::BaseMutation` (
|
|
11
|
+
| Mutation | `app/graphql/mutations` · `Mutations::` | `Mutations::BaseMutation` (graphql-ruby's `GraphQL::Schema::RelayClassicMutation`) | `Mutation` fields |
|
|
12
12
|
| Subscription | `app/graphql/subscriptions` · `Subscriptions::` | `Subscriptions::BaseSubscription` (a `GraphQL::Schema::Subscription`) | `Subscription` fields ([subscriptions.md](subscriptions.md)) |
|
|
13
|
-
| Type | `app/graphql/types` · `Types::` | `Types::BaseObject` (a `GraphQL::Schema::Object`) including `Lighthouse::GraphQL::Type`, named `<Type>Type` | Fields of
|
|
13
|
+
| Type | `app/graphql/types` · `Types::` | `Types::BaseObject` (a `GraphQL::Schema::Object`) including `Lighthouse::GraphQL::Type`, named `<Type>Type` | Fields of any object type, through its methods ([type classes](#type-classes), [root type classes](#root-type-classes)); unions, interfaces and scalars too ([types.md](types.md)) |
|
|
14
14
|
| Builder | `app/graphql/builders` · `Builders::` | `call(relation, args, context)` → relation | `@builder` |
|
|
15
15
|
|
|
16
16
|
Validators (`app/graphql/validators`) are one more kind. They are covered in
|
|
@@ -19,12 +19,13 @@ Validators (`app/graphql/validators`) are one more kind. They are covered in
|
|
|
19
19
|
## Base classes
|
|
20
20
|
|
|
21
21
|
As in any graphql-ruby app, each kind of class has a base class in its namespace,
|
|
22
|
-
and every class of the kind inherits from it. graphql-ruby's `graphql:install
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
and every class of the kind inherits from it. graphql-ruby's `graphql:install`
|
|
23
|
+
writes `Resolvers::BaseResolver`, `Mutations::BaseMutation` and the `Types::Base*`
|
|
24
|
+
classes, and `rails g lighthouse:install` builds on them: it keeps them as
|
|
25
|
+
graphql-ruby wrote them, includes `Lighthouse::GraphQL::Type` in the base object,
|
|
26
|
+
interface, union and scalar, and with `--subscriptions` adds
|
|
27
|
+
`Subscriptions::BaseSubscription`, which graphql-ruby has none of
|
|
28
|
+
([generators.md](generators.md)):
|
|
28
29
|
|
|
29
30
|
```ruby
|
|
30
31
|
# app/graphql/resolvers/base_resolver.rb (graphql-ruby's)
|
|
@@ -33,16 +34,17 @@ module Resolvers
|
|
|
33
34
|
end
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
# app/graphql/mutations/base_mutation.rb (graphql-ruby's
|
|
37
|
+
# app/graphql/mutations/base_mutation.rb (graphql-ruby's)
|
|
37
38
|
module Mutations
|
|
38
|
-
class BaseMutation < GraphQL::Schema::
|
|
39
|
+
class BaseMutation < GraphQL::Schema::RelayClassicMutation
|
|
39
40
|
argument_class Types::BaseArgument
|
|
40
41
|
field_class Types::BaseField
|
|
42
|
+
input_object_class Types::BaseInputObject
|
|
41
43
|
object_class Types::BaseObject
|
|
42
44
|
end
|
|
43
45
|
end
|
|
44
46
|
|
|
45
|
-
# app/graphql/subscriptions/base_subscription.rb (Lighthouse's)
|
|
47
|
+
# app/graphql/subscriptions/base_subscription.rb (Lighthouse's, --subscriptions)
|
|
46
48
|
module Subscriptions
|
|
47
49
|
class BaseSubscription < GraphQL::Schema::Subscription
|
|
48
50
|
include Lighthouse::GraphQL::Subscription
|
|
@@ -62,9 +64,12 @@ schema holds every class to them:
|
|
|
62
64
|
|
|
63
65
|
```ruby
|
|
64
66
|
# app/graphql/app_schema.rb
|
|
65
|
-
AppSchema
|
|
67
|
+
class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
66
68
|
options: {
|
|
67
69
|
base_types: {
|
|
70
|
+
query_type: Types::QueryType,
|
|
71
|
+
mutation_type: Types::MutationType,
|
|
72
|
+
node: Types::NodeType,
|
|
68
73
|
resolver: Resolvers::BaseResolver,
|
|
69
74
|
mutation: Mutations::BaseMutation,
|
|
70
75
|
subscription: Subscriptions::BaseSubscription,
|
|
@@ -77,6 +82,8 @@ AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
)
|
|
85
|
+
use GraphQL::Dataloader
|
|
86
|
+
end
|
|
80
87
|
```
|
|
81
88
|
|
|
82
89
|
- A class a `Query`, `Mutation` or `Subscription` field resolves to, by convention
|
|
@@ -89,12 +96,17 @@ AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
|
89
96
|
(`GraphQL::Schema::Resolver`, `GraphQL::Schema::Mutation`, which
|
|
90
97
|
`RelayClassicMutation` is, `GraphQL::Schema::Subscription`), or the schema does not
|
|
91
98
|
build. graphql-ruby never sees these three keys.
|
|
99
|
+
- `query_type:` and `mutation_type:` are the code-first classes the SDL's `Query`
|
|
100
|
+
and `Mutation` are built on (see [root type classes](#root-type-classes)), and
|
|
101
|
+
`node:` is the SDL's `Node` interface ([relay.md](relay.md#relay-through-graphql-ruby)).
|
|
102
|
+
`subscription_type:` does the same for `Subscription`.
|
|
92
103
|
- The others are graphql-ruby's: it builds the SDL's types of each kind from them.
|
|
93
104
|
A [type class](#type-classes) (one including `Lighthouse::GraphQL::Type`) that
|
|
94
105
|
does not inherit from its kind's raises `Lighthouse::DefinitionError` when the
|
|
95
106
|
schema is built.
|
|
96
107
|
|
|
97
|
-
`rails g lighthouse:install`
|
|
108
|
+
`rails g lighthouse:install` names in the schema class every one graphql-ruby's
|
|
109
|
+
install wrote. Without them,
|
|
98
110
|
Lighthouse requires only the graphql-ruby class (`GraphQL::Schema::Resolver`, …),
|
|
99
111
|
and a `Subscription` field with no class resolves with a built-in subscription that
|
|
100
112
|
includes the concern.
|
|
@@ -149,8 +161,9 @@ end
|
|
|
149
161
|
|
|
150
162
|
### Resolution by convention
|
|
151
163
|
|
|
152
|
-
A field on `Query` or `Mutation` with **no resolving directive
|
|
153
|
-
|
|
164
|
+
A field on `Query` or `Mutation` with **no resolving directive**, and no method of
|
|
165
|
+
its name on the [root type class](#root-type-classes), resolves to the class named
|
|
166
|
+
after it, as in PHP Lighthouse:
|
|
154
167
|
|
|
155
168
|
- `Query.latestPosts` resolves to `Resolvers::LatestPosts`;
|
|
156
169
|
- `Mutation.publishPost` resolves to `Mutations::PublishPost`.
|
|
@@ -162,9 +175,11 @@ field with no class is reported through [`on_error`](configuration.md#on_error):
|
|
|
162
175
|
it raises in test and logs elsewhere. Lighthouse never falls back to a top-level
|
|
163
176
|
constant, so a `post` field is never mistaken for the `Post` model.
|
|
164
177
|
|
|
165
|
-
graphql-ruby's `RelayClassicMutation
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
graphql-ruby's `RelayClassicMutation`, which `Mutations::BaseMutation` inherits from,
|
|
179
|
+
takes a single `input:` argument. A `Mutation` field whose SDL declares its arguments
|
|
180
|
+
one by one calls the class as a plain mutation, with those arguments as keywords
|
|
181
|
+
(`resolve(id:)` above). A field with a single `input:` argument goes through
|
|
182
|
+
`RelayClassicMutation`'s own `input:` handling.
|
|
168
183
|
|
|
169
184
|
Wrapping directives (`@guard`, the `@can*` family, `@validator`, `@cache`,
|
|
170
185
|
`@clearCache`) are not resolvers, so a field carrying only those still resolves by
|
|
@@ -176,6 +191,45 @@ type Mutation {
|
|
|
176
191
|
}
|
|
177
192
|
```
|
|
178
193
|
|
|
194
|
+
### Root type classes
|
|
195
|
+
|
|
196
|
+
graphql-ruby's install writes `Types::QueryType` and `Types::MutationType`, and the
|
|
197
|
+
schema's `base_types` name them (`query_type:`, `mutation_type:`). The SDL's `Query`
|
|
198
|
+
and `Mutation` are built on them, so they work as in a graphql-ruby app:
|
|
199
|
+
|
|
200
|
+
- A root field with no resolving directive calls the method of its snake_case name
|
|
201
|
+
on the class, when the class defines it and includes `Lighthouse::GraphQL::Type`
|
|
202
|
+
(`Types::BaseObject` does, after `lighthouse:install`). It comes before the
|
|
203
|
+
conventional class: `Query.latestPosts` calls `Types::QueryType#latest_posts`
|
|
204
|
+
before `Resolvers::LatestPosts`.
|
|
205
|
+
- The class's own `field`s are the schema's too, beside the SDL's, and resolve as
|
|
206
|
+
in graphql-ruby: Relay's `node` / `nodes`, and the `field :publish_post, mutation:
|
|
207
|
+
Mutations::PublishPost` that `rails g graphql:mutation` adds. An SDL field of the
|
|
208
|
+
same name wins.
|
|
209
|
+
- When the SDL has no `Mutation` type, `Types::MutationType` itself is the root,
|
|
210
|
+
once it declares a field.
|
|
211
|
+
|
|
212
|
+
```graphql
|
|
213
|
+
type Query {
|
|
214
|
+
latestPosts(limit: Int = 10): [Post!]!
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
# app/graphql/types/query_type.rb
|
|
220
|
+
module Types
|
|
221
|
+
class QueryType < Types::BaseObject
|
|
222
|
+
def latest_posts(limit:)
|
|
223
|
+
Post.order(created_at: :desc).limit(limit)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
As in a [type class](#type-classes), the method has `object`, `context`, the
|
|
230
|
+
`dataload*` helpers and `resolve_info`, and gets the field's arguments as snake_case
|
|
231
|
+
keywords. `Subscription` fields resolve with their classes, as before.
|
|
232
|
+
|
|
179
233
|
### Naming a class explicitly
|
|
180
234
|
|
|
181
235
|
`@field(resolver:)` points a field at a class by name. On `Query` and `Mutation`
|
|
@@ -317,8 +371,9 @@ what every resolver shares.
|
|
|
317
371
|
|
|
318
372
|
graphql-ruby keeps the Ruby side of each object type in `app/graphql/types`:
|
|
319
373
|
`Types::PostType`, inheriting from `Types::BaseObject`, as `rails g graphql:object
|
|
320
|
-
Post` writes it. Lighthouse keeps that structure. A type class that includes
|
|
321
|
-
Lighthouse
|
|
374
|
+
Post` writes it. Lighthouse keeps that structure. A type class that includes
|
|
375
|
+
`Lighthouse::GraphQL::Type` is handed to Lighthouse. After `lighthouse:install`,
|
|
376
|
+
`Types::BaseObject` includes it, so every type class does: the SDL still declares the type and its fields, and the class says how
|
|
322
377
|
the fields with no resolving directive resolve. A field calls the method of its
|
|
323
378
|
snake_case name, when the class defines it. Fields it does not define are read off
|
|
324
379
|
the object, as before, and fields with a resolving directive keep it.
|
|
@@ -343,8 +398,6 @@ type ReadingTime {
|
|
|
343
398
|
# app/graphql/types/post_type.rb
|
|
344
399
|
module Types
|
|
345
400
|
class PostType < BaseObject
|
|
346
|
-
include Lighthouse::GraphQL::Type
|
|
347
|
-
|
|
348
401
|
def reading_time
|
|
349
402
|
{ words: object.body.to_s.split.size }
|
|
350
403
|
end
|
|
@@ -354,8 +407,6 @@ end
|
|
|
354
407
|
# app/graphql/types/reading_time_type.rb
|
|
355
408
|
module Types
|
|
356
409
|
class ReadingTimeType < BaseObject
|
|
357
|
-
include Lighthouse::GraphQL::Type
|
|
358
|
-
|
|
359
410
|
def minutes(words_per_minute:)
|
|
360
411
|
[(object[:words] / words_per_minute.to_f).ceil, 1].max
|
|
361
412
|
end
|
|
@@ -403,9 +454,11 @@ interface to `Article` ([types.md](types.md#the-default)).
|
|
|
403
454
|
|
|
404
455
|
The class is the type's name plus `Type`, looked up in `namespaces[:types]`
|
|
405
456
|
(default `['Types']`) when the schema is built. Only a class that includes
|
|
406
|
-
`Lighthouse::GraphQL::Type` counts: any other `Types::PostType
|
|
407
|
-
|
|
408
|
-
|
|
457
|
+
`Lighthouse::GraphQL::Type` counts: any other `Types::PostType` is left alone, and
|
|
458
|
+
its methods resolve nothing. `lighthouse:install` includes the concern in
|
|
459
|
+
`Types::BaseObject`, `BaseInterface`, `BaseUnion` and `BaseScalar`, which hands every
|
|
460
|
+
type class over at once. In an app that did not run it, include the concern in
|
|
461
|
+
those bases, or in each type class (`include Lighthouse::GraphQL::Type`).
|
|
409
462
|
|
|
410
463
|
A class that includes the concern must inherit from the schema's
|
|
411
464
|
`base_types[:object]` when one is given, or the schema does not build. The concern
|
|
@@ -413,9 +466,9 @@ also hands over the classes of unions, interfaces and scalars, which pick the ty
|
|
|
413
466
|
of a union's values or coerce a scalar's: see [types.md](types.md). It is not the schema's type: it only lends its
|
|
414
467
|
methods. Fields are declared in the SDL, so `field` declarations in it are ignored,
|
|
415
468
|
and graphql-ruby's type-level `authorized?` does not run; authorize with
|
|
416
|
-
[`@can*`](authorization.md). `Query
|
|
417
|
-
|
|
418
|
-
|
|
469
|
+
[`@can*`](authorization.md). `Query` and `Mutation` are the exception: they are
|
|
470
|
+
built on their [root type classes](#root-type-classes), whose `field`s count.
|
|
471
|
+
`Subscription` fields resolve with their own classes.
|
|
419
472
|
|
|
420
473
|
### `@spread` and `@inject`
|
|
421
474
|
|
|
@@ -476,18 +529,18 @@ end
|
|
|
476
529
|
## Generators
|
|
477
530
|
|
|
478
531
|
```shell
|
|
479
|
-
bin/rails generate lighthouse:query LatestPosts # app/graphql/resolvers/latest_posts.rb
|
|
480
|
-
bin/rails generate lighthouse:mutation PublishPost # app/graphql/mutations/publish_post.rb
|
|
481
532
|
bin/rails generate lighthouse:subscription PostUpdated # app/graphql/subscriptions/post_updated.rb
|
|
482
533
|
bin/rails generate lighthouse:builder Published # app/graphql/builders/published.rb
|
|
483
534
|
bin/rails generate lighthouse:validator CreatePostInput # app/graphql/validators/create_post_input_validator.rb
|
|
484
|
-
bin/rails generate graphql:object Post # a type class
|
|
535
|
+
bin/rails generate graphql:object Post # a type class, Lighthouse's through Types::BaseObject
|
|
536
|
+
bin/rails generate graphql:mutation PublishPost # a code-first mutation, a field of Types::MutationType
|
|
485
537
|
```
|
|
486
538
|
|
|
487
|
-
Each writes the class with its contract, into the first
|
|
488
|
-
kind (or `--namespace=…`)
|
|
489
|
-
|
|
490
|
-
`graphql:interface`.
|
|
539
|
+
Each Lighthouse generator writes the class with its contract, into the first
|
|
540
|
+
namespace configured for its kind (or `--namespace=…`). Type classes come from
|
|
541
|
+
graphql-ruby's own `graphql:object`, `graphql:scalar`, `graphql:union` and
|
|
542
|
+
`graphql:interface`. A resolver class has no generator, as in graphql-ruby: write
|
|
543
|
+
it by hand, as above. [generators.md](generators.md) has what each generator writes,
|
|
491
544
|
and which of graphql-ruby's fit a Lighthouse app.
|
|
492
545
|
|
|
493
546
|
## Changing the namespaces
|