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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +182 -1
  3. data/README.md +18 -7
  4. data/docs/best-practices.md +16 -6
  5. data/docs/configuration.md +33 -8
  6. data/docs/directives.md +318 -10
  7. data/docs/events.md +3 -1
  8. data/docs/federation.md +3 -2
  9. data/docs/generators.md +82 -0
  10. data/docs/getting-started.md +66 -32
  11. data/docs/motivation.md +3 -2
  12. data/docs/relationships.md +42 -0
  13. data/docs/relay.md +137 -0
  14. data/docs/resolvers.md +338 -25
  15. data/docs/security.md +227 -0
  16. data/docs/subscriptions.md +60 -37
  17. data/docs/types.md +219 -0
  18. data/docs/uploads.md +80 -0
  19. data/lib/generators/lighthouse/base_classes.rb +70 -0
  20. data/lib/generators/lighthouse/class_generator.rb +19 -8
  21. data/lib/generators/lighthouse/install/install_generator.rb +96 -20
  22. data/lib/generators/lighthouse/install/templates/initializer.rb.tt +19 -10
  23. data/lib/generators/lighthouse/install/templates/schema.rb.tt +21 -2
  24. data/lib/generators/lighthouse/mutation_generator.rb +5 -4
  25. data/lib/generators/lighthouse/query_generator.rb +5 -4
  26. data/lib/generators/lighthouse/subscription_generator.rb +21 -16
  27. data/lib/lighthouse/async_mutation_job.rb +17 -0
  28. data/lib/lighthouse/complexity.rb +175 -0
  29. data/lib/lighthouse/complexity_analyzer.rb +56 -0
  30. data/lib/lighthouse/config.rb +16 -4
  31. data/lib/lighthouse/controller.rb +27 -9
  32. data/lib/lighthouse/dataloader_sources/preload_loader.rb +16 -0
  33. data/lib/lighthouse/dataloader_sources/relation_counts_loader.rb +46 -0
  34. data/lib/lighthouse/default_resolve_dispatcher.rb +26 -0
  35. data/lib/lighthouse/directive_resolvers/all_resolver.rb +1 -1
  36. data/lib/lighthouse/directive_resolvers/argument_binding.rb +61 -0
  37. data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +220 -0
  38. data/lib/lighthouse/directive_resolvers/association_resolvers.rb +2 -2
  39. data/lib/lighthouse/directive_resolvers/async_resolver.rb +90 -0
  40. data/lib/lighthouse/directive_resolvers/base.rb +63 -14
  41. data/lib/lighthouse/directive_resolvers/builder_applier.rb +89 -19
  42. data/lib/lighthouse/directive_resolvers/can_resolvers.rb +1 -1
  43. data/lib/lighthouse/directive_resolvers/convention_resolver.rb +16 -5
  44. data/lib/lighthouse/directive_resolvers/convert_empty_strings_resolver.rb +1 -1
  45. data/lib/lighthouse/directive_resolvers/count_resolver.rb +1 -1
  46. data/lib/lighthouse/directive_resolvers/field_resolver.rb +35 -7
  47. data/lib/lighthouse/directive_resolvers/first_resolver.rb +1 -1
  48. data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +57 -0
  49. data/lib/lighthouse/directive_resolvers/lazy_load_resolver.rb +61 -0
  50. data/lib/lighthouse/directive_resolvers/limit_resolver.rb +59 -0
  51. data/lib/lighthouse/directive_resolvers/method_resolver.rb +38 -3
  52. data/lib/lighthouse/directive_resolvers/model_mutation_resolver.rb +1 -1
  53. data/lib/lighthouse/directive_resolvers/namespaced_resolver.rb +31 -0
  54. data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +1 -1
  55. data/lib/lighthouse/directive_resolvers/resolution.rb +19 -4
  56. data/lib/lighthouse/directive_resolvers/subscription_resolver.rb +14 -5
  57. data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +77 -0
  58. data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +81 -0
  59. data/lib/lighthouse/directive_resolvers/with_count_resolver.rb +43 -0
  60. data/lib/lighthouse/directive_resolvers/with_resolver.rb +42 -0
  61. data/lib/lighthouse/directives/arguments/more_filters.rb +215 -0
  62. data/lib/lighthouse/directives/definitions.rb +52 -0
  63. data/lib/lighthouse/directives/model_directive.rb +131 -0
  64. data/lib/lighthouse/directives/node_directive.rb +134 -0
  65. data/lib/lighthouse/directives/pagination_manipulator.rb +1 -1
  66. data/lib/lighthouse/directives/registry.rb +21 -20
  67. data/lib/lighthouse/directives/visibility_directive.rb +71 -0
  68. data/lib/lighthouse/enum_directive.rb +41 -0
  69. data/lib/lighthouse/events/execution_trace.rb +2 -1
  70. data/lib/lighthouse/events/lifecycle.rb +3 -1
  71. data/lib/lighthouse/field_resolver_stack.rb +59 -11
  72. data/lib/lighthouse/generated_resolvers.rb +13 -8
  73. data/lib/lighthouse/global_id.rb +31 -0
  74. data/lib/lighthouse/graphql/version.rb +1 -1
  75. data/lib/lighthouse/multipart_request.rb +43 -0
  76. data/lib/lighthouse/node_registry.rb +68 -0
  77. data/lib/lighthouse/pagination.rb +3 -0
  78. data/lib/lighthouse/rb_lighthouse.rb +36 -0
  79. data/lib/lighthouse/relation_counts.rb +29 -0
  80. data/lib/lighthouse/resolve_info.rb +80 -0
  81. data/lib/lighthouse/resolver_bases.rb +68 -0
  82. data/lib/lighthouse/resolver_dispatch.rb +36 -11
  83. data/lib/lighthouse/scalar_coercion.rb +72 -0
  84. data/lib/lighthouse/schema_cache.rb +2 -1
  85. data/lib/lighthouse/schema_factory.rb +0 -1
  86. data/lib/lighthouse/schema_generator.rb +21 -12
  87. data/lib/lighthouse/schema_implementation.rb +4 -8
  88. data/lib/lighthouse/sdl_loader.rb +0 -2
  89. data/lib/lighthouse/support/environment.rb +13 -0
  90. data/lib/lighthouse/support/model_resolver.rb +8 -6
  91. data/lib/lighthouse/support/preload.rb +49 -0
  92. data/lib/lighthouse/type.rb +103 -0
  93. data/lib/lighthouse/type_resolution.rb +150 -0
  94. data/lib/lighthouse/upload.rb +26 -0
  95. data/lib/lighthouse/upload_store.rb +32 -0
  96. data/sig/lighthouse/directives.rbs +3 -2
  97. data/sig/lighthouse/graphql.rbs +29 -0
  98. data/sig/lighthouse.rbs +18 -0
  99. metadata +43 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44b6e89e140483d01ad4cb9c945cfc29d6f025e05798ed4e5e11868060b86885
4
- data.tar.gz: cac88d1820f171e4cbe6dfaa51b737c405cacf6ec3c08ed4ce8694eb2039aa18
3
+ metadata.gz: 5dd6b5839f6520df6489135c1a37382ce4f7889d136140f652443ad5a3cd1a81
4
+ data.tar.gz: 60fdb10b55abe72a1dc3df89705b904b424344e5bffa611e695a46fa8767ddc7
5
5
  SHA512:
6
- metadata.gz: b70da4e82689cc2808b00c514f4de0eafb60fedcff45d6f0fa28b560bc3dbe502e6d3b4c0a7615d33065f95fa82fc756332added5dd9f2ba9396ef6b38ff79ce
7
- data.tar.gz: 8a30bc33a00599f3302196e3e24c881be56a68f94026ae1c0ab64246afa7b2114f6e723fba4f203fe51be03661bc6351f65fc12e3d4827624a6e04a2798b53fd
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.3.0...HEAD
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 creates the initializer, a starter schema in `graphql/schema.graphql`, the
82
- `AppSchema` class, a `GraphqlController` with its `POST /graphql` route, and a
83
- `graphql.config.yml` for your IDE. Run `bin/rails generate lighthouse:install --help`
84
- for its options (`--schema`, `--sdl-folder`, `--skip-controller`, `--skip-ide`).
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: { base_types: { object: BaseObject } } # your graphql-ruby base classes
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 `Queries::`/`Mutations::`/`Builders::`/`Validators::` classes,
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
- | [Queries, mutations & builders](docs/resolvers.md) | `Queries::` / `Mutations::` classes (graphql-ruby's contract), builders, generators |
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) |
@@ -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 queries, mutations and builders apart
48
-
49
- A custom `Query` field is a `GraphQL::Schema::Resolver` in `app/graphql/queries`,
50
- a `Mutation` field a `GraphQL::Schema::Mutation` in `app/graphql/mutations`, and a
51
- query refinement a builder in `app/graphql/builders`. They have different jobs and
52
- contracts; don't fold them into one "resolvers" folder. See [resolvers.md](resolvers.md).
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
 
@@ -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: ['Queries'], mutations: ['Mutations'] }
14
- config.resolver_namespaces = ['App::Graphql', 'Resolvers']
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` | `['Queries']` | `GraphQL::Schema::Resolver` classes for `Query` fields |
35
- | `mutations` | `['Mutations']` | `GraphQL::Schema::Mutation` classes for `Mutation` fields |
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` classes (optionally including `Lighthouse::GraphQL::Subscription`) for `Subscription` fields, and `@subscription(class:)` ([subscriptions.md](subscriptions.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)) |
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::Queries'] } # the other kinds keep their defaults
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`. Fully-qualified names (containing `::`) are used as-is. Defaults
50
- to `['App::Graphql']`.
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