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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +95 -1
- data/README.md +15 -7
- data/docs/best-practices.md +6 -4
- data/docs/configuration.md +19 -5
- data/docs/custom-directives.md +2 -1
- data/docs/directives.md +65 -7
- data/docs/federation.md +1 -1
- data/docs/filtering-and-ordering.md +330 -30
- data/docs/generators.md +1 -1
- data/docs/getting-started.md +43 -10
- data/docs/motivation.md +3 -2
- data/docs/performance.md +4 -3
- data/docs/relationships.md +2 -1
- data/docs/resolvers.md +2 -2
- data/docs/schema-organisation.md +177 -0
- data/lib/generators/lighthouse/install/install_generator.rb +16 -11
- data/lib/generators/lighthouse/install/templates/graphql.config.yml.tt +2 -1
- data/lib/generators/lighthouse/install/templates/initializer.rb.tt +11 -4
- data/lib/lighthouse/ast_builder.rb +124 -0
- data/lib/lighthouse/config.rb +23 -4
- data/lib/lighthouse/contracts/arg_manipulator.rb +9 -5
- data/lib/lighthouse/contracts/field_builder.rb +13 -0
- data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +1 -2
- data/lib/lighthouse/directive_resolvers/all_resolver.rb +0 -2
- data/lib/lighthouse/directive_resolvers/association_resolvers.rb +9 -6
- data/lib/lighthouse/directive_resolvers/base.rb +6 -37
- data/lib/lighthouse/directive_resolvers/builder_applier.rb +21 -6
- data/lib/lighthouse/directive_resolvers/count_resolver.rb +0 -1
- data/lib/lighthouse/directive_resolvers/first_resolver.rb +0 -2
- data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +2 -3
- data/lib/lighthouse/directives/arguments/order_by.rb +190 -26
- data/lib/lighthouse/directives/arguments/where_conditions.rb +25 -0
- data/lib/lighthouse/directives/arguments/where_conditions_base.rb +84 -0
- data/lib/lighthouse/directives/arguments/where_has_conditions.rb +56 -0
- data/lib/lighthouse/directives/definitions.rb +0 -1
- data/lib/lighthouse/directives/generated_types.rb +103 -0
- data/lib/lighthouse/directives/registry.rb +39 -10
- data/lib/lighthouse/document_ast.rb +111 -0
- data/lib/lighthouse/graphql/version.rb +1 -1
- data/lib/lighthouse/ide_helper.rb +80 -23
- data/lib/lighthouse/namespaces.rb +1 -0
- data/lib/lighthouse/order_by/types.rb +137 -0
- data/lib/lighthouse/railtie.rb +9 -2
- data/lib/lighthouse/rb_lighthouse.rb +15 -5
- data/lib/lighthouse/resolve_info.rb +5 -5
- data/lib/lighthouse/schema_cache.rb +8 -4
- data/lib/lighthouse/schema_factory.rb +6 -9
- data/lib/lighthouse/schema_generator.rb +4 -15
- data/lib/lighthouse/schema_stitcher.rb +64 -0
- data/lib/lighthouse/support/naming.rb +0 -12
- data/lib/lighthouse/where_conditions/columns.rb +36 -0
- data/lib/lighthouse/where_conditions/handler.rb +152 -0
- data/lib/lighthouse/where_conditions/json_column.rb +99 -0
- data/lib/lighthouse/where_conditions/operator_map.rb +1 -4
- data/lib/lighthouse/where_conditions/types.rb +110 -0
- data/sig/lighthouse/directives.rbs +16 -3
- data/sig/lighthouse/graphql.rbs +48 -4
- data/sig/lighthouse.rbs +8 -2
- metadata +15 -4
- data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +0 -170
- data/lib/lighthouse/directives/where_conditions_directive.rb +0 -244
- 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
|
-
|
|
96
|
-
def self.
|
|
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.
|
|
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/
|
|
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/
|
|
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
|