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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5dd6b5839f6520df6489135c1a37382ce4f7889d136140f652443ad5a3cd1a81
|
|
4
|
+
data.tar.gz: 60fdb10b55abe72a1dc3df89705b904b424344e5bffa611e695a46fa8767ddc7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f2aff4008f41238a6ebca9c598add10069e7f0c4c8c5578bc1041f3da50f36b859b9ce1c91a29576442562a42afca20845b8fb3704b19d374570381a3588262
|
|
7
|
+
data.tar.gz: b985fc24dfb2d0196c96f84cb0423d0b21e38659a0bc1cd4db13dc3818a8939fd2340006d85605cb5a50fde37f638d177ba90bf5af26e2cc3b9a3a0c2043986a
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,185 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-10-18
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `Lighthouse::GraphQL::Type`, a concern that hands a graphql-ruby type class to Lighthouse, in
|
|
14
|
+
graphql-ruby's `app/graphql/types` structure. A field of an object type with no resolving
|
|
15
|
+
directive resolves with the method of its snake_case name on `Types::<Type>Type`, when that class
|
|
16
|
+
includes the concern and defines it (`Post.readingTime` → `Types::PostType#reading_time`). As in
|
|
17
|
+
graphql-ruby, the method has `object` (the parent), `context`, the `dataload*` helpers, and the
|
|
18
|
+
field's arguments as keywords. The SDL still declares the fields; the ones the class does not
|
|
19
|
+
define are read off the object as before. A type class that does not include the concern is left
|
|
20
|
+
alone. The class is looked up in the new `types` namespace (default `['Types']`), and must inherit
|
|
21
|
+
from `base_types[:object]` when one is given.
|
|
22
|
+
- `@model(class:)`, as PHP Lighthouse's: maps an object type to the ActiveRecord model of another
|
|
23
|
+
name. Every directive taking `model:` (`@all`, `@find`, `@paginate`, `@create`, `@update`, the
|
|
24
|
+
`@can*` family...) on a field returning the type gets it, unless it names its own; unions,
|
|
25
|
+
interfaces and `@node` resolve the model's records to the type; the schema does not build unless
|
|
26
|
+
it is a model; and a type class's methods only run on its records (another parent is reported
|
|
27
|
+
through `on_error`).
|
|
28
|
+
- Unions and interfaces resolve each value to its object type as PHP Lighthouse does: a Hash's
|
|
29
|
+
`__typename`, else the type named after the value's class or a superclass (STI), else the type
|
|
30
|
+
whose `@model` names its model. A union's type class (`Types::SearchResultType` including the
|
|
31
|
+
concern) can take over with graphql-ruby's `self.resolve_type(object, context)`, an interface's
|
|
32
|
+
with `definition_methods { def resolve_type }`, and `@union(resolveType:)` /
|
|
33
|
+
`@interface(resolveType:)` name a class in the SDL. A value nothing resolves, or one resolved
|
|
34
|
+
outside the union, raises `Lighthouse::DefinitionError` naming the ways out.
|
|
35
|
+
- Custom scalars read and write their values through their class: `Types::DateTimeType` (a
|
|
36
|
+
`GraphQL::Schema::Scalar` including the concern), or the class `@scalar(class:)` names, with
|
|
37
|
+
graphql-ruby's `coerce_input` / `coerce_result`. A `GraphQL::CoercionError` makes the argument
|
|
38
|
+
invalid with its message. A scalar with no class passes its values through, as before.
|
|
39
|
+
- `@method` passes the field's arguments to the method: as snake_case keywords when it takes
|
|
40
|
+
keywords, otherwise positionally in SDL order, as PHP Lighthouse does.
|
|
41
|
+
- `base_types[:union]`, `[:interface]` and `[:scalar]`, like `[:object]`, are the bases the type
|
|
42
|
+
classes of their kind must inherit from.
|
|
43
|
+
- `docs/types.md`: unions, interfaces and scalars.
|
|
44
|
+
- `@field(resolver: "Class@method")`, as PHP Lighthouse's: the method replaces `call` on a plain
|
|
45
|
+
class (as a class method, else an instance method), or `resolve` on a resolver class, whose
|
|
46
|
+
`ready?` / `authorized?` still run.
|
|
47
|
+
- `ResolveInfo`, PHP Lighthouse's `$resolveInfo`: `resolve_info` in `Resolvers::` / `Mutations::`
|
|
48
|
+
classes, classes `@field` names and type class methods, and a fourth argument for a plain callable
|
|
49
|
+
that takes one. It has the field, its parent type, object, arguments, context and path, and
|
|
50
|
+
`enhance_builder(relation, scopes: [])`, which applies the field's `@builder` classes and argument
|
|
51
|
+
directives (`@eq`, `@where`, `@orderBy`, `@whereConditions`, ...) to a query the class builds.
|
|
52
|
+
- `@with(relation:, scopes:)` and `@withCount(relation:, scopes:)` eager-load a relation, or its
|
|
53
|
+
count (kept on the parent as `<relation>_count`), before the field resolves; `@lazyLoad(relations:)`
|
|
54
|
+
eager-loads relations of the records a field returns. All three load across every parent in the
|
|
55
|
+
response at once, through the Dataloader, and take nested relations (`"posts.comments"`).
|
|
56
|
+
- Relay global object identification: `@globalId` encodes a field (base64 of `Type:id`) and decodes
|
|
57
|
+
an argument or input field (`decode: ARRAY | TYPE | ID`); `@node` makes a type implement `Node` and
|
|
58
|
+
adds `Query.node(id:)`, which finds the record through the type's model or `@node(resolver:)`. The
|
|
59
|
+
new `global_id` setting takes your own encoder. `docs/relay.md`.
|
|
60
|
+
- A union's or interface's class may answer `[type, value]`, as graphql-ruby allows.
|
|
61
|
+
- File uploads: the `Upload` scalar (Lighthouse's own for `scalar Upload`), the GraphQL multipart
|
|
62
|
+
request specification in `Lighthouse::GraphQL::Controller`, and `@upload(disk:, path:)`, which
|
|
63
|
+
stores the file through the new `upload_store` setting (an ActiveStorage blob by default) and
|
|
64
|
+
passes its signed id. `docs/uploads.md`.
|
|
65
|
+
- Filters: `@whereNull`, `@whereNotNull`, `@whereBetween`, `@whereNotBetween`, `@whereJsonContains`
|
|
66
|
+
(PostgreSQL, MySQL, SQLite), `@scope`, `@nest` (the filters of an input's fields) and `@limit`
|
|
67
|
+
(a LIMIT, or a per-parent cut of a relation's records); `@whereAuth(relation:)` keeps the
|
|
68
|
+
signed-in user's records.
|
|
69
|
+
- Argument directives: `@trim`, `@drop`, `@hash` (BCrypt) and `@bind(class:)`, which replaces an id
|
|
70
|
+
with its record(s) or fails validation. Rewriting ones (`@globalId`, `@trim`, `@drop`) run before
|
|
71
|
+
`@guard`, `@can*` and `@validator`; effectful ones (`@hash`, `@bind`, `@upload`) after them.
|
|
72
|
+
- `@builder` on an argument or input field, as PHP Lighthouse's: the method gets the argument's
|
|
73
|
+
value whenever the client sends it, `null` included. `method: "Class@method"` names the builder
|
|
74
|
+
without `class:`, and `value:` on a field passes a fixed value in place of the arguments.
|
|
75
|
+
- `@limit(builder: true)` puts the LIMIT on the field's query; `@limit` must be on an `Int` argument,
|
|
76
|
+
and is accepted on the field itself, as PHP Lighthouse allows.
|
|
77
|
+
- `@hasManyThrough` and `@hasOneThrough`, PHP Lighthouse's names, as aliases of `@hasMany` and
|
|
78
|
+
`@hasOne`, which resolve `has_many` / `has_one :through`.
|
|
79
|
+
- `ResolveInfo#lookahead`: graphql-ruby's Lookahead for a field resolved by a class (a `Resolvers::`
|
|
80
|
+
class, `@field`, a type class method). It never reaches the arguments, so `@cache` keys are unchanged.
|
|
81
|
+
- `@throttle` logs a warning when the cache store is a `:null_store`, which counts nothing.
|
|
82
|
+
- `@enum(value:)` for an enum value's internal value; `@namespaced` to group fields; `@throttle` to
|
|
83
|
+
rate-limit a field per user or IP (the controller concern now puts `:remote_ip` in the context);
|
|
84
|
+
`@async` to run a mutation in an ActiveJob and answer `true`; `@hide(env:)` / `@show(env:)` to leave
|
|
85
|
+
fields out by environment (the schema cache key now includes it).
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- Generators build on graphql-ruby's instead of copying them. `lighthouse:install` runs
|
|
90
|
+
`graphql:install --no-relay --skip-graphiql --skip-query-logs` for the base classes every
|
|
91
|
+
graphql-ruby app has (`Types::Base*`, `Resolvers::BaseResolver`, `Mutations::BaseMutation`), then
|
|
92
|
+
replaces what it wrote that an SDL-first app does differently: the schema class (built from the
|
|
93
|
+
SDL, naming every base in `base_types`), the controller, no `Types::QueryType` /
|
|
94
|
+
`MutationType`, and a `BaseMutation` that is a `GraphQL::Schema::Mutation`. It writes
|
|
95
|
+
`Subscriptions::BaseSubscription` itself, graphql-ruby having none. Files the app had are kept.
|
|
96
|
+
- `docs/generators.md`: every generator, and which of graphql-ruby's fit a Lighthouse app
|
|
97
|
+
(`graphql:object`, `:scalar`, `:union`, `:interface` for type classes; `graphql:mutation` and
|
|
98
|
+
`graphql:detailed_trace` not as they are).
|
|
99
|
+
|
|
100
|
+
- `@field(resolver:)` on a field of a type other than `Query` and `Mutation` looks the class up in
|
|
101
|
+
the resolvers namespace first, nested names included: `@field(resolver: "Posts::TagsCount")`
|
|
102
|
+
resolves to `Resolvers::Posts::TagsCount`. Then it tries `resolver_namespaces`, as before, so
|
|
103
|
+
full names and classes outside `Resolvers::` resolve as they did. A class `Resolvers::X` now
|
|
104
|
+
wins over one that `resolver_namespaces` would find for the same name.
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- A wrapping directive (`@globalId`, `@guard`, `@cache`, ...) on a field of a type whose objects are
|
|
109
|
+
Hashes read nothing: the wrapped default reader only called methods. It now reads the Hash's
|
|
110
|
+
keys, as the default reader does.
|
|
111
|
+
|
|
112
|
+
- A field returning a union or an interface no longer raises `undefined method 'resolve_type' for
|
|
113
|
+
an instance of Proc`. Lighthouse's field dispatcher replaced the schema's default resolve with a
|
|
114
|
+
Proc, which graphql-ruby also asks for `resolve_type` and scalar coercion; it now hands both on.
|
|
115
|
+
|
|
116
|
+
- `@field(resolver:)` on a `Query` or `Mutation` field finds a nested name in the resolvers /
|
|
117
|
+
mutations namespaces, as documented: `"Feeds::Home"` resolves to `Resolvers::Feeds::Home`,
|
|
118
|
+
then to `Feeds::Home`. It used to try only `Feeds::Home`, so the field resolved to `null`.
|
|
119
|
+
The field's `complexity` is looked up the same way.
|
|
120
|
+
|
|
121
|
+
### Documentation
|
|
122
|
+
|
|
123
|
+
- `docs/resolvers.md`: fields of types other than `Query` and `Mutation` resolved with `@field`,
|
|
124
|
+
whose class receives the parent record as `object`. The example app resolves `Post.tagsCount`
|
|
125
|
+
(batched through the dataloader), `Post.excerpt(length:)` and `Query.popularTags` this way.
|
|
126
|
+
It resolves `Post.readingTime` and the `ReadingTime` type, which is not a model, with
|
|
127
|
+
`Types::PostType` and `Types::ReadingTimeType`, `Query.search` (a union of `Post` and `User`)
|
|
128
|
+
and `Query.latestActivity` (an `Authored` interface) by the default, `DateTime` with
|
|
129
|
+
`Types::DateTimeType`, and `Post.summary(words:)` with `@method`. `Tag` is a Relay node with
|
|
130
|
+
global ids; `User.latestPostTitle` uses `@with`, `Tag.usage` `@withCount`, `User.posts`
|
|
131
|
+
`@lazyLoad`, `Query.counts` `@field(resolver: "Statistics@counts")`, and `Query.myPosts`
|
|
132
|
+
`resolve_info.enhance_builder`. `Mutation.updateAvatar` takes a multipart upload with `@upload`
|
|
133
|
+
(the example now loads ActiveStorage). `Mutation.schedulePublish` is `@async` on Solid Queue,
|
|
134
|
+
`Query.searchPosts` is `@throttle`d, and `Query.posts` filters `metadata` with `@whereJsonContains`;
|
|
135
|
+
the example's suite runs on PostgreSQL and MySQL too (`DATABASE_URL`, a new CI job), with Redis.
|
|
136
|
+
|
|
137
|
+
### Removed
|
|
138
|
+
|
|
139
|
+
- `rails g lighthouse:type`: it was graphql-ruby's `graphql:object` (and `:scalar`, `:union`,
|
|
140
|
+
`:interface`) with an `include`. Generate the class with graphql-ruby, then add
|
|
141
|
+
`include Lighthouse::GraphQL::Type`.
|
|
142
|
+
- `lighthouse:query` and `lighthouse:mutation` no longer write `Resolvers::BaseResolver` /
|
|
143
|
+
`Mutations::BaseMutation` when they are missing: graphql-ruby's install writes them, which
|
|
144
|
+
`lighthouse:install` runs. They say so instead.
|
|
145
|
+
|
|
146
|
+
## [0.4.0] - 2026-10-17
|
|
147
|
+
|
|
148
|
+
### Added
|
|
149
|
+
|
|
150
|
+
- `@complexity` sets what a field costs in graphql-ruby's query complexity analysis, so
|
|
151
|
+
`AppSchema.max_complexity(n)` refuses expensive queries before they run. A bare `@complexity`
|
|
152
|
+
costs `1 + child complexity × first`. `@complexity(resolver: "Name")` names a
|
|
153
|
+
`GraphQL::Analysis::Analyzer` subclass in the new `analyzers` namespace (`Analyzers::Name`),
|
|
154
|
+
which Lighthouse installs on the schema. Including `Lighthouse::GraphQL::ComplexityAnalyzer`
|
|
155
|
+
gives the analyzer a `complexity` (a number, or a proc of `context, args, child_complexity`,
|
|
156
|
+
as graphql-ruby's) that is the cost of the fields naming it.
|
|
157
|
+
- A root field resolved by a `GraphQL::Schema::Resolver` class, by convention or through `@field`,
|
|
158
|
+
takes the `complexity` that class declares, as graphql-ruby's `field ..., resolver:` does.
|
|
159
|
+
- `BuildExtensionsResponse#query_complexity` holds the query's complexity score while something
|
|
160
|
+
listens to the event. It used to be `nil`.
|
|
161
|
+
- `docs/security.md`: query complexity, depth and introspection limits.
|
|
162
|
+
- graphql-ruby's base classes: `rails g lighthouse:install` writes `Resolvers::BaseResolver`,
|
|
163
|
+
`Mutations::BaseMutation` and `Subscriptions::BaseSubscription` (which includes
|
|
164
|
+
`Lighthouse::GraphQL::Subscription`), and `lighthouse:query`, `:mutation` and `:subscription`
|
|
165
|
+
write the one they need when it is missing. Generated classes inherit from them. An existing
|
|
166
|
+
base, such as the ones graphql-ruby's `graphql:install` writes, is never replaced.
|
|
167
|
+
- `get_schema`'s `base_types` takes `resolver:`, `mutation:` and `subscription:` beside
|
|
168
|
+
graphql-ruby's own keys. Every class a `Query`, `Mutation` or `Subscription` field resolves to
|
|
169
|
+
must then inherit from its kind's base (`Lighthouse::DefinitionError` otherwise), and a
|
|
170
|
+
`Subscription` field with no class of its own resolves with the subscription base. The
|
|
171
|
+
generated schema class names the three.
|
|
172
|
+
|
|
173
|
+
### Changed
|
|
174
|
+
|
|
175
|
+
- The classes of `Query` fields are looked up in `Resolvers` (`app/graphql/resolvers`), graphql-ruby's
|
|
176
|
+
convention, instead of `Queries`: `Query.latestPosts` resolves to `Resolvers::LatestPosts`, and
|
|
177
|
+
`lighthouse:query` writes there. To keep `app/graphql/queries`, set
|
|
178
|
+
`config.namespaces = { queries: ['Queries'] }`.
|
|
179
|
+
- `lighthouse:subscription` no longer takes `--lighthouse`. The class inherits from
|
|
180
|
+
`Subscriptions::BaseSubscription`, which includes the concern, and gets its hooks; under a base
|
|
181
|
+
without the concern, it gets graphql-ruby's `authorized?` and `update`.
|
|
182
|
+
|
|
183
|
+
- Paginated fields (`@paginate`, and relationship directives given `type:`) cost
|
|
184
|
+
`1 + child complexity × first` in the complexity score. A page of 1,000 items now scores
|
|
185
|
+
1,000 times one item's cost; before, a paginator scored the same whatever `first` was.
|
|
186
|
+
A schema that already sets `max_complexity` may need a higher limit.
|
|
187
|
+
|
|
9
188
|
## [0.3.0] - 2026-10-17
|
|
10
189
|
|
|
11
190
|
### Added
|
|
@@ -282,7 +461,9 @@ had been running inside a private monorepo as a path gem before being extracted.
|
|
|
282
461
|
- Config-driven extension seams: `Lighthouse.configure`, `resolver_namespaces`,
|
|
283
462
|
`on_error`, and `DirectiveRegistry.register` for app-defined directives.
|
|
284
463
|
|
|
285
|
-
[Unreleased]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.
|
|
464
|
+
[Unreleased]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.5.0...HEAD
|
|
465
|
+
[0.5.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.4.0...v0.5.0
|
|
466
|
+
[0.4.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.3.0...v0.4.0
|
|
286
467
|
[0.3.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.2.1...v0.3.0
|
|
287
468
|
[0.2.1]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.2.0...v0.2.1
|
|
288
469
|
[0.2.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.1.1...v0.2.0
|
data/README.md
CHANGED
|
@@ -78,10 +78,13 @@ Then let the generator set the app up:
|
|
|
78
78
|
bin/rails generate lighthouse:install
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
It
|
|
82
|
-
|
|
83
|
-
`graphql.
|
|
84
|
-
|
|
81
|
+
It runs graphql-ruby's own `graphql:install` for the base classes every graphql-ruby
|
|
82
|
+
app has, then writes Lighthouse's on top: the initializer, a starter schema in
|
|
83
|
+
`graphql/schema.graphql`, the `AppSchema` class built from it, a `GraphqlController`
|
|
84
|
+
with its `POST /graphql` route, and a `graphql.config.yml` for your IDE. Run
|
|
85
|
+
`bin/rails generate lighthouse:install --help` for its options (`--schema`,
|
|
86
|
+
`--sdl-folder`, `--skip-controller`, `--skip-ide`), and see
|
|
87
|
+
[docs/generators.md](docs/generators.md) for every generator, graphql-ruby's included.
|
|
85
88
|
|
|
86
89
|
### IDE support
|
|
87
90
|
|
|
@@ -127,7 +130,14 @@ Prefer to wire it by hand? These are the steps the generator performs.
|
|
|
127
130
|
|
|
128
131
|
AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
129
132
|
sdl_folder: Rails.root.join('graphql').to_s,
|
|
130
|
-
options: {
|
|
133
|
+
options: {
|
|
134
|
+
base_types: {
|
|
135
|
+
resolver: Resolvers::BaseResolver, # graphql-ruby's base classes, which
|
|
136
|
+
mutation: Mutations::BaseMutation, # rails g lighthouse:install has graphql-ruby write
|
|
137
|
+
subscription: Subscriptions::BaseSubscription
|
|
138
|
+
# object: Types::BaseObject, ...
|
|
139
|
+
}
|
|
140
|
+
}
|
|
131
141
|
)
|
|
132
142
|
```
|
|
133
143
|
|
|
@@ -143,7 +153,8 @@ including base types and configuration.
|
|
|
143
153
|
### Example app
|
|
144
154
|
|
|
145
155
|
[`example/`](example) is a small blog API built with Lighthouse on Rails 8.1. It shows
|
|
146
|
-
the schema, the generated `
|
|
156
|
+
the schema, the generated `Resolvers::`/`Mutations::`/`Subscriptions::`/`Builders::`/`Validators::` classes
|
|
157
|
+
under graphql-ruby's base classes,
|
|
147
158
|
Pundit policies and the opt-in performance features working together, and its specs
|
|
148
159
|
are the library's end-to-end suite. See [example/README.md](example/README.md).
|
|
149
160
|
|
|
@@ -161,7 +172,7 @@ or as Markdown in [`docs/`](docs):
|
|
|
161
172
|
| [Directives reference](docs/directives.md) | Every built-in directive, grouped by purpose |
|
|
162
173
|
| [Relationships](docs/relationships.md) | `@hasMany`, `@belongsTo`, `@belongsToMany`, `@hasOne`, the `@morph*` variants, batching and `type:` pagination |
|
|
163
174
|
| [Filtering & ordering](docs/filtering-and-ordering.md) | `@eq`/`@where`/`@in`/`@like`, `@whereConditions`, `@orderBy` |
|
|
164
|
-
| [
|
|
175
|
+
| [Resolvers, mutations & builders](docs/resolvers.md) | `Resolvers::` / `Mutations::` classes and their base classes (graphql-ruby's contract), builders, generators |
|
|
165
176
|
| [Mutations](docs/mutations.md) | `@create`, `@update`, `@upsert`, `@delete` (and `*Many`), nested mutations on every kind of association, `@spread`, `@inject` |
|
|
166
177
|
| [Validation](docs/validation.md) | Model validations, `@validator` with Dry::Validation contracts |
|
|
167
178
|
| [Authentication & authorization](docs/authorization.md) | `@guard`, `@canFind` / `@canModel` / `@canQuery` / `@canResolved` / `@canRoot` (Pundit by default) |
|
data/docs/best-practices.md
CHANGED
|
@@ -44,12 +44,22 @@ Use `@guard` and the `@can*` directives on fields that require them. They wrap t
|
|
|
44
44
|
resolver, so the check runs before any data is loaded (only `@canResolved`, which
|
|
45
45
|
needs the result, runs after — keep it to fields that don't write).
|
|
46
46
|
|
|
47
|
-
## Keep
|
|
48
|
-
|
|
49
|
-
A custom `Query` field is a `
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
contracts;
|
|
47
|
+
## Keep resolvers, mutations and builders apart
|
|
48
|
+
|
|
49
|
+
A custom `Query` field is a `Resolvers::BaseResolver` in `app/graphql/resolvers`, a
|
|
50
|
+
`Mutation` field a `Mutations::BaseMutation` in `app/graphql/mutations`, and a query
|
|
51
|
+
refinement a builder in `app/graphql/builders`. They have different jobs and
|
|
52
|
+
contracts; keep each kind in its own folder. See [resolvers.md](resolvers.md).
|
|
53
|
+
|
|
54
|
+
## Share through the base classes
|
|
55
|
+
|
|
56
|
+
What every resolver, mutation or subscription needs (a concern, a helper,
|
|
57
|
+
graphql-ruby's `argument_class`) goes in `Resolvers::BaseResolver`,
|
|
58
|
+
`Mutations::BaseMutation` or `Subscriptions::BaseSubscription`, once, as in any
|
|
59
|
+
graphql-ruby app. Name them in the schema's `base_types` (`resolver:`, `mutation:`,
|
|
60
|
+
`subscription:`), and a class that forgets to inherit from its base is refused
|
|
61
|
+
instead of quietly missing what it shares. See
|
|
62
|
+
[resolvers.md](resolvers.md#base-classes).
|
|
53
63
|
|
|
54
64
|
## Validate in the model first
|
|
55
65
|
|
data/docs/configuration.md
CHANGED
|
@@ -10,8 +10,8 @@ require 'lighthouse-graphql'
|
|
|
10
10
|
|
|
11
11
|
Lighthouse.configure do |config|
|
|
12
12
|
config.sdl_folder = Rails.root.join('graphql').to_s
|
|
13
|
-
config.namespaces = { queries: ['
|
|
14
|
-
config.resolver_namespaces = ['App::Graphql'
|
|
13
|
+
config.namespaces = { queries: ['Resolvers'], mutations: ['Mutations'] }
|
|
14
|
+
config.resolver_namespaces = ['App::Graphql']
|
|
15
15
|
config.on_error = ->(error, _context) { Rails.error.report(error) }
|
|
16
16
|
end
|
|
17
17
|
```
|
|
@@ -31,23 +31,26 @@ different contracts, so they live apart ([resolvers.md](resolvers.md)):
|
|
|
31
31
|
|
|
32
32
|
| Kind | Default | Holds |
|
|
33
33
|
| --- | --- | --- |
|
|
34
|
-
| `queries` | `['
|
|
35
|
-
| `mutations` | `['Mutations']` | `GraphQL::Schema::Mutation`
|
|
34
|
+
| `queries` | `['Resolvers']` | `Resolvers::BaseResolver` classes (a `GraphQL::Schema::Resolver`) for `Query` fields. `['Queries']` before 0.4.0 |
|
|
35
|
+
| `mutations` | `['Mutations']` | `Mutations::BaseMutation` classes (a `GraphQL::Schema::Mutation`) for `Mutation` fields |
|
|
36
36
|
| `builders` | `['Builders']` | `@builder` classes |
|
|
37
37
|
| `validators` | `['Validators']` | `@validator` contracts |
|
|
38
38
|
| `events` | `['Events']` | Classes `@event(dispatch:)` names ([events.md](events.md)) |
|
|
39
|
-
| `subscriptions` | `['Subscriptions']` | `GraphQL::Schema::Subscription
|
|
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
|
+
| `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 |
|
|
40
42
|
|
|
41
43
|
```ruby
|
|
42
|
-
config.namespaces = { queries: ['Api::
|
|
44
|
+
config.namespaces = { queries: ['Api::Resolvers'] } # the other kinds keep their defaults
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
### `resolver_namespaces`
|
|
46
48
|
|
|
47
49
|
An ordered list of namespaces tried for **models** named by a directive
|
|
48
50
|
(`@all(model: "User")`) and for `@field(resolver:)` on types other than `Query`
|
|
49
|
-
and `Mutation
|
|
50
|
-
|
|
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']`.
|
|
51
54
|
|
|
52
55
|
```ruby
|
|
53
56
|
config.resolver_namespaces = ['App::Graphql', 'Resolvers']
|
|
@@ -114,6 +117,28 @@ initializer.
|
|
|
114
117
|
config.convert_empty_strings_to_null = true
|
|
115
118
|
```
|
|
116
119
|
|
|
120
|
+
### `global_id`
|
|
121
|
+
|
|
122
|
+
Encodes and decodes Relay global ids for [`@globalId` and `@node`](relay.md): any
|
|
123
|
+
object with `encode(type, id)` returning the global id, and `decode(global_id)`
|
|
124
|
+
returning `[type, id]`. Defaults to `Lighthouse::GraphQL::GlobalId`, the base64 of
|
|
125
|
+
`Type:id`, as PHP Lighthouse's.
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
config.global_id = SignedGlobalIds
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `upload_store`
|
|
132
|
+
|
|
133
|
+
Stores the file an [`@upload`](uploads.md) argument carries: any object with
|
|
134
|
+
`call(file, disk:, path:, public:)`, whose return value the argument then holds.
|
|
135
|
+
Defaults to `Lighthouse::GraphQL::UploadStore`, which makes an ActiveStorage blob and
|
|
136
|
+
returns its signed id.
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
config.upload_store = ->(file, path: '/', **) { S3Uploader.put(file, prefix: path) }
|
|
140
|
+
```
|
|
141
|
+
|
|
117
142
|
### `queue_broadcasts`, `broadcast_queue`
|
|
118
143
|
|
|
119
144
|
Whether [`@broadcast`](directives.md#broadcast) triggers its subscription in an
|