graphql 1.12.11 → 1.12.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![CI Suite](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql)
|
5
|
-
[![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
|
6
|
-
[![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
|
7
|
-
[![built with love](https://cloud.githubusercontent.com/assets/2231765/6766607/d07992c6-cfc9-11e4-813f-d9240714dd50.png)](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: []
|