lighthouse-graphql 0.6.0 → 0.7.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +95 -1
  3. data/README.md +15 -7
  4. data/docs/best-practices.md +6 -4
  5. data/docs/configuration.md +19 -5
  6. data/docs/custom-directives.md +2 -1
  7. data/docs/directives.md +65 -7
  8. data/docs/federation.md +1 -1
  9. data/docs/filtering-and-ordering.md +330 -30
  10. data/docs/generators.md +1 -1
  11. data/docs/getting-started.md +43 -10
  12. data/docs/motivation.md +3 -2
  13. data/docs/performance.md +4 -3
  14. data/docs/relationships.md +2 -1
  15. data/docs/resolvers.md +2 -2
  16. data/docs/schema-organisation.md +177 -0
  17. data/lib/generators/lighthouse/install/install_generator.rb +16 -11
  18. data/lib/generators/lighthouse/install/templates/graphql.config.yml.tt +2 -1
  19. data/lib/generators/lighthouse/install/templates/initializer.rb.tt +11 -4
  20. data/lib/lighthouse/ast_builder.rb +124 -0
  21. data/lib/lighthouse/config.rb +23 -4
  22. data/lib/lighthouse/contracts/arg_manipulator.rb +9 -5
  23. data/lib/lighthouse/contracts/field_builder.rb +13 -0
  24. data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +1 -2
  25. data/lib/lighthouse/directive_resolvers/all_resolver.rb +0 -2
  26. data/lib/lighthouse/directive_resolvers/association_resolvers.rb +9 -6
  27. data/lib/lighthouse/directive_resolvers/base.rb +6 -37
  28. data/lib/lighthouse/directive_resolvers/builder_applier.rb +21 -6
  29. data/lib/lighthouse/directive_resolvers/count_resolver.rb +0 -1
  30. data/lib/lighthouse/directive_resolvers/first_resolver.rb +0 -2
  31. data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +2 -3
  32. data/lib/lighthouse/directives/arguments/order_by.rb +190 -26
  33. data/lib/lighthouse/directives/arguments/where_conditions.rb +25 -0
  34. data/lib/lighthouse/directives/arguments/where_conditions_base.rb +84 -0
  35. data/lib/lighthouse/directives/arguments/where_has_conditions.rb +56 -0
  36. data/lib/lighthouse/directives/definitions.rb +0 -1
  37. data/lib/lighthouse/directives/generated_types.rb +103 -0
  38. data/lib/lighthouse/directives/registry.rb +39 -10
  39. data/lib/lighthouse/document_ast.rb +111 -0
  40. data/lib/lighthouse/graphql/version.rb +1 -1
  41. data/lib/lighthouse/ide_helper.rb +80 -23
  42. data/lib/lighthouse/namespaces.rb +1 -0
  43. data/lib/lighthouse/order_by/types.rb +137 -0
  44. data/lib/lighthouse/railtie.rb +9 -2
  45. data/lib/lighthouse/rb_lighthouse.rb +15 -5
  46. data/lib/lighthouse/resolve_info.rb +5 -5
  47. data/lib/lighthouse/schema_cache.rb +8 -4
  48. data/lib/lighthouse/schema_factory.rb +6 -9
  49. data/lib/lighthouse/schema_generator.rb +4 -15
  50. data/lib/lighthouse/schema_stitcher.rb +64 -0
  51. data/lib/lighthouse/support/naming.rb +0 -12
  52. data/lib/lighthouse/where_conditions/columns.rb +36 -0
  53. data/lib/lighthouse/where_conditions/handler.rb +152 -0
  54. data/lib/lighthouse/where_conditions/json_column.rb +99 -0
  55. data/lib/lighthouse/where_conditions/operator_map.rb +1 -4
  56. data/lib/lighthouse/where_conditions/types.rb +110 -0
  57. data/sig/lighthouse/directives.rbs +16 -3
  58. data/sig/lighthouse/graphql.rbs +48 -4
  59. data/sig/lighthouse.rbs +8 -2
  60. metadata +15 -4
  61. data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +0 -170
  62. data/lib/lighthouse/directives/where_conditions_directive.rb +0 -244
  63. data/lib/lighthouse/sdl_loader.rb +0 -22
data/sig/lighthouse.rbs CHANGED
@@ -92,8 +92,14 @@ module Lighthouse
92
92
  def self.[]: (Symbol | String key) -> untyped
93
93
  def self.reset!: () -> void
94
94
 
95
- def self.sdl_folder: () -> path?
96
- def self.sdl_folder=: (path? folder) -> path?
95
+ # The schema's entrypoint, which imports the other files (SchemaStitcher).
96
+ def self.schema_path: () -> path?
97
+ def self.schema_path=: (path? path) -> path?
98
+ def self.schema_directory: () -> String?
99
+
100
+ # Rewrite the IDE helper files on boot and every reload; nil: in development with a graphql.config.yml.
101
+ def self.ide_helper: () -> bool?
102
+ def self.ide_helper=: (bool? enabled) -> bool?
97
103
 
98
104
  # { queries:, mutations:, subscriptions:, builders:, validators:, events: } => [namespace, ...].
99
105
  # Assigning a partial hash overrides only the kinds it names.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lighthouse-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vaz Innovation
@@ -70,6 +70,7 @@ files:
70
70
  - docs/relationships.md
71
71
  - docs/relay.md
72
72
  - docs/resolvers.md
73
+ - docs/schema-organisation.md
73
74
  - docs/security.md
74
75
  - docs/subscriptions.md
75
76
  - docs/types.md
@@ -87,6 +88,7 @@ files:
87
88
  - lib/generators/lighthouse/subscription_generator.rb
88
89
  - lib/generators/lighthouse/validator_generator.rb
89
90
  - lib/lighthouse-graphql.rb
91
+ - lib/lighthouse/ast_builder.rb
90
92
  - lib/lighthouse/async_mutation_job.rb
91
93
  - lib/lighthouse/broadcast_job.rb
92
94
  - lib/lighthouse/broadcasting.rb
@@ -98,6 +100,7 @@ files:
98
100
  - lib/lighthouse/contracts/arg_builder.rb
99
101
  - lib/lighthouse/contracts/arg_manipulator.rb
100
102
  - lib/lighthouse/contracts/directive.rb
103
+ - lib/lighthouse/contracts/field_builder.rb
101
104
  - lib/lighthouse/contracts/field_manipulator.rb
102
105
  - lib/lighthouse/contracts/field_middleware.rb
103
106
  - lib/lighthouse/contracts/field_resolver.rb
@@ -143,7 +146,6 @@ files:
143
146
  - lib/lighthouse/directive_resolvers/throttle_resolver.rb
144
147
  - lib/lighthouse/directive_resolvers/type_method_resolver.rb
145
148
  - lib/lighthouse/directive_resolvers/validator_resolver.rb
146
- - lib/lighthouse/directive_resolvers/where_conditions_applier.rb
147
149
  - lib/lighthouse/directive_resolvers/with_count_resolver.rb
148
150
  - lib/lighthouse/directive_resolvers/with_resolver.rb
149
151
  - lib/lighthouse/directives/arguments/base.rb
@@ -151,7 +153,11 @@ files:
151
153
  - lib/lighthouse/directives/arguments/more_filters.rb
152
154
  - lib/lighthouse/directives/arguments/order_by.rb
153
155
  - lib/lighthouse/directives/arguments/soft_deletes.rb
156
+ - lib/lighthouse/directives/arguments/where_conditions.rb
157
+ - lib/lighthouse/directives/arguments/where_conditions_base.rb
158
+ - lib/lighthouse/directives/arguments/where_has_conditions.rb
154
159
  - lib/lighthouse/directives/definitions.rb
160
+ - lib/lighthouse/directives/generated_types.rb
155
161
  - lib/lighthouse/directives/model_directive.rb
156
162
  - lib/lighthouse/directives/node_directive.rb
157
163
  - lib/lighthouse/directives/pagination_info_types.rb
@@ -159,7 +165,7 @@ files:
159
165
  - lib/lighthouse/directives/registry.rb
160
166
  - lib/lighthouse/directives/soft_deletes_directive.rb
161
167
  - lib/lighthouse/directives/visibility_directive.rb
162
- - lib/lighthouse/directives/where_conditions_directive.rb
168
+ - lib/lighthouse/document_ast.rb
163
169
  - lib/lighthouse/enum_directive.rb
164
170
  - lib/lighthouse/errors.rb
165
171
  - lib/lighthouse/events.rb
@@ -184,6 +190,7 @@ files:
184
190
  - lib/lighthouse/nested_mutations/many_to_many_operations.rb
185
191
  - lib/lighthouse/nested_mutations/partitioner.rb
186
192
  - lib/lighthouse/node_registry.rb
193
+ - lib/lighthouse/order_by/types.rb
187
194
  - lib/lighthouse/pagination.rb
188
195
  - lib/lighthouse/persisted_queries.rb
189
196
  - lib/lighthouse/persisted_queries/batch.rb
@@ -201,7 +208,7 @@ files:
201
208
  - lib/lighthouse/schema_factory.rb
202
209
  - lib/lighthouse/schema_generator.rb
203
210
  - lib/lighthouse/schema_implementation.rb
204
- - lib/lighthouse/sdl_loader.rb
211
+ - lib/lighthouse/schema_stitcher.rb
205
212
  - lib/lighthouse/subscription.rb
206
213
  - lib/lighthouse/support/arguments.rb
207
214
  - lib/lighthouse/support/authorization.rb
@@ -217,7 +224,11 @@ files:
217
224
  - lib/lighthouse/type_resolution.rb
218
225
  - lib/lighthouse/upload.rb
219
226
  - lib/lighthouse/upload_store.rb
227
+ - lib/lighthouse/where_conditions/columns.rb
228
+ - lib/lighthouse/where_conditions/handler.rb
229
+ - lib/lighthouse/where_conditions/json_column.rb
220
230
  - lib/lighthouse/where_conditions/operator_map.rb
231
+ - lib/lighthouse/where_conditions/types.rb
221
232
  - sig/lighthouse.rbs
222
233
  - sig/lighthouse/controller.rbs
223
234
  - sig/lighthouse/directives.rbs
@@ -1,170 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Lighthouse::GraphQL::DirectiveResolvers::WhereConditionsApplier
4
- # Operator semantics live in Lighthouse::WhereConditions::OperatorMap so
5
- # they can be shared with the simple argument-filter directives.
6
- SUPPORTED_OPERATORS = Lighthouse::WhereConditions::OperatorMap::OPERATORS
7
- # graphql-ruby hands input objects over with Ruby-style keys, so the
8
- # generated input's AND/OR/HAS fields arrive as :and/:or/:has.
9
- LOGICAL_KEYS = { and: :AND, or: :OR, has: :HAS }.freeze
10
-
11
- class << self
12
- def apply(relation, value)
13
- return relation unless relation.respond_to?(:where)
14
-
15
- condition = normalize_condition(value)
16
- return relation unless condition
17
-
18
- # AND narrows the incoming scope step by step; each OR branch starts
19
- # from the incoming scope, and their union replaces it.
20
- result = apply_all(relation, condition[:AND])
21
- result = apply_any(relation, condition[:OR]) || result
22
- result = apply_has_condition(result, condition[:HAS])
23
- result = apply_single_condition(result, condition) if condition.key?(:column)
24
- result
25
- end
26
-
27
- def apply_has(relation, has_cond)
28
- relation_name = has_cond[:relation].to_s
29
- return relation if relation_name.blank?
30
-
31
- resolved_relation_name = resolve_relation_name(relation, relation_name)
32
- return relation if resolved_relation_name.blank?
33
-
34
- reflection = reflection_for(relation, resolved_relation_name)
35
- return relation unless reflection&.klass
36
-
37
- joined = relation.joins(resolved_relation_name.to_sym)
38
- return joined.distinct if has_cond[:condition].blank?
39
-
40
- # Use an EXISTS-style filter to safely apply conditions from the related model.
41
- child_scope = apply(reflection.klass.all, has_cond[:condition])
42
- joined.where(reflection.klass.table_name => { id: child_scope.select(:id) }).distinct
43
- rescue StandardError
44
- relation
45
- end
46
-
47
- def resolve_relation_name(relation, relation_name)
48
- candidate_names = [relation_name.to_s, relation_name.to_s.singularize, relation_name.to_s.pluralize].uniq
49
-
50
- candidate_names.each do |candidate|
51
- next if candidate.blank?
52
- return candidate if association_exists?(relation, candidate)
53
- end
54
-
55
- nil
56
- rescue StandardError
57
- nil
58
- end
59
-
60
- def association_exists?(relation, name)
61
- reflection_for(relation, name).present?
62
- rescue StandardError
63
- false
64
- end
65
-
66
- def apply_has_argument(relation, arg_name, arg_value, relation_name = nil)
67
- relation_name ||= derive_relation_name(arg_name)
68
- return relation unless relation_name.present? && arg_value.present?
69
-
70
- condition = arg_value.is_a?(Hash) ? arg_value : symbolized(arg_value)
71
- has_payload = { relation: relation_name, condition: condition }
72
- apply_has(relation, has_payload)
73
- end
74
-
75
- private
76
-
77
- # The condition as a symbol-keyed Hash, or nil when there is none.
78
- def normalize_condition(value)
79
- return nil if value.nil?
80
-
81
- condition = value.is_a?(Hash) ? value.deep_symbolize_keys : symbolized(value)
82
- condition.transform_keys { |key| LOGICAL_KEYS.fetch(key, key) } if condition.is_a?(Hash)
83
- end
84
-
85
- def apply_all(relation, conditions)
86
- Array(conditions).reduce(relation) { |acc, sub| apply(acc, sub) }
87
- end
88
-
89
- # The union of the branches, or nil when there are none.
90
- def apply_any(relation, conditions)
91
- Array(conditions).map { |sub| apply(relation, sub) }.reduce { |acc, sub_scope| acc.or(sub_scope) }
92
- end
93
-
94
- def apply_has_condition(relation, has_cond)
95
- return relation unless has_cond.is_a?(Hash) && has_cond[:relation]
96
-
97
- apply_has(relation, has_cond)
98
- end
99
-
100
- def reflection_for(relation, name)
101
- klass = relation.respond_to?(:klass) ? relation.klass : relation
102
- klass.reflect_on_association(name.to_sym) if klass.respond_to?(:reflect_on_association)
103
- end
104
-
105
- def derive_relation_name(arg_name)
106
- Lighthouse::Support::Naming.derive_relation_name(arg_name)
107
- end
108
-
109
- def apply_single_condition(relation, condition)
110
- return relation if condition[:column].blank? || !relation.respond_to?(:where)
111
-
112
- column_name = resolve_column_name(condition[:column])
113
- return relation if column_name.blank?
114
-
115
- apply_operator(relation, column_name, operator_of(condition), condition[:value])
116
- rescue StandardError
117
- relation
118
- end
119
-
120
- def operator_of(condition)
121
- condition[:operator]&.to_s&.upcase || 'EQ'
122
- end
123
-
124
- # Null and set operators are plain hash conditions; the rest need Arel.
125
- def apply_operator(relation, column_name, operator, value)
126
- case operator
127
- when 'IS_NULL' then relation.where(column_name => nil)
128
- when 'IS_NOT_NULL' then relation.where.not(column_name => nil)
129
- when 'IN' then relation.where(column_name => Array(value))
130
- when 'NIN' then relation.where.not(column_name => Array(value))
131
- else apply_arel_operator(relation, column_name, operator, value)
132
- end
133
- end
134
-
135
- # An unknown operator falls back to equality rather than failing the query.
136
- def apply_arel_operator(relation, column_name, operator, value)
137
- pattern = like_pattern(operator, value)
138
- return relation.where(arel_table(relation)[column_name].matches(pattern)) if pattern
139
-
140
- predicate = SUPPORTED_OPERATORS[operator]
141
- return relation.where(column_name => value) unless predicate
142
-
143
- relation.where(arel_table(relation)[column_name].public_send(predicate, value))
144
- end
145
-
146
- def like_pattern(operator, value)
147
- case operator
148
- when 'CONTAINS' then "%#{value}%"
149
- when 'STARTS_WITH' then "#{value}%"
150
- when 'ENDS_WITH' then "%#{value}"
151
- end
152
- end
153
-
154
- def resolve_column_name(column)
155
- Lighthouse::Support::Naming.column(column)
156
- end
157
-
158
- def arel_table(relation)
159
- relation.klass.arel_table
160
- end
161
-
162
- def symbolized(obj)
163
- if obj.respond_to?(:to_h)
164
- obj.to_h.deep_symbolize_keys
165
- else
166
- {}
167
- end
168
- end
169
- end
170
- end
@@ -1,244 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'graphql'
4
-
5
- class Lighthouse::GraphQL::Directives::WhereConditionsDirective
6
- SQLOPERATOR_TYPE_NAME = 'SQLOperator'
7
- MIXED_SCALAR_NAME = 'Mixed'
8
- PLACEHOLDER_SCALAR_NAME = '_'
9
- DEFINITIONS = {
10
- 'whereConditions' =>
11
- 'directive @whereConditions(columns: [String!], columnsEnum: String) on ARGUMENT_DEFINITION',
12
- 'whereHasConditions' =>
13
- 'directive @whereHasConditions(relation: String, columns: [String!], columnsEnum: String) on ARGUMENT_DEFINITION'
14
- }.freeze
15
-
16
- # Default description of an undocumented argument, per directive.
17
- ARGUMENT_DESCRIPTIONS = {
18
- 'whereConditions' => 'Filter results by field conditions.',
19
- 'whereHasConditions' => 'Filter results by related records conditions.'
20
- }.freeze
21
-
22
- # Types whose field arguments may carry the directives.
23
- OBJECT_DEFINITIONS = [
24
- ::GraphQL::Language::Nodes::ObjectTypeDefinition,
25
- ::GraphQL::Language::Nodes::InterfaceTypeDefinition
26
- ].freeze
27
-
28
- # Supported operators in the generated GraphQL input type performance.
29
- SUPPORTED_OPERATORS = %w[EQ NEQ GT GTE LT LTE IN NIN CONTAINS STARTS_WITH ENDS_WITH IS_NULL IS_NOT_NULL].freeze
30
-
31
- # `definitions` maps every type name to its generated SDL. Names the schema
32
- # already declares are seeded with nil, so they are never generated again
33
- # and drop out of the output.
34
- def self.manipulate(sdl)
35
- return sdl unless sdl.is_a?(String) && !sdl.empty?
36
-
37
- ast = parse(sdl)
38
- return sdl unless ast
39
-
40
- definitions = ast.definitions.filter_map { |defn| defn.respond_to?(:name) ? defn.name : nil }.index_with(nil)
41
- each_where_argument(ast) do |type_def, field, arg, directive|
42
- expand_argument(type_def, field, arg, directive, definitions)
43
- end
44
- define_utility_types(ast, definitions)
45
-
46
- generated = definitions.values.compact
47
- return sdl if generated.empty?
48
-
49
- modified_sdl = ::GraphQL::Language::Printer.new.print(ast)
50
- "#{modified_sdl}\n\n#{generated.join("\n\n")}"
51
- end
52
-
53
- def self.parse(sdl)
54
- ::GraphQL::Language::Parser.parse(sdl)
55
- rescue StandardError => e
56
- warn("Lighthouse::GraphQL::Directives::WhereConditionsDirective: parse failed: #{e.class}: #{e.message}")
57
- nil
58
- end
59
-
60
- # Yields every object/interface field argument carrying @whereConditions or
61
- # @whereHasConditions, along with that directive.
62
- def self.each_where_argument(ast)
63
- each_field(ast) do |type_def, field|
64
- (field.arguments || []).each do |arg|
65
- directive = arg.directives&.find { |d| DEFINITIONS.key?(d.name) }
66
- yield type_def, field, arg, directive if directive
67
- end
68
- end
69
- end
70
-
71
- def self.each_field(ast)
72
- ast.definitions.each do |type_def|
73
- next unless OBJECT_DEFINITIONS.any? { |kind| type_def.is_a?(kind) }
74
-
75
- (type_def.fields || []).each { |field| yield type_def, field }
76
- end
77
- end
78
-
79
- # Points the argument at its generated input type and defines the types it
80
- # needs: the conditions input, its HAS relation input and the column enum.
81
- def self.expand_argument(type_def, field, arg, directive, definitions)
82
- describe_argument(arg, directive)
83
-
84
- prefix = "#{camelize(type_def.name)}#{camelize(field.name)}#{camelize(arg.name)}"
85
- input_type_name = "#{prefix}WhereConditions"
86
- column_enum_name = "#{prefix}Column"
87
- relation_input_name = "#{input_type_name}Relation"
88
-
89
- apply_arg_type_replacement(arg, input_type_name)
90
-
91
- # Build input type if needed.
92
- define_once(definitions, input_type_name) do
93
- build_where_condition_input(input_type_name, column_enum_name, relation_input_name, directive)
94
- end
95
-
96
- # Build relation type for HAS support in whereConditions
97
- if directive.name == 'whereConditions'
98
- define_once(definitions, relation_input_name) { build_where_condition_relation_input(relation_input_name, input_type_name) }
99
- end
100
-
101
- define_column_enum(definitions, column_enum_name, directive)
102
- end
103
-
104
- def self.describe_argument(arg, directive)
105
- return if arg.description.present?
106
-
107
- arg.instance_variable_set(:@description, ARGUMENT_DESCRIPTIONS.fetch(directive.name))
108
- end
109
-
110
- # Build column enum type unless columnsEnum is provided.
111
- def self.define_column_enum(definitions, column_enum_name, directive)
112
- return unless directive_argument_value(directive, 'columnsEnum').nil?
113
-
114
- columns = directive_argument_value(directive, 'columns')
115
- return if Array(columns).empty?
116
-
117
- define_once(definitions, column_enum_name) { build_column_enum(column_enum_name, columns) }
118
- end
119
-
120
- def self.define_utility_types(ast, definitions)
121
- # Append base utility types only once
122
- define_once(definitions, SQLOPERATOR_TYPE_NAME) { build_sql_operator_enum }
123
- define_once(definitions, MIXED_SCALAR_NAME) { "scalar #{MIXED_SCALAR_NAME}" }
124
-
125
- # Some SDL files use `_` as a placeholder input type, even when no
126
- # whereConditions directive is attached (e.g. WIP schema pieces). If we
127
- # don't define it, GraphQL-Ruby raises UnresolvedLateBoundTypeError.
128
- return unless contains_placeholder_type?(ast)
129
-
130
- define_once(definitions, PLACEHOLDER_SCALAR_NAME) { "scalar #{PLACEHOLDER_SCALAR_NAME}" }
131
- end
132
-
133
- def self.define_once(definitions, name)
134
- definitions[name] = yield unless definitions.key?(name)
135
- end
136
-
137
- def self.apply_arg_type_replacement(arg, input_type_name)
138
- case arg.type
139
- when ::GraphQL::Language::Nodes::TypeName
140
- arg.instance_variable_set(:@type, ::GraphQL::Language::Nodes::TypeName.new(name: input_type_name)) if arg.type.name == '_'
141
- when ::GraphQL::Language::Nodes::NonNullType
142
- inner = arg.type.of_type
143
- return unless inner.is_a?(::GraphQL::Language::Nodes::TypeName) && inner.name == '_'
144
-
145
- new_type = ::GraphQL::Language::Nodes::NonNullType.new(of_type: ::GraphQL::Language::Nodes::TypeName.new(name: input_type_name))
146
- arg.instance_variable_set(:@type, new_type)
147
- end
148
- end
149
-
150
- def self.build_where_condition_input(type_name, column_enum_name, relation_type_name, directive)
151
- column_type = directive_argument_value(directive, 'columnsEnum') || column_enum_name
152
-
153
- fields = []
154
- fields << " column: #{column_type}"
155
- fields << " operator: #{SQLOPERATOR_TYPE_NAME} = EQ"
156
- fields << " value: #{MIXED_SCALAR_NAME}"
157
- fields << " AND: [#{type_name}!]"
158
- fields << " OR: [#{type_name}!]"
159
- fields << " HAS: #{relation_type_name}" if directive.name == 'whereConditions'
160
-
161
- <<~TEXT.strip
162
- input #{type_name} {
163
- #{fields.join("\n")}
164
- }
165
- TEXT
166
- end
167
-
168
- def self.build_where_condition_relation_input(type_name, condition_type_name)
169
- <<~TEXT.strip
170
- input #{type_name} {
171
- relation: String!
172
- operator: #{SQLOPERATOR_TYPE_NAME} = GTE
173
- amount: Int = 1
174
- condition: #{condition_type_name}
175
- }
176
- TEXT
177
- end
178
-
179
- def self.build_column_enum(name, columns)
180
- values = Array(columns).filter_map do |column|
181
- value = column.to_s
182
- key = value.gsub(/[^0-9A-Za-z]/, '_').upcase
183
- " #{key}"
184
- end.uniq
185
-
186
- <<~TEXT.strip
187
- enum #{name} {
188
- #{values.join("\n")}
189
- }
190
- TEXT
191
- end
192
-
193
- def self.build_sql_operator_enum
194
- values = SUPPORTED_OPERATORS.map { |op| " #{op}" }
195
- <<~TEXT.strip
196
- enum #{SQLOPERATOR_TYPE_NAME} {
197
- #{values.join("\n")}
198
- }
199
- TEXT
200
- end
201
-
202
- def self.directive_argument_value(directive, name)
203
- arg = directive.arguments&.find { |a| a.name == name }
204
- return nil unless arg
205
-
206
- value = unwrap_value(arg.value)
207
- name == 'columns' && value.is_a?(Array) ? value.map { |item| unwrap_value(item) } : value
208
- end
209
-
210
- # Supports both Babel-style AST and raw Ruby value.
211
- def self.unwrap_value(node)
212
- node.respond_to?(:value) ? node.value : node
213
- end
214
-
215
- def self.contains_placeholder_type?(document)
216
- document.definitions.any? do |defn|
217
- next false unless defn.respond_to?(:fields) && defn.fields.respond_to?(:any?)
218
-
219
- (defn.fields || []).any? { |field| placeholder_argument?(field) }
220
- end
221
- rescue StandardError
222
- false
223
- end
224
-
225
- def self.placeholder_argument?(field)
226
- arguments = (field.respond_to?(:arguments) && field.arguments) || []
227
- arguments.any? { |arg| type_references_placeholder?(arg.type) }
228
- end
229
-
230
- def self.type_references_placeholder?(type_node)
231
- case type_node
232
- when ::GraphQL::Language::Nodes::TypeName
233
- type_node.name == PLACEHOLDER_SCALAR_NAME
234
- when ::GraphQL::Language::Nodes::NonNullType, ::GraphQL::Language::Nodes::ListType
235
- type_references_placeholder?(type_node.of_type)
236
- else
237
- false
238
- end
239
- end
240
-
241
- def self.camelize(value)
242
- value.to_s.split(/[_\s]+/).map { |part| part.slice(0, 1).upcase + part.slice(1..).to_s }.join
243
- end
244
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lighthouse::GraphQL::SDLLoader
4
- module_function
5
-
6
- def load_from_folder(folder)
7
- files = Dir[File.join(folder, '**', '*.graphql')]
8
- if files.empty?
9
- default = File.expand_path('../../..//schema.graphql', __dir__)
10
- return File.read(default) if File.exist?(default)
11
-
12
- return ''
13
- end
14
-
15
- files.map! { |f| File.read(f) }
16
- files.join("\n\n")
17
- end
18
-
19
- def load_from_file(path)
20
- File.read(path)
21
- end
22
- end