graphql 1.12.3 → 1.12.4
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/generators/graphql/install_generator.rb +3 -1
- data/lib/generators/graphql/relay.rb +55 -0
- data/lib/generators/graphql/relay_generator.rb +3 -46
- data/lib/graphql.rb +1 -1
- data/lib/graphql/backtrace/inspect_result.rb +0 -1
- data/lib/graphql/backtrace/table.rb +0 -1
- data/lib/graphql/backtrace/traced_error.rb +0 -1
- data/lib/graphql/backtrace/tracer.rb +2 -6
- data/lib/graphql/dataloader.rb +102 -92
- data/lib/graphql/dataloader/null_dataloader.rb +5 -5
- data/lib/graphql/dataloader/request.rb +1 -6
- data/lib/graphql/dataloader/request_all.rb +1 -4
- data/lib/graphql/dataloader/source.rb +20 -6
- data/lib/graphql/execution/interpreter.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +37 -14
- data/lib/graphql/execution/interpreter/resolve.rb +33 -25
- data/lib/graphql/execution/interpreter/runtime.rb +36 -74
- data/lib/graphql/execution/multiplex.rb +21 -22
- data/lib/graphql/object_type.rb +0 -2
- data/lib/graphql/parse_error.rb +0 -1
- data/lib/graphql/query.rb +8 -2
- data/lib/graphql/query/arguments_cache.rb +0 -1
- data/lib/graphql/query/context.rb +1 -3
- data/lib/graphql/query/executor.rb +0 -1
- data/lib/graphql/query/null_context.rb +3 -2
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/schema/argument.rb +61 -0
- data/lib/graphql/schema/field.rb +10 -5
- data/lib/graphql/schema/find_inherited_value.rb +3 -1
- data/lib/graphql/schema/input_object.rb +6 -2
- data/lib/graphql/schema/member/has_arguments.rb +43 -56
- data/lib/graphql/schema/member/has_fields.rb +1 -4
- data/lib/graphql/schema/member/instrumentation.rb +0 -1
- data/lib/graphql/subscriptions/event.rb +0 -1
- data/lib/graphql/subscriptions/instrumentation.rb +0 -1
- data/lib/graphql/subscriptions/serialize.rb +0 -1
- data/lib/graphql/version.rb +1 -1
- metadata +7 -90
@@ -7,15 +7,15 @@ module GraphQL
|
|
7
7
|
# The Dataloader interface isn't public, but it enables
|
8
8
|
# simple internal code while adding the option to add Dataloader.
|
9
9
|
class NullDataloader < Dataloader
|
10
|
-
def enqueue
|
11
|
-
yield
|
12
|
-
end
|
13
|
-
|
14
10
|
# These are all no-ops because code was
|
15
11
|
# executed sychronously.
|
16
12
|
def run; end
|
17
13
|
def yield; end
|
18
|
-
|
14
|
+
|
15
|
+
def append_job
|
16
|
+
yield
|
17
|
+
nil
|
18
|
+
end
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -3,9 +3,6 @@
|
|
3
3
|
module GraphQL
|
4
4
|
class Dataloader
|
5
5
|
class Source
|
6
|
-
# @api private
|
7
|
-
attr_reader :results
|
8
|
-
|
9
6
|
# Called by {Dataloader} to prepare the {Source}'s internal state
|
10
7
|
# @api private
|
11
8
|
def setup(dataloader)
|
@@ -35,11 +32,11 @@ module GraphQL
|
|
35
32
|
# @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded.
|
36
33
|
def load(key)
|
37
34
|
if @results.key?(key)
|
38
|
-
|
35
|
+
result_for(key)
|
39
36
|
else
|
40
37
|
@pending_keys << key
|
41
38
|
sync
|
42
|
-
|
39
|
+
result_for(key)
|
43
40
|
end
|
44
41
|
end
|
45
42
|
|
@@ -52,7 +49,7 @@ module GraphQL
|
|
52
49
|
sync
|
53
50
|
end
|
54
51
|
|
55
|
-
keys.map { |k|
|
52
|
+
keys.map { |k| result_for(k) }
|
56
53
|
end
|
57
54
|
|
58
55
|
# Subclasses must implement this method to return a value for each of `keys`
|
@@ -86,8 +83,25 @@ module GraphQL
|
|
86
83
|
fetch_keys.each_with_index do |key, idx|
|
87
84
|
@results[key] = results[idx]
|
88
85
|
end
|
86
|
+
rescue StandardError => error
|
87
|
+
fetch_keys.each { |key| @results[key] = error }
|
88
|
+
ensure
|
89
89
|
nil
|
90
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
|
95
|
+
# @param key [Object] key passed to {#load} or {#load_all}
|
96
|
+
# @return [Object] The result from {#fetch} for `key`.
|
97
|
+
# @api private
|
98
|
+
def result_for(key)
|
99
|
+
result = @results[key]
|
100
|
+
|
101
|
+
raise result if result.class <= StandardError
|
102
|
+
|
103
|
+
result
|
104
|
+
end
|
91
105
|
end
|
92
106
|
end
|
93
107
|
end
|
@@ -95,7 +95,7 @@ module GraphQL
|
|
95
95
|
end
|
96
96
|
final_values.compact!
|
97
97
|
tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
|
98
|
-
Interpreter::Resolve.resolve_all(final_values)
|
98
|
+
Interpreter::Resolve.resolve_all(final_values, multiplex.dataloader)
|
99
99
|
end
|
100
100
|
queries.each do |query|
|
101
101
|
runtime = query.context.namespace(:interpreter)[:runtime]
|
@@ -6,17 +6,21 @@ module GraphQL
|
|
6
6
|
class ArgumentsCache
|
7
7
|
def initialize(query)
|
8
8
|
@query = query
|
9
|
+
@dataloader = query.context.dataloader
|
9
10
|
@storage = Hash.new do |h, ast_node|
|
10
11
|
h[ast_node] = Hash.new do |h2, arg_owner|
|
11
12
|
h2[arg_owner] = Hash.new do |h3, parent_object|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
dataload_for(ast_node, arg_owner, parent_object) do |kwarg_arguments|
|
14
|
+
h3[parent_object] = @query.schema.after_lazy(kwarg_arguments) do |resolved_args|
|
15
|
+
h3[parent_object] = resolved_args
|
16
|
+
end
|
17
|
+
end
|
16
18
|
|
17
|
-
h3
|
18
|
-
#
|
19
|
-
h3[parent_object] =
|
19
|
+
if !h3.key?(parent_object)
|
20
|
+
# TODO should i bother putting anything here?
|
21
|
+
h3[parent_object] = NO_ARGUMENTS
|
22
|
+
else
|
23
|
+
h3[parent_object]
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
@@ -25,6 +29,25 @@ module GraphQL
|
|
25
29
|
|
26
30
|
def fetch(ast_node, argument_owner, parent_object)
|
27
31
|
@storage[ast_node][argument_owner][parent_object]
|
32
|
+
# If any jobs were enqueued, run them now,
|
33
|
+
# since this might have been called outside of execution.
|
34
|
+
# (The jobs are responsible for updating `result` in-place.)
|
35
|
+
@dataloader.run
|
36
|
+
# Ack, the _hash_ is updated, but the key is eventually
|
37
|
+
# overridden with an immutable arguments instance.
|
38
|
+
# The first call queues up the job,
|
39
|
+
# then this call fetches the result.
|
40
|
+
# TODO this should be better, find a solution
|
41
|
+
# that works with merging the runtime.rb code
|
42
|
+
@storage[ast_node][argument_owner][parent_object]
|
43
|
+
end
|
44
|
+
|
45
|
+
# @yield [Interpreter::Arguments, Lazy<Interpreter::Arguments>] The finally-loaded arguments
|
46
|
+
def dataload_for(ast_node, argument_owner, parent_object, &block)
|
47
|
+
# First, normalize all AST or Ruby values to a plain Ruby hash
|
48
|
+
args_hash = self.class.prepare_args_hash(@query, ast_node)
|
49
|
+
argument_owner.coerce_arguments(parent_object, args_hash, @query.context, &block)
|
50
|
+
nil
|
28
51
|
end
|
29
52
|
|
30
53
|
private
|
@@ -33,7 +56,7 @@ module GraphQL
|
|
33
56
|
|
34
57
|
NO_VALUE_GIVEN = Object.new
|
35
58
|
|
36
|
-
def prepare_args_hash(ast_arg_or_hash_or_value)
|
59
|
+
def self.prepare_args_hash(query, ast_arg_or_hash_or_value)
|
37
60
|
case ast_arg_or_hash_or_value
|
38
61
|
when Hash
|
39
62
|
if ast_arg_or_hash_or_value.empty?
|
@@ -41,27 +64,27 @@ module GraphQL
|
|
41
64
|
end
|
42
65
|
args_hash = {}
|
43
66
|
ast_arg_or_hash_or_value.each do |k, v|
|
44
|
-
args_hash[k] = prepare_args_hash(v)
|
67
|
+
args_hash[k] = prepare_args_hash(query, v)
|
45
68
|
end
|
46
69
|
args_hash
|
47
70
|
when Array
|
48
|
-
ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
|
71
|
+
ast_arg_or_hash_or_value.map { |v| prepare_args_hash(query, v) }
|
49
72
|
when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
|
50
73
|
if ast_arg_or_hash_or_value.arguments.empty?
|
51
74
|
return NO_ARGUMENTS
|
52
75
|
end
|
53
76
|
args_hash = {}
|
54
77
|
ast_arg_or_hash_or_value.arguments.each do |arg|
|
55
|
-
v = prepare_args_hash(arg.value)
|
78
|
+
v = prepare_args_hash(query, arg.value)
|
56
79
|
if v != NO_VALUE_GIVEN
|
57
80
|
args_hash[arg.name] = v
|
58
81
|
end
|
59
82
|
end
|
60
83
|
args_hash
|
61
84
|
when GraphQL::Language::Nodes::VariableIdentifier
|
62
|
-
if
|
63
|
-
variable_value =
|
64
|
-
prepare_args_hash(variable_value)
|
85
|
+
if query.variables.key?(ast_arg_or_hash_or_value.name)
|
86
|
+
variable_value = query.variables[ast_arg_or_hash_or_value.name]
|
87
|
+
prepare_args_hash(query, variable_value)
|
65
88
|
else
|
66
89
|
NO_VALUE_GIVEN
|
67
90
|
end
|
@@ -6,10 +6,9 @@ module GraphQL
|
|
6
6
|
module Resolve
|
7
7
|
# Continue field results in `results` until there's nothing else to continue.
|
8
8
|
# @return [void]
|
9
|
-
def self.resolve_all(results)
|
10
|
-
|
11
|
-
|
12
|
-
end
|
9
|
+
def self.resolve_all(results, dataloader)
|
10
|
+
dataloader.append_job { resolve(results, dataloader) }
|
11
|
+
nil
|
13
12
|
end
|
14
13
|
|
15
14
|
# After getting `results` back from an interpreter evaluation,
|
@@ -24,33 +23,42 @@ module GraphQL
|
|
24
23
|
# return {Lazy} instances if there's more work to be done,
|
25
24
|
# or return {Hash}/{Array} if the query should be continued.
|
26
25
|
#
|
27
|
-
# @
|
28
|
-
|
29
|
-
|
26
|
+
# @return [void]
|
27
|
+
def self.resolve(results, dataloader)
|
28
|
+
# There might be pending jobs here that _will_ write lazies
|
29
|
+
# into the result hash. We should run them out, so we
|
30
|
+
# can be sure that all lazies will be present in the result hashes.
|
31
|
+
# A better implementation would somehow interleave (or unify)
|
32
|
+
# these approaches.
|
33
|
+
dataloader.run
|
30
34
|
next_results = []
|
31
|
-
|
32
|
-
# Work through the queue until it's empty
|
33
|
-
while results.size > 0
|
35
|
+
while results.any?
|
34
36
|
result_value = results.shift
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
if result_value.is_a?(Lazy)
|
41
|
-
# Since this field returned another lazy,
|
42
|
-
# add it to the same queue
|
43
|
-
results << result_value
|
44
|
-
elsif result_value.is_a?(Hash)
|
45
|
-
# This is part of the next level, add it
|
46
|
-
next_results.concat(result_value.values)
|
37
|
+
if result_value.is_a?(Hash)
|
38
|
+
results.concat(result_value.values)
|
39
|
+
next
|
47
40
|
elsif result_value.is_a?(Array)
|
48
|
-
|
49
|
-
|
41
|
+
results.concat(result_value)
|
42
|
+
next
|
43
|
+
elsif result_value.is_a?(Lazy)
|
44
|
+
loaded_value = result_value.value
|
45
|
+
if loaded_value.is_a?(Lazy)
|
46
|
+
# Since this field returned another lazy,
|
47
|
+
# add it to the same queue
|
48
|
+
results << loaded_value
|
49
|
+
elsif loaded_value.is_a?(Hash) || loaded_value.is_a?(Array)
|
50
|
+
# Add these values in wholesale --
|
51
|
+
# they might be modified by later work in the dataloader.
|
52
|
+
next_results << loaded_value
|
53
|
+
end
|
50
54
|
end
|
51
55
|
end
|
52
56
|
|
53
|
-
next_results
|
57
|
+
if next_results.any?
|
58
|
+
dataloader.append_job { resolve(next_results, dataloader) }
|
59
|
+
end
|
60
|
+
|
61
|
+
nil
|
54
62
|
end
|
55
63
|
end
|
56
64
|
end
|
@@ -56,17 +56,18 @@ module GraphQL
|
|
56
56
|
# Root .authorized? returned false.
|
57
57
|
write_in_response(path, nil)
|
58
58
|
else
|
59
|
-
|
60
|
-
@progress_path = path
|
61
|
-
@progress_scoped_context = context.scoped_context
|
62
|
-
@progress_object = object_proxy
|
63
|
-
@progress_object_type = root_type
|
64
|
-
@progress_index = nil
|
65
|
-
@progress_is_eager_selection = root_op_type == "mutation"
|
66
|
-
@progress_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
67
|
-
|
59
|
+
gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
68
60
|
# Make the first fiber which will begin execution
|
69
|
-
|
61
|
+
@dataloader.append_job {
|
62
|
+
evaluate_selections(
|
63
|
+
path,
|
64
|
+
context.scoped_context,
|
65
|
+
object_proxy,
|
66
|
+
root_type,
|
67
|
+
root_op_type == "mutation",
|
68
|
+
gathered_selections,
|
69
|
+
)
|
70
|
+
}
|
70
71
|
end
|
71
72
|
delete_interpreter_context(:current_path)
|
72
73
|
delete_interpreter_context(:current_field)
|
@@ -75,32 +76,6 @@ module GraphQL
|
|
75
76
|
nil
|
76
77
|
end
|
77
78
|
|
78
|
-
# Use `@dataloader` to enqueue a fiber that will pick up from the current point.
|
79
|
-
# @return [void]
|
80
|
-
def enqueue_selections_fiber
|
81
|
-
# Read these into local variables so that later assignments don't affect the block below.
|
82
|
-
path = @progress_path
|
83
|
-
scoped_context = @progress_scoped_context
|
84
|
-
owner_object = @progress_object
|
85
|
-
owner_type = @progress_object_type
|
86
|
-
idx = @progress_index
|
87
|
-
is_eager_selection = @progress_is_eager_selection
|
88
|
-
gathered_selections = @progress_selections
|
89
|
-
|
90
|
-
@dataloader.enqueue {
|
91
|
-
evaluate_selections(
|
92
|
-
path,
|
93
|
-
scoped_context,
|
94
|
-
owner_object,
|
95
|
-
owner_type,
|
96
|
-
is_eager_selection: is_eager_selection,
|
97
|
-
after: idx,
|
98
|
-
gathered_selections: gathered_selections,
|
99
|
-
)
|
100
|
-
}
|
101
|
-
nil
|
102
|
-
end
|
103
|
-
|
104
79
|
def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
|
105
80
|
selections.each do |node|
|
106
81
|
# Skip gathering this if the directive says so
|
@@ -159,39 +134,17 @@ module GraphQL
|
|
159
134
|
NO_ARGS = {}.freeze
|
160
135
|
|
161
136
|
# @return [void]
|
162
|
-
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection
|
137
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections)
|
163
138
|
set_all_interpreter_context(owner_object, nil, nil, path)
|
164
139
|
|
165
|
-
@progress_path = path
|
166
|
-
@progress_scoped_context = scoped_context
|
167
|
-
@progress_object = owner_object
|
168
|
-
@progress_object_type = owner_type
|
169
|
-
@progress_index = nil
|
170
|
-
@progress_is_eager_selection = is_eager_selection
|
171
|
-
@progress_selections = gathered_selections
|
172
|
-
|
173
|
-
# Track `idx` manually to avoid an allocation on this hot path
|
174
|
-
idx = 0
|
175
140
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
if after && prev_idx <= after
|
182
|
-
next
|
183
|
-
end
|
184
|
-
@progress_index = prev_idx
|
185
|
-
# This is how the current runtime gives itself to `dataloader`
|
186
|
-
# so that the dataloader can enqueue another fiber to resume if needed.
|
187
|
-
@dataloader.current_runtime = self
|
188
|
-
evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection)
|
189
|
-
# The dataloader knows if ^^ that selection halted and later selections were executed in another fiber.
|
190
|
-
# If that's the case, then don't continue execution here.
|
191
|
-
if @dataloader.yielded?(path)
|
192
|
-
break
|
193
|
-
end
|
141
|
+
@dataloader.append_job {
|
142
|
+
evaluate_selection(
|
143
|
+
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection
|
144
|
+
)
|
145
|
+
}
|
194
146
|
end
|
147
|
+
|
195
148
|
nil
|
196
149
|
end
|
197
150
|
|
@@ -243,13 +196,21 @@ module GraphQL
|
|
243
196
|
object = authorized_new(field_defn.owner, object, context, next_path)
|
244
197
|
end
|
245
198
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
199
|
+
total_args_count = field_defn.arguments.size
|
200
|
+
if total_args_count == 0
|
201
|
+
kwarg_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
202
|
+
evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
|
203
|
+
else
|
204
|
+
# TODO remove all arguments(...) usages?
|
205
|
+
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
206
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
|
207
|
+
end
|
251
208
|
end
|
209
|
+
end
|
252
210
|
|
211
|
+
def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field) # rubocop:disable Metrics/ParameterLists
|
212
|
+
context.scoped_context = scoped_context
|
213
|
+
return_type = field_defn.type
|
253
214
|
after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
|
254
215
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
255
216
|
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
|
@@ -329,10 +290,11 @@ module GraphQL
|
|
329
290
|
# all of its child fields before moving on to the next root mutation field.
|
330
291
|
# (Subselections of this mutation will still be resolved level-by-level.)
|
331
292
|
if is_eager_field
|
332
|
-
Interpreter::Resolve.resolve_all([field_result])
|
293
|
+
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
294
|
+
else
|
295
|
+
# Return this from `after_lazy` because it might be another lazy that needs to be resolved
|
296
|
+
field_result
|
333
297
|
end
|
334
|
-
|
335
|
-
nil
|
336
298
|
end
|
337
299
|
end
|
338
300
|
|
@@ -419,7 +381,7 @@ module GraphQL
|
|
419
381
|
response_hash = {}
|
420
382
|
write_in_response(path, response_hash)
|
421
383
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
422
|
-
evaluate_selections(path, context.scoped_context, continue_value, current_type,
|
384
|
+
evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections)
|
423
385
|
response_hash
|
424
386
|
end
|
425
387
|
end
|