lighthouse-graphql 0.6.0 → 0.7.1
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 +107 -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 +2 -2
- 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/relay.md +52 -3
- data/docs/resolvers.md +2 -2
- data/docs/schema-organisation.md +177 -0
- data/lib/generators/lighthouse/install/graphql_ruby_install.rb +14 -0
- data/lib/generators/lighthouse/install/install_generator.rb +31 -12
- 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/generators/lighthouse/install/templates/schema.graphql.tt +30 -0
- 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
|
@@ -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
|