lighthouse-graphql 0.1.1 → 0.2.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +154 -1
  3. data/README.md +42 -2
  4. data/docs/authorization.md +116 -57
  5. data/docs/best-practices.md +16 -3
  6. data/docs/caching.md +161 -0
  7. data/docs/configuration.md +83 -4
  8. data/docs/custom-directives.md +5 -1
  9. data/docs/directives.md +117 -38
  10. data/docs/getting-started.md +84 -0
  11. data/docs/mutations.md +260 -0
  12. data/docs/performance.md +138 -0
  13. data/docs/relationships.md +80 -33
  14. data/docs/resolvers.md +179 -0
  15. data/docs/validation.md +132 -0
  16. data/lib/generators/lighthouse/builder_generator.rb +30 -0
  17. data/lib/generators/lighthouse/class_generator.rb +61 -0
  18. data/lib/generators/lighthouse/install/install_generator.rb +127 -0
  19. data/lib/generators/lighthouse/install/templates/graphql.config.yml.tt +8 -0
  20. data/lib/generators/lighthouse/install/templates/graphql_controller.rb.tt +57 -0
  21. data/lib/generators/lighthouse/install/templates/initializer.rb.tt +67 -0
  22. data/lib/generators/lighthouse/install/templates/schema.graphql.tt +19 -0
  23. data/lib/generators/lighthouse/install/templates/schema.rb.tt +10 -0
  24. data/lib/generators/lighthouse/mutation_generator.rb +30 -0
  25. data/lib/generators/lighthouse/query_generator.rb +29 -0
  26. data/lib/generators/lighthouse/validator_generator.rb +36 -0
  27. data/lib/lighthouse/cache_control.rb +126 -0
  28. data/lib/lighthouse/caching.rb +68 -0
  29. data/lib/lighthouse/config.rb +85 -19
  30. data/lib/lighthouse/contracts/arg_builder.rb +8 -12
  31. data/lib/lighthouse/contracts/arg_manipulator.rb +8 -12
  32. data/lib/lighthouse/contracts/directive.rb +10 -14
  33. data/lib/lighthouse/contracts/field_manipulator.rb +8 -12
  34. data/lib/lighthouse/contracts/field_middleware.rb +7 -11
  35. data/lib/lighthouse/contracts/field_resolver.rb +9 -13
  36. data/lib/lighthouse/dataloader_sources/association_loader.rb +28 -19
  37. data/lib/lighthouse/dataloader_sources/model_loader.rb +10 -14
  38. data/lib/lighthouse/dataloader_sources/relation_count_loader.rb +16 -0
  39. data/lib/lighthouse/dataloader_sources/relation_page_loader.rb +18 -0
  40. data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +57 -69
  41. data/lib/lighthouse/directive_resolvers/all_resolver.rb +26 -57
  42. data/lib/lighthouse/directive_resolvers/association_resolvers.rb +161 -0
  43. data/lib/lighthouse/directive_resolvers/auth_resolver.rb +41 -38
  44. data/lib/lighthouse/directive_resolvers/base.rb +142 -86
  45. data/lib/lighthouse/directive_resolvers/builder_applier.rb +58 -56
  46. data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +133 -0
  47. data/lib/lighthouse/directive_resolvers/can_resolvers.rb +177 -0
  48. data/lib/lighthouse/directive_resolvers/convention_resolver.rb +70 -0
  49. data/lib/lighthouse/directive_resolvers/count_resolver.rb +42 -48
  50. data/lib/lighthouse/directive_resolvers/field_resolver.rb +27 -68
  51. data/lib/lighthouse/directive_resolvers/find_resolver.rb +42 -52
  52. data/lib/lighthouse/directive_resolvers/first_resolver.rb +39 -45
  53. data/lib/lighthouse/directive_resolvers/guard_resolver.rb +16 -22
  54. data/lib/lighthouse/directive_resolvers/method_resolver.rb +19 -25
  55. data/lib/lighthouse/directive_resolvers/model_mutation_resolver.rb +219 -0
  56. data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +70 -128
  57. data/lib/lighthouse/directive_resolvers/rename_resolver.rb +18 -24
  58. data/lib/lighthouse/directive_resolvers/resolution.rb +31 -27
  59. data/lib/lighthouse/directive_resolvers/validator_resolver.rb +193 -0
  60. data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +164 -163
  61. data/lib/lighthouse/directives/arguments/base.rb +23 -31
  62. data/lib/lighthouse/directives/arguments/filters.rb +113 -93
  63. data/lib/lighthouse/directives/arguments/order_by.rb +41 -41
  64. data/lib/lighthouse/directives/pagination_info_types.rb +69 -0
  65. data/lib/lighthouse/directives/pagination_manipulator.rb +176 -0
  66. data/lib/lighthouse/directives/registry.rb +210 -141
  67. data/lib/lighthouse/directives/where_conditions_directive.rb +236 -200
  68. data/lib/lighthouse/errors.rb +27 -0
  69. data/lib/lighthouse/federation_directives.rb +53 -0
  70. data/lib/lighthouse/field_resolver_stack.rb +56 -0
  71. data/lib/lighthouse/generated_resolvers.rb +82 -0
  72. data/lib/lighthouse/graphql/version.rb +1 -1
  73. data/lib/lighthouse/graphql_ruby_patches.rb +18 -0
  74. data/lib/lighthouse/ide_helper.rb +98 -0
  75. data/lib/lighthouse/namespaces.rb +21 -0
  76. data/lib/lighthouse/nested_mutations/belongs_to_operations.rb +50 -0
  77. data/lib/lighthouse/nested_mutations/executor.rb +149 -0
  78. data/lib/lighthouse/nested_mutations/has_operations.rb +66 -0
  79. data/lib/lighthouse/nested_mutations/many_to_many_operations.rb +44 -0
  80. data/lib/lighthouse/nested_mutations/partitioner.rb +125 -0
  81. data/lib/lighthouse/nested_mutations.rb +210 -0
  82. data/lib/lighthouse/pagination.rb +221 -0
  83. data/lib/lighthouse/persisted_queries.rb +167 -0
  84. data/lib/lighthouse/railtie.rb +26 -0
  85. data/lib/lighthouse/rb_lighthouse.rb +50 -29
  86. data/lib/lighthouse/reference_resolver.rb +13 -15
  87. data/lib/lighthouse/relation_batch.rb +156 -0
  88. data/lib/lighthouse/resolver_dispatch.rb +26 -0
  89. data/lib/lighthouse/schema_cache.rb +120 -0
  90. data/lib/lighthouse/schema_factory.rb +27 -37
  91. data/lib/lighthouse/schema_generator.rb +196 -239
  92. data/lib/lighthouse/schema_implementation.rb +51 -48
  93. data/lib/lighthouse/sdl_loader.rb +17 -20
  94. data/lib/lighthouse/support/arguments.rb +119 -0
  95. data/lib/lighthouse/support/authorization.rb +18 -19
  96. data/lib/lighthouse/support/batching.rb +16 -0
  97. data/lib/lighthouse/support/directive_args.rb +26 -24
  98. data/lib/lighthouse/support/model_resolver.rb +56 -56
  99. data/lib/lighthouse/support/naming.rb +55 -59
  100. data/lib/lighthouse/tasks/lighthouse.rake +33 -0
  101. data/lib/lighthouse/where_conditions/operator_map.rb +25 -29
  102. metadata +51 -63
  103. data/lib/lighthouse/directive_resolvers/belongs_to_many_resolver.rb +0 -56
  104. data/lib/lighthouse/directive_resolvers/belongs_to_resolver.rb +0 -47
  105. data/lib/lighthouse/directive_resolvers/can_resolver.rb +0 -86
  106. data/lib/lighthouse/directive_resolvers/has_many_resolver.rb +0 -109
  107. data/lib/lighthouse/directive_resolvers/has_one_resolver.rb +0 -43
  108. data/lib/lighthouse/directives/relation_directive.rb +0 -332
@@ -1,332 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lighthouse
4
- module GraphQL
5
- module Directives
6
- # AST transformer that converts list-typed relationship fields annotated
7
- # with relationship directives (e.g. @hasMany, @paginate) into
8
- # Connection/Paginator return types and injects pagination arguments.
9
- class RelationDirective
10
- DIRECTIVE_NAMES = %w[hasMany belongsToMany hasManyThrough paginate all].freeze
11
-
12
- def self.manipulate(sdl)
13
- return sdl unless sdl.is_a?(String) && !sdl.empty?
14
-
15
- begin
16
- document = ::GraphQL::Language::Parser.parse(sdl)
17
- rescue StandardError => e
18
- warn("Lighthouse::GraphQL::Directives::RelationDirective: parse failed: #{e.class}: #{e.message}")
19
- return sdl
20
- end
21
-
22
- existing_def_names = document.definitions.filter_map { |d| d.respond_to?(:name) ? d.name : nil }
23
- produced_inner_types = []
24
-
25
- document.definitions.each do |definition|
26
- next unless definition.is_a?(::GraphQL::Language::Nodes::ObjectTypeDefinition)
27
-
28
- definition.fields.each do |field|
29
- next unless field.type
30
- next if field.directives.nil? || field.directives.empty?
31
- next unless field.directives.any? { |d| DIRECTIVE_NAMES.include?(d.name) }
32
-
33
- list_inner = list_inner_named_type(field.type)
34
- next unless list_inner
35
-
36
- produce_connection, produce_paginator = pagination_kind_for(field.directives)
37
- next unless produce_connection || produce_paginator
38
-
39
- produced_inner_types << list_inner unless produced_inner_types.include?(list_inner)
40
-
41
- # Replace list type with Connection/Paginator return type, preserving outer nullability.
42
- new_named_type =
43
- if produce_connection
44
- "#{list_inner}Connection"
45
- else
46
- "#{list_inner}Paginator"
47
- end
48
-
49
- field.instance_variable_set(:@type, replace_with_named_type(field.type, new_named_type))
50
-
51
- if produce_connection
52
- field.instance_variable_set(:@arguments, ensure_connection_arguments(field.arguments || []))
53
- end
54
- end
55
- end
56
-
57
- return sdl if produced_inner_types.empty?
58
-
59
- # Add Connection/Edge/Paginator utility types where missing.
60
- new_defs = []
61
- produced_inner_types.each do |inner|
62
- conn_name = "#{inner}Connection"
63
- edge_name = "#{inner}Edge"
64
- paginator_name = "#{inner}Paginator"
65
-
66
- unless existing_def_names.include?(conn_name)
67
- new_defs << build_connection_type(conn_name, edge_name, inner)
68
- existing_def_names << conn_name
69
- end
70
-
71
- unless existing_def_names.include?(edge_name)
72
- new_defs << build_edge_type(edge_name, inner)
73
- existing_def_names << edge_name
74
- end
75
-
76
- unless existing_def_names.include?(paginator_name)
77
- new_defs << build_paginator_type(paginator_name, inner)
78
- existing_def_names << paginator_name
79
- end
80
- end
81
-
82
- unless existing_def_names.include?('PaginatorInfo')
83
- new_defs << build_paginator_info_type
84
- existing_def_names << 'PaginatorInfo'
85
- end
86
-
87
- unless existing_def_names.include?('PageInfo')
88
- new_defs << build_page_info_type
89
- existing_def_names << 'PageInfo'
90
- end
91
-
92
- modified_sdl = ::GraphQL::Language::Printer.new.print(document)
93
- modified_sdl + "\n\n" + new_defs.map { |d| ::GraphQL::Language::Printer.new.print(d) }.join("\n\n")
94
- end
95
-
96
- def self.pagination_kind_for(directives)
97
- # For relation directives, always use Connection.
98
- if directives.any? { |d| d.name != 'paginate' && DIRECTIVE_NAMES.include?(d.name) }
99
- return [true, false]
100
- end
101
-
102
- paginate = directives.find { |d| d.name == 'paginate' }
103
- return [false, false] unless paginate
104
-
105
- type_arg = directive_arg_literal(paginate, 'type')&.to_s&.upcase
106
- if %w[CONNECTION CURSOR].include?(type_arg)
107
- [true, false]
108
- else
109
- [false, true]
110
- end
111
- end
112
-
113
- def self.directive_arg_literal(directive, name)
114
- arg = directive.arguments&.find { |a| a.name == name }
115
- return nil unless arg
116
- arg.value.respond_to?(:value) ? arg.value.value : arg.value
117
- end
118
-
119
- def self.list_inner_named_type(type_node)
120
- case type_node
121
- when ::GraphQL::Language::Nodes::NonNullType
122
- list_inner_named_type(type_node.of_type)
123
- when ::GraphQL::Language::Nodes::ListType
124
- unwrap_named_type(type_node.of_type)
125
- else
126
- nil
127
- end
128
- end
129
-
130
- def self.unwrap_named_type(type_node)
131
- case type_node
132
- when ::GraphQL::Language::Nodes::NonNullType
133
- unwrap_named_type(type_node.of_type)
134
- when ::GraphQL::Language::Nodes::TypeName
135
- type_node.name
136
- else
137
- nil
138
- end
139
- end
140
-
141
- def self.replace_with_named_type(type_node, new_name)
142
- new_type = ::GraphQL::Language::Nodes::TypeName.new(name: new_name)
143
- if type_node.is_a?(::GraphQL::Language::Nodes::NonNullType)
144
- ::GraphQL::Language::Nodes::NonNullType.new(of_type: new_type)
145
- else
146
- new_type
147
- end
148
- end
149
-
150
- def self.ensure_connection_arguments(existing_args)
151
- existing_names = existing_args.map(&:name)
152
- args = existing_args.map do |arg|
153
- next arg unless %w[after before first last].include?(arg.name)
154
- next arg if arg.description && !arg.description.empty?
155
-
156
- ::GraphQL::Language::Nodes::InputValueDefinition.new(
157
- name: arg.name,
158
- type: arg.type,
159
- directives: arg.directives || [],
160
- default_value: arg.default_value,
161
- description: connection_arg_description(arg.name)
162
- )
163
- end
164
-
165
- %w[after before first last].each do |name|
166
- next if existing_names.include?(name)
167
-
168
- type =
169
- if %w[first last].include?(name)
170
- ::GraphQL::Language::Nodes::TypeName.new(name: 'Int')
171
- else
172
- ::GraphQL::Language::Nodes::TypeName.new(name: 'String')
173
- end
174
-
175
- args << ::GraphQL::Language::Nodes::InputValueDefinition.new(
176
- name: name,
177
- type: type,
178
- directives: [],
179
- default_value: nil,
180
- description: connection_arg_description(name)
181
- )
182
- end
183
-
184
- args
185
- end
186
-
187
- def self.build_connection_type(conn_name, edge_name, node_name)
188
- ::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
189
- name: conn_name,
190
- interfaces: [],
191
- directives: [],
192
- description: "The connection type for #{node_name}.",
193
- fields: [
194
- ::GraphQL::Language::Nodes::FieldDefinition.new(
195
- name: 'edges',
196
- type: ::GraphQL::Language::Nodes::ListType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: edge_name)),
197
- arguments: [],
198
- directives: [],
199
- description: 'A list of edges.'
200
- ),
201
- ::GraphQL::Language::Nodes::FieldDefinition.new(
202
- name: 'nodes',
203
- type: ::GraphQL::Language::Nodes::ListType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: node_name)),
204
- arguments: [],
205
- directives: [],
206
- description: 'A list of nodes.'
207
- ),
208
- ::GraphQL::Language::Nodes::FieldDefinition.new(
209
- name: 'pageInfo',
210
- type: ::GraphQL::Language::Nodes::NonNullType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: 'PageInfo')),
211
- arguments: [],
212
- directives: [],
213
- description: 'Information to aid in pagination.'
214
- )
215
- ]
216
- )
217
- end
218
-
219
- def self.build_edge_type(edge_name, node_name)
220
- ::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
221
- name: edge_name,
222
- interfaces: [],
223
- directives: [],
224
- description: 'An edge in a connection.',
225
- fields: [
226
- ::GraphQL::Language::Nodes::FieldDefinition.new(
227
- name: 'cursor',
228
- type: ::GraphQL::Language::Nodes::NonNullType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: 'String')),
229
- arguments: [],
230
- directives: [],
231
- description: 'A cursor for use in pagination.'
232
- ),
233
- ::GraphQL::Language::Nodes::FieldDefinition.new(
234
- name: 'node',
235
- type: ::GraphQL::Language::Nodes::TypeName.new(name: node_name),
236
- arguments: [],
237
- directives: [],
238
- description: 'The item at the end of the edge.'
239
- )
240
- ]
241
- )
242
- end
243
-
244
- def self.build_paginator_type(paginator_name, inner_name)
245
- ::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
246
- name: paginator_name,
247
- interfaces: [],
248
- directives: [],
249
- description: "A paginated list of #{inner_name} items.",
250
- fields: [
251
- ::GraphQL::Language::Nodes::FieldDefinition.new(
252
- name: 'paginatorInfo',
253
- type: ::GraphQL::Language::Nodes::NonNullType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: 'PaginatorInfo')),
254
- arguments: [],
255
- directives: [],
256
- description: 'Pagination information about the list of items.'
257
- ),
258
- ::GraphQL::Language::Nodes::FieldDefinition.new(
259
- name: 'data',
260
- type: ::GraphQL::Language::Nodes::ListType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: inner_name)),
261
- arguments: [],
262
- directives: [],
263
- description: "A list of #{inner_name} items."
264
- )
265
- ]
266
- )
267
- end
268
-
269
- def self.build_paginator_info_type
270
- ::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
271
- name: 'PaginatorInfo',
272
- interfaces: [],
273
- directives: [],
274
- description: 'Information about pagination in a paginator.',
275
- fields: [
276
- field_def('count', 'Int', non_null: true),
277
- field_def('currentPage', 'Int', non_null: true),
278
- field_def('firstItem', 'Int'),
279
- field_def('hasMorePages', 'Boolean', non_null: true),
280
- field_def('lastItem', 'Int'),
281
- field_def('lastPage', 'Int', non_null: true),
282
- field_def('perPage', 'Int', non_null: true),
283
- field_def('total', 'Int', non_null: true)
284
- ]
285
- )
286
- end
287
-
288
- def self.build_page_info_type
289
- # Minimal Relay PageInfo. GraphQL-Ruby can also supply this via Relay,
290
- # but SDL-first apps may not include it.
291
- ::GraphQL::Language::Nodes::ObjectTypeDefinition.new(
292
- name: 'PageInfo',
293
- interfaces: [],
294
- directives: [],
295
- description: nil,
296
- fields: [
297
- field_def('hasNextPage', 'Boolean', non_null: true),
298
- field_def('hasPreviousPage', 'Boolean', non_null: true),
299
- field_def('startCursor', 'String'),
300
- field_def('endCursor', 'String')
301
- ]
302
- )
303
- end
304
-
305
- def self.field_def(name, type_name, non_null: false)
306
- type = ::GraphQL::Language::Nodes::TypeName.new(name: type_name)
307
- type = ::GraphQL::Language::Nodes::NonNullType.new(of_type: type) if non_null
308
- ::GraphQL::Language::Nodes::FieldDefinition.new(
309
- name: name,
310
- type: type,
311
- arguments: [],
312
- directives: [],
313
- description: nil
314
- )
315
- end
316
-
317
- def self.connection_arg_description(name)
318
- case name
319
- when 'after'
320
- 'Returns the elements in the list that come after the specified cursor.'
321
- when 'before'
322
- 'Returns the elements in the list that come before the specified cursor.'
323
- when 'first'
324
- 'Returns the first _n_ elements from the list.'
325
- when 'last'
326
- 'Returns the last _n_ elements from the list.'
327
- end
328
- end
329
- end
330
- end
331
- end
332
- end