lighthouse-graphql 0.2.1 → 0.4.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 +99 -1
- data/README.md +22 -3
- data/docs/best-practices.md +16 -6
- data/docs/caching.md +1 -1
- data/docs/configuration.md +74 -5
- data/docs/custom-directives.md +11 -1
- data/docs/directives.md +333 -2
- data/docs/events.md +203 -0
- data/docs/federation.md +3 -2
- data/docs/getting-started.md +90 -23
- data/docs/motivation.md +3 -2
- data/docs/mutations.md +15 -0
- data/docs/resolvers.md +95 -19
- data/docs/security.md +210 -0
- data/docs/subscriptions.md +446 -0
- data/lib/generators/lighthouse/base_classes.rb +87 -0
- data/lib/generators/lighthouse/class_generator.rb +19 -8
- data/lib/generators/lighthouse/install/install_generator.rb +23 -6
- data/lib/generators/lighthouse/install/templates/graphql_controller.rb.tt +7 -45
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +34 -5
- data/lib/generators/lighthouse/install/templates/schema.rb.tt +14 -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 +100 -0
- data/lib/lighthouse/broadcast_job.rb +14 -0
- data/lib/lighthouse/broadcasting.rb +143 -0
- data/lib/lighthouse/cache_control.rb +5 -2
- data/lib/lighthouse/complexity.rb +174 -0
- data/lib/lighthouse/complexity_analyzer.rb +56 -0
- data/lib/lighthouse/config.rb +56 -14
- data/lib/lighthouse/controller.rb +131 -0
- data/lib/lighthouse/directive_resolvers/broadcast_resolver.rb +31 -0
- data/lib/lighthouse/directive_resolvers/convention_resolver.rb +14 -4
- data/lib/lighthouse/directive_resolvers/convert_empty_strings_resolver.rb +134 -0
- data/lib/lighthouse/directive_resolvers/event_resolver.rb +37 -0
- data/lib/lighthouse/directive_resolvers/model_mutation_resolver.rb +28 -13
- data/lib/lighthouse/directive_resolvers/soft_delete_resolvers.rb +66 -0
- data/lib/lighthouse/directive_resolvers/subscription_resolver.rb +76 -0
- data/lib/lighthouse/directives/arguments/soft_deletes.rb +112 -0
- data/lib/lighthouse/directives/registry.rb +15 -6
- data/lib/lighthouse/directives/soft_deletes_directive.rb +123 -0
- data/lib/lighthouse/events/execution_trace.rb +52 -0
- data/lib/lighthouse/events/lifecycle.rb +148 -0
- data/lib/lighthouse/events/schema_build.rb +44 -0
- data/lib/lighthouse/events.rb +76 -0
- data/lib/lighthouse/feature_directive.rb +137 -0
- data/lib/lighthouse/field_resolver_stack.rb +28 -7
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/namespaces.rb +1 -0
- data/lib/lighthouse/persisted_queries/batch.rb +34 -0
- data/lib/lighthouse/persisted_queries.rb +17 -4
- data/lib/lighthouse/rb_lighthouse.rb +23 -0
- data/lib/lighthouse/resolver_bases.rb +61 -0
- data/lib/lighthouse/schema_cache.rb +12 -5
- data/lib/lighthouse/schema_factory.rb +0 -1
- data/lib/lighthouse/schema_generator.rb +42 -9
- data/lib/lighthouse/sdl_loader.rb +0 -2
- data/lib/lighthouse/subscription.rb +120 -0
- data/lib/lighthouse/support/model_resolver.rb +1 -1
- data/lib/lighthouse/support/soft_deletes.rb +164 -0
- data/lib/lighthouse/tasks/lighthouse.rake +10 -0
- data/sig/lighthouse/controller.rbs +30 -0
- data/sig/lighthouse/events.rbs +122 -0
- data/sig/lighthouse/graphql.rbs +62 -0
- data/sig/lighthouse.rbs +52 -1
- metadata +29 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a15bb4d92716a08d5d248c5155b9cf336407a7988fed6e00c825b07bea4a206b
|
|
4
|
+
data.tar.gz: 421fca84187c5030f38c2cdd07ed26f9909747944f788cfd9662e67b6e322e2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 388112925823baa63d0a7a4436bb1df4511f4a4c13b01001dd72b351156b78982ca4eab136a131c9c25b997915bff26099fa2a485bc4df74bfef390138e37c3a
|
|
7
|
+
data.tar.gz: e2f90e9654cfcdc2d26e084f68476ac59f56eb708683b3b4817cfc41006843363feb0f9a08b838bdcdd888395bc8f13ae29394d28b0bc37566c26dad63657cac
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,102 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-10-17
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `@complexity` sets what a field costs in graphql-ruby's query complexity analysis, so
|
|
14
|
+
`AppSchema.max_complexity(n)` refuses expensive queries before they run. A bare `@complexity`
|
|
15
|
+
costs `1 + child complexity × first`. `@complexity(resolver: "Name")` names a
|
|
16
|
+
`GraphQL::Analysis::Analyzer` subclass in the new `analyzers` namespace (`Analyzers::Name`),
|
|
17
|
+
which Lighthouse installs on the schema. Including `Lighthouse::GraphQL::ComplexityAnalyzer`
|
|
18
|
+
gives the analyzer a `complexity` (a number, or a proc of `context, args, child_complexity`,
|
|
19
|
+
as graphql-ruby's) that is the cost of the fields naming it.
|
|
20
|
+
- A root field resolved by a `GraphQL::Schema::Resolver` class, by convention or through `@field`,
|
|
21
|
+
takes the `complexity` that class declares, as graphql-ruby's `field ..., resolver:` does.
|
|
22
|
+
- `BuildExtensionsResponse#query_complexity` holds the query's complexity score while something
|
|
23
|
+
listens to the event. It used to be `nil`.
|
|
24
|
+
- `docs/security.md`: query complexity, depth and introspection limits.
|
|
25
|
+
- graphql-ruby's base classes: `rails g lighthouse:install` writes `Resolvers::BaseResolver`,
|
|
26
|
+
`Mutations::BaseMutation` and `Subscriptions::BaseSubscription` (which includes
|
|
27
|
+
`Lighthouse::GraphQL::Subscription`), and `lighthouse:query`, `:mutation` and `:subscription`
|
|
28
|
+
write the one they need when it is missing. Generated classes inherit from them. An existing
|
|
29
|
+
base, such as the ones graphql-ruby's `graphql:install` writes, is never replaced.
|
|
30
|
+
- `get_schema`'s `base_types` takes `resolver:`, `mutation:` and `subscription:` beside
|
|
31
|
+
graphql-ruby's own keys. Every class a `Query`, `Mutation` or `Subscription` field resolves to
|
|
32
|
+
must then inherit from its kind's base (`Lighthouse::DefinitionError` otherwise), and a
|
|
33
|
+
`Subscription` field with no class of its own resolves with the subscription base. The
|
|
34
|
+
generated schema class names the three.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- The classes of `Query` fields are looked up in `Resolvers` (`app/graphql/resolvers`), graphql-ruby's
|
|
39
|
+
convention, instead of `Queries`: `Query.latestPosts` resolves to `Resolvers::LatestPosts`, and
|
|
40
|
+
`lighthouse:query` writes there. To keep `app/graphql/queries`, set
|
|
41
|
+
`config.namespaces = { queries: ['Queries'] }`.
|
|
42
|
+
- `lighthouse:subscription` no longer takes `--lighthouse`. The class inherits from
|
|
43
|
+
`Subscriptions::BaseSubscription`, which includes the concern, and gets its hooks; under a base
|
|
44
|
+
without the concern, it gets graphql-ruby's `authorized?` and `update`.
|
|
45
|
+
|
|
46
|
+
- Paginated fields (`@paginate`, and relationship directives given `type:`) cost
|
|
47
|
+
`1 + child complexity × first` in the complexity score. A page of 1,000 items now scores
|
|
48
|
+
1,000 times one item's cost; before, a paginator scored the same whatever `first` was.
|
|
49
|
+
A schema that already sets `max_complexity` may need a higher limit.
|
|
50
|
+
|
|
51
|
+
## [0.3.0] - 2026-10-17
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- `Lighthouse::GraphQL::Controller`, a Rails controller concern: `include` it, name the schema
|
|
56
|
+
with `lighthouse_schema AppSchema`, and its `execute` action runs the operation through
|
|
57
|
+
persisted queries, batches a JSON array body, reads the variables, sets the Cache-Control
|
|
58
|
+
header and renders the result. Its private hooks (`graphql_context`, `graphql_variables`,
|
|
59
|
+
`graphql_schema`) adjust it, and `lighthouse_request { }` gives an action of your own the
|
|
60
|
+
request events. The generated `GraphqlController` and the example app now use it.
|
|
61
|
+
- Events, as PHP Lighthouse dispatches them (`docs/events.md`): `Lighthouse::Events.listen`
|
|
62
|
+
for `StartRequest`, `StartOperationOrOperations`, `BuildSchemaString` (its listeners return SDL to add),
|
|
63
|
+
`RegisterDirectiveNamespaces` (its listeners return namespaces of directive classes to register), `ManipulateAST`,
|
|
64
|
+
`StartExecution`, `BuildExtensionsResponse` (its listeners return an `ExtensionsResponse` for the result's
|
|
65
|
+
`extensions`), `ManipulateResult`, `EndExecution`, `EndOperationOrOperations`, `EndRequest`
|
|
66
|
+
and `ValidateSchema`. Each one is also instrumented through `ActiveSupport::Notifications`
|
|
67
|
+
as `<event>.lighthouse`.
|
|
68
|
+
- `@event(dispatch:)` dispatches an event class, constructed with the field's resolved value,
|
|
69
|
+
once the field resolves. Event classes are looked up in `Config.namespaces[:events]` (`Events`).
|
|
70
|
+
- `PersistedQueries.multiplex` runs a batch of operations.
|
|
71
|
+
- `bin/rails lighthouse:validate_schema` dispatches `ValidateSchema` and builds every type.
|
|
72
|
+
- `@feature(name:, when:)` shows a field or object type only while a feature flag is in that
|
|
73
|
+
state, checked per query through graphql-ruby's visibility. It uses Flipper by default;
|
|
74
|
+
`Config.feature_checker` and `Config.feature_actor` change that.
|
|
75
|
+
- `@convertEmptyStringsToNull` on an argument, input field or field, or on every field with
|
|
76
|
+
`Config.convert_empty_strings_to_null`, turns incoming `""` into `null`.
|
|
77
|
+
- Soft deletes: `@softDeletes` adds a `trashed: Trashed @trashed` argument (`ONLY`, `WITH`,
|
|
78
|
+
`WITHOUT`), and `@restore` and `@forceDelete` restore or permanently delete the soft-deleted models
|
|
79
|
+
their filters select. They work with paranoia, discard or a plain `deleted_at` column,
|
|
80
|
+
detected per model, or with an adapter set in `Config.soft_deletes`.
|
|
81
|
+
- `@withoutGlobalScopes(names:)` lifts a model's default scope conditions when the client passes
|
|
82
|
+
`true`. Each name is a `without_<name>` scope or a column.
|
|
83
|
+
- Subscriptions (`docs/subscriptions.md`), on graphql-ruby's subscriptions: `Subscription`
|
|
84
|
+
fields resolve to plain `GraphQL::Schema::Subscription` classes in `Subscriptions::` by convention,
|
|
85
|
+
or to the class `@subscription(class:)` names, or work with no class at all.
|
|
86
|
+
`@broadcast(subscription:, shouldQueue:)` triggers a subscription once a mutation succeeds,
|
|
87
|
+
inline or in an ActiveJob (`Config.queue_broadcasts`, `Config.broadcast_queue`), with the class's
|
|
88
|
+
`self.trigger_arguments(root, context)` and `trigger_scope`; a plain class with required arguments
|
|
89
|
+
and no `trigger_arguments` (which could never reach a subscriber) is reported as a
|
|
90
|
+
`Lighthouse::DefinitionError`. The optional concern `Lighthouse::GraphQL::Subscription` adds PHP
|
|
91
|
+
Lighthouse's `authorize?`, `filter?` and `resolve_payload` hooks. `rails generate lighthouse:subscription`
|
|
92
|
+
writes a `GraphQL::Schema::Subscription` (`--lighthouse` to include the concern). The example app
|
|
93
|
+
has real ActionCable subscriptions (`GraphqlChannel`) with channel and end-to-end delivery specs.
|
|
94
|
+
- The initializer `rails generate lighthouse:install` writes lists the new settings, and
|
|
95
|
+
`docs/custom-directives.md` shows registering a namespace of directives through
|
|
96
|
+
`RegisterDirectiveNamespaces`.
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- Schemas using `@cacheControl` (or `@feature`) build on graphql 2.5 again. Their arguments
|
|
101
|
+
with a default are now nullable (`maxAge: Int = 0`, not `Int! = 0`), because graphql 2.5 does not
|
|
102
|
+
fill in a directive argument's default and so refused every usage that left one out. CI now
|
|
103
|
+
also runs the suite on graphql 2.5.
|
|
104
|
+
|
|
9
105
|
## [0.2.1] - 2026-10-17
|
|
10
106
|
|
|
11
107
|
### Added
|
|
@@ -228,7 +324,9 @@ had been running inside a private monorepo as a path gem before being extracted.
|
|
|
228
324
|
- Config-driven extension seams: `Lighthouse.configure`, `resolver_namespaces`,
|
|
229
325
|
`on_error`, and `DirectiveRegistry.register` for app-defined directives.
|
|
230
326
|
|
|
231
|
-
[Unreleased]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.
|
|
327
|
+
[Unreleased]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.4.0...HEAD
|
|
328
|
+
[0.4.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.3.0...v0.4.0
|
|
329
|
+
[0.3.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.2.1...v0.3.0
|
|
232
330
|
[0.2.1]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.2.0...v0.2.1
|
|
233
331
|
[0.2.0]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.1.1...v0.2.0
|
|
234
332
|
[0.1.1]: https://github.com/Vaz-Innovation/lighthouse-graphql/compare/v0.1.0...v0.1.1
|
data/README.md
CHANGED
|
@@ -41,6 +41,13 @@ and falls back to a sensible default reader for plain fields. You write Ruby onl
|
|
|
41
41
|
for the genuinely custom parts (`@field(resolver: "...")`), and your schema stays
|
|
42
42
|
the single source of truth. [Motivation](docs/motivation.md) explains the idea in depth.
|
|
43
43
|
|
|
44
|
+
The same directives reach the rest of an API, too:
|
|
45
|
+
- real-time updates: `Subscription` fields on graphql-ruby's subscriptions, triggered
|
|
46
|
+
by a mutation's `@broadcast`;
|
|
47
|
+
- soft deletes (`@softDeletes`, `@restore`, `@forceDelete`) and feature flags (`@feature`);
|
|
48
|
+
- request lifecycle [events](docs/events.md);
|
|
49
|
+
- a controller concern that serves the schema in a few lines.
|
|
50
|
+
|
|
44
51
|
### Inspirations
|
|
45
52
|
|
|
46
53
|
- **[Lighthouse (PHP)](https://lighthouse-php.com)** — the directive vocabulary,
|
|
@@ -62,6 +69,8 @@ gem 'lighthouse-graphql'
|
|
|
62
69
|
|
|
63
70
|
It depends on `graphql ~> 2.5` and `activesupport`. For federation you also need
|
|
64
71
|
`apollo-federation` in your app (it is an optional, app-provided dependency).
|
|
72
|
+
On graphql 2.5, keep the `json` gem below 3: graphql 2.5 calls `JSON.generate`
|
|
73
|
+
with an option json 3 removed (graphql 2.6 no longer does).
|
|
65
74
|
|
|
66
75
|
Then let the generator set the app up:
|
|
67
76
|
|
|
@@ -118,7 +127,14 @@ Prefer to wire it by hand? These are the steps the generator performs.
|
|
|
118
127
|
|
|
119
128
|
AppSchema = Lighthouse::GraphQL::RbLightHouse.get_schema(
|
|
120
129
|
sdl_folder: Rails.root.join('graphql').to_s,
|
|
121
|
-
options: {
|
|
130
|
+
options: {
|
|
131
|
+
base_types: {
|
|
132
|
+
resolver: Resolvers::BaseResolver, # graphql-ruby's base classes, which
|
|
133
|
+
mutation: Mutations::BaseMutation, # rails g lighthouse:install writes
|
|
134
|
+
subscription: Subscriptions::BaseSubscription
|
|
135
|
+
# object: Types::BaseObject, ...
|
|
136
|
+
}
|
|
137
|
+
}
|
|
122
138
|
)
|
|
123
139
|
```
|
|
124
140
|
|
|
@@ -134,7 +150,8 @@ including base types and configuration.
|
|
|
134
150
|
### Example app
|
|
135
151
|
|
|
136
152
|
[`example/`](example) is a small blog API built with Lighthouse on Rails 8.1. It shows
|
|
137
|
-
the schema, the generated `
|
|
153
|
+
the schema, the generated `Resolvers::`/`Mutations::`/`Subscriptions::`/`Builders::`/`Validators::` classes
|
|
154
|
+
under graphql-ruby's base classes,
|
|
138
155
|
Pundit policies and the opt-in performance features working together, and its specs
|
|
139
156
|
are the library's end-to-end suite. See [example/README.md](example/README.md).
|
|
140
157
|
|
|
@@ -152,11 +169,13 @@ or as Markdown in [`docs/`](docs):
|
|
|
152
169
|
| [Directives reference](docs/directives.md) | Every built-in directive, grouped by purpose |
|
|
153
170
|
| [Relationships](docs/relationships.md) | `@hasMany`, `@belongsTo`, `@belongsToMany`, `@hasOne`, the `@morph*` variants, batching and `type:` pagination |
|
|
154
171
|
| [Filtering & ordering](docs/filtering-and-ordering.md) | `@eq`/`@where`/`@in`/`@like`, `@whereConditions`, `@orderBy` |
|
|
155
|
-
| [
|
|
172
|
+
| [Resolvers, mutations & builders](docs/resolvers.md) | `Resolvers::` / `Mutations::` classes and their base classes (graphql-ruby's contract), builders, generators |
|
|
156
173
|
| [Mutations](docs/mutations.md) | `@create`, `@update`, `@upsert`, `@delete` (and `*Many`), nested mutations on every kind of association, `@spread`, `@inject` |
|
|
157
174
|
| [Validation](docs/validation.md) | Model validations, `@validator` with Dry::Validation contracts |
|
|
158
175
|
| [Authentication & authorization](docs/authorization.md) | `@guard`, `@canFind` / `@canModel` / `@canQuery` / `@canResolved` / `@canRoot` (Pundit by default) |
|
|
159
176
|
| [Performance at scale](docs/performance.md) | Opt-in schema cache, SDL reloading in development, persisted queries (APQ) and trusted-document allowlists |
|
|
177
|
+
| [Events](docs/events.md) | Request, schema-build and execution events, `@event`, the controller concern |
|
|
178
|
+
| [Subscriptions](docs/subscriptions.md) | `Subscription` fields, `@subscription`, `@broadcast`, filtering and queued broadcasts |
|
|
160
179
|
| [Caching](docs/caching.md) | `@cache`, `@cacheKey`, `@clearCache` (Rails.cache), `@cacheControl` (HTTP) |
|
|
161
180
|
| [Apollo Federation](docs/federation.md) | `@key`, `resolve_reference`, the `ReferenceResolver` registry |
|
|
162
181
|
| [Custom directives](docs/custom-directives.md) | The contracts, the registry, writing your own directive |
|
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/caching.md
CHANGED
|
@@ -99,7 +99,7 @@ Each cached read costs one `read_multi` for the tokens.
|
|
|
99
99
|
## `@cacheControl`
|
|
100
100
|
|
|
101
101
|
```graphql
|
|
102
|
-
directive @cacheControl(maxAge: Int
|
|
102
|
+
directive @cacheControl(maxAge: Int = 0, scope: CacheControlScope = PUBLIC, inheritMaxAge: Boolean = false)
|
|
103
103
|
on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
|
|
104
104
|
```
|
|
105
105
|
|
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,13 +31,16 @@ 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
|
+
| `events` | `['Events']` | Classes `@event(dispatch:)` names ([events.md](events.md)) |
|
|
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)) |
|
|
38
41
|
|
|
39
42
|
```ruby
|
|
40
|
-
config.namespaces = { queries: ['Api::
|
|
43
|
+
config.namespaces = { queries: ['Api::Resolvers'] } # the other kinds keep their defaults
|
|
41
44
|
```
|
|
42
45
|
|
|
43
46
|
### `resolver_namespaces`
|
|
@@ -100,17 +103,83 @@ config.persisted_queries_manifest = Rails.root.join('persisted-queries.json') #
|
|
|
100
103
|
config.persisted_queries_only = Rails.env.production? # run nothing else
|
|
101
104
|
```
|
|
102
105
|
|
|
106
|
+
### `convert_empty_strings_to_null`
|
|
107
|
+
|
|
108
|
+
Makes every field behave as if it carried
|
|
109
|
+
[`@convertEmptyStringsToNull`](directives.md#convertemptystringstonull): empty
|
|
110
|
+
strings sent for nullable inputs arrive as `nil`, non-null inputs unchanged.
|
|
111
|
+
Defaults to `false`. It is read when the schema is built, so set it in the
|
|
112
|
+
initializer.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
config.convert_empty_strings_to_null = true
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `queue_broadcasts`, `broadcast_queue`
|
|
119
|
+
|
|
120
|
+
Whether [`@broadcast`](directives.md#broadcast) triggers its subscription in an
|
|
121
|
+
ActiveJob (`Lighthouse::GraphQL::BroadcastJob`, needs the `activejob` gem)
|
|
122
|
+
instead of inside the mutation's request, and the queue it goes to. A directive's
|
|
123
|
+
`shouldQueue:` overrides `queue_broadcasts`. Defaults: `false`, and `nil` (the
|
|
124
|
+
default queue of ActiveJob). See [subscriptions.md](subscriptions.md#queueing).
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
config.queue_broadcasts = true
|
|
128
|
+
config.broadcast_queue = :subscriptions
|
|
129
|
+
```
|
|
130
|
+
|
|
103
131
|
### `transactional_mutations`
|
|
104
132
|
|
|
105
133
|
Whether `@create` / `@update` / `@createMany` / `@updateMany` run in a database
|
|
106
134
|
transaction. Defaults to `true`.
|
|
107
135
|
|
|
136
|
+
### `soft_deletes`
|
|
137
|
+
|
|
138
|
+
What "trashed" means, for `@softDeletes`, `@trashed`, `@restore`, `@forceDelete`
|
|
139
|
+
and `@withoutGlobalScopes`. By default each model is inspected: paranoia
|
|
140
|
+
(`acts_as_paranoid`), then discard (`Discard::Model`), then a `deleted_at` column.
|
|
141
|
+
Set it for another column, or a scheme of your own:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Another column, for models without a gem:
|
|
145
|
+
config.soft_deletes = Lighthouse::Support::SoftDeletes::Column.new(:archived_at)
|
|
146
|
+
|
|
147
|
+
# Any object answering these (relations in, relations out; records in, truthy out):
|
|
148
|
+
class SoftDeleteAdapter
|
|
149
|
+
def soft_deletes?(model) = model.include?(Archivable)
|
|
150
|
+
def only_trashed(relation) = relation.unscope(where: :archived_at).where.not(archived_at: nil)
|
|
151
|
+
def with_trashed(relation) = relation.unscope(where: :archived_at)
|
|
152
|
+
def without_trashed(relation) = relation.unscope(where: :archived_at).where(archived_at: nil)
|
|
153
|
+
def restore(record) = record.unarchive
|
|
154
|
+
def force_delete(record) = record.destroy
|
|
155
|
+
end
|
|
156
|
+
config.soft_deletes = SoftDeleteAdapter.new
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
See [Soft deletes & global scopes](directives.md#soft-deletes-global-scopes).
|
|
160
|
+
|
|
108
161
|
### `authenticated_user`, `policy_user`, `authorizer`
|
|
109
162
|
|
|
110
163
|
The authentication and authorization seams behind `@guard` and the `@can*`
|
|
111
164
|
family. The authorizer is `->(user, record, ability, args = [])`, Pundit by
|
|
112
165
|
default. See [authorization.md](authorization.md#configuration).
|
|
113
166
|
|
|
167
|
+
### `feature_checker`, `feature_actor`
|
|
168
|
+
|
|
169
|
+
The seams behind [`@feature`](directives.md#feature). The checker is
|
|
170
|
+
`->(name, actor, ctx) { true/false }`, called with the feature's name as written
|
|
171
|
+
in the SDL; by default it asks Flipper, `Flipper.enabled?(name.to_sym, actor)`,
|
|
172
|
+
and raises a clear error when Flipper is not loaded. The actor is
|
|
173
|
+
`->(ctx) { actor }`; by default it is the authenticated user (a Flipper actor
|
|
174
|
+
answers `flipper_id`), and `nil` checks the feature globally.
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
Lighthouse.configure do |config|
|
|
178
|
+
config.feature_actor = ->(ctx) { ctx[:current_account] }
|
|
179
|
+
config.feature_checker = ->(name, account, _ctx) { account&.features&.include?(name) }
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
114
183
|
## Extending the registry
|
|
115
184
|
|
|
116
185
|
Register app-defined directives (see [custom-directives.md](custom-directives.md))
|
data/docs/custom-directives.md
CHANGED
|
@@ -109,7 +109,17 @@ Lighthouse::DirectiveRegistry.register(App::Graphql::Directives::StartsWith)
|
|
|
109
109
|
Lighthouse::DirectiveRegistry.register(App::Graphql::Directives::UpcaseDirective)
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
To register every directive in a namespace at once, as PHP Lighthouse locates
|
|
113
|
+
directives by namespace, listen to the
|
|
114
|
+
[`RegisterDirectiveNamespaces`](events.md) event. Each class there with a
|
|
115
|
+
`directive_name` that is a resolver or an argument builder gets registered when
|
|
116
|
+
the schema is built:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
Lighthouse::Events.listen(Lighthouse::Events::RegisterDirectiveNamespaces) { 'App::Graphql::Directives' }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Either way, it is now usable in your SDL — no other changes:
|
|
113
123
|
|
|
114
124
|
```graphql
|
|
115
125
|
type Query {
|