graphql 1.12.11 → 1.12.15
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/ast/field_usage.rb +24 -1
- data/lib/graphql/backtrace/table.rb +1 -1
- data/lib/graphql/backtrace/tracer.rb +7 -4
- data/lib/graphql/dataloader/null_dataloader.rb +1 -0
- data/lib/graphql/dataloader.rb +15 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -2
- data/lib/graphql/execution/interpreter/resolve.rb +6 -2
- data/lib/graphql/execution/interpreter/runtime.rb +332 -76
- data/lib/graphql/execution/interpreter.rb +3 -3
- data/lib/graphql/execution/lazy.rb +5 -1
- data/lib/graphql/query.rb +1 -1
- data/lib/graphql/schema/build_from_definition.rb +8 -2
- data/lib/graphql/schema/directive/transform.rb +1 -1
- data/lib/graphql/schema/enum.rb +10 -1
- data/lib/graphql/schema/input_object.rb +11 -15
- data/lib/graphql/schema/member/build_type.rb +1 -0
- data/lib/graphql/schema/printer.rb +11 -16
- data/lib/graphql/schema/resolver.rb +6 -1
- data/lib/graphql/schema.rb +8 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -1
- data/lib/graphql/types/relay/has_node_field.rb +1 -1
- data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
- data/lib/graphql/types/relay/node_field.rb +2 -2
- data/lib/graphql/types/relay/nodes_field.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- data/readme.md +0 -3
- metadata +6 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f858de6eaac686676c159e87ae2f200b6c69b9b6c1da8e7459595b49f4f44e7a
|
4
|
+
data.tar.gz: afd8686f7897896919bef91076285f0763e49076759036352bd212d41c1bb47f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6774f423bdf61cf07d842ca00f517068139a86da2d6e0d6b7e15e7970aaf5ce1b07e58d78c0a13a1ccf0a8db87c48d4061e151ca42bc21731e26958ef96ad922
|
7
|
+
data.tar.gz: 31ca7b94bacf8df5a8c27c0e3c2f831362970e49c65c8b0a284095c1bd392af26347cecc78e60dc91ff4581a9a3d55dc08204eb0230ed641f2907e009a4f7f3e
|
@@ -7,6 +7,7 @@ module GraphQL
|
|
7
7
|
super
|
8
8
|
@used_fields = Set.new
|
9
9
|
@used_deprecated_fields = Set.new
|
10
|
+
@used_deprecated_arguments = Set.new
|
10
11
|
end
|
11
12
|
|
12
13
|
def on_leave_field(node, parent, visitor)
|
@@ -14,14 +15,36 @@ module GraphQL
|
|
14
15
|
field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
|
15
16
|
@used_fields << field
|
16
17
|
@used_deprecated_fields << field if field_defn.deprecation_reason
|
18
|
+
|
19
|
+
extract_deprecated_arguments(visitor.query.arguments_for(node, visitor.field_definition).argument_values)
|
17
20
|
end
|
18
21
|
|
19
22
|
def result
|
20
23
|
{
|
21
24
|
used_fields: @used_fields.to_a,
|
22
|
-
used_deprecated_fields: @used_deprecated_fields.to_a
|
25
|
+
used_deprecated_fields: @used_deprecated_fields.to_a,
|
26
|
+
used_deprecated_arguments: @used_deprecated_arguments.to_a,
|
23
27
|
}
|
24
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def extract_deprecated_arguments(argument_values)
|
33
|
+
argument_values.each_pair do |_argument_name, argument|
|
34
|
+
if argument.definition.deprecation_reason
|
35
|
+
@used_deprecated_arguments << argument.definition.path
|
36
|
+
end
|
37
|
+
|
38
|
+
if argument.definition.type.kind.input_object?
|
39
|
+
extract_deprecated_arguments(argument.value.arguments.argument_values)
|
40
|
+
elsif argument.definition.type.list?
|
41
|
+
argument
|
42
|
+
.value
|
43
|
+
.select { |value| value.respond_to?(:arguments) }
|
44
|
+
.each { |value| extract_deprecated_arguments(value.arguments.argument_values) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
25
48
|
end
|
26
49
|
end
|
27
50
|
end
|
@@ -15,14 +15,17 @@ module GraphQL
|
|
15
15
|
# No query context yet
|
16
16
|
nil
|
17
17
|
when "validate", "analyze_query", "execute_query", "execute_query_lazy"
|
18
|
-
query = metadata[:query] || metadata[:queries].first
|
19
18
|
push_key = []
|
20
|
-
|
21
|
-
|
19
|
+
if (query = metadata[:query]) || ((queries = metadata[:queries]) && (query = queries.first))
|
20
|
+
push_data = query
|
21
|
+
multiplex = query.multiplex
|
22
|
+
elsif (multiplex = metadata[:multiplex])
|
23
|
+
push_data = multiplex.queries.first
|
24
|
+
end
|
22
25
|
when "execute_field", "execute_field_lazy"
|
23
26
|
query = metadata[:query] || raise(ArgumentError, "Add `legacy: true` to use GraphQL::Backtrace without the interpreter runtime.")
|
24
27
|
multiplex = query.multiplex
|
25
|
-
push_key = metadata[:path]
|
28
|
+
push_key = metadata[:path]
|
26
29
|
parent_frame = multiplex.context[:graphql_backtrace_contexts][push_key[0..-2]]
|
27
30
|
|
28
31
|
if parent_frame.is_a?(GraphQL::Query)
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -77,6 +77,21 @@ module GraphQL
|
|
77
77
|
nil
|
78
78
|
end
|
79
79
|
|
80
|
+
# Use a self-contained queue for the work in the block.
|
81
|
+
def run_isolated
|
82
|
+
prev_queue = @pending_jobs
|
83
|
+
@pending_jobs = []
|
84
|
+
res = nil
|
85
|
+
# Make sure the block is inside a Fiber, so it can `Fiber.yield`
|
86
|
+
append_job {
|
87
|
+
res = yield
|
88
|
+
}
|
89
|
+
run
|
90
|
+
res
|
91
|
+
ensure
|
92
|
+
@pending_jobs = prev_queue
|
93
|
+
end
|
94
|
+
|
80
95
|
# @api private Move along, move along
|
81
96
|
def run
|
82
97
|
# At a high level, the algorithm is:
|
@@ -28,11 +28,12 @@ module GraphQL
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def fetch(ast_node, argument_owner, parent_object)
|
31
|
-
@storage[ast_node][argument_owner][parent_object]
|
32
31
|
# If any jobs were enqueued, run them now,
|
33
32
|
# since this might have been called outside of execution.
|
34
33
|
# (The jobs are responsible for updating `result` in-place.)
|
35
|
-
@dataloader.
|
34
|
+
@dataloader.run_isolated do
|
35
|
+
@storage[ast_node][argument_owner][parent_object]
|
36
|
+
end
|
36
37
|
# Ack, the _hash_ is updated, but the key is eventually
|
37
38
|
# overridden with an immutable arguments instance.
|
38
39
|
# The first call queues up the job,
|
@@ -34,7 +34,10 @@ module GraphQL
|
|
34
34
|
next_results = []
|
35
35
|
while results.any?
|
36
36
|
result_value = results.shift
|
37
|
-
if result_value.is_a?(Hash)
|
37
|
+
if result_value.is_a?(Runtime::GraphQLResultHash) || result_value.is_a?(Hash)
|
38
|
+
results.concat(result_value.values)
|
39
|
+
next
|
40
|
+
elsif result_value.is_a?(Runtime::GraphQLResultArray)
|
38
41
|
results.concat(result_value.values)
|
39
42
|
next
|
40
43
|
elsif result_value.is_a?(Array)
|
@@ -46,7 +49,8 @@ module GraphQL
|
|
46
49
|
# Since this field returned another lazy,
|
47
50
|
# add it to the same queue
|
48
51
|
results << loaded_value
|
49
|
-
elsif loaded_value.is_a?(
|
52
|
+
elsif loaded_value.is_a?(Runtime::GraphQLResultHash) || loaded_value.is_a?(Runtime::GraphQLResultArray) ||
|
53
|
+
loaded_value.is_a?(Hash) || loaded_value.is_a?(Array)
|
50
54
|
# Add these values in wholesale --
|
51
55
|
# they might be modified by later work in the dataloader.
|
52
56
|
next_results << loaded_value
|
@@ -10,8 +10,6 @@ module GraphQL
|
|
10
10
|
class Runtime
|
11
11
|
|
12
12
|
module GraphQLResult
|
13
|
-
# These methods are private concerns of GraphQL-Ruby,
|
14
|
-
# they aren't guaranteed to continue working in the future.
|
15
13
|
attr_accessor :graphql_dead, :graphql_parent, :graphql_result_name
|
16
14
|
# Although these are used by only one of the Result classes,
|
17
15
|
# it's handy to have the methods implemented on both (even though they just return `nil`)
|
@@ -20,14 +18,116 @@ module GraphQL
|
|
20
18
|
attr_accessor :graphql_non_null_field_names
|
21
19
|
# @return [nil, true]
|
22
20
|
attr_accessor :graphql_non_null_list_items
|
21
|
+
|
22
|
+
# @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
|
23
|
+
attr_accessor :graphql_result_data
|
23
24
|
end
|
24
25
|
|
25
|
-
class GraphQLResultHash
|
26
|
+
class GraphQLResultHash
|
27
|
+
def initialize
|
28
|
+
# Jump through some hoops to avoid creating this duplicate hash if at all possible.
|
29
|
+
@graphql_metadata = nil
|
30
|
+
@graphql_result_data = {}
|
31
|
+
end
|
32
|
+
|
26
33
|
include GraphQLResult
|
34
|
+
|
35
|
+
attr_accessor :graphql_merged_into
|
36
|
+
|
37
|
+
def []=(key, value)
|
38
|
+
# This is a hack.
|
39
|
+
# Basically, this object is merged into the root-level result at some point.
|
40
|
+
# But the problem is, some lazies are created whose closures retain reference to _this_
|
41
|
+
# object. When those lazies are resolved, they cause an update to this object.
|
42
|
+
#
|
43
|
+
# In order to return a proper top-level result, we have to update that top-level result object.
|
44
|
+
# In order to return a proper partial result (eg, for a directive), we have to update this object, too.
|
45
|
+
# Yowza.
|
46
|
+
if (t = @graphql_merged_into)
|
47
|
+
t[key] = value
|
48
|
+
end
|
49
|
+
|
50
|
+
if value.respond_to?(:graphql_result_data)
|
51
|
+
@graphql_result_data[key] = value.graphql_result_data
|
52
|
+
# If we encounter some part of this response that requires metadata tracking,
|
53
|
+
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
54
|
+
(@graphql_metadata ||= @graphql_result_data.dup)[key] = value
|
55
|
+
else
|
56
|
+
@graphql_result_data[key] = value
|
57
|
+
# keep this up-to-date if it's been initialized
|
58
|
+
@graphql_metadata && @graphql_metadata[key] = value
|
59
|
+
end
|
60
|
+
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete(key)
|
65
|
+
@graphql_metadata && @graphql_metadata.delete(key)
|
66
|
+
@graphql_result_data.delete(key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def each
|
70
|
+
(@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def values
|
74
|
+
(@graphql_metadata || @graphql_result_data).values
|
75
|
+
end
|
76
|
+
|
77
|
+
def key?(k)
|
78
|
+
@graphql_result_data.key?(k)
|
79
|
+
end
|
80
|
+
|
81
|
+
def [](k)
|
82
|
+
(@graphql_metadata || @graphql_result_data)[k]
|
83
|
+
end
|
27
84
|
end
|
28
85
|
|
29
|
-
class GraphQLResultArray
|
86
|
+
class GraphQLResultArray
|
30
87
|
include GraphQLResult
|
88
|
+
|
89
|
+
def initialize
|
90
|
+
# Avoid this duplicate allocation if possible -
|
91
|
+
# but it will require some work to keep it up-to-date if it's created.
|
92
|
+
@graphql_metadata = nil
|
93
|
+
@graphql_result_data = []
|
94
|
+
end
|
95
|
+
|
96
|
+
def graphql_skip_at(index)
|
97
|
+
# Mark this index as dead. It's tricky because some indices may already be storing
|
98
|
+
# `Lazy`s. So the runtime is still holding indexes _before_ skipping,
|
99
|
+
# this object has to coordinate incoming writes to account for any already-skipped indices.
|
100
|
+
@skip_indices ||= []
|
101
|
+
@skip_indices << index
|
102
|
+
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
|
103
|
+
delete_at_index = index - offset_by
|
104
|
+
@graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
|
105
|
+
@graphql_result_data.delete_at(delete_at_index)
|
106
|
+
end
|
107
|
+
|
108
|
+
def []=(idx, value)
|
109
|
+
if @skip_indices
|
110
|
+
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
111
|
+
idx -= offset_by
|
112
|
+
end
|
113
|
+
if value.respond_to?(:graphql_result_data)
|
114
|
+
@graphql_result_data[idx] = value.graphql_result_data
|
115
|
+
(@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
|
116
|
+
else
|
117
|
+
@graphql_result_data[idx] = value
|
118
|
+
@graphql_metadata && @graphql_metadata[idx] = value
|
119
|
+
end
|
120
|
+
|
121
|
+
value
|
122
|
+
end
|
123
|
+
|
124
|
+
def values
|
125
|
+
(@graphql_metadata || @graphql_result_data)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class GraphQLSelectionSet < Hash
|
130
|
+
attr_accessor :graphql_directives
|
31
131
|
end
|
32
132
|
|
33
133
|
# @return [GraphQL::Query]
|
@@ -39,9 +139,6 @@ module GraphQL
|
|
39
139
|
# @return [GraphQL::Query::Context]
|
40
140
|
attr_reader :context
|
41
141
|
|
42
|
-
# @return [Hash]
|
43
|
-
attr_reader :response
|
44
|
-
|
45
142
|
def initialize(query:)
|
46
143
|
@query = query
|
47
144
|
@dataloader = query.multiplex.dataloader
|
@@ -50,6 +147,14 @@ module GraphQL
|
|
50
147
|
@multiplex_context = query.multiplex.context
|
51
148
|
@interpreter_context = @context.namespace(:interpreter)
|
52
149
|
@response = GraphQLResultHash.new
|
150
|
+
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
|
151
|
+
@runtime_directive_names = []
|
152
|
+
noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
|
153
|
+
schema.directives.each do |name, dir_defn|
|
154
|
+
if dir_defn.method(:resolve).owner != noop_resolve_owner
|
155
|
+
@runtime_directive_names << name
|
156
|
+
end
|
157
|
+
end
|
53
158
|
# A cache of { Class => { String => Schema::Field } }
|
54
159
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
55
160
|
# during the lifetime of a query
|
@@ -58,10 +163,24 @@ module GraphQL
|
|
58
163
|
@lazy_cache = {}
|
59
164
|
end
|
60
165
|
|
166
|
+
def final_result
|
167
|
+
@response && @response.graphql_result_data
|
168
|
+
end
|
169
|
+
|
61
170
|
def inspect
|
62
171
|
"#<#{self.class.name} response=#{@response.inspect}>"
|
63
172
|
end
|
64
173
|
|
174
|
+
def tap_or_each(obj_or_array)
|
175
|
+
if obj_or_array.is_a?(Array)
|
176
|
+
obj_or_array.each do |item|
|
177
|
+
yield(item, true)
|
178
|
+
end
|
179
|
+
else
|
180
|
+
yield(obj_or_array, false)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
65
184
|
# This _begins_ the execution. Some deferred work
|
66
185
|
# might be stored up in lazies.
|
67
186
|
# @return [void]
|
@@ -78,20 +197,40 @@ module GraphQL
|
|
78
197
|
# Root .authorized? returned false.
|
79
198
|
@response = nil
|
80
199
|
else
|
81
|
-
resolve_with_directives(object_proxy, root_operation) do # execute query level directives
|
200
|
+
resolve_with_directives(object_proxy, root_operation.directives) do # execute query level directives
|
82
201
|
gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
202
|
+
# This is kind of a hack -- `gathered_selections` is an Array if any of the selections
|
203
|
+
# require isolation during execution (because of runtime directives). In that case,
|
204
|
+
# make a new, isolated result hash for writing the result into. (That isolated response
|
205
|
+
# is eventually merged back into the main response)
|
206
|
+
#
|
207
|
+
# Otherwise, `gathered_selections` is a hash of selections which can be
|
208
|
+
# directly evaluated and the results can be written right into the main response hash.
|
209
|
+
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
210
|
+
if is_selection_array
|
211
|
+
selection_response = GraphQLResultHash.new
|
212
|
+
final_response = @response
|
213
|
+
else
|
214
|
+
selection_response = @response
|
215
|
+
final_response = nil
|
216
|
+
end
|
217
|
+
|
218
|
+
@dataloader.append_job {
|
219
|
+
set_all_interpreter_context(query.root_value, nil, nil, path)
|
220
|
+
resolve_with_directives(object_proxy, selections.graphql_directives) do
|
221
|
+
evaluate_selections(
|
222
|
+
path,
|
223
|
+
context.scoped_context,
|
224
|
+
object_proxy,
|
225
|
+
root_type,
|
226
|
+
root_op_type == "mutation",
|
227
|
+
selections,
|
228
|
+
selection_response,
|
229
|
+
final_response,
|
230
|
+
)
|
231
|
+
end
|
232
|
+
}
|
233
|
+
end
|
95
234
|
end
|
96
235
|
end
|
97
236
|
delete_interpreter_context(:current_path)
|
@@ -101,15 +240,36 @@ module GraphQL
|
|
101
240
|
nil
|
102
241
|
end
|
103
242
|
|
104
|
-
|
243
|
+
# @return [void]
|
244
|
+
def deep_merge_selection_result(from_result, into_result)
|
245
|
+
from_result.each do |key, value|
|
246
|
+
if !into_result.key?(key)
|
247
|
+
into_result[key] = value
|
248
|
+
else
|
249
|
+
case value
|
250
|
+
when GraphQLResultHash
|
251
|
+
deep_merge_selection_result(value, into_result[key])
|
252
|
+
else
|
253
|
+
# We have to assume that, since this passed the `fields_will_merge` selection,
|
254
|
+
# that the old and new values are the same.
|
255
|
+
# There's no special handling of arrays because currently, there's no way to split the execution
|
256
|
+
# of a list over several concurrent flows.
|
257
|
+
into_result[key] = value
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
from_result.graphql_merged_into = into_result
|
262
|
+
nil
|
263
|
+
end
|
264
|
+
|
265
|
+
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
|
105
266
|
selections.each do |node|
|
106
267
|
# Skip gathering this if the directive says so
|
107
268
|
if !directives_include?(node, owner_object, owner_type)
|
108
269
|
next
|
109
270
|
end
|
110
271
|
|
111
|
-
|
112
|
-
when GraphQL::Language::Nodes::Field
|
272
|
+
if node.is_a?(GraphQL::Language::Nodes::Field)
|
113
273
|
response_key = node.alias || node.name
|
114
274
|
selections = selections_by_name[response_key]
|
115
275
|
# if there was already a selection of this field,
|
@@ -125,52 +285,77 @@ module GraphQL
|
|
125
285
|
# No selection was found for this field yet
|
126
286
|
selections_by_name[response_key] = node
|
127
287
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
288
|
+
else
|
289
|
+
# This is an InlineFragment or a FragmentSpread
|
290
|
+
if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
291
|
+
next_selections = GraphQLSelectionSet.new
|
292
|
+
next_selections.graphql_directives = node.directives
|
293
|
+
if selections_to_run
|
294
|
+
selections_to_run << next_selections
|
295
|
+
else
|
296
|
+
selections_to_run = []
|
297
|
+
selections_to_run << selections_by_name
|
298
|
+
selections_to_run << next_selections
|
299
|
+
end
|
300
|
+
else
|
301
|
+
next_selections = selections_by_name
|
302
|
+
end
|
303
|
+
|
304
|
+
case node
|
305
|
+
when GraphQL::Language::Nodes::InlineFragment
|
306
|
+
if node.type
|
307
|
+
type_defn = schema.get_type(node.type.name)
|
308
|
+
|
309
|
+
# Faster than .map{}.include?()
|
310
|
+
query.warden.possible_types(type_defn).each do |t|
|
311
|
+
if t == owner_type
|
312
|
+
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
313
|
+
break
|
314
|
+
end
|
315
|
+
end
|
316
|
+
else
|
317
|
+
# it's an untyped fragment, definitely continue
|
318
|
+
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
319
|
+
end
|
320
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
321
|
+
fragment_def = query.fragments[node.name]
|
322
|
+
type_defn = schema.get_type(fragment_def.type.name)
|
323
|
+
possible_types = query.warden.possible_types(type_defn)
|
324
|
+
possible_types.each do |t|
|
133
325
|
if t == owner_type
|
134
|
-
gather_selections(owner_object, owner_type,
|
326
|
+
gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
|
135
327
|
break
|
136
328
|
end
|
137
329
|
end
|
138
330
|
else
|
139
|
-
|
140
|
-
gather_selections(owner_object, owner_type, node.selections, selections_by_name)
|
331
|
+
raise "Invariant: unexpected selection class: #{node.class}"
|
141
332
|
end
|
142
|
-
when GraphQL::Language::Nodes::FragmentSpread
|
143
|
-
fragment_def = query.fragments[node.name]
|
144
|
-
type_defn = schema.get_type(fragment_def.type.name)
|
145
|
-
possible_types = query.warden.possible_types(type_defn)
|
146
|
-
possible_types.each do |t|
|
147
|
-
if t == owner_type
|
148
|
-
gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
|
149
|
-
break
|
150
|
-
end
|
151
|
-
end
|
152
|
-
else
|
153
|
-
raise "Invariant: unexpected selection class: #{node.class}"
|
154
333
|
end
|
155
334
|
end
|
156
|
-
selections_by_name
|
335
|
+
selections_to_run || selections_by_name
|
157
336
|
end
|
158
337
|
|
159
338
|
NO_ARGS = {}.freeze
|
160
339
|
|
161
340
|
# @return [void]
|
162
|
-
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result)
|
341
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result) # rubocop:disable Metrics/ParameterLists
|
163
342
|
set_all_interpreter_context(owner_object, nil, nil, path)
|
164
343
|
|
344
|
+
finished_jobs = 0
|
345
|
+
enqueued_jobs = gathered_selections.size
|
165
346
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
166
347
|
@dataloader.append_job {
|
167
348
|
evaluate_selection(
|
168
349
|
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
|
169
350
|
)
|
351
|
+
finished_jobs += 1
|
352
|
+
if target_result && finished_jobs == enqueued_jobs
|
353
|
+
deep_merge_selection_result(selections_result, target_result)
|
354
|
+
end
|
170
355
|
}
|
171
356
|
end
|
172
357
|
|
173
|
-
|
358
|
+
selections_result
|
174
359
|
end
|
175
360
|
|
176
361
|
attr_reader :progress_path
|
@@ -292,12 +477,17 @@ module GraphQL
|
|
292
477
|
# Optimize for the case that field is selected only once
|
293
478
|
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
294
479
|
next_selections = ast_node.selections
|
480
|
+
directives = ast_node.directives
|
295
481
|
else
|
296
482
|
next_selections = []
|
297
|
-
|
483
|
+
directives = []
|
484
|
+
field_ast_nodes.each { |f|
|
485
|
+
next_selections.concat(f.selections)
|
486
|
+
directives.concat(f.directives)
|
487
|
+
}
|
298
488
|
end
|
299
489
|
|
300
|
-
field_result = resolve_with_directives(object,
|
490
|
+
field_result = resolve_with_directives(object, directives) do
|
301
491
|
# Actually call the field resolver and capture the result
|
302
492
|
app_result = begin
|
303
493
|
query.with_error_handling do
|
@@ -329,15 +519,7 @@ module GraphQL
|
|
329
519
|
end
|
330
520
|
|
331
521
|
def dead_result?(selection_result)
|
332
|
-
|
333
|
-
while r
|
334
|
-
if r.graphql_dead
|
335
|
-
return true
|
336
|
-
else
|
337
|
-
r = r.graphql_parent
|
338
|
-
end
|
339
|
-
end
|
340
|
-
false
|
522
|
+
selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
341
523
|
end
|
342
524
|
|
343
525
|
def set_result(selection_result, result_name, value)
|
@@ -361,9 +543,7 @@ module GraphQL
|
|
361
543
|
@response = nil
|
362
544
|
else
|
363
545
|
set_result(parent, name_in_parent, nil)
|
364
|
-
|
365
|
-
# a `nil`, it's marked dead. TODO: check the spec, is there a reason for this?
|
366
|
-
parent.graphql_dead = true
|
546
|
+
set_graphql_dead(selection_result)
|
367
547
|
end
|
368
548
|
else
|
369
549
|
selection_result[result_name] = value
|
@@ -371,6 +551,21 @@ module GraphQL
|
|
371
551
|
end
|
372
552
|
end
|
373
553
|
|
554
|
+
# Mark this node and any already-registered children as dead,
|
555
|
+
# so that it accepts no more writes.
|
556
|
+
def set_graphql_dead(selection_result)
|
557
|
+
case selection_result
|
558
|
+
when GraphQLResultArray
|
559
|
+
selection_result.graphql_dead = true
|
560
|
+
selection_result.values.each { |v| set_graphql_dead(v) }
|
561
|
+
when GraphQLResultHash
|
562
|
+
selection_result.graphql_dead = true
|
563
|
+
selection_result.each { |k, v| set_graphql_dead(v) }
|
564
|
+
else
|
565
|
+
# It's a scalar, no way to mark it dead.
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
374
569
|
HALT = Object.new
|
375
570
|
def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
376
571
|
case value
|
@@ -390,11 +585,13 @@ module GraphQL
|
|
390
585
|
# to avoid the overhead of checking three different classes
|
391
586
|
# every time.
|
392
587
|
if value.is_a?(GraphQL::ExecutionError)
|
393
|
-
if !dead_result?(selection_result)
|
588
|
+
if selection_result.nil? || !dead_result?(selection_result)
|
394
589
|
value.path ||= path
|
395
590
|
value.ast_node ||= ast_node
|
396
591
|
context.errors << value
|
397
|
-
|
592
|
+
if selection_result
|
593
|
+
set_result(selection_result, result_name, nil)
|
594
|
+
end
|
398
595
|
end
|
399
596
|
HALT
|
400
597
|
elsif value.is_a?(GraphQL::UnauthorizedError)
|
@@ -407,6 +604,17 @@ module GraphQL
|
|
407
604
|
end
|
408
605
|
continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
409
606
|
elsif GraphQL::Execution::Execute::SKIP == value
|
607
|
+
# It's possible a lazy was already written here
|
608
|
+
case selection_result
|
609
|
+
when GraphQLResultHash
|
610
|
+
selection_result.delete(result_name)
|
611
|
+
when GraphQLResultArray
|
612
|
+
selection_result.graphql_skip_at(result_name)
|
613
|
+
when nil
|
614
|
+
# this can happen with directives
|
615
|
+
else
|
616
|
+
raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})"
|
617
|
+
end
|
410
618
|
HALT
|
411
619
|
else
|
412
620
|
# What could this actually _be_? Anyhow,
|
@@ -416,13 +624,15 @@ module GraphQL
|
|
416
624
|
when Array
|
417
625
|
# It's an array full of execution errors; add them all.
|
418
626
|
if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
|
419
|
-
if !dead_result?(selection_result)
|
627
|
+
if selection_result.nil? || !dead_result?(selection_result)
|
420
628
|
value.each_with_index do |error, index|
|
421
629
|
error.ast_node ||= ast_node
|
422
|
-
error.path ||= path + (field.type.list? ? [index] : [])
|
630
|
+
error.path ||= path + ((field && field.type.list?) ? [index] : [])
|
423
631
|
context.errors << error
|
424
632
|
end
|
425
|
-
|
633
|
+
if selection_result
|
634
|
+
set_result(selection_result, result_name, nil)
|
635
|
+
end
|
426
636
|
end
|
427
637
|
HALT
|
428
638
|
else
|
@@ -488,8 +698,39 @@ module GraphQL
|
|
488
698
|
response_hash.graphql_result_name = result_name
|
489
699
|
set_result(selection_result, result_name, response_hash)
|
490
700
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
491
|
-
|
492
|
-
|
701
|
+
# There are two possibilities for `gathered_selections`:
|
702
|
+
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
703
|
+
# This case is handled below, and the result can be written right into the main `response_hash` above.
|
704
|
+
# In this case, `gathered_selections` is a hash of selections.
|
705
|
+
# 2. Some selections of this object have runtime directives that may or may not modify execution.
|
706
|
+
# That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
|
707
|
+
# eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
|
708
|
+
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
|
709
|
+
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
710
|
+
if is_selection_array
|
711
|
+
this_result = GraphQLResultHash.new
|
712
|
+
this_result.graphql_parent = selection_result
|
713
|
+
this_result.graphql_result_name = result_name
|
714
|
+
final_result = response_hash
|
715
|
+
else
|
716
|
+
this_result = response_hash
|
717
|
+
final_result = nil
|
718
|
+
end
|
719
|
+
set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
|
720
|
+
resolve_with_directives(continue_value, selections.graphql_directives) do
|
721
|
+
evaluate_selections(
|
722
|
+
path,
|
723
|
+
context.scoped_context,
|
724
|
+
continue_value,
|
725
|
+
current_type,
|
726
|
+
false,
|
727
|
+
selections,
|
728
|
+
this_result,
|
729
|
+
final_result,
|
730
|
+
)
|
731
|
+
this_result
|
732
|
+
end
|
733
|
+
end
|
493
734
|
end
|
494
735
|
end
|
495
736
|
when "LIST"
|
@@ -534,13 +775,13 @@ module GraphQL
|
|
534
775
|
end
|
535
776
|
end
|
536
777
|
|
537
|
-
def resolve_with_directives(object,
|
538
|
-
return yield if
|
539
|
-
run_directive(object,
|
778
|
+
def resolve_with_directives(object, directives, &block)
|
779
|
+
return yield if directives.nil? || directives.empty?
|
780
|
+
run_directive(object, directives, 0, &block)
|
540
781
|
end
|
541
782
|
|
542
|
-
def run_directive(object,
|
543
|
-
dir_node =
|
783
|
+
def run_directive(object, directives, idx, &block)
|
784
|
+
dir_node = directives[idx]
|
544
785
|
if !dir_node
|
545
786
|
yield
|
546
787
|
else
|
@@ -548,9 +789,24 @@ module GraphQL
|
|
548
789
|
if !dir_defn.is_a?(Class)
|
549
790
|
dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
|
550
791
|
end
|
551
|
-
|
552
|
-
|
553
|
-
|
792
|
+
raw_dir_args = arguments(nil, dir_defn, dir_node)
|
793
|
+
dir_args = continue_value(
|
794
|
+
@context[:current_path], # path
|
795
|
+
raw_dir_args, # value
|
796
|
+
dir_defn, # parent_type
|
797
|
+
nil, # field
|
798
|
+
false, # is_non_null
|
799
|
+
dir_node, # ast_node
|
800
|
+
nil, # result_name
|
801
|
+
nil, # selection_result
|
802
|
+
)
|
803
|
+
|
804
|
+
if dir_args == HALT
|
805
|
+
nil
|
806
|
+
else
|
807
|
+
dir_defn.resolve(object, dir_args, context) do
|
808
|
+
run_directive(object, directives, idx + 1, &block)
|
809
|
+
end
|
554
810
|
end
|
555
811
|
end
|
556
812
|
end
|
@@ -559,7 +815,7 @@ module GraphQL
|
|
559
815
|
def directives_include?(node, graphql_object, parent_type)
|
560
816
|
node.directives.each do |dir_node|
|
561
817
|
dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
|
562
|
-
args = arguments(graphql_object, dir_defn, dir_node)
|
818
|
+
args = arguments(graphql_object, dir_defn, dir_node)
|
563
819
|
if !dir_defn.include?(graphql_object, args, context)
|
564
820
|
return false
|
565
821
|
end
|
@@ -18,7 +18,7 @@ module GraphQL
|
|
18
18
|
def execute(_operation, _root_type, query)
|
19
19
|
runtime = evaluate(query)
|
20
20
|
sync_lazies(query: query)
|
21
|
-
runtime.
|
21
|
+
runtime.final_result
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.use(schema_class)
|
@@ -56,7 +56,7 @@ module GraphQL
|
|
56
56
|
|
57
57
|
def self.finish_query(query, _multiplex)
|
58
58
|
{
|
59
|
-
"data" => query.context.namespace(:interpreter)[:runtime].
|
59
|
+
"data" => query.context.namespace(:interpreter)[:runtime].final_result
|
60
60
|
}
|
61
61
|
end
|
62
62
|
|
@@ -87,7 +87,7 @@ module GraphQL
|
|
87
87
|
final_values = queries.map do |query|
|
88
88
|
runtime = query.context.namespace(:interpreter)[:runtime]
|
89
89
|
# it might not be present if the query has an error
|
90
|
-
runtime ? runtime.
|
90
|
+
runtime ? runtime.final_result : nil
|
91
91
|
end
|
92
92
|
final_values.compact!
|
93
93
|
tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
|
@@ -48,7 +48,11 @@ module GraphQL
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
# `SKIP` was made into a subclass of `GraphQL::Error` to improve runtime performance
|
52
|
+
# (fewer clauses in a hot `case` block), but now it requires special handling here.
|
53
|
+
# I think it's still worth it for the performance win, but if the number of special
|
54
|
+
# cases grows, then maybe it's worth rethinking somehow.
|
55
|
+
if @value.is_a?(StandardError) && @value != GraphQL::Execution::Execute::SKIP
|
52
56
|
raise @value
|
53
57
|
else
|
54
58
|
@value
|
data/lib/graphql/query.rb
CHANGED
@@ -270,7 +270,7 @@ module GraphQL
|
|
270
270
|
# @return [String, nil] Returns nil if the query is invalid.
|
271
271
|
def sanitized_query_string(inline_variables: true)
|
272
272
|
with_prepared_ast {
|
273
|
-
|
273
|
+
schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string
|
274
274
|
}
|
275
275
|
end
|
276
276
|
|
@@ -47,10 +47,16 @@ module GraphQL
|
|
47
47
|
# _while_ building the schema.
|
48
48
|
# It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
|
49
49
|
directive_type_resolver = nil
|
50
|
-
directive_type_resolver = build_resolve_type(
|
50
|
+
directive_type_resolver = build_resolve_type(types, directives, ->(type_name) {
|
51
51
|
types[type_name] ||= begin
|
52
52
|
defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
|
53
|
-
|
53
|
+
if defn
|
54
|
+
build_definition_from_node(defn, directive_type_resolver, default_resolve)
|
55
|
+
elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
|
56
|
+
built_in_defn
|
57
|
+
else
|
58
|
+
raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it."
|
59
|
+
end
|
54
60
|
end
|
55
61
|
})
|
56
62
|
|
@@ -39,7 +39,7 @@ module GraphQL
|
|
39
39
|
transform_name = arguments[:by]
|
40
40
|
if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name)
|
41
41
|
return_value = return_value.public_send(transform_name)
|
42
|
-
response = context.namespace(:interpreter)[:runtime].
|
42
|
+
response = context.namespace(:interpreter)[:runtime].final_result
|
43
43
|
*keys, last = path
|
44
44
|
keys.each do |key|
|
45
45
|
if response && (response = response[key])
|
data/lib/graphql/schema/enum.rb
CHANGED
@@ -24,6 +24,15 @@ module GraphQL
|
|
24
24
|
extend GraphQL::Schema::Member::ValidatesInput
|
25
25
|
|
26
26
|
class UnresolvedValueError < GraphQL::EnumType::UnresolvedValueError
|
27
|
+
def initialize(value:, enum:, context:)
|
28
|
+
fix_message = ", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead."
|
29
|
+
message = if (cp = context[:current_path]) && (cf = context[:current_field])
|
30
|
+
"`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}"
|
31
|
+
else
|
32
|
+
"`#{value.inspect}` was returned for `#{enum.graphql_name}`#{fix_message}"
|
33
|
+
end
|
34
|
+
super(message)
|
35
|
+
end
|
27
36
|
end
|
28
37
|
|
29
38
|
class << self
|
@@ -100,7 +109,7 @@ module GraphQL
|
|
100
109
|
if enum_value
|
101
110
|
enum_value.graphql_name
|
102
111
|
else
|
103
|
-
raise
|
112
|
+
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
|
104
113
|
end
|
105
114
|
end
|
106
115
|
|
@@ -11,6 +11,14 @@ module GraphQL
|
|
11
11
|
|
12
12
|
include GraphQL::Dig
|
13
13
|
|
14
|
+
# @return [GraphQL::Query::Context] The context for this query
|
15
|
+
attr_reader :context
|
16
|
+
# @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
|
17
|
+
attr_reader :arguments
|
18
|
+
|
19
|
+
# Ruby-like hash behaviors, read-only
|
20
|
+
def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty?
|
21
|
+
|
14
22
|
def initialize(arguments = nil, ruby_kwargs: nil, context:, defaults_used:)
|
15
23
|
@context = context
|
16
24
|
if ruby_kwargs
|
@@ -54,19 +62,8 @@ module GraphQL
|
|
54
62
|
@maybe_lazies = maybe_lazies
|
55
63
|
end
|
56
64
|
|
57
|
-
# @return [GraphQL::Query::Context] The context for this query
|
58
|
-
attr_reader :context
|
59
|
-
|
60
|
-
# @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
|
61
|
-
attr_reader :arguments
|
62
|
-
|
63
|
-
# Ruby-like hash behaviors, read-only
|
64
|
-
def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty?
|
65
|
-
|
66
65
|
def to_h
|
67
|
-
@ruby_style_hash
|
68
|
-
h.merge(key => unwrap_value(value))
|
69
|
-
end
|
66
|
+
unwrap_value(@ruby_style_hash)
|
70
67
|
end
|
71
68
|
|
72
69
|
def to_hash
|
@@ -91,8 +88,8 @@ module GraphQL
|
|
91
88
|
when Array
|
92
89
|
value.map { |item| unwrap_value(item) }
|
93
90
|
when Hash
|
94
|
-
value.
|
95
|
-
h.merge(key => unwrap_value(value))
|
91
|
+
value.reduce({}) do |h, (key, value)|
|
92
|
+
h.merge!(key => unwrap_value(value))
|
96
93
|
end
|
97
94
|
when InputObject
|
98
95
|
value.to_h
|
@@ -162,7 +159,6 @@ module GraphQL
|
|
162
159
|
# @api private
|
163
160
|
INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
|
164
161
|
|
165
|
-
|
166
162
|
def validate_non_null_input(input, ctx)
|
167
163
|
result = GraphQL::Query::InputValidationResult.new
|
168
164
|
|
@@ -4,37 +4,32 @@ module GraphQL
|
|
4
4
|
# Used to convert your {GraphQL::Schema} to a GraphQL schema string
|
5
5
|
#
|
6
6
|
# @example print your schema to standard output (via helper)
|
7
|
-
# MySchema = GraphQL::Schema.define(query: QueryType)
|
8
7
|
# puts GraphQL::Schema::Printer.print_schema(MySchema)
|
9
8
|
#
|
10
9
|
# @example print your schema to standard output
|
11
|
-
# MySchema = GraphQL::Schema.define(query: QueryType)
|
12
10
|
# puts GraphQL::Schema::Printer.new(MySchema).print_schema
|
13
11
|
#
|
14
12
|
# @example print a single type to standard output
|
15
|
-
#
|
16
|
-
# name "Query"
|
13
|
+
# class Types::Query < GraphQL::Schema::Object
|
17
14
|
# description "The query root of this schema"
|
18
15
|
#
|
19
|
-
# field :post
|
20
|
-
# type post_type
|
21
|
-
# resolve ->(obj, args, ctx) { Post.find(args["id"]) }
|
22
|
-
# end
|
16
|
+
# field :post, Types::Post, null: true
|
23
17
|
# end
|
24
18
|
#
|
25
|
-
#
|
26
|
-
# name "Post"
|
19
|
+
# class Types::Post < GraphQL::Schema::Object
|
27
20
|
# description "A blog post"
|
28
21
|
#
|
29
|
-
# field :id,
|
30
|
-
# field :title,
|
31
|
-
# field :body,
|
22
|
+
# field :id, ID, null: false
|
23
|
+
# field :title, String, null: false
|
24
|
+
# field :body, String, null: false
|
32
25
|
# end
|
33
26
|
#
|
34
|
-
# MySchema
|
27
|
+
# class MySchema < GraphQL::Schema
|
28
|
+
# query(Types::Query)
|
29
|
+
# end
|
35
30
|
#
|
36
31
|
# printer = GraphQL::Schema::Printer.new(MySchema)
|
37
|
-
# puts printer.print_type(
|
32
|
+
# puts printer.print_type(Types::Post)
|
38
33
|
#
|
39
34
|
class Printer < GraphQL::Language::Printer
|
40
35
|
attr_reader :schema, :warden
|
@@ -87,7 +82,7 @@ module GraphQL
|
|
87
82
|
|
88
83
|
# Return a GraphQL schema string for the defined types in the schema
|
89
84
|
def print_schema
|
90
|
-
print(@document)
|
85
|
+
print(@document) + "\n"
|
91
86
|
end
|
92
87
|
|
93
88
|
def print_type(type)
|
@@ -307,10 +307,15 @@ module GraphQL
|
|
307
307
|
arguments: arguments,
|
308
308
|
null: null,
|
309
309
|
complexity: complexity,
|
310
|
-
extensions: extensions,
|
311
310
|
broadcastable: broadcastable?,
|
312
311
|
}
|
313
312
|
|
313
|
+
# If there aren't any, then the returned array is `[].freeze`,
|
314
|
+
# but passing that along breaks some user code.
|
315
|
+
if (exts = extensions).any?
|
316
|
+
field_opts[:extensions] = exts
|
317
|
+
end
|
318
|
+
|
314
319
|
if has_max_page_size?
|
315
320
|
field_opts[:max_page_size] = max_page_size
|
316
321
|
end
|
data/lib/graphql/schema.rb
CHANGED
@@ -1631,6 +1631,14 @@ module GraphQL
|
|
1631
1631
|
find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers
|
1632
1632
|
end
|
1633
1633
|
|
1634
|
+
def sanitized_printer(new_sanitized_printer = nil)
|
1635
|
+
if new_sanitized_printer
|
1636
|
+
@own_sanitized_printer = new_sanitized_printer
|
1637
|
+
else
|
1638
|
+
@own_sanitized_printer || GraphQL::Language::SanitizedPrinter
|
1639
|
+
end
|
1640
|
+
end
|
1641
|
+
|
1634
1642
|
# Execute a query on itself.
|
1635
1643
|
# @see {Query#initialize} for arguments.
|
1636
1644
|
# @return [Hash] query result, ready to be serialized as JSON
|
@@ -95,6 +95,14 @@ module GraphQL
|
|
95
95
|
@action_cable = action_cable
|
96
96
|
@action_cable_coder = action_cable_coder
|
97
97
|
@serializer = serializer
|
98
|
+
@serialize_with_context = case @serializer.method(:load).arity
|
99
|
+
when 1
|
100
|
+
false
|
101
|
+
when 2
|
102
|
+
true
|
103
|
+
else
|
104
|
+
raise ArgumentError, "#{@serializer} must repond to `.load` accepting one or two arguments"
|
105
|
+
end
|
98
106
|
@transmit_ns = namespace
|
99
107
|
super
|
100
108
|
end
|
@@ -154,7 +162,7 @@ module GraphQL
|
|
154
162
|
# so just run it once, then deliver the result to every subscriber
|
155
163
|
first_event = events.first
|
156
164
|
first_subscription_id = first_event.context.fetch(:subscription_id)
|
157
|
-
object ||=
|
165
|
+
object ||= load_action_cable_message(message, first_event.context)
|
158
166
|
result = execute_update(first_subscription_id, first_event, object)
|
159
167
|
# Having calculated the result _once_, send the same payload to all subscribers
|
160
168
|
events.each do |event|
|
@@ -167,6 +175,18 @@ module GraphQL
|
|
167
175
|
end
|
168
176
|
end
|
169
177
|
|
178
|
+
# This is called to turn an ActionCable-broadcasted string (JSON)
|
179
|
+
# into a query-ready application object.
|
180
|
+
# @param message [String] n ActionCable-broadcasted string (JSON)
|
181
|
+
# @param context [GraphQL::Query::Context] the context of the first event for a given subscription fingerprint
|
182
|
+
def load_action_cable_message(message, context)
|
183
|
+
if @serialize_with_context
|
184
|
+
@serializer.load(message, context)
|
185
|
+
else
|
186
|
+
@serializer.load(message)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
170
190
|
# Return the query from "storage" (in memory)
|
171
191
|
def read_subscription(subscription_id)
|
172
192
|
query = @subscriptions[subscription_id]
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module GraphQL
|
4
4
|
module Types
|
5
5
|
module Relay
|
6
|
+
# Include this module to your root Query type to get a Relay-compliant `node(id: ID!): Node` field that uses the schema's `object_from_id` hook.
|
6
7
|
module HasNodeField
|
7
8
|
def self.included(child_class)
|
8
9
|
child_class.field(**field_options, &field_block)
|
@@ -12,7 +13,6 @@ module GraphQL
|
|
12
13
|
def field_options
|
13
14
|
{
|
14
15
|
name: "node",
|
15
|
-
owner: nil,
|
16
16
|
type: GraphQL::Types::Relay::Node,
|
17
17
|
null: true,
|
18
18
|
description: "Fetches an object given its ID.",
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module GraphQL
|
4
4
|
module Types
|
5
5
|
module Relay
|
6
|
+
# Include this module to your root Query type to get a Relay-style `nodes(id: ID!): [Node]` field that uses the schema's `object_from_id` hook.
|
6
7
|
module HasNodesField
|
7
8
|
def self.included(child_class)
|
8
9
|
child_class.field(**field_options, &field_block)
|
@@ -12,7 +13,6 @@ module GraphQL
|
|
12
13
|
def field_options
|
13
14
|
{
|
14
15
|
name: "nodes",
|
15
|
-
owner: nil,
|
16
16
|
type: [GraphQL::Types::Relay::Node, null: true],
|
17
17
|
null: false,
|
18
18
|
description: "Fetches a list of objects given a list of IDs.",
|
@@ -6,7 +6,7 @@ module GraphQL
|
|
6
6
|
# or use it for inspiration for your own field definition.
|
7
7
|
#
|
8
8
|
# @example Adding this field directly
|
9
|
-
#
|
9
|
+
# include GraphQL::Types::Relay::HasNodeField
|
10
10
|
#
|
11
11
|
# @example Implementing a similar field in your own Query root
|
12
12
|
#
|
@@ -19,7 +19,7 @@ module GraphQL
|
|
19
19
|
# context.schema.object_from_id(id, context)
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
NodeField = GraphQL::Schema::Field.new(**HasNodeField.field_options, &HasNodeField.field_block)
|
22
|
+
NodeField = GraphQL::Schema::Field.new(owner: nil, **HasNodeField.field_options, &HasNodeField.field_block)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -6,7 +6,7 @@ module GraphQL
|
|
6
6
|
# or use it for inspiration for your own field definition.
|
7
7
|
#
|
8
8
|
# @example Adding this field directly
|
9
|
-
#
|
9
|
+
# include GraphQL::Types::Relay::HasNodesField
|
10
10
|
#
|
11
11
|
# @example Implementing a similar field in your own Query root
|
12
12
|
#
|
@@ -21,7 +21,7 @@ module GraphQL
|
|
21
21
|
# end
|
22
22
|
# end
|
23
23
|
#
|
24
|
-
NodesField = GraphQL::Schema::Field.new(**HasNodesField.field_options, &HasNodesField.field_block)
|
24
|
+
NodesField = GraphQL::Schema::Field.new(owner: nil, **HasNodesField.field_options, &HasNodesField.field_block)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -2,9 +2,6 @@
|
|
2
2
|
|
3
3
|
[](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml)
|
4
4
|
[](https://rubygems.org/gems/graphql)
|
5
|
-
[](https://codeclimate.com/github/rmosolgo/graphql-ruby)
|
6
|
-
[](https://codeclimate.com/github/rmosolgo/graphql-ruby)
|
7
|
-
[](https://rmosolgo.github.io/react-badges/)
|
8
5
|
|
9
6
|
A Ruby implementation of [GraphQL](https://graphql.org/).
|
10
7
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.12.
|
4
|
+
version: 1.12.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: codeclimate-test-reporter
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.4'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.4'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: concurrent-ruby
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -699,7 +685,7 @@ metadata:
|
|
699
685
|
source_code_uri: https://github.com/rmosolgo/graphql-ruby
|
700
686
|
bug_tracker_uri: https://github.com/rmosolgo/graphql-ruby/issues
|
701
687
|
mailing_list_uri: https://tinyletter.com/graphql-ruby
|
702
|
-
post_install_message:
|
688
|
+
post_install_message:
|
703
689
|
rdoc_options: []
|
704
690
|
require_paths:
|
705
691
|
- lib
|
@@ -714,8 +700,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
714
700
|
- !ruby/object:Gem::Version
|
715
701
|
version: '0'
|
716
702
|
requirements: []
|
717
|
-
rubygems_version: 3.
|
718
|
-
signing_key:
|
703
|
+
rubygems_version: 3.2.15
|
704
|
+
signing_key:
|
719
705
|
specification_version: 4
|
720
706
|
summary: A GraphQL language and runtime for Ruby
|
721
707
|
test_files: []
|