lighthouse-graphql 0.4.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 +256 -1
- data/README.md +26 -15
- data/docs/caching.md +4 -3
- data/docs/configuration.md +16 -12
- data/docs/directives.md +294 -4
- data/docs/generators.md +84 -0
- data/docs/getting-started.md +93 -60
- data/docs/performance.md +2 -2
- data/docs/relationships.md +47 -4
- data/docs/relay.md +200 -0
- data/docs/resolvers.md +326 -34
- data/docs/security.md +73 -8
- data/docs/subscriptions.md +16 -9
- data/docs/types.md +218 -0
- data/docs/uploads.md +107 -0
- data/lib/generators/lighthouse/base_classes.rb +27 -49
- data/lib/generators/lighthouse/install/graphql_ruby_install.rb +113 -0
- data/lib/generators/lighthouse/install/install_generator.rb +125 -45
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +10 -8
- data/lib/lighthouse/async_mutation_job.rb +17 -0
- data/lib/lighthouse/caching.rb +9 -3
- data/lib/lighthouse/complexity.rb +5 -4
- data/lib/lighthouse/config.rb +12 -18
- data/lib/lighthouse/controller.rb +53 -12
- 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/cache_resolvers.rb +1 -1
- 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 +59 -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 +136 -0
- data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +87 -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 +56 -6
- data/lib/lighthouse/generated_resolvers.rb +13 -8
- data/lib/lighthouse/global_id.rb +57 -0
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/graphql_ruby_patches.rb +45 -0
- 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 +34 -0
- data/lib/lighthouse/relation_counts.rb +29 -0
- data/lib/lighthouse/resolve_info.rb +80 -0
- data/lib/lighthouse/resolver_bases.rb +25 -4
- data/lib/lighthouse/resolver_dispatch.rb +51 -11
- data/lib/lighthouse/root_types.rb +117 -0
- data/lib/lighthouse/scalar_coercion.rb +72 -0
- data/lib/lighthouse/schema_cache.rb +2 -1
- data/lib/lighthouse/schema_generator.rb +13 -11
- 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 +28 -0
- data/lib/lighthouse/upload_store.rb +57 -0
- data/sig/lighthouse/directives.rbs +3 -2
- data/sig/lighthouse.rbs +10 -2
- metadata +40 -4
- data/lib/generators/lighthouse/install/templates/schema.rb.tt +0 -22
- data/lib/generators/lighthouse/mutation_generator.rb +0 -31
- data/lib/generators/lighthouse/query_generator.rb +0 -30
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da632a31f353bc75d798a228769150a76f8cd3a0e8dce13e8bc19c363641f850
|
|
4
|
+
data.tar.gz: 6d0e92dc7818bccd89d4d9d1bc2e4905421cedd84190a0d0ae09c8ad31cb546f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30c076b60a32754472f1360eedc72c46b2ee518230d8ce686658f6a436f9a7d74cbcb13c24bf6a1d7dc8c05f29fba8f6ddd0d53e5614ea00ab498753340429c3
|
|
7
|
+
data.tar.gz: d558ad506a5bfebda1486cf8b62450113d0a1022f0ff458a8b040924aca458189a176cf9dbdcab95f877b571bfc18b1408109b5db36bfc128a6754df55b1bc11
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,259 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.6.0] - 2026-10-18
|
|
10
|
+
|
|
11
|
+
Lighthouse now uses what the Rails app and graphql-ruby already have instead of settings and
|
|
12
|
+
files of its own: the app's cache, its controllers' rate limiting, its ActiveStorage, and
|
|
13
|
+
everything graphql-ruby's `graphql:install` writes, which `lighthouse:install` now builds on.
|
|
14
|
+
Several defaults change.
|
|
15
|
+
|
|
16
|
+
**Upgrading from 0.5:**
|
|
17
|
+
|
|
18
|
+
1. Remove `config.cache_store =` and `config.upload_store =` from
|
|
19
|
+
`config/initializers/lighthouse.rb`. Configure the app's `config.cache_store` instead, and
|
|
20
|
+
`config.action_controller.cache_store` if rate limits should be counted somewhere else.
|
|
21
|
+
2. `@throttle` counts per IP now. Add `by: "current_user.id"` where a field counted per user.
|
|
22
|
+
3. Past a `@throttle` limit the request is answered with a 429, as Rails' `rate_limit` answers it.
|
|
23
|
+
To fail only the field, as before, give `with:` a controller method that raises a
|
|
24
|
+
`GraphQL::ExecutionError`.
|
|
25
|
+
4. `@upload` checks `public:` against the ActiveStorage service. On a public service, add
|
|
26
|
+
`public: true`. Where `public: true` meets a private service, name a public one with `disk:`.
|
|
27
|
+
5. `rails g lighthouse:query` and `lighthouse:mutation` are gone. Resolve a root field with a
|
|
28
|
+
method of `Types::QueryType` / `MutationType`, with `rails g graphql:mutation`, or with a
|
|
29
|
+
`Resolvers::` / `Mutations::` class written by hand, which convention still finds. An app
|
|
30
|
+
installed with 0.5 keeps working as it is: `AppSchema = get_schema(...)` is still supported.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `@throttle(by:)`: who the client is, as `rate_limit`'s `by:`. It is a path in the context, read
|
|
35
|
+
as `@inject` reads one (`by: "current_user.id"`).
|
|
36
|
+
- `@throttle(with:)`: a controller method to call past the limit, as `rate_limit`'s `with:`. One
|
|
37
|
+
that renders or redirects ends the request with its response, as a `before_action` that renders
|
|
38
|
+
does. One that returns lets the call through, and one that raises a `GraphQL::ExecutionError`
|
|
39
|
+
fails only the field.
|
|
40
|
+
- `@throttle` refusals are instrumented as `rate_limit.action_controller`, with `rate_limit`'s
|
|
41
|
+
payload (`request`, `count`, `to`, `within`, `by`, `name`, `scope`, `cache_key`).
|
|
42
|
+
- `base_types` takes graphql-ruby's code-first root types and Node interface. The SDL's root type
|
|
43
|
+
of a kind is built on `query_type:` / `mutation_type:` / `subscription_type:` (`Types::QueryType`),
|
|
44
|
+
so the `field`s the class declares (graphql-ruby's Relay `node` and `nodes`, what
|
|
45
|
+
`graphql:mutation` adds to `Types::MutationType`) are the schema's too, beside the SDL's, and
|
|
46
|
+
resolve as in graphql-ruby. With no such root type in the SDL, the class is the root. `node:`
|
|
47
|
+
(`Types::NodeType`) is the SDL's `Node` interface, which `@node` types implement, so
|
|
48
|
+
graphql-ruby's Relay and `@node` share one `Node`.
|
|
49
|
+
- A root field with no resolving directive resolves with the method of its name on the root type
|
|
50
|
+
class, when the class includes `Lighthouse::GraphQL::Type` (`Query.latestPosts` →
|
|
51
|
+
`Types::QueryType#latest_posts`), before the conventional `Resolvers::` / `Mutations::` class.
|
|
52
|
+
- A schema that implements graphql-ruby's `id_from_object` / `object_from_id` has one kind of
|
|
53
|
+
global id: `@globalId` encodes an object's own id with `id_from_object`, and decodes an
|
|
54
|
+
argument with `object_from_id`.
|
|
55
|
+
- `lighthouse:install --subscriptions` writes `Subscriptions::BaseSubscription` and names it in
|
|
56
|
+
`base_types`; without it, the install writes no subscription base.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- `@cache`, `@clearCache` and Automatic Persisted Queries use the app's `Rails.cache`
|
|
61
|
+
(`config.cache_store`), as PHP Lighthouse uses Laravel's cache. Outside Rails they use an
|
|
62
|
+
in-memory store.
|
|
63
|
+
- `@throttle` is Rails' `rate_limit` on one field, and counts as it does:
|
|
64
|
+
- It counts in the controller's `cache_store` (`config.action_controller.cache_store`,
|
|
65
|
+
`Rails.cache` unless set), under `rate_limit`'s key, `rate-limit:<scope>:<name>:<by>`.
|
|
66
|
+
Counts kept under the old `lighthouse:throttle:` keys start over.
|
|
67
|
+
- `name:` is `rate_limit`'s. Each field counts under its own name (`Query.x`) by default, apart
|
|
68
|
+
from the controller's `rate_limit`, or together with it when both use the same name.
|
|
69
|
+
`prefix:` is `rate_limit`'s `scope:`, the controller's path by default.
|
|
70
|
+
- The client is its IP, as `rate_limit`'s default `by:`, no longer the signed-in user.
|
|
71
|
+
- Past the limit it does what `rate_limit` does by default: it raises
|
|
72
|
+
`ActionController::TooManyRequests`, which Rails answers with a 429
|
|
73
|
+
(`head :too_many_requests` on Rails 7.2). The field used to fail on its own, with the rest of
|
|
74
|
+
the operation answered; that remains the behavior without Action Pack.
|
|
75
|
+
- `lighthouse:install` builds on graphql-ruby's `graphql:install`, run with whichever of its
|
|
76
|
+
options the app chose (`--relay`, `--batch`, `--skip-mutation-root-type`, `--schema`,
|
|
77
|
+
`--directory`, `--api`), or run first with its defaults when the app has none. It writes no
|
|
78
|
+
schema class of its own: graphql-ruby's `class AppSchema < GraphQL::Schema` becomes
|
|
79
|
+
`class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(options: { base_types: ... })`,
|
|
80
|
+
naming every class graphql-ruby wrote, and keeps the rest of the class (`use
|
|
81
|
+
GraphQL::Dataloader`, `max_depth`, `id_from_object`, ...). It takes out what Lighthouse replaces:
|
|
82
|
+
the `query(...)` / `mutation(...)` root types the SDL's are built on, the `resolve_type` stub,
|
|
83
|
+
and the example `test_field`. Its base object, interface, union and scalar include
|
|
84
|
+
`Lighthouse::GraphQL::Type`, so every type class is Lighthouse's without an `include` of its
|
|
85
|
+
own. With `--batch`, the schema uses `GraphQL::Dataloader` too. Its `Mutations::BaseMutation`, a
|
|
86
|
+
`RelayClassicMutation`, is kept; graphql-ruby's controller, unchanged since its install, becomes
|
|
87
|
+
one with `Lighthouse::GraphQL::Controller`. `Types::QueryType` and `MutationType` are kept.
|
|
88
|
+
- An SDL Mutation field that declares its arguments one by one resolves by convention to a
|
|
89
|
+
`GraphQL::Schema::RelayClassicMutation` subclass as to a plain mutation, its arguments as
|
|
90
|
+
keywords. It used to fail looking for `input:`.
|
|
91
|
+
- A subscriptions backend the app's schema class `use`s in its body
|
|
92
|
+
(`use GraphQL::Subscriptions::ActionCableSubscriptions`) delivers the SDL's Subscription fields.
|
|
93
|
+
graphql-ruby adds its subscription extension only to a root type of the class's own.
|
|
94
|
+
- `Lighthouse::GraphQL::Controller` puts the controller in the context (`:lighthouse_controller`)
|
|
95
|
+
for `@throttle`. In development, an error Rails answers with a status of its own
|
|
96
|
+
(`config.action_dispatch.rescue_responses`, such as a 429 or a 404) now gets that status, as in
|
|
97
|
+
production, instead of being rendered as a JSON 500.
|
|
98
|
+
- `@upload` stores files on the app's ActiveStorage only, with PHP Lighthouse's rules. `disk:`
|
|
99
|
+
names a service of `config/storage.yml` (`config.active_storage.service` without it), and
|
|
100
|
+
`path:` starts the blob's key. ActiveStorage sets visibility per service, so `public:` must now
|
|
101
|
+
agree with the service's: `public: true` on a private service, or the default `false` on a
|
|
102
|
+
public one, fails instead of being ignored. An unknown service, or a value that is not a file,
|
|
103
|
+
fails naming the argument. The file is rewound before it is stored, so a `@validator` may read
|
|
104
|
+
it first.
|
|
105
|
+
- The `Upload` scalar's errors are PHP Lighthouse's: a value that is not a file names the GraphQL
|
|
106
|
+
multipart request specification and what was sent instead.
|
|
107
|
+
|
|
108
|
+
### Removed
|
|
109
|
+
|
|
110
|
+
- `Config.cache_store`: configure the app's `config.cache_store` instead, and
|
|
111
|
+
`config.action_controller.cache_store` (or a controller's `self.cache_store =`) for `@throttle`.
|
|
112
|
+
`Config.persisted_queries_store` stays, defaulting to `Rails.cache`.
|
|
113
|
+
- `Config.upload_store`: to store files elsewhere, declare an ActiveStorage service and name it
|
|
114
|
+
with `@upload(disk:)`, as a Laravel app declares a disk.
|
|
115
|
+
- `rails g lighthouse:query` and `lighthouse:mutation`: graphql-ruby has no resolver generator,
|
|
116
|
+
and `graphql:mutation` works in a Lighthouse app now. See Upgrading.
|
|
117
|
+
|
|
118
|
+
### Fixed
|
|
119
|
+
|
|
120
|
+
- On graphql-ruby 2.5, a schema using `@limit`, `@bind` or `@upload` without one of their optional
|
|
121
|
+
arguments did not build (`Expected value to not be null`): graphql 2.5 does not apply the default
|
|
122
|
+
of a non-null directive argument. Those arguments (`builder`, `column`, `with`, `optional`,
|
|
123
|
+
`path`, `public`) are nullable now, with the same defaults.
|
|
124
|
+
|
|
125
|
+
## [0.5.0] - 2026-10-18
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
|
|
129
|
+
- `Lighthouse::GraphQL::Type`, a concern that hands a graphql-ruby type class to Lighthouse, in
|
|
130
|
+
graphql-ruby's `app/graphql/types` structure. A field of an object type with no resolving
|
|
131
|
+
directive resolves with the method of its snake_case name on `Types::<Type>Type`, when that class
|
|
132
|
+
includes the concern and defines it (`Post.readingTime` → `Types::PostType#reading_time`). As in
|
|
133
|
+
graphql-ruby, the method has `object` (the parent), `context`, the `dataload*` helpers, and the
|
|
134
|
+
field's arguments as keywords. The SDL still declares the fields; the ones the class does not
|
|
135
|
+
define are read off the object as before. A type class that does not include the concern is left
|
|
136
|
+
alone. The class is looked up in the new `types` namespace (default `['Types']`), and must inherit
|
|
137
|
+
from `base_types[:object]` when one is given.
|
|
138
|
+
- `@model(class:)`, as PHP Lighthouse's: maps an object type to the ActiveRecord model of another
|
|
139
|
+
name. Every directive taking `model:` (`@all`, `@find`, `@paginate`, `@create`, `@update`, the
|
|
140
|
+
`@can*` family...) on a field returning the type gets it, unless it names its own; unions,
|
|
141
|
+
interfaces and `@node` resolve the model's records to the type; the schema does not build unless
|
|
142
|
+
it is a model; and a type class's methods only run on its records (another parent is reported
|
|
143
|
+
through `on_error`).
|
|
144
|
+
- Unions and interfaces resolve each value to its object type as PHP Lighthouse does: a Hash's
|
|
145
|
+
`__typename`, else the type named after the value's class or a superclass (STI), else the type
|
|
146
|
+
whose `@model` names its model. A union's type class (`Types::SearchResultType` including the
|
|
147
|
+
concern) can take over with graphql-ruby's `self.resolve_type(object, context)`, an interface's
|
|
148
|
+
with `definition_methods { def resolve_type }`, and `@union(resolveType:)` /
|
|
149
|
+
`@interface(resolveType:)` name a class in the SDL. A value nothing resolves, or one resolved
|
|
150
|
+
outside the union, raises `Lighthouse::DefinitionError` naming the ways out.
|
|
151
|
+
- Custom scalars read and write their values through their class: `Types::DateTimeType` (a
|
|
152
|
+
`GraphQL::Schema::Scalar` including the concern), or the class `@scalar(class:)` names, with
|
|
153
|
+
graphql-ruby's `coerce_input` / `coerce_result`. A `GraphQL::CoercionError` makes the argument
|
|
154
|
+
invalid with its message. A scalar with no class passes its values through, as before.
|
|
155
|
+
- `@method` passes the field's arguments to the method: as snake_case keywords when it takes
|
|
156
|
+
keywords, otherwise positionally in SDL order, as PHP Lighthouse does.
|
|
157
|
+
- `base_types[:union]`, `[:interface]` and `[:scalar]`, like `[:object]`, are the bases the type
|
|
158
|
+
classes of their kind must inherit from.
|
|
159
|
+
- `docs/types.md`: unions, interfaces and scalars.
|
|
160
|
+
- `@field(resolver: "Class@method")`, as PHP Lighthouse's: the method replaces `call` on a plain
|
|
161
|
+
class (as a class method, else an instance method), or `resolve` on a resolver class, whose
|
|
162
|
+
`ready?` / `authorized?` still run.
|
|
163
|
+
- `ResolveInfo`, PHP Lighthouse's `$resolveInfo`: `resolve_info` in `Resolvers::` / `Mutations::`
|
|
164
|
+
classes, classes `@field` names and type class methods, and a fourth argument for a plain callable
|
|
165
|
+
that takes one. It has the field, its parent type, object, arguments, context and path, and
|
|
166
|
+
`enhance_builder(relation, scopes: [])`, which applies the field's `@builder` classes and argument
|
|
167
|
+
directives (`@eq`, `@where`, `@orderBy`, `@whereConditions`, ...) to a query the class builds.
|
|
168
|
+
- `@with(relation:, scopes:)` and `@withCount(relation:, scopes:)` eager-load a relation, or its
|
|
169
|
+
count (kept on the parent as `<relation>_count`), before the field resolves; `@lazyLoad(relations:)`
|
|
170
|
+
eager-loads relations of the records a field returns. All three load across every parent in the
|
|
171
|
+
response at once, through the Dataloader, and take nested relations (`"posts.comments"`).
|
|
172
|
+
- Relay global object identification: `@globalId` encodes a field (base64 of `Type:id`) and decodes
|
|
173
|
+
an argument or input field (`decode: ARRAY | TYPE | ID`); `@node` makes a type implement `Node` and
|
|
174
|
+
adds `Query.node(id:)`, which finds the record through the type's model or `@node(resolver:)`. The
|
|
175
|
+
new `global_id` setting takes your own encoder. `docs/relay.md`.
|
|
176
|
+
- A union's or interface's class may answer `[type, value]`, as graphql-ruby allows.
|
|
177
|
+
- File uploads: the `Upload` scalar (Lighthouse's own for `scalar Upload`), the GraphQL multipart
|
|
178
|
+
request specification in `Lighthouse::GraphQL::Controller`, and `@upload(disk:, path:)`, which
|
|
179
|
+
stores the file through the new `upload_store` setting (an ActiveStorage blob by default) and
|
|
180
|
+
passes its signed id. `docs/uploads.md`.
|
|
181
|
+
- Filters: `@whereNull`, `@whereNotNull`, `@whereBetween`, `@whereNotBetween`, `@whereJsonContains`
|
|
182
|
+
(PostgreSQL, MySQL, SQLite), `@scope`, `@nest` (the filters of an input's fields) and `@limit`
|
|
183
|
+
(a LIMIT, or a per-parent cut of a relation's records); `@whereAuth(relation:)` keeps the
|
|
184
|
+
signed-in user's records.
|
|
185
|
+
- Argument directives: `@trim`, `@drop`, `@hash` (BCrypt) and `@bind(class:)`, which replaces an id
|
|
186
|
+
with its record(s) or fails validation. Rewriting ones (`@globalId`, `@trim`, `@drop`) run before
|
|
187
|
+
`@guard`, `@can*` and `@validator`; effectful ones (`@hash`, `@bind`, `@upload`) after them.
|
|
188
|
+
- `@builder` on an argument or input field, as PHP Lighthouse's: the method gets the argument's
|
|
189
|
+
value whenever the client sends it, `null` included. `method: "Class@method"` names the builder
|
|
190
|
+
without `class:`, and `value:` on a field passes a fixed value in place of the arguments.
|
|
191
|
+
- `@limit(builder: true)` puts the LIMIT on the field's query; `@limit` must be on an `Int` argument,
|
|
192
|
+
and is accepted on the field itself, as PHP Lighthouse allows.
|
|
193
|
+
- `@hasManyThrough` and `@hasOneThrough`, PHP Lighthouse's names, as aliases of `@hasMany` and
|
|
194
|
+
`@hasOne`, which resolve `has_many` / `has_one :through`.
|
|
195
|
+
- `ResolveInfo#lookahead`: graphql-ruby's Lookahead for a field resolved by a class (a `Resolvers::`
|
|
196
|
+
class, `@field`, a type class method). It never reaches the arguments, so `@cache` keys are unchanged.
|
|
197
|
+
- `@throttle` logs a warning when the cache store is a `:null_store`, which counts nothing.
|
|
198
|
+
- `@enum(value:)` for an enum value's internal value; `@namespaced` to group fields; `@throttle` to
|
|
199
|
+
rate-limit a field per user or IP (the controller concern now puts `:remote_ip` in the context);
|
|
200
|
+
`@async` to run a mutation in an ActiveJob and answer `true`; `@hide(env:)` / `@show(env:)` to leave
|
|
201
|
+
fields out by environment (the schema cache key now includes it).
|
|
202
|
+
|
|
203
|
+
### Changed
|
|
204
|
+
|
|
205
|
+
- Generators build on graphql-ruby's instead of copying them. `lighthouse:install` runs
|
|
206
|
+
`graphql:install --no-relay --skip-graphiql --skip-query-logs` for the base classes every
|
|
207
|
+
graphql-ruby app has (`Types::Base*`, `Resolvers::BaseResolver`, `Mutations::BaseMutation`), then
|
|
208
|
+
replaces what it wrote that an SDL-first app does differently: the schema class (built from the
|
|
209
|
+
SDL, naming every base in `base_types`), the controller, no `Types::QueryType` /
|
|
210
|
+
`MutationType`, and a `BaseMutation` that is a `GraphQL::Schema::Mutation`. It writes
|
|
211
|
+
`Subscriptions::BaseSubscription` itself, graphql-ruby having none. Files the app had are kept.
|
|
212
|
+
- `docs/generators.md`: every generator, and which of graphql-ruby's fit a Lighthouse app
|
|
213
|
+
(`graphql:object`, `:scalar`, `:union`, `:interface` for type classes; `graphql:mutation` and
|
|
214
|
+
`graphql:detailed_trace` not as they are).
|
|
215
|
+
|
|
216
|
+
- `@field(resolver:)` on a field of a type other than `Query` and `Mutation` looks the class up in
|
|
217
|
+
the resolvers namespace first, nested names included: `@field(resolver: "Posts::TagsCount")`
|
|
218
|
+
resolves to `Resolvers::Posts::TagsCount`. Then it tries `resolver_namespaces`, as before, so
|
|
219
|
+
full names and classes outside `Resolvers::` resolve as they did. A class `Resolvers::X` now
|
|
220
|
+
wins over one that `resolver_namespaces` would find for the same name.
|
|
221
|
+
|
|
222
|
+
### Fixed
|
|
223
|
+
|
|
224
|
+
- A wrapping directive (`@globalId`, `@guard`, `@cache`, ...) on a field of a type whose objects are
|
|
225
|
+
Hashes read nothing: the wrapped default reader only called methods. It now reads the Hash's
|
|
226
|
+
keys, as the default reader does.
|
|
227
|
+
|
|
228
|
+
- A field returning a union or an interface no longer raises `undefined method 'resolve_type' for
|
|
229
|
+
an instance of Proc`. Lighthouse's field dispatcher replaced the schema's default resolve with a
|
|
230
|
+
Proc, which graphql-ruby also asks for `resolve_type` and scalar coercion; it now hands both on.
|
|
231
|
+
|
|
232
|
+
- `@field(resolver:)` on a `Query` or `Mutation` field finds a nested name in the resolvers /
|
|
233
|
+
mutations namespaces, as documented: `"Feeds::Home"` resolves to `Resolvers::Feeds::Home`,
|
|
234
|
+
then to `Feeds::Home`. It used to try only `Feeds::Home`, so the field resolved to `null`.
|
|
235
|
+
The field's `complexity` is looked up the same way.
|
|
236
|
+
|
|
237
|
+
### Documentation
|
|
238
|
+
|
|
239
|
+
- `docs/resolvers.md`: fields of types other than `Query` and `Mutation` resolved with `@field`,
|
|
240
|
+
whose class receives the parent record as `object`. The example app resolves `Post.tagsCount`
|
|
241
|
+
(batched through the dataloader), `Post.excerpt(length:)` and `Query.popularTags` this way.
|
|
242
|
+
It resolves `Post.readingTime` and the `ReadingTime` type, which is not a model, with
|
|
243
|
+
`Types::PostType` and `Types::ReadingTimeType`, `Query.search` (a union of `Post` and `User`)
|
|
244
|
+
and `Query.latestActivity` (an `Authored` interface) by the default, `DateTime` with
|
|
245
|
+
`Types::DateTimeType`, and `Post.summary(words:)` with `@method`. `Tag` is a Relay node with
|
|
246
|
+
global ids; `User.latestPostTitle` uses `@with`, `Tag.usage` `@withCount`, `User.posts`
|
|
247
|
+
`@lazyLoad`, `Query.counts` `@field(resolver: "Statistics@counts")`, and `Query.myPosts`
|
|
248
|
+
`resolve_info.enhance_builder`. `Mutation.updateAvatar` takes a multipart upload with `@upload`
|
|
249
|
+
(the example now loads ActiveStorage). `Mutation.schedulePublish` is `@async` on Solid Queue,
|
|
250
|
+
`Query.searchPosts` is `@throttle`d, and `Query.posts` filters `metadata` with `@whereJsonContains`;
|
|
251
|
+
the example's suite runs on PostgreSQL and MySQL too (`DATABASE_URL`, a new CI job), with Redis.
|
|
252
|
+
|
|
253
|
+
### Removed
|
|
254
|
+
|
|
255
|
+
- `rails g lighthouse:type`: it was graphql-ruby's `graphql:object` (and `:scalar`, `:union`,
|
|
256
|
+
`:interface`) with an `include`. Generate the class with graphql-ruby, then add
|
|
257
|
+
`include Lighthouse::GraphQL::Type`.
|
|
258
|
+
- `lighthouse:query` and `lighthouse:mutation` no longer write `Resolvers::BaseResolver` /
|
|
259
|
+
`Mutations::BaseMutation` when they are missing: graphql-ruby's install writes them, which
|
|
260
|
+
`lighthouse:install` runs. They say so instead.
|
|
261
|
+
|
|
9
262
|
## [0.4.0] - 2026-10-17
|
|
10
263
|
|
|
11
264
|
### Added
|
|
@@ -324,7 +577,9 @@ had been running inside a private monorepo as a path gem before being extracted.
|
|
|
324
577
|
- Config-driven extension seams: `Lighthouse.configure`, `resolver_namespaces`,
|
|
325
578
|
`on_error`, and `DirectiveRegistry.register` for app-defined directives.
|
|
326
579
|
|
|
327
|
-
[Unreleased]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.
|
|
580
|
+
[Unreleased]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.6.0...HEAD
|
|
581
|
+
[0.6.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.5.0...v0.6.0
|
|
582
|
+
[0.5.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.4.0...v0.5.0
|
|
328
583
|
[0.4.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.3.0...v0.4.0
|
|
329
584
|
[0.3.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.2.1...v0.3.0
|
|
330
585
|
[0.2.1]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.2.0...v0.2.1
|
data/README.md
CHANGED
|
@@ -72,16 +72,23 @@ It depends on `graphql ~> 2.5` and `activesupport`. For federation you also need
|
|
|
72
72
|
On graphql 2.5, keep the `json` gem below 3: graphql 2.5 calls `JSON.generate`
|
|
73
73
|
with an option json 3 removed (graphql 2.6 no longer does).
|
|
74
74
|
|
|
75
|
-
Then let the
|
|
75
|
+
Then let the generators set the app up:
|
|
76
76
|
|
|
77
77
|
```shell
|
|
78
|
+
bin/rails generate graphql:install # graphql-ruby's, with the options you choose (optional)
|
|
78
79
|
bin/rails generate lighthouse:install
|
|
79
80
|
```
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
`lighthouse:install` builds on graphql-ruby's own install, and runs `graphql:install`
|
|
83
|
+
with its defaults when the app has none. It adapts what graphql-ruby wrote: the
|
|
84
|
+
schema class becomes Lighthouse's, built from the SDL on graphql-ruby's root types
|
|
85
|
+
and base classes, and the rest of its body stays; the base object, interface, union
|
|
86
|
+
and scalar include `Lighthouse::GraphQL::Type`; the controller serves the schema
|
|
87
|
+
through `Lighthouse::GraphQL::Controller`. It adds the initializer, a starter schema
|
|
88
|
+
in `graphql/schema.graphql` and a `graphql.config.yml` for your IDE. Run
|
|
89
|
+
`bin/rails generate lighthouse:install --help` for its options (`--schema`,
|
|
90
|
+
`--sdl-folder`, `--subscriptions`, `--skip-controller`, `--skip-ide`), and see
|
|
91
|
+
[docs/generators.md](docs/generators.md) for every generator, graphql-ruby's included.
|
|
85
92
|
|
|
86
93
|
### IDE support
|
|
87
94
|
|
|
@@ -120,22 +127,26 @@ Prefer to wire it by hand? These are the steps the generator performs.
|
|
|
120
127
|
}
|
|
121
128
|
```
|
|
122
129
|
|
|
123
|
-
2. **Build the schema
|
|
130
|
+
2. **Build the schema**: graphql-ruby's schema class (`app/graphql/app_schema.rb`),
|
|
131
|
+
inheriting from the schema Lighthouse builds instead of `GraphQL::Schema`:
|
|
124
132
|
|
|
125
133
|
```ruby
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
129
|
-
sdl_folder: Rails.root.join('graphql').to_s,
|
|
134
|
+
class AppSchema < Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
130
135
|
options: {
|
|
131
|
-
base_types: {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
base_types: { # the classes graphql-ruby's install writes
|
|
137
|
+
query_type: Types::QueryType,
|
|
138
|
+
mutation_type: Types::MutationType,
|
|
139
|
+
node: Types::NodeType,
|
|
140
|
+
resolver: Resolvers::BaseResolver,
|
|
141
|
+
mutation: Mutations::BaseMutation,
|
|
142
|
+
object: Types::BaseObject
|
|
143
|
+
# interface: Types::BaseInterface, union:, scalar:, enum:, input_object: ...
|
|
136
144
|
}
|
|
137
145
|
}
|
|
138
146
|
)
|
|
147
|
+
use GraphQL::Dataloader # Lighthouse batches relationships through it
|
|
148
|
+
max_depth(15)
|
|
149
|
+
end
|
|
139
150
|
```
|
|
140
151
|
|
|
141
152
|
3. **Execute** it from your controller exactly like any graphql-ruby schema:
|
data/docs/caching.md
CHANGED
|
@@ -41,9 +41,10 @@ such as a Relay connection object, is reported through
|
|
|
41
41
|
[`on_error`](configuration.md#on_error) and returned uncached. So cache list
|
|
42
42
|
fields, not connection fields.
|
|
43
43
|
|
|
44
|
-
The store is
|
|
45
|
-
|
|
46
|
-
you enable caching
|
|
44
|
+
The store is your app's cache, `Rails.cache` (`config.cache_store`), as PHP
|
|
45
|
+
Lighthouse uses Laravel's: Lighthouse keeps no store of its own. Note that Rails
|
|
46
|
+
uses a `:null_store` in development and test unless you enable caching
|
|
47
|
+
(`bin/rails dev:cache`).
|
|
47
48
|
|
|
48
49
|
A cached value is still authorized and validated on every request: `@cache` runs
|
|
49
50
|
inside `@guard`, `@can*` and `@validator`, never around them.
|
data/docs/configuration.md
CHANGED
|
@@ -38,6 +38,7 @@ different contracts, so they live apart ([resolvers.md](resolvers.md)):
|
|
|
38
38
|
| `events` | `['Events']` | Classes `@event(dispatch:)` names ([events.md](events.md)) |
|
|
39
39
|
| `subscriptions` | `['Subscriptions']` | `Subscriptions::BaseSubscription` classes (a `GraphQL::Schema::Subscription`; the base includes `Lighthouse::GraphQL::Subscription`) for `Subscription` fields, and `@subscription(class:)` ([subscriptions.md](subscriptions.md)) |
|
|
40
40
|
| `analyzers` | `['Analyzers']` | `GraphQL::Analysis::Analyzer` classes (optionally including `Lighthouse::GraphQL::ComplexityAnalyzer`) that `@complexity(resolver:)` names ([security.md](security.md)) |
|
|
41
|
+
| `types` | `['Types']` | `Types::<Name>Type` classes including `Lighthouse::GraphQL::Type`: an object type's, whose methods resolve the fields of the SDL type `<Name>` ([resolvers.md](resolvers.md#type-classes)); a union's or interface's, which picks its values' types; a scalar's, which coerces its values ([types.md](types.md)). Also where `@union`, `@interface` and `@scalar` look classes up |
|
|
41
42
|
|
|
42
43
|
```ruby
|
|
43
44
|
config.namespaces = { queries: ['Api::Resolvers'] } # the other kinds keep their defaults
|
|
@@ -47,8 +48,9 @@ config.namespaces = { queries: ['Api::Resolvers'] } # the other kinds keep the
|
|
|
47
48
|
|
|
48
49
|
An ordered list of namespaces tried for **models** named by a directive
|
|
49
50
|
(`@all(model: "User")`) and for `@field(resolver:)` on types other than `Query`
|
|
50
|
-
and `Mutation
|
|
51
|
-
|
|
51
|
+
and `Mutation`, after the resolvers namespace
|
|
52
|
+
([resolvers.md](resolvers.md#fields-of-other-types)). Fully-qualified names
|
|
53
|
+
(containing `::`) are used as-is. Defaults to `['App::Graphql']`.
|
|
52
54
|
|
|
53
55
|
```ruby
|
|
54
56
|
config.resolver_namespaces = ['App::Graphql', 'Resolvers']
|
|
@@ -68,15 +70,6 @@ reporter in production:
|
|
|
68
70
|
config.on_error = ->(error, _ctx) { Sentry.capture_exception(error) }
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
### `cache_store`
|
|
72
|
-
|
|
73
|
-
The store behind [`@cache` / `@clearCache`](caching.md). Defaults to
|
|
74
|
-
`Rails.cache` (an in-memory store outside Rails).
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
config.cache_store = ActiveSupport::Cache::RedisCacheStore.new(url: ENV['REDIS_URL'])
|
|
78
|
-
```
|
|
79
|
-
|
|
80
73
|
### `pagination_default_count`, `pagination_max_count`
|
|
81
74
|
|
|
82
75
|
Defaults for paginated fields: the `first` used when a client leaves it out (so
|
|
@@ -97,7 +90,7 @@ Options for large schemas and high traffic, all off by default. See
|
|
|
97
90
|
config.schema_cache = Rails.env.production? # cache the built schema in tmp/cache
|
|
98
91
|
config.watch_sdl = Rails.env.development? # reload when a .graphql file changes
|
|
99
92
|
config.persisted_queries = true # Apollo's Automatic Persisted Queries
|
|
100
|
-
config.persisted_queries_store = Rails.cache # where APQ keeps queries (default:
|
|
93
|
+
config.persisted_queries_store = Rails.cache # where APQ keeps queries (default: Rails.cache)
|
|
101
94
|
config.persisted_queries_ttl = 1.week.to_i # how long they live there
|
|
102
95
|
config.persisted_queries_manifest = Rails.root.join('persisted-queries.json') # trusted operations
|
|
103
96
|
config.persisted_queries_only = Rails.env.production? # run nothing else
|
|
@@ -115,6 +108,17 @@ initializer.
|
|
|
115
108
|
config.convert_empty_strings_to_null = true
|
|
116
109
|
```
|
|
117
110
|
|
|
111
|
+
### `global_id`
|
|
112
|
+
|
|
113
|
+
Encodes and decodes Relay global ids for [`@globalId` and `@node`](relay.md): any
|
|
114
|
+
object with `encode(type, id)` returning the global id, and `decode(global_id)`
|
|
115
|
+
returning `[type, id]`. Defaults to `Lighthouse::GraphQL::GlobalId`, the base64 of
|
|
116
|
+
`Type:id`, as PHP Lighthouse's.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
config.global_id = SignedGlobalIds
|
|
120
|
+
```
|
|
121
|
+
|
|
118
122
|
### `queue_broadcasts`, `broadcast_queue`
|
|
119
123
|
|
|
120
124
|
Whether [`@broadcast`](directives.md#broadcast) triggers its subscription in an
|