graphql 1.12.11 → 1.12.12
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 296954a3b03f2fd5dae9fae56eaa08ea04b3dfb8f25923201ea27c67147c71fa
|
4
|
+
data.tar.gz: 4de323637c29b729a3eb3f07b620e2014515b37235c485002d016ead490d86e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdf50f6c6f170aa4c50d46356e4089c40a939bf3d0dfd4ab7bdb667d169f01a38320c971cdec1dde32353a20823c1756196312df5e425af41a157caa1179e845
|
7
|
+
data.tar.gz: 4202ca4741998ee1f5535eea14ff93c1a8529ff5085625d67171e75736f65f55d0433b93512c272e16f4f9a48dd630eba49cf37a54e49b0e9e3dad5f0496930c
|
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,
|
@@ -24,12 +24,33 @@ module GraphQL
|
|
24
24
|
|
25
25
|
class GraphQLResultHash < Hash
|
26
26
|
include GraphQLResult
|
27
|
+
|
28
|
+
attr_accessor :graphql_merged_into
|
29
|
+
|
30
|
+
def []=(key, value)
|
31
|
+
# This is a hack.
|
32
|
+
# Basically, this object is merged into the root-level result at some point.
|
33
|
+
# But the problem is, some lazies are created whose closures retain reference to _this_
|
34
|
+
# object. When those lazies are resolved, they cause an update to this object.
|
35
|
+
#
|
36
|
+
# In order to return a proper top-level result, we have to update that top-level result object.
|
37
|
+
# In order to return a proper partial result (eg, for a directive), we have to update this object, too.
|
38
|
+
# Yowza.
|
39
|
+
if (t = @graphql_merged_into)
|
40
|
+
t[key] = value
|
41
|
+
end
|
42
|
+
super
|
43
|
+
end
|
27
44
|
end
|
28
45
|
|
29
46
|
class GraphQLResultArray < Array
|
30
47
|
include GraphQLResult
|
31
48
|
end
|
32
49
|
|
50
|
+
class GraphQLSelectionSet < Hash
|
51
|
+
attr_accessor :graphql_directives
|
52
|
+
end
|
53
|
+
|
33
54
|
# @return [GraphQL::Query]
|
34
55
|
attr_reader :query
|
35
56
|
|
@@ -50,6 +71,14 @@ module GraphQL
|
|
50
71
|
@multiplex_context = query.multiplex.context
|
51
72
|
@interpreter_context = @context.namespace(:interpreter)
|
52
73
|
@response = GraphQLResultHash.new
|
74
|
+
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
|
75
|
+
@runtime_directive_names = []
|
76
|
+
noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
|
77
|
+
schema.directives.each do |name, dir_defn|
|
78
|
+
if dir_defn.method(:resolve).owner != noop_resolve_owner
|
79
|
+
@runtime_directive_names << name
|
80
|
+
end
|
81
|
+
end
|
53
82
|
# A cache of { Class => { String => Schema::Field } }
|
54
83
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
55
84
|
# during the lifetime of a query
|
@@ -62,6 +91,16 @@ module GraphQL
|
|
62
91
|
"#<#{self.class.name} response=#{@response.inspect}>"
|
63
92
|
end
|
64
93
|
|
94
|
+
def tap_or_each(obj_or_array)
|
95
|
+
if obj_or_array.is_a?(Array)
|
96
|
+
obj_or_array.each do |item|
|
97
|
+
yield(item, true)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
yield(obj_or_array, false)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
65
104
|
# This _begins_ the execution. Some deferred work
|
66
105
|
# might be stored up in lazies.
|
67
106
|
# @return [void]
|
@@ -78,20 +117,40 @@ module GraphQL
|
|
78
117
|
# Root .authorized? returned false.
|
79
118
|
@response = nil
|
80
119
|
else
|
81
|
-
resolve_with_directives(object_proxy, root_operation) do # execute query level directives
|
120
|
+
resolve_with_directives(object_proxy, root_operation.directives) do # execute query level directives
|
82
121
|
gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
122
|
+
# This is kind of a hack -- `gathered_selections` is an Array if any of the selections
|
123
|
+
# require isolation during execution (because of runtime directives). In that case,
|
124
|
+
# make a new, isolated result hash for writing the result into. (That isolated response
|
125
|
+
# is eventually merged back into the main response)
|
126
|
+
#
|
127
|
+
# Otherwise, `gathered_selections` is a hash of selections which can be
|
128
|
+
# directly evaluated and the results can be written right into the main response hash.
|
129
|
+
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
130
|
+
if is_selection_array
|
131
|
+
selection_response = GraphQLResultHash.new
|
132
|
+
final_response = @response
|
133
|
+
else
|
134
|
+
selection_response = @response
|
135
|
+
final_response = nil
|
136
|
+
end
|
137
|
+
|
138
|
+
@dataloader.append_job {
|
139
|
+
set_all_interpreter_context(query.root_value, nil, nil, path)
|
140
|
+
resolve_with_directives(object_proxy, selections.graphql_directives) do
|
141
|
+
evaluate_selections(
|
142
|
+
path,
|
143
|
+
context.scoped_context,
|
144
|
+
object_proxy,
|
145
|
+
root_type,
|
146
|
+
root_op_type == "mutation",
|
147
|
+
selections,
|
148
|
+
selection_response,
|
149
|
+
final_response,
|
150
|
+
)
|
151
|
+
end
|
152
|
+
}
|
153
|
+
end
|
95
154
|
end
|
96
155
|
end
|
97
156
|
delete_interpreter_context(:current_path)
|
@@ -101,15 +160,36 @@ module GraphQL
|
|
101
160
|
nil
|
102
161
|
end
|
103
162
|
|
104
|
-
|
163
|
+
# @return [void]
|
164
|
+
def deep_merge_selection_result(from_result, into_result)
|
165
|
+
from_result.each do |key, value|
|
166
|
+
if !into_result.key?(key)
|
167
|
+
into_result[key] = value
|
168
|
+
else
|
169
|
+
case value
|
170
|
+
when Hash
|
171
|
+
deep_merge_selection_result(value, into_result[key])
|
172
|
+
else
|
173
|
+
# We have to assume that, since this passed the `fields_will_merge` selection,
|
174
|
+
# that the old and new values are the same.
|
175
|
+
# There's no special handling of arrays because currently, there's no way to split the execution
|
176
|
+
# of a list over several concurrent flows.
|
177
|
+
into_result[key] = value
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
from_result.graphql_merged_into = into_result
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
|
185
|
+
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
|
105
186
|
selections.each do |node|
|
106
187
|
# Skip gathering this if the directive says so
|
107
188
|
if !directives_include?(node, owner_object, owner_type)
|
108
189
|
next
|
109
190
|
end
|
110
191
|
|
111
|
-
|
112
|
-
when GraphQL::Language::Nodes::Field
|
192
|
+
if node.is_a?(GraphQL::Language::Nodes::Field)
|
113
193
|
response_key = node.alias || node.name
|
114
194
|
selections = selections_by_name[response_key]
|
115
195
|
# if there was already a selection of this field,
|
@@ -125,52 +205,77 @@ module GraphQL
|
|
125
205
|
# No selection was found for this field yet
|
126
206
|
selections_by_name[response_key] = node
|
127
207
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
208
|
+
else
|
209
|
+
# This is an InlineFragment or a FragmentSpread
|
210
|
+
if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
211
|
+
next_selections = GraphQLSelectionSet.new
|
212
|
+
next_selections.graphql_directives = node.directives
|
213
|
+
if selections_to_run
|
214
|
+
selections_to_run << next_selections
|
215
|
+
else
|
216
|
+
selections_to_run = []
|
217
|
+
selections_to_run << selections_by_name
|
218
|
+
selections_to_run << next_selections
|
219
|
+
end
|
220
|
+
else
|
221
|
+
next_selections = selections_by_name
|
222
|
+
end
|
223
|
+
|
224
|
+
case node
|
225
|
+
when GraphQL::Language::Nodes::InlineFragment
|
226
|
+
if node.type
|
227
|
+
type_defn = schema.get_type(node.type.name)
|
228
|
+
|
229
|
+
# Faster than .map{}.include?()
|
230
|
+
query.warden.possible_types(type_defn).each do |t|
|
231
|
+
if t == owner_type
|
232
|
+
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
233
|
+
break
|
234
|
+
end
|
235
|
+
end
|
236
|
+
else
|
237
|
+
# it's an untyped fragment, definitely continue
|
238
|
+
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
239
|
+
end
|
240
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
241
|
+
fragment_def = query.fragments[node.name]
|
242
|
+
type_defn = schema.get_type(fragment_def.type.name)
|
243
|
+
possible_types = query.warden.possible_types(type_defn)
|
244
|
+
possible_types.each do |t|
|
133
245
|
if t == owner_type
|
134
|
-
gather_selections(owner_object, owner_type,
|
246
|
+
gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
|
135
247
|
break
|
136
248
|
end
|
137
249
|
end
|
138
250
|
else
|
139
|
-
|
140
|
-
gather_selections(owner_object, owner_type, node.selections, selections_by_name)
|
141
|
-
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
|
251
|
+
raise "Invariant: unexpected selection class: #{node.class}"
|
151
252
|
end
|
152
|
-
else
|
153
|
-
raise "Invariant: unexpected selection class: #{node.class}"
|
154
253
|
end
|
155
254
|
end
|
156
|
-
selections_by_name
|
255
|
+
selections_to_run || selections_by_name
|
157
256
|
end
|
158
257
|
|
159
258
|
NO_ARGS = {}.freeze
|
160
259
|
|
161
260
|
# @return [void]
|
162
|
-
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result)
|
261
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result) # rubocop:disable Metrics/ParameterLists
|
163
262
|
set_all_interpreter_context(owner_object, nil, nil, path)
|
164
263
|
|
264
|
+
finished_jobs = 0
|
265
|
+
enqueued_jobs = gathered_selections.size
|
165
266
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
166
267
|
@dataloader.append_job {
|
167
268
|
evaluate_selection(
|
168
269
|
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
|
169
270
|
)
|
271
|
+
finished_jobs += 1
|
272
|
+
if target_result && finished_jobs == enqueued_jobs
|
273
|
+
deep_merge_selection_result(selections_result, target_result)
|
274
|
+
end
|
170
275
|
}
|
171
276
|
end
|
172
277
|
|
173
|
-
|
278
|
+
selections_result
|
174
279
|
end
|
175
280
|
|
176
281
|
attr_reader :progress_path
|
@@ -292,12 +397,17 @@ module GraphQL
|
|
292
397
|
# Optimize for the case that field is selected only once
|
293
398
|
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
294
399
|
next_selections = ast_node.selections
|
400
|
+
directives = ast_node.directives
|
295
401
|
else
|
296
402
|
next_selections = []
|
297
|
-
|
403
|
+
directives = []
|
404
|
+
field_ast_nodes.each { |f|
|
405
|
+
next_selections.concat(f.selections)
|
406
|
+
directives.concat(f.directives)
|
407
|
+
}
|
298
408
|
end
|
299
409
|
|
300
|
-
field_result = resolve_with_directives(object,
|
410
|
+
field_result = resolve_with_directives(object, directives) do
|
301
411
|
# Actually call the field resolver and capture the result
|
302
412
|
app_result = begin
|
303
413
|
query.with_error_handling do
|
@@ -488,8 +598,39 @@ module GraphQL
|
|
488
598
|
response_hash.graphql_result_name = result_name
|
489
599
|
set_result(selection_result, result_name, response_hash)
|
490
600
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
491
|
-
|
492
|
-
|
601
|
+
# There are two possibilities for `gathered_selections`:
|
602
|
+
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
603
|
+
# This case is handled below, and the result can be written right into the main `response_hash` above.
|
604
|
+
# In this case, `gathered_selections` is a hash of selections.
|
605
|
+
# 2. Some selections of this object have runtime directives that may or may not modify execution.
|
606
|
+
# That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
|
607
|
+
# eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
|
608
|
+
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
|
609
|
+
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
610
|
+
if is_selection_array
|
611
|
+
this_result = GraphQLResultHash.new
|
612
|
+
this_result.graphql_parent = selection_result
|
613
|
+
this_result.graphql_result_name = result_name
|
614
|
+
final_result = response_hash
|
615
|
+
else
|
616
|
+
this_result = response_hash
|
617
|
+
final_result = nil
|
618
|
+
end
|
619
|
+
set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
|
620
|
+
resolve_with_directives(continue_value, selections.graphql_directives) do
|
621
|
+
evaluate_selections(
|
622
|
+
path,
|
623
|
+
context.scoped_context,
|
624
|
+
continue_value,
|
625
|
+
current_type,
|
626
|
+
false,
|
627
|
+
selections,
|
628
|
+
this_result,
|
629
|
+
final_result,
|
630
|
+
)
|
631
|
+
this_result
|
632
|
+
end
|
633
|
+
end
|
493
634
|
end
|
494
635
|
end
|
495
636
|
when "LIST"
|
@@ -534,13 +675,13 @@ module GraphQL
|
|
534
675
|
end
|
535
676
|
end
|
536
677
|
|
537
|
-
def resolve_with_directives(object,
|
538
|
-
return yield if
|
539
|
-
run_directive(object,
|
678
|
+
def resolve_with_directives(object, directives, &block)
|
679
|
+
return yield if directives.nil? || directives.empty?
|
680
|
+
run_directive(object, directives, 0, &block)
|
540
681
|
end
|
541
682
|
|
542
|
-
def run_directive(object,
|
543
|
-
dir_node =
|
683
|
+
def run_directive(object, directives, idx, &block)
|
684
|
+
dir_node = directives[idx]
|
544
685
|
if !dir_node
|
545
686
|
yield
|
546
687
|
else
|
@@ -548,9 +689,9 @@ module GraphQL
|
|
548
689
|
if !dir_defn.is_a?(Class)
|
549
690
|
dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
|
550
691
|
end
|
551
|
-
dir_args = arguments(nil, dir_defn, dir_node)
|
692
|
+
dir_args = arguments(nil, dir_defn, dir_node)
|
552
693
|
dir_defn.resolve(object, dir_args, context) do
|
553
|
-
run_directive(object,
|
694
|
+
run_directive(object, directives, idx + 1, &block)
|
554
695
|
end
|
555
696
|
end
|
556
697
|
end
|
@@ -559,7 +700,7 @@ module GraphQL
|
|
559
700
|
def directives_include?(node, graphql_object, parent_type)
|
560
701
|
node.directives.each do |dir_node|
|
561
702
|
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)
|
703
|
+
args = arguments(graphql_object, dir_defn, dir_node)
|
563
704
|
if !dir_defn.include?(graphql_object, args, context)
|
564
705
|
return false
|
565
706
|
end
|
data/lib/graphql/version.rb
CHANGED
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.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -150,6 +150,20 @@ dependencies:
|
|
150
150
|
- - '='
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0.68'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: stackprof
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
153
167
|
- !ruby/object:Gem::Dependency
|
154
168
|
name: parser
|
155
169
|
requirement: !ruby/object:Gem::Requirement
|